diff --git a/libswiftnav b/libswiftnav index 8bbc35e..da4bfcb 160000 --- a/libswiftnav +++ b/libswiftnav @@ -1 +1 @@ -Subproject commit 8bbc35e7292b639c7f4be636ef3ccb1227312c8b +Subproject commit da4bfcb7db04021a730e9bd6b1b0781dfd69abc0 diff --git a/peregrine/acquisition.py b/peregrine/acquisition.py index 9d4cc56..57ddbb3 100644 --- a/peregrine/acquisition.py +++ b/peregrine/acquisition.py @@ -17,9 +17,11 @@ import numpy as np import pyfftw import cPickle -import defaults from include.generateCAcode import caCodes +from include.generateGLOcode import GLOCode +from peregrine.gps_constants import L1CA +from peregrine.glo_constants import GLO_L1, glo_l1_step import logging logger = logging.getLogger(__name__) @@ -191,13 +193,15 @@ def interpolate(self, S_0, S_1, S_2, interpolation='gaussian'): **Parabolic interpolation:** - .. math:: \Delta = \\frac{1}{2} \\frac{S[k+1] - S[k-1]}{2S[k] - S[k-1] - S[k+1]} + .. math:: \Delta = \\frac{1}{2} \\frac{S[k+1] - + S[k-1]}{2S[k] - S[k-1] - S[k+1]} Where :math:`S[n]` is the magnitude of FFT bin :math:`n`. **Gaussian interpolation:** - .. math:: \Delta = \\frac{1}{2} \\frac{\ln(S[k+1]) - \ln(S[k-1])}{2\ln(S[k]) - \ln(S[k-1]) - \ln(S[k+1])} + .. math:: \Delta = \\frac{1}{2} \\frac{\ln(S[k+1]) - + \ln(S[k-1])}{2\ln(S[k]) - \ln(S[k-1]) - \ln(S[k+1])} The Gaussian interpolation method gives better results, especially when used with a Gaussian window function, at the expense of computational @@ -342,6 +346,8 @@ def find_peak(self, freqs, results, interpolation='gaussian'): freq_index, cp_samples = np.unravel_index(results.argmax(), results.shape) + code_phase = float(cp_samples) / self.samples_per_chip + if freq_index > 1 and freq_index < len(freqs) - 1: delta = self.interpolate( results[freq_index - 1][cp_samples], @@ -358,8 +364,6 @@ def find_peak(self, freqs, results, interpolation='gaussian'): else: freq = freqs[freq_index] - code_phase = float(cp_samples) / self.samples_per_chip - # Calculate SNR for the peak. results_mean = np.mean(results) if results_mean != 0: @@ -370,7 +374,8 @@ def find_peak(self, freqs, results, interpolation='gaussian'): return (code_phase, freq, snr) def acquisition(self, - prns=range(32), + prns=xrange(32), + channels=[x - 7 for x in xrange(14)], doppler_priors=None, doppler_search=7000, doppler_step=None, @@ -379,10 +384,10 @@ def acquisition(self, multi=True ): """ - Perform an acquisition for a given list of PRNs. + Perform an acquisition for a given list of PRNs/channels. - Perform an acquisition for a given list of PRNs across a range of Doppler - frequencies. + Perform an acquisition for a given list of PRNs/channels across a range of + Doppler frequencies. This function returns :class:`AcquisitionResult` objects containing the location of the acquisition peak for PRNs that have an acquisition @@ -394,8 +399,13 @@ def acquisition(self, Parameters ---------- + bandcode : optional + String defining the acquisition code. Default: L1CA + choices: L1CA, GLO_L1 (in gps_constants.py) prns : iterable, optional List of PRNs to acquire. Default: 0..31 (0-indexed) + channels : iterable, optional + List of channels to acquire. Default: -7..6 doppler_prior: list of floats, optional List of expected Doppler frequencies in Hz (one per PRN). Search will be centered about these. If None, will search around 0 for all PRNs. @@ -413,10 +423,11 @@ def acquisition(self, Returns ------- out : [AcquisitionResult] - A list of :class:`AcquisitionResult` objects, one per PRN in `prns`. + A list of :class:`AcquisitionResult` objects, one per PRN in `prns` or + channel in 'channels'. """ - logger.info("Acquisition starting") + logger.info("Acquisition starting for " + self.signal) from peregrine.parallel_processing import parmap # If the Doppler step is not specified, compute it from the coarse @@ -428,9 +439,6 @@ def acquisition(self, # magnitude. doppler_step = self.sampling_freq / self.n_integrate - if doppler_priors is None: - doppler_priors = np.zeros_like(prns) - if progress_bar_output == 'stdout': show_progress = True progress_fd = sys.stdout @@ -446,33 +454,55 @@ def acquisition(self, show_progress = False logger.warning("show_progress = True but progressbar module not found.") + if self.signal == L1CA: + input_len = len(prns) + offset = 1 + pb_attr = progressbar.Attribute('prn', '(PRN: %02d)', '(PRN --)') + if doppler_priors is None: + doppler_priors = np.zeros_like(prns) + else: + input_len = len(channels) + offset = 0 + pb_attr = progressbar.Attribute('ch', '(CH: %02d)', '(CH --)') + if doppler_priors is None: + doppler_priors = np.zeros_like(channels) + # Setup our progress bar if we need it if show_progress and not multi: widgets = [' Acquisition ', - progressbar.Attribute('prn', '(PRN: %02d)', '(PRN --)'), ' ', + pb_attr, ' ', progressbar.Percentage(), ' ', progressbar.ETA(), ' ', progressbar.Bar()] pbar = progressbar.ProgressBar(widgets=widgets, - maxval=int(len(prns) * - (2 * doppler_search / doppler_step + 1)), + maxval=int(input_len * + (2 * doppler_search / doppler_step + 1)), fd=progress_fd) pbar.start() else: pbar = None def do_acq(n): - prn = prns[n] + if self.signal == L1CA: + prn = prns[n] + code = caCodes[prn] + int_f = self.IF + attr = {'prn': prn + 1} + else: + ch = channels[n] + code = GLOCode + int_f = self.IF + ch * glo_l1_step + attr = {'ch': ch} doppler_prior = doppler_priors[n] freqs = np.arange(doppler_prior - doppler_search, - doppler_prior + doppler_search, doppler_step) + self.IF + doppler_prior + doppler_search, doppler_step) + int_f if pbar: def progress_callback(freq_num, num_freqs): - pbar.update(n * len(freqs) + freq_num, attr={'prn': prn + 1}) + pbar.update(n * len(freqs) + freq_num, attr=attr) else: progress_callback = None - coarse_results = self.acquire(caCodes[prn], freqs, + coarse_results = self.acquire(code, freqs, progress_callback=progress_callback) code_phase, carr_freq, snr = self.find_peak(freqs, coarse_results, @@ -485,13 +515,22 @@ def progress_callback(freq_num, num_freqs): status = 'A' # Save properties of the detected satellite signal - acq_result = AcquisitionResult(prn, - carr_freq, - carr_freq - self.IF, - code_phase, - snr, - status, - self.signal) + if self.signal == L1CA: + acq_result = AcquisitionResult(prn, + carr_freq, + carr_freq - int_f, + code_phase, + snr, + status, + L1CA) + else: + acq_result = GloAcquisitionResult(ch, + carr_freq, + carr_freq - int_f, + code_phase, + snr, + status, + GLO_L1) # If the acquisition was successful, log it if (snr > threshold): @@ -501,9 +540,9 @@ def progress_callback(freq_num, num_freqs): if multi: acq_results = parmap( - do_acq, range(len(prns)), show_progress=show_progress) + do_acq, xrange(input_len), show_progress=show_progress) else: - acq_results = map(do_acq, range(len(prns))) + acq_results = map(do_acq, xrange(input_len)) # Acquisition is finished @@ -512,9 +551,11 @@ def progress_callback(freq_num, num_freqs): pbar.finish() logger.info("Acquisition finished") - acquired_prns = [ar.prn + 1 for ar in acq_results if ar.status == 'A'] - logger.info("Acquired %d satellites, PRNs: %s.", - len(acquired_prns), acquired_prns) + acq = [ar.prn + offset for ar in acq_results if ar.status == 'A'] + if self.signal == L1CA: + logger.info("Acquired %d satellites, PRNs: %s.", len(acq), acq) + else: + logger.info("Acquired %d channels: %s.", len(acq), acq) return acq_results @@ -531,7 +572,7 @@ def save_wisdom(self, wisdom_file=DEFAULT_WISDOM_FILE): pyfftw.export_wisdom(), f, protocol=cPickle.HIGHEST_PROTOCOL) -class AcquisitionResult: +class AcquisitionResult(object): """ Stores the acquisition parameters of a single satellite. @@ -560,7 +601,7 @@ class AcquisitionResult: """ __slots__ = ('prn', 'carr_freq', 'doppler', - 'code_phase', 'snr', 'status', 'signal') + 'code_phase', 'snr', 'status', 'signal', 'sample_index') def __init__(self, prn, carr_freq, doppler, code_phase, snr, status, signal, sample_index=0): @@ -574,7 +615,7 @@ def __init__(self, prn, carr_freq, doppler, code_phase, snr, status, signal, self.sample_index = sample_index def __str__(self): - return "PRN %2d (%s) SNR %6.2f @ CP %6.1f, %+8.2f Hz %s" % \ + return "PRN %2d (%s) SNR %6.2f @ CP %6.3f, %+8.2f Hz %s" % \ (self.prn + 1, self.signal, self.snr, self.code_phase, self.doppler, self.status) @@ -615,6 +656,20 @@ def _equal(self, other): return True +class GloAcquisitionResult(AcquisitionResult): + + def __init__(self, channel, carr_freq, doppler, code_phase, snr, status, + signal, sample_index=0): + super(GloAcquisitionResult, self).__init__(channel, carr_freq, doppler, + code_phase, snr, status, + signal, sample_index) + + def __str__(self): + return "CH %2d (%s) SNR %6.2f @ CP %6.3f, %+8.2f Hz %s" % \ + (self.prn, self.signal, self.snr, self.code_phase, self.doppler, + self.status) + + def save_acq_results(filename, acq_results): """ Save a set of acquisition results to a file. @@ -676,4 +731,5 @@ def print_scores(acq_results, pred, pred_dopp=None): print "Found %d of %d, mean doppler error = %+5.0f Hz, mean abs err = %4.0f Hz, worst = %+5.0f Hz"\ % (n_match, len(pred), - sum_dopp_err / max(1, n_match), sum_abs_dopp_err / max(1, n_match), worst_dopp_err) + sum_dopp_err / max(1, n_match), sum_abs_dopp_err / + max(1, n_match), worst_dopp_err) diff --git a/peregrine/alias_detector.py b/peregrine/alias_detector.py new file mode 100644 index 0000000..1325e9e --- /dev/null +++ b/peregrine/alias_detector.py @@ -0,0 +1,111 @@ +# Copyright (C) 2016 Swift Navigation Inc. +# Contact: Adel Mamin +# +# This source is subject to the license found in the file 'LICENSE' which must +# be be distributed together with this source. All other rights reserved. +# +# THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +# EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. + +import numpy as np +from peregrine import defaults +from peregrine import gps_constants +from peregrine import glo_constants + + +class AliasDetector(object): + + def __init__(self): + """ + Initialize the alias lock detector parameters. + + Parameters + ---------- + None + + """ + self.reinit() + + def reinit(self): + """ + Alias detector reinitialization. + + Parameters + ---------- + None + + """ + + self.err_hz = 0. + self.first_P = 0 + 0j + self.acc_len = defaults.alias_detect_interval_ms / \ + defaults.alias_detect_slice_ms + self.dot = 0. + self.cross = 0. + self.fl_count = 0 + self.dt = defaults.alias_detect_slice_ms * 1e-3 + self.first_set = False + + def first(self, P): + """ + Provide the first reading of prompt correlator output. + + Parameters + ---------- + P : prompt I/Q + The prompt I/Q samples from correlator. + + """ + self.first_P = P + self.first_set = True + + def second(self, P): + """ + Provide the second reading of prompt correlator output + + Parameters + ---------- + P : prompt I/Q + The prompt I/Q samples from correlator. + + """ + if not self.first_set: + return + + self.dot += (np.absolute(P.real * self.first_P.real) + + np.absolute(P.imag * self.first_P.imag)) / self.acc_len + self.cross += (self.first_P.real * P.imag - P.real * + self.first_P.imag) / self.acc_len + self.fl_count += 1 + if self.fl_count == self.acc_len: + self.err_hz = np.arctan2(self.cross, self.dot) / (2 * np.pi * self.dt) + self.fl_count = 0 + self.cross = 0 + self.dot = 0 + else: + self.err_hz = 0 + + abs_err_hz = abs(self.err_hz) + err_sign = np.sign(self.err_hz) + # The expected frequency errors are +-(25 + N * 50) Hz + # For the reference, see: + # https://swiftnav.hackpad.com/Alias-PLL-lock-detector-in-L2C-4fWUJWUNnOE + if abs_err_hz > 25.: + self.err_hz = 25 + self.err_hz += 50 * int((abs_err_hz - 25) / 50) + abs_err_hz -= self.err_hz + if abs_err_hz + 25. > 50.: + self.err_hz += 50 + elif abs_err_hz > 25 / 2.: + self.err_hz = 25 + else: + self.err_hz = 0 + self.err_hz *= err_sign + + def get_err_hz(self): + """ + Return the last detected frequency error in Hz + + """ + return self.err_hz diff --git a/peregrine/analysis/README-sim.txt b/peregrine/analysis/README-sim.txt index c210db4..d62f87c 100644 --- a/peregrine/analysis/README-sim.txt +++ b/peregrine/analysis/README-sim.txt @@ -1,14 +1,14 @@ -Running L2C Performance Simulations +Running L1CA/L2C Performance Simulations =================================== Performance simulations are run with run_sim.py tool which invokes iqgen_main.py and tracking_loop.py. The tool stores all the simulation results (including commands -for generating iq data and tracking l2c signal) in json file. Data is always appended +for generating iq data and tracking l1ca/l2c signal) in json file. Data is always appended in given json file. Thereafter, results are illustrated with plt_res.py which reads data from json file. -The json file contains following data for each iqgen and L2C tracking run: +The json file contains following data for each iqgen and L1CA/L2C tracking run: { "acc": # Acceleration (L1CA Hz / s) "avgcn0": # Average of all tracker CN0 estimates @@ -18,9 +18,9 @@ The json file contains following data for each iqgen and L2C tracking run: "dopSigma3": # 3-sigma Doppler error (99.7% of errors is less than this) "duration": # Length of simulation in seconds "iqgencmd": # Command line for iqgen - "iqgencn0": # L2C CN0 reported by iqgen - "l2chip": # Initial L2C code phase reported by iqgen - "l2dop": # Initial L2C Doppler frequency reported by iqgen + "iqgencn0": # L1CA/L2C CN0 reported by iqgen + "l_chip": # Initial L1CA/L2C code phase reported by iqgen + "l_dop": # Initial L1CA/L2C Doppler frequency reported by iqgen "lockrate": # PLL lock rate "snr": # SNR argument for iqgen "stamp": # Wall clock time stamp of simulation run @@ -52,6 +52,13 @@ Performance data can be generated with following commands: $ ./plt_res.py -e -f result_3.json +Band selection +============== +By default simulation is run for L2C band. The L1CA is selected with command line option: +-b l1ca (--band) +FPGA delay control simulation option --short-long-cycles can be given for L1CA band simulation with option: +-s (--short-long-cycles) + Varying simulation parameters ============================= Changing values in the python file is as easy as value in configuration file @@ -89,3 +96,4 @@ Changing CN0 estimator of plt_res.py In plt_res.py there is a string which defines which CN0 estimate is used: CN0STRING="avgcn0" # CN0 from tracking #CN0STRING="iqgencn0" # CN0 from iqgen + diff --git a/peregrine/analysis/plt_res.py b/peregrine/analysis/plt_res.py index cc47c06..1ba5b3d 100755 --- a/peregrine/analysis/plt_res.py +++ b/peregrine/analysis/plt_res.py @@ -14,11 +14,24 @@ import sys import argparse import json -import numpy +import numpy as np import matplotlib.pyplot as plt +import peregrine.gps_constants -CN0STRING = "avgcn0" # CN0 from tracking -# CN0STRING="iqgencn0" # CN0 from iqgen + +class CfgClass: + + def __init__(self): + # self.CN0STRING = "avgcn0" # CN0 from tracking + self.CN0STRING = "iqgencn0" # CN0 from iqgen + self.BAND = "l2c" + + def isL1CA(self): + if "l1ca" == self.BAND: + return True + return False + +cfg = CfgClass() def sigmaFreqPlot(filename): @@ -30,7 +43,7 @@ def sigmaFreqPlot(filename): jsarray = json.loads(s) r = [] for j in jsarray: - r.append((j["dopSigma1"], j[CN0STRING])) + r.append((j["dopSigma1"], j[cfg.CN0STRING])) r = sorted(r, key=lambda x: x[1]) dopSigma1 = map(lambda x: x[0], r) @@ -40,6 +53,7 @@ def sigmaFreqPlot(filename): plt.plot(avgcn0, dopSigma1, 'o-') plt.xlabel('CN0') plt.ylabel('Doppler sigma-1 error (Hz)') + plt.grid(True) plt.show() @@ -52,21 +66,29 @@ def lockrateCn0Plot(filename): jsarray = json.loads(s) r = [] for j in jsarray: - r.append((j["lockrate"], j[CN0STRING])) + r.append((j["lockrate"], j[cfg.CN0STRING])) r = sorted(r, key=lambda x: x[1]) lockrate = map(lambda x: x[0], r) avgcn0 = map(lambda x: x[1], r) + x, y = zip(*sorted((xVal, np.mean([yVal for a, yVal in zip( + avgcn0, lockrate) if xVal == a])) for xVal in set(avgcn0))) + fig = plt.figure() - plt.plot(avgcn0, lockrate, 'o-') + plt.plot(avgcn0, lockrate, 'o') + plt.plot(x, y, '-') plt.xlabel('CN0') plt.ylabel('PLL lock rate') #plt.scatter(avgcn0, lockrate) + plt.grid(True) plt.show() def dynamicAccPlot(filename, mode): + GRAV_G = 9.80665 # m/s^2 + HzToMps = (peregrine.gps_constants.c / peregrine.gps_constants.l1) + HzToG = HzToMps / GRAV_G fp = open(filename, "r") s = fp.read() fp.close() @@ -78,8 +100,8 @@ def dynamicAccPlot(filename, mode): maxCN0 = 0.0 r = [] for j in jsarray: - minCN0 = min(minCN0, j[CN0STRING]) - maxCN0 = max(maxCN0, j[CN0STRING]) + minCN0 = min(minCN0, j[cfg.CN0STRING]) + maxCN0 = max(maxCN0, j[cfg.CN0STRING]) #r.append( (float(j["acc"]),j[CN0STRING]) ) #accVecAll = map(lambda x:x[0], r) #cn0VecAll = map(lambda x:x[1], r) @@ -89,23 +111,28 @@ def dynamicAccPlot(filename, mode): fig = plt.figure() r = [] + if cfg.isL1CA(): + Tcoh = 0.005 # Integration time 5 ms + else: + Tcoh = 0.02 # Integration time 20 ms + for cn0bin in cn0Range: bestAcc = -1000.0 bestCN0 = 0 print "BIN", cn0bin, for j in jsarray: - cn0 = j[CN0STRING] + cn0 = j[cfg.CN0STRING] lockrate = j["lockrate"] doperr = j["dopSigma1"] - acc = float(j["acc"]) + acc = float(j["acc"]) * HzToG if cn0bin - 0.5 <= cn0 and cn0 < cn0bin + 0.5: if acc > bestAcc: if mode == "lockrate" and lockrate >= 0.68: bestAcc = acc bestCN0 = cn0 plt.plot(bestCN0, bestAcc, 'bo') - # 1/12T, Tcoh=20 ms - elif mode == "doperr" and doperr <= 1.0 / (12 * 0.02): + # 1/12T, Tcoh=20 ms, Tcoh=5 ms + elif mode == "doperr" and doperr <= 1.0 / (12 * Tcoh): bestAcc = acc bestCN0 = cn0 plt.plot(bestCN0, bestAcc, 'bo') @@ -122,12 +149,15 @@ def dynamicAccPlot(filename, mode): #plt.plot(cn0VecAll, accVecAll, 'bo') plt.plot(bestCN0, bestAcc, 'r.-') plt.xlabel('CN0') - plt.ylabel('Acceleration Hz/s') - plt.grid() + plt.ylabel('Acceleration (g)') + plt.grid(True) if mode == "lockrate": plt.title("PLL lock rate >= 0.68 (1-sigma)") elif mode == "doperr": - plt.title("Doppler 1-sigma error <= 1/(12*0.02) = 4.2 Hz") + if cfg.isL1CA(): + plt.title("Doppler 1-sigma error <= 1/(12*0.005) = 16.7 Hz") + else: + plt.title("Doppler 1-sigma error <= 1/(12*0.02) = 4.2 Hz") plt.show() @@ -147,8 +177,12 @@ def main(): parser.add_argument("-e", "--dyn-acc-dop", help="x-sigma Doppler error acceleration tolerance vs. CN0", action="store_true") + parser.add_argument("-b", "--band", + help="l1ca or l2c (default)") args = parser.parse_args() + if args.band: + cfg.BAND = args.band if args.lockrate: lockrateCn0Plot(args.filename) elif args.dyn_acc_lockrate: diff --git a/peregrine/analysis/print_track_res.py b/peregrine/analysis/print_track_res.py index f97b86b..50b9ba9 100755 --- a/peregrine/analysis/print_track_res.py +++ b/peregrine/analysis/print_track_res.py @@ -15,6 +15,7 @@ import argparse from peregrine import defaults + def main(): parser = argparse.ArgumentParser() @@ -24,6 +25,10 @@ def main(): parser.add_argument("-p", "--par-to-print", default="CN0", help="parameter to print") + parser.add_argument("--no-toolbar", + action='store_true', + help="Disable toolbar") + parser.add_argument("--profile", choices=['peregrine', 'custom_rate', 'low_rate', 'normal_rate', 'piksi_v3', 'high_rate'], @@ -46,24 +51,40 @@ def main(): else: raise NotImplementedError() - fig = plt.figure() - plt.title(args.par_to_print.replace('_', ' ').title() + ' vs Time') - ax1 = fig.add_subplot(111) + if args.no_toolbar: + plt.rcParams['toolbar'] = 'None' + + params = [x.strip() for x in args.par_to_print.split(',')] + params_num = len(params) + if params_num == 1: + params.append('111') - plt.ylabel(args.par_to_print.replace('_', ' ').title(), color='b') - plt.xlabel('Time [s]') + params = [tuple(params[i:i + 2]) for i in range(0, params_num, 2)] data = np.genfromtxt(args.file, dtype=float, delimiter=',', names=True) - time_stamps = np.array(data['sample_index']) - time_stamps = time_stamps - data['sample_index'][0] + time_stamps = np.array(data['sample']) + time_stamps = time_stamps - data['sample'][0] time_stamps = time_stamps / freq_profile['sampling_freq'] - - plt.plot(time_stamps, np.array(data[args.par_to_print]), 'r.') + time_stamp_min = min(time_stamps) + time_stamp_max = max(time_stamps) + + fig = plt.figure(figsize=(11, 15)) + fig.patch.set_facecolor('white') + plt.subplots_adjust(wspace=0.25, hspace=0.75) + + for (par_to_print, layout) in params: + sub = fig.add_subplot(layout) + sub.set_title(par_to_print.replace('_', ' ').title() + ' vs Time') + sub.grid() + sub.set_xlim([time_stamp_min, time_stamp_max]) + + sub.set_ylabel(par_to_print.replace('_', ' ').title(), color='b') + sub.set_xlabel('Time [s]') + sub.legend(loc='upper right') - plt.legend(loc='upper right') + sub.plot(time_stamps, np.array(data[par_to_print]), 'r.') - plt.grid() plt.axis('tight') plt.show() diff --git a/peregrine/analysis/run_sim.py b/peregrine/analysis/run_sim.py index 9929cb7..fed63c7 100755 --- a/peregrine/analysis/run_sim.py +++ b/peregrine/analysis/run_sim.py @@ -20,95 +20,173 @@ import peregrine.iqgen.bits.signals as signals import numpy as np -IQ_DATA = "iqdata.bin" -TRACK_DATA = "track_res" -TRACK_RES_DATA = "track_res.json" + +class CfgClass: + + def __init__(self): + self.IQ_DATA = "iqdata.bin" + self.TRACK_DATA = "track_res" + self.TRACK_RES_DATA = "track_res.json" + self.BAND = 'l1ca' # "l2c" + #self.fpgaSim = " " # "--short-long-cycles " + self.fpgaSim = "--short-long-cycles " + + def isL1CA(self): + if "l1ca" == self.BAND: + return True + return False + + def __str__(self): + s = "band:" + self.BAND + "\n" + s = s + "fpga delay control simulation: " + self.fpgaSim + return s + + +cfg = CfgClass() def runCmd(cmd): print cmd # Do not use shell=True for untrusted input! - out = subprocess.check_output(cmd, shell=True) + try: + out = subprocess.check_output(cmd, shell=True) + except subprocess.CalledProcessError as e: + # Peregrine exits with failure if acquisition fails. + # Therefore handle error case + print e.output + return e.output, False + print out - return out + return out, True def runIqGen(lens, snr, dop, acc): cmd = "python " + peregrinePath() + "/peregrine/iqgen/iqgen_main.py" - cmd = cmd + " --gps-sv 1 --encoder 1bit --bands l1ca+l2c --message-type crc --profile low_rate" + if cfg.isL1CA(): + cmd = cmd + " --gps-sv 1 --encoder 1bit --bands l1ca --message-type zero+one --profile low_rate" + else: + cmd = cmd + " --gps-sv 1 --encoder 1bit --bands l1ca+l2c --message-type zero+one --profile low_rate" + if float(acc) != 0.0: cmd = cmd + " --doppler-type linear --doppler-speed " + acc + " " elif float(dop) != 0.0: cmd = cmd + " --doppler-type const " + dop + " " else: cmd = cmd + " --doppler-type zero " - cmd = cmd + " --snr " + snr + " --generate " + lens + " " - cmd = cmd + " --output " + IQ_DATA + " " - out = runCmd(cmd) + cmd = cmd + "--amplitude-type poly --amplitude-units snr-db --amplitude-a0 " + snr + cmd = cmd + " --generate " + lens + " " + + cmd = cmd + " --output " + cfg.IQ_DATA + " " + out, success = runCmd(cmd) + if not success: + print "iqgen failed" + sys.exit(1) + lines = out.split('\n') cn0 = None - l2dop = None - l2chip = None + l_dop = None + l_chip = None for ln in lines: words = ln.split() if len(words) == 0: continue - if words[0] == ".L2": - cn0 = words[2] - if words[0] == ".l2_doppler:": - l2dop = words[1] - if words[0] == ".l2_chip:": - l2chip = words[1] + if cfg.isL1CA(): + if words[0] == ".l1": + cn0 = words[2] + if words[0] == ".l1_doppler:": + l_dop = words[1] + if words[0] == ".l1_chip:": + l_chip = words[1] + else: + if words[0] == ".l2": + cn0 = words[2] + if words[0] == ".l2_doppler:": + l_dop = words[1] + if words[0] == ".l2_chip:": + l_chip = words[1] + if words[0] == ".SNR": if float(words[2]) != float(snr): print "snr unexpected" sys.exit(1) - if cn0 is None or l2dop is None or l2chip is None: + if cn0 is None or l_dop is None or l_chip is None: print "iqgen output parse error" sys.exit(1) - return lens, snr, l2dop, acc, l2chip, cn0, cmd + return lens, snr, l_dop, acc, l_chip, cn0, cmd def runTracker(dopp, cp, lens): - cmd = "python " + peregrinePath() + \ - "/peregrine/analysis/tracking_loop.py -f 1bit_x2 -P 1 --profile low_rate " - cmd = cmd + "-p " + cp + " -d " + dopp - cmd = cmd + " -o " + TRACK_DATA + " " - cmd = cmd + " -S l2c " - cmd = cmd + IQ_DATA + " " - out = runCmd(cmd) + if cfg.isL1CA(): + cmd = "python " + peregrinePath() + \ + "/peregrine/analysis/tracking_loop.py -f 1bit -P 1 --profile low_rate --l1ca-profile med " + # "--short-long-cycles " tracking loop corrections are taken in use in FPGA with a delay of 1 ms + cmd = cmd + cfg.fpgaSim + cmd = cmd + "-p " + cp + " -d " + dopp + cmd = cmd + " -o " + cfg.TRACK_DATA + " " + cmd = cmd + " -S l1ca " + cmd = cmd + " --file " + cfg.IQ_DATA + " " + + else: + cmd = "python " + peregrinePath() + \ + "/peregrine/analysis/tracking_loop.py -f 1bit_x2 -P 1 --profile low_rate --ms-to-process -1 " + cmd = cmd + "-p " + cp + " -d " + dopp + cmd = cmd + " -o " + cfg.TRACK_DATA + " " + cmd = cmd + " -S l2c " + cmd = cmd + " --file " + cfg.IQ_DATA + " " + + out, success = runCmd(cmd) lines = out.split('\n') + # This is usefull if acquisition is run instead of + # running tracking_loop.py directly + if not success: + for ln in lines: + if ln.find("No satellites acquired"): + # Acceptable failure + return cmd, False + # Unacceptable failure + print "Acquisition/tracking failed unexpectedly" + sys.exit(1) + durationOk = False for ln in lines: words = ln.split() if len(words) == 0: continue if words[0] == "Time" and words[1] == "to" and words[2] == "process": - if int(words[4]) / 1000 == int(lens): + if round(float(words[4])) == int(lens): durationOk = True if not durationOk: print "Data duration mismatch" sys.exit(1) - return cmd + return cmd, True def processTrackResults(acc): - data = np.genfromtxt(TRACK_DATA + ".PRN-1.l2c", - dtype=float, delimiter=',', names=True) + if cfg.isL1CA(): + data = np.genfromtxt(cfg.TRACK_DATA + ".PRN-1.l1ca", + dtype=float, delimiter=',', names=True) + else: + data = np.genfromtxt(cfg.TRACK_DATA + ".PRN-1.l2c", + dtype=float, delimiter=',', names=True) CN0 = data['CN0'] dopp = data['carr_doppler'] lock = data['lock_detect_outp'] - nsamples = len(CN0) + coherent_ms = data['coherent_ms'] acc = float(acc) avgCN0 = np.mean(CN0) - lockRate = np.sum(lock) / nsamples + lockRate = np.sum([a * b for a, b in zip(lock, coherent_ms)]) / np.sum(coherent_ms) dopErr = np.ndarray(shape=(1, len(dopp)), dtype=float) - i = np.linspace(1, len(dopp), len(dopp), dtype=float) - # Doppler - (i * 20 ms * acc Hz/s * L2/L1 Hz relation) - dopErr = np.abs(dopp - i * 0.02 * acc * - (signals.GPS.L2C.CENTER_FREQUENCY_HZ / signals.GPS.L1CA.CENTER_FREQUENCY_HZ)) + + if cfg.isL1CA(): + ms_tracked = data['ms_tracked'] + dopErr = np.abs(dopp - ms_tracked / 1000.0 * acc) + else: + i = np.linspace(1, len(dopp), len(dopp), dtype=float) + # Doppler - (i * 20 ms * acc Hz/s * L2/L1 Hz relation) + dopErr = np.abs(dopp - i * 0.02 * acc * + (signals.GPS.L2C.CENTER_FREQUENCY_HZ / signals.GPS.L1CA.CENTER_FREQUENCY_HZ)) maxDopErr = np.max(dopErr) sortedDopErr = np.sort(dopErr) ix1 = int(0.5 + 0.6827 * (len(sortedDopErr) - 1)) @@ -126,42 +204,44 @@ def produce(lens, snr, dop, acc): snr = str(float(snr)) dop = str(float(dop)) acc = str(float(acc)) - lens, snr, l2dop, acc, l2chip, cn0, iqgenCmd = runIqGen(lens, snr, dop, acc) - trackerCmd = runTracker(l2dop, l2chip, lens) - avgCN0, lockRate, dopSigma1, dopSigma2, dopSigma3, maxDopErr = processTrackResults( - acc) - fpout = open(TRACK_RES_DATA, "a") - d = datetime.datetime(2000, 1, 1) - js = json.dumps({"stamp": d.utcnow().isoformat(), - "snr": snr, - "duration": float(lens), - "iqgencn0": float(cn0), - "l2dop": float(l2dop), - "l2chip": float(l2chip), - "acc": float(acc), - "avgcn0": avgCN0, - "lockrate": lockRate, - "dopSigma1": dopSigma1, - "dopSigma2": dopSigma2, - "dopSigma3": dopSigma3, - "dopMaxErr": maxDopErr, - "iqgencmd": iqgenCmd, - "trackcmd": trackerCmd}, - sort_keys=True, indent=4, separators=(',', ': ')) - print js - fpout.write(js + ',') - fpout.close() - - return lockRate, dopSigma1, dopSigma2 + lens, snr, l_dop, acc, l_chip, cn0, iqgenCmd = runIqGen(lens, snr, dop, acc) + trackerCmd, trackSuccess = runTracker(l_dop, l_chip, lens) + if trackSuccess: + avgCN0, lockRate, dopSigma1, dopSigma2, dopSigma3, maxDopErr = processTrackResults( + acc) + fpout = open(cfg.TRACK_RES_DATA, "a") + d = datetime.datetime(2000, 1, 1) + js = json.dumps({"stamp": d.utcnow().isoformat(), + "snr": snr, + "duration": float(lens), + "iqgencn0": float(cn0), + "l_dop": float(l_dop), + "l_chip": float(l_chip), + "acc": float(acc), + "avgcn0": avgCN0, + "lockrate": lockRate, + "dopSigma1": dopSigma1, + "dopSigma2": dopSigma2, + "dopSigma3": dopSigma3, + "dopMaxErr": maxDopErr, + "iqgencmd": iqgenCmd, + "trackcmd": trackerCmd}, + sort_keys=True, indent=4, separators=(',', ': ')) + print js + fpout.write(js + ',') + fpout.close() + return lockRate, dopSigma1, dopSigma2, trackSuccess + else: + return 0, 0, 0, trackSuccess def runCn0Range(): - length = 6 # Duration (s) - snrRng = range(-270, -350, -10) # SNR for iqgen command. Unit 0.1 dBHz + length = 30 # Duration (s) + snrRng = range(-330, -370, -1) # SNR for iqgen command. Unit 0.1 dBHz doppler = 0 # Hz acceleration = 0.0 # Hz / s for snr in snrRng: - lockRate, dopSigma1, dopSigma2 = produce( + lockRate, dopSigma1, dopSigma2, success = produce( length, snr / 10.0, doppler, acceleration) @@ -188,10 +268,10 @@ def runDynamicLockRate(): bestLockRate = lockRate bestAcc = acc - lockRate, dopSigma1, dopSigma2 = produce( + lockRate, dopSigma1, dopSigma2, success = produce( length, snr / 10.0, doppler, acc) print "SNR", snr / 10.0, "ACC", acc, "LOCKRATE", lockRate - if lockRate >= lockRateThreshold: + if lockRate >= lockRateThreshold and success: acc = acc + accStep else: print "BEST ACC", bestAcc, "LOCKRATE", bestLockRate @@ -205,7 +285,12 @@ def runDynamicFreq(): # Must run from low CN0 to high CN0 doppler = 0 # Hz accStep = 10 # Size of single acceleration step (L1CA Hz/s) - freqErrThreshold = 1.0 / (12 * 0.02) # 1/12T, Tcoh=20 ms. Failure threshold. + if cfg.isL1CA(): + # 1/12T, Tcoh=5 ms. Failure threshold. + freqErrThreshold = 1.0 / (12 * 0.005) + else: + # 1/12T, Tcoh=20 ms. Failure threshold. + freqErrThreshold = 1.0 / (12 * 0.02) # When it is reached, the next CN0 bin is tried freqErr = 0.0 # 1-sigma @@ -220,11 +305,11 @@ def runDynamicFreq(): bestFreqErr = freqErr bestAcc = acc - lockRate, dopSigma1, dopSigma2 = produce( + lockRate, dopSigma1, dopSigma2, trackSuccess = produce( length, snr / 10.0, doppler, acc) freqErr = dopSigma1 print "SNR", snr / 10.0, "ACC", acc, "1-SIGMA", freqErr - if freqErr <= freqErrThreshold: + if freqErr <= freqErrThreshold and trackSuccess: acc = acc + accStep else: print "BEST ACC", bestAcc, "1-SIGMA", bestFreqErr @@ -237,23 +322,32 @@ def peregrinePath(): def main(): - global TRACK_RES_DATA parser = argparse.ArgumentParser() parser.add_argument("-l", "--lockrate", help="Simple lockrate vs. CN0", action="store_true") parser.add_argument("-f", "--filename", - help="Output file which is appended. Default " + TRACK_RES_DATA) + help="Output file which is appended. Default " + cfg.TRACK_RES_DATA) parser.add_argument("-d", "--dyn-lockrate", help="Lockrate, acceleration, CN0", action="store_true") parser.add_argument("-e", "--dyn-freq", help="Fequency error, acceleration, CN0", action="store_true") + parser.add_argument("-b", "--band", + help="l1ca or l2c (default)") + parser.add_argument("-s", "--short-long-cycles", + help="FPGA delay control simulation", + action="store_true") args = parser.parse_args() if args.filename: - TRACK_RES_DATA = args.filename + cfg.TRACK_RES_DATA = args.filename + if args.band: + cfg.BAND = args.band + if args.short_long_cycles: + cfg.fpgaSim = "--short-long-cycles " + if args.lockrate: runCn0Range() elif args.dyn_lockrate: diff --git a/peregrine/analysis/samples.py b/peregrine/analysis/samples.py index 0ca1821..8b0901b 100755 --- a/peregrine/analysis/samples.py +++ b/peregrine/analysis/samples.py @@ -15,6 +15,8 @@ import matplotlib import matplotlib.pyplot as plt import matplotlib.mlab as mlab +from peregrine import defaults +from peregrine.gps_constants import L1CA, L2C __all__ = ['hist', 'psd', 'summary'] @@ -77,9 +79,20 @@ def hist(samples, ax=None, value_range=None, bin_width=1.0, max_len=ANALYSIS_MAX ticks = np.linspace(min_val, max_val, n_bins) + if min_val == -3 and max_val == 3: + # taken from https://hal-enac.archives-ouvertes.fr/hal-01021721/document + expected_dist = '16.35 33.65 33.65 16.35 [%]' + total = len(samples) + have = str(100 * np.count_nonzero(samples == -3) / total) + ' ' + \ + str(100 * np.count_nonzero(samples == -1) / total) + ' ' + \ + str(100 * np.count_nonzero(samples == 1) / total) + ' ' + \ + str(100 * np.count_nonzero(samples == 3) / total) + ' [%] ' + else: + expected_dist = 'TBD' + ax.hist(samples, bins=bins, color='0.9') - ax.set_title('Histogram') + ax.set_title('Histogram: ' + have + '(expected: ' + expected_dist + ')') ax.set_xlabel('Sample value') if len(ticks) < 22: ax.set_xticks(ticks) @@ -168,12 +181,13 @@ def summary(samples, sampling_freq=None, max_len=ANALYSIS_MAX_LEN): `None` then the whole array will be used. """ + fig = plt.figure() - ax1 = fig.add_subplot(121) - ax2 = fig.add_subplot(122) + ax1 = fig.add_subplot(111) + # ax2 = fig.add_subplot(122) - hist(samples[0], ax=ax1, max_len=max_len) - psd(samples[0], sampling_freq, ax=ax2, max_len=max_len) + hist(samples, ax=ax1, max_len=max_len) + # psd(samples, sampling_freq, ax=ax2, max_len=max_len) fig.set_size_inches(10, 4, forward=True) fig.tight_layout() @@ -193,8 +207,17 @@ def main(): + "'int8', '1bit', '1bitrev' or 'piksi' (default)") args = parser.parse_args() - samples = peregrine.samples.load_samples(args.file, args.num_samples, file_format=args.format) - summary(samples) + # the actual frequency profile is irrelevant for this utility + freq_profile = defaults.freq_profile_high_rate + samples = {L1CA: {'IF': freq_profile['GPS_L1_IF']}, + L2C: {'IF': freq_profile['GPS_L2_IF']}, + 'samples_total': args.num_samples, + 'sample_index': 0} + + samples = peregrine.samples.load_samples(samples=samples, + filename=args.file, + file_format=args.format) + summary(samples['l1ca']['samples']) plt.show() diff --git a/peregrine/analysis/tracking_loop.py b/peregrine/analysis/tracking_loop.py index 1832821..658200d 100755 --- a/peregrine/analysis/tracking_loop.py +++ b/peregrine/analysis/tracking_loop.py @@ -18,7 +18,9 @@ from peregrine.log import default_logging_config from peregrine.tracking import Tracker from peregrine.gps_constants import L1CA, L2C +from peregrine.glo_constants import GLO_L1, GLO_L2, glo_l1_step, glo_l2_step from peregrine.run import populate_peregrine_cmd_line_arguments +from peregrine.tracking_loop import TrackingLoop3, TrackingLoop3b, TrackingLoop2b def main(): @@ -40,9 +42,9 @@ def main(): help="carrier Doppler frequency [Hz]. ") signalParam.add_argument("-S", "--signal", - choices=[L1CA, L2C], + choices=[L1CA, L2C, GLO_L1, GLO_L2], metavar='BAND', - help="Signal type (l1ca / l2c)") + help="Signal type (l1ca, l2c, glo_l1, glo_l2)") signalParam.add_argument("--l2c-handover", action='store_true', help="Perform L2C handover", @@ -78,13 +80,25 @@ def main(): isL1CA = (args.signal == L1CA) isL2C = (args.signal == L2C) + isGLO_L1 = (args.signal == GLO_L1) + isGLO_L2 = (args.signal == GLO_L2) if isL1CA: signal = L1CA IF = freq_profile['GPS_L1_IF'] + prn = int(args.prn) - 1 elif isL2C: signal = L2C IF = freq_profile['GPS_L2_IF'] + prn = int(args.prn) - 1 + elif isGLO_L1: + signal = GLO_L1 + IF = freq_profile['GLO_L1_IF'] + glo_l1_step * int(args.prn) + prn = int(args.prn) + elif isGLO_L2: + signal = GLO_L2 + IF = freq_profile['GLO_L2_IF'] + glo_l2_step * int(args.prn) + prn = int(args.prn) else: raise NotImplementedError() @@ -97,16 +111,11 @@ def main(): carr_doppler = float(args.carr_doppler) code_phase = float(args.code_phase) - prn = int(args.prn) - 1 ms_to_process = int(args.ms_to_process) - if args.pipelining is not None: - tracker_options = {'mode': 'pipelining', - 'k': args.pipelining} - elif args.short_long_cycles is not None: - tracker_options = {'mode': 'short-long-cycles', - 'k': args.short_long_cycles} + if args.short_long_cycles: + tracker_options = {'mode': 'short-long-cycles'} else: tracker_options = None @@ -119,16 +128,10 @@ def main(): signal=signal, sample_index=skip_samples) - if args.l1ca_profile: - profile = defaults.l1ca_stage_profiles[args.l1ca_profile] - stage2_coherent_ms = profile[1]['coherent_ms'] - stage2_params = profile[1]['loop_filter_params'] - else: - stage2_coherent_ms = None - stage2_params = None - samples = {L1CA: {'IF': freq_profile['GPS_L1_IF']}, L2C: {'IF': freq_profile['GPS_L2_IF']}, + GLO_L1: {'IF': freq_profile['GLO_L1_IF']}, + GLO_L2: {'IF': freq_profile['GLO_L2_IF']}, 'samples_total': -1, 'sample_index': skip_samples} @@ -145,13 +148,14 @@ def main(): print "File format: %s" % args.file_format print "PRN to track [1-32]: %s" % args.prn print "Time to process [s]: %s" % (ms_to_process / 1e3) - print "L1 IF [Hz]: %f" % freq_profile['GPS_L1_IF'] - print "L2 IF [Hz]: %f" % freq_profile['GPS_L2_IF'] + print "GPS L1 IF [Hz]: %f" % freq_profile['GPS_L1_IF'] + print "GPS L2 IF [Hz]: %f" % freq_profile['GPS_L2_IF'] + print "GLO L1 IF [Hz]: %f" % freq_profile['GLO_L1_IF'] + print "GLO L2 IF [Hz]: %f" % freq_profile['GLO_L2_IF'] print "Sampling frequency [Hz]: %f" % sampling_freq print "Initial carrier Doppler frequency [Hz]: %s" % carr_doppler print "Initial code phase [chips]: %s" % code_phase print "Signal: %s" % args.signal - print "L1 stage profile: %s" % args.l1ca_profile print "Tracker options: %s" % str(tracker_options) print "L2C handover: %s" % str(l2c_handover) print "======================================================================" @@ -161,11 +165,10 @@ def main(): ms_to_track=ms_to_process, sampling_freq=sampling_freq, # [Hz] l2c_handover=l2c_handover, - stage2_coherent_ms=stage2_coherent_ms, - stage2_loop_filter_params=stage2_params, tracker_options=tracker_options, output_file=args.output_file, - progress_bar_output=args.progress_bar) + progress_bar_output=args.progress_bar, + loop_filter_class=TrackingLoop3) tracker.start() condition = True while condition: diff --git a/peregrine/cn0.py b/peregrine/cn0.py new file mode 100644 index 0000000..c37134a --- /dev/null +++ b/peregrine/cn0.py @@ -0,0 +1,258 @@ +# Copyright (C) 2016 Swift Navigation Inc. +# Contact: Adel Mamin +# +# This source is subject to the license found in the file 'LICENSE' which must +# be be distributed together with this source. All other rights reserved. +# +# THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +# EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. + +import numpy as np + +# Multiplier for checking out-of bounds NSR +CN0_MM_NSR_MIN_MULTIPLIER = 1e-6 + +# Maximum supported NSR value (1/CN0_MM_NSR_MIN_MULTIPLIER) +CN0_MM_NSR_MIN = 1e6 + +# Moving average filter window size +CN0_MOVING_AVG_WINDOW_SIZE = 500 + + +class CN0_Est_MM(object): + + def __init__(self, bw, cn0_0, cutoff_freq, loop_freq): + """ + Initialize the C/N0 estimator state. + + Initializes Moment method C/N0 estimator. + + The method uses the following function for SNR computation: + + C/N0(n) = P_d / P_n, + where P_n(n) = M2(n) - P_d(n), + where P_d(n) = sqrt(2 * M2(n)^2 - M4(n)), + where + M2(n) = sum(1,N)(I(n)^2 + I(n-1)^2 + Q(n)^2 + Q(n-1)^2) / N + M4(n) = sum(1,N)(I(n)^4 + I(n-1)^4 + Q(n)^4 + Q(n-1)^4) / N + + Parameters + ---------- + bw : float + Unused + cn0_0 : float + The initial value of C/N_0 in dB-Hz. + cutoff_freq : float + Unused + loop_freq : float + The estimator update rate [Hz]. + + + """ + self.cn0_db = cn0_0 + self.M2_arr = np.ndarray(CN0_MOVING_AVG_WINDOW_SIZE, dtype=np.double) + self.M4_arr = np.ndarray(CN0_MOVING_AVG_WINDOW_SIZE, dtype=np.double) + self.index = 0 + self.log_bw = 10 * np.log10(loop_freq) + self.snr_db = 0 + self.snr = 0 + + def _compute_cn0(self, M_2, M_4): + """ + Compute C/N0 value + + Parameters + ---------- + M_2 : float + M2 value + M_4 : float + M4 value + + Return + ------ + out : float + Computed C/N0 value + + """ + + tmp = 2 * M_2 * M_2 - M_4 + + if tmp < 0: + self.snr = 1 / CN0_MM_NSR_MIN + else: + P_d = np.sqrt(tmp) + P_n = M_2 - P_d + + # Ensure the SNR is within the limit + if P_d < P_n * CN0_MM_NSR_MIN_MULTIPLIER: + return 60 + elif P_n == 0: + return 10 + else: + self.snr = P_d / P_n + + self.snr_db = 10 * np.log10(self.snr) + + # Compute CN0 + x = self.log_bw + self.snr_db + if x < 10: + return 10 + if x > 60: + return 60 + + return x + + def _moving_average(self, arr, x): + """ + Moving average filter. + + Parameters + ---------- + arr : list + The list of past values + x : float + New value + + Return + ------ + out : float + Filtered value + + """ + if self.index < CN0_MOVING_AVG_WINDOW_SIZE: + arr[self.index] = x + return np.average(arr[:self.index + 1]) + else: + arr[:-1] = arr[1:] + arr[-1] = x + return np.average(arr) + + def update(self, I, Q): + """ + Compute C/N0 with Moment method. + + Parameters + ---------- + I : int + In-phase signal component. + Q : int + Quadrature signal component. + + Return + ------ + out : tuple + cn0_db - CN0 value in dB-Hz + snr - SNR + snr_db - SNR [dB] + + """ + m_2 = I * I + Q * Q + m_4 = m_2 * m_2 + + M_2 = self._moving_average(self.M2_arr, m_2) + M_4 = self._moving_average(self.M4_arr, m_4) + + if self.index < CN0_MOVING_AVG_WINDOW_SIZE: + self.index += 1 + + # Compute and store updated CN0 + self.cn0_db = self._compute_cn0(M_2, M_4) + + return (self.cn0_db, self.snr, self.snr_db) + + +class CN0_Est_BL(object): + + def __init__(self, bw, cn0_0, cutoff_freq, loop_freq): + """ + Initialize the C/N0 estimator state. + + Parameters + ---------- + bw : float + Unused + cn0_0 : float + The initial value of C/N_0 in dB-Hz. + cutoff_freq : float + Unused + loop_freq : float + The estimator update rate [Hz]. + + """ + self.nsr_arr = np.ndarray(CN0_MOVING_AVG_WINDOW_SIZE, dtype=np.double) + self.snr_arr = np.ndarray(CN0_MOVING_AVG_WINDOW_SIZE, dtype=np.double) + self.index = 0 + self.log_bw = 10. * np.log10(loop_freq) + self.I_prev_abs = -1. + self.Q_prev_abs = -1. + self.nsr = 10. ** (0.1 * (self.log_bw - cn0_0)) + self.snr = 0 + + def _moving_average(self, arr, x): + """ + Moving average filter. + + Parameters + ---------- + arr : list + The list of past values + x : float + New value + + Return + ------ + out : float + Filtered value + + """ + + if self.index < CN0_MOVING_AVG_WINDOW_SIZE: + arr[self.index] = x + return np.average(arr[:self.index + 1]) + else: + arr[:-1] = arr[1:] + arr[-1] = x + return np.average(arr) + + def update(self, I, Q): + """ + Compute C/N0 with BL method. + + Parameters + ---------- + I : int + In-phase signal component. + Q : int + Quadrature signal component. + + Return + ------ + out : tuple + cn0_db - CN0 value in dB-Hz + snr - SNR + snr_db - SNR [dB] + + """ + + if self.I_prev_abs < 0.: + # This is the first iteration, just update the prev state. + self.I_prev_abs = np.absolute(I) + self.Q_prev_abs = np.absolute(Q) + else: + P_n = np.absolute(I) - self.I_prev_abs + P_n = P_n * P_n + + P_s = 0.5 * (I * I + self.I_prev_abs * self.I_prev_abs) + + self.I_prev_abs = np.absolute(I) + self.Q_prev_abs = np.absolute(Q) + + self.nsr = self._moving_average(self.nsr_arr, P_n / P_s) + self.snr = self._moving_average(self.snr_arr, I ** 2 / (2 * Q ** 2)) + if self.index < CN0_MOVING_AVG_WINDOW_SIZE: + self.index += 1 + + cn0 = self.log_bw - 10. * np.log10(self.nsr) + + return (cn0, self.snr, 10 * np.log10(self.snr)) diff --git a/peregrine/defaults.py b/peregrine/defaults.py index 3620e9c..f7359f8 100644 --- a/peregrine/defaults.py +++ b/peregrine/defaults.py @@ -1,4 +1,4 @@ -# Copyright (C) 2014 Swift Navigation Inc. +# Copyright (C) 2014,2016 Swift Navigation Inc. # Contact: Adel Mamin # # This source is subject to the license found in the file 'LICENSE' which must @@ -8,6 +8,8 @@ # EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED # WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +import gps_constants + acqThreshold = 21.0 # SNR (unitless) acqSanityCheck = True # Check for sats known to be below the horizon navSanityMaxResid = 25.0 # meters per SV, normalized nav residuals @@ -21,7 +23,7 @@ # used to simulate real HW # [0..10230] -l2c_short_step_chips = 500 # used to simulate real HW +l2c_short_step_chips = 300 # used to simulate real HW chipping_rate = 1.023e6 # Hz code_length = 1023 # chips @@ -34,11 +36,25 @@ sample_channel_GLO_L1 = 2 sample_channel_GLO_L2 = 3 -file_encoding_1bit_x2 = [ +file_encoding_x1_gpsl1 = [ + sample_channel_GPS_L1] # GPS L1 + +file_encoding_x1_gpsl2 = [ + sample_channel_GPS_L2] # GPS L2 + +file_encoding_x1_glol1 = [ + sample_channel_GLO_L1] # GLO L1 + +file_encoding_x1_glol2 = [ + sample_channel_GLO_L2] # GLO L2 + +file_encoding_x2_gpsl1l2 = [ sample_channel_GPS_L1, # GPS L1 sample_channel_GPS_L2] # GPS L2 -file_encoding_2bits_x2 = file_encoding_1bit_x2 +file_encoding_x2_glol1l2 = [ + sample_channel_GLO_L1, # GLO L1 + sample_channel_GLO_L2] # GLO L2 # encoding is taken from here: # https://swiftnav.hackpad.com/MicroZed-Sample-Grabber-IFgt5DbAunD @@ -63,39 +79,98 @@ # GPS L2 @ 7.4MHz (1227.6MHz) # Galileo E5b-I/Q @ 27.86MHz (1207.14MHz) # Beidou B2 @ 27.86MHz (1207.14MHz) -file_encoding_2bits_x4 = [ +file_encoding_x4_gps_glo = [ sample_channel_GPS_L2, # RF4 sample_channel_GLO_L2, # RF3 sample_channel_GLO_L1, # RF2 sample_channel_GPS_L1] # RF1 +# Some of the format names for use with other components +# The format has the following pattern: _x. +FORMAT_PIKSI_X1_GPS_L1 = 'piksi_x1.gpsl1' +FORMAT_PIKSI_X1_GPS_L2 = 'piksi_x1.gpsl2' +FORMAT_PIKSI_X1_GLO_L1 = 'piksi_x1.glol1' +FORMAT_PIKSI_X1_GLO_L2 = 'piksi_x1.glol2' + +FORMAT_1BIT_X1_GPS_L1 = '1bit_x1.gpsl1' +FORMAT_1BIT_X1_GPS_L2 = '1bit_x1.gpsl2' +FORMAT_1BIT_X2_GPS_L1L2 = '1bit_x2.gps' +FORMAT_1BIT_X1_GLO_L1 = '1bit_x1.glol1' +FORMAT_1BIT_X1_GLO_L2 = '1bit_x1.glol2' +FORMAT_1BIT_X2_GLO_L1L2 = '1bit_x2.glo' +FORMAT_1BIT_X4_GPS_L1L2_GLO_L1L2 = '1bit_x4' + +FORMAT_2BITS_X1_GPS_L1 = '2bits_x1.gpsl1' +FORMAT_2BITS_X1_GPS_L2 = '2bits_x1.gpsl2' +FORMAT_2BITS_X2_GPS_L1L2 = '2bits_x2.gps' +FORMAT_2BITS_X1_GLO_L1 = '2bits_x1.glol1' +FORMAT_2BITS_X1_GLO_L2 = '2bits_x1.glol2' +FORMAT_2BITS_X2_GLO_L1L2 = '2bits_x2.glo' +FORMAT_2BITS_X4_GPS_L1L2_GLO_L1L2 = '2bits_x4' + +# All supported file formats +# The map contains encoding name as a key and value as a list of channels in +# the file. file_encoding_profile = { - '1bit_x2': file_encoding_1bit_x2, - '2bits_x2': file_encoding_2bits_x2, - '2bits_x4': file_encoding_2bits_x4} + 'piksi': file_encoding_x1_gpsl1, + FORMAT_PIKSI_X1_GPS_L1: file_encoding_x1_gpsl1, + FORMAT_PIKSI_X1_GPS_L2: file_encoding_x1_gpsl2, + FORMAT_PIKSI_X1_GLO_L1: file_encoding_x1_glol1, + FORMAT_PIKSI_X1_GLO_L2: file_encoding_x1_glol2, + 'piksinew': file_encoding_x1_gpsl1, + 'int8': file_encoding_x1_gpsl1, + 'c8c8': file_encoding_x2_gpsl1l2, + '1bit': file_encoding_x1_gpsl1, + '1bitrev': file_encoding_x1_gpsl1, + '1bit_x1': file_encoding_x1_gpsl1, + FORMAT_1BIT_X1_GPS_L1: file_encoding_x1_gpsl1, + FORMAT_1BIT_X1_GPS_L2: file_encoding_x1_gpsl2, + FORMAT_1BIT_X1_GLO_L1: file_encoding_x1_glol1, + FORMAT_1BIT_X1_GLO_L2: file_encoding_x1_glol2, + '1bit_x2': file_encoding_x2_gpsl1l2, + FORMAT_1BIT_X2_GPS_L1L2: file_encoding_x2_gpsl1l2, + FORMAT_1BIT_X2_GLO_L1L2: file_encoding_x2_glol1l2, + FORMAT_1BIT_X4_GPS_L1L2_GLO_L1L2: file_encoding_x4_gps_glo, + '2bits': file_encoding_x1_gpsl1, + '2bits_x2': file_encoding_x2_gpsl1l2, + FORMAT_2BITS_X1_GPS_L1: file_encoding_x1_gpsl1, + FORMAT_2BITS_X1_GPS_L2: file_encoding_x1_gpsl2, + FORMAT_2BITS_X1_GLO_L1: file_encoding_x1_glol1, + FORMAT_2BITS_X1_GLO_L2: file_encoding_x1_glol2, + FORMAT_2BITS_X2_GPS_L1L2: file_encoding_x2_gpsl1l2, + FORMAT_2BITS_X2_GLO_L1L2: file_encoding_x2_glol1l2, + FORMAT_2BITS_X4_GPS_L1L2_GLO_L1L2: file_encoding_x4_gps_glo} # 'peregrine' frequencies profile freq_profile_peregrine = { 'GPS_L1_IF': 4.092e6, 'GPS_L2_IF': 4.092e6, + 'GLO_L1_IF': 6e6, + 'GLO_L2_IF': 6e6, 'sampling_freq': 16.368e6} # 'low_rate' frequencies profile freq_profile_low_rate = { 'GPS_L1_IF': 1026375.0, 'GPS_L2_IF': 7.4e5, + 'GLO_L1_IF': 12e5, + 'GLO_L2_IF': 12e5, 'sampling_freq': 24.84375e5} # 'normal_rate' frequencies profile freq_profile_normal_rate = { 'GPS_L1_IF': 10263750.0, 'GPS_L2_IF': 7.4e6, + 'GLO_L1_IF': 12e6, + 'GLO_L2_IF': 5.6e6, 'sampling_freq': 24.84375e6} # 'high_rate' frequencies profile freq_profile_high_rate = { - 'GPS_L1_IF': 14.58e6, - 'GPS_L2_IF': 7.4e6, + 'GPS_L1_IF': freq_profile_normal_rate['GPS_L1_IF'], + 'GPS_L2_IF': freq_profile_normal_rate['GPS_L2_IF'], + 'GLO_L1_IF': freq_profile_normal_rate['GLO_L1_IF'], + 'GLO_L2_IF': freq_profile_normal_rate['GLO_L2_IF'], 'sampling_freq': 99.375e6} freq_profile_lookup = { @@ -106,6 +181,7 @@ L1CA_CHANNEL_BANDWIDTH_HZ = 1000 L2C_CHANNEL_BANDWIDTH_HZ = 1000 +GLOL1_CHANNEL_BANDWIDTH_HZ = 1000 l1ca_stage1_loop_filter_params = { "loop_freq": 1e3, # loop frequency [Hz] @@ -113,10 +189,10 @@ "code_zeta": 0.7, # Code loop zeta "code_k": 1, # Code loop k "carr_to_code": 1540, # Carrier-to-code freq ratio (carrier aiding) - "carr_bw": 10, # Carrier loop NBW + "carr_bw": 15, # Carrier loop NBW "carr_zeta": 0.7, # Carrier loop zeta "carr_k": 1, # Carrier loop k - "carr_freq_b1": 5} # Carrier loop aiding_igain + "carr_freq_b1": 1} # Carrier loop aiding_igain l2c_loop_filter_params = { "loop_freq": 50, # loop frequency [Hz] @@ -127,8 +203,46 @@ "carr_bw": 13, # Carrier loop NBW "carr_zeta": 0.707, # Carrier loop zeta "carr_k": 1, # Carrier loop k - "carr_freq_b1": 5} # Carrier loop aiding_igain + "carr_freq_b1": 1} # Carrier loop aiding_igain + +l1ca_track_params = { + 'fll_bw': (1, 0), + 'pll_bw': (40, 35, 30, 25, 20, 18, 16, 14, 12, 10, 8, 7, 6.5, 6, 5.5, 5), + 'coherent_ms': (1, 5, 10, 20)} + +l2c_track_params = { + 'fll_bw': (1, 0), + 'pll_bw': (20, 18, 16, 14, 12, 10, 8, 7, 6.5, 6, 5.5, 5), + 'coherent_ms': (20,)} +glol1_track_params = { + 'fll_bw': (2, 1, 0), + 'pll_bw': (40, 20, 18, 16, 14, 12, 10, 8, 7, 6.5, 6, 5.5, 5), + 'coherent_ms': (1,)} + +l1ca_loop_filter_params_template = { + 'code_params': (1., 0.7, 1.), # NBW, zeta, k + 'carr_params': (20., 0.7, 1.), # NBW, zeta, k + 'loop_freq': 1000., # 1000/coherent_ms + 'carr_freq_b1': 1., # FLL NBW + 'carr_to_code': 1540. # carr_to_code +} + +l2c_loop_filter_params_template = { + 'code_params': (1., 0.7, 1.), # NBW, zeta, k + 'carr_params': (20., 0.7, 1.), # NBW, zeta, k + 'loop_freq': 1000., # 1000/coherent_ms + 'carr_freq_b1': 1., # FLL NBW + 'carr_to_code': 1200. # carr_to_code +} + +glol1_loop_filter_params_template = { + 'code_params': (1., 0.7, 1.), # NBW, zeta, k + 'carr_params': (20., 0.7, 1.), # NBW, zeta, k + 'loop_freq': 1000., # 1000/coherent_ms + 'carr_freq_b1': 1., # FLL NBW + 'carr_to_code': 3135.0293542074364 # carr_to_code +} # Tracking stages. See track.c for more details. # 1;20 ms stages @@ -137,15 +251,15 @@ 'loop_filter_params': {'code_params': (1., 0.7, 1.), # NBW, zeta, k 'carr_params': (10., 0.7, 1.), # NBW, zeta, k 'loop_freq': 1000., # 1000/coherent_ms - 'carr_freq_igain': 5., # fll_aid + 'carr_freq_b1': 5., # fll_aid 'carr_to_code': 1540. # carr_to_code } }, {'coherent_ms': 20, 'loop_filter_params': {'code_params': (1., 0.7, 1.), # NBW, zeta, k - 'carr_params': (12., 0.7, 1.), # NBW, zeta, k + 'carr_params': (2., 0.7, 1.), # NBW, zeta, k 'loop_freq': 1000. / 20, # 1000/coherent_ms - 'carr_freq_igain': 0., # fll_aid + 'carr_freq_b1': 0., # fll_aid 'carr_to_code': 1540. # carr_to_code } } @@ -156,7 +270,7 @@ 'loop_filter_params': {'code_params': (1., 0.7, 1.), # NBW, zeta, k 'carr_params': (10., 0.7, 1.), # NBW, zeta, k 'loop_freq': 1000., # 1000/coherent_ms - 'carr_freq_igain': 5., # fll_aid + 'carr_freq_b1': 5., # fll_aid 'carr_to_code': 1540. # carr_to_code } }, @@ -164,28 +278,49 @@ 'loop_filter_params': {'code_params': (1., 0.7, 1.), # NBW, zeta, k 'carr_params': (12., 0.7, 1.), # NBW, zeta, k 'loop_freq': 1000. / 10, # 1000/coherent_ms - 'carr_freq_igain': 0., # fll_aid + 'carr_freq_b1': 0., # fll_aid 'carr_to_code': 1540. # carr_to_code } } ) +# 1;5 ms stages +# l1ca_stage_params_med = \ +# ({'coherent_ms': 1, +# 'loop_filter_params': {'code_params': (3., 0.7, 1.), # NBW, zeta, k +# 'carr_params': (15., 0.7, 1.), # NBW, zeta, k +# 'loop_freq': 1000., # 1000/coherent_ms +# 'carr_freq_b1': 5., # fll_aid +# 'carr_to_code': 1540. # carr_to_code +# } +# }, + +# {'coherent_ms': 2, +# 'loop_filter_params': {'code_params': (1., 0.7, 1.), # NBW, zeta, k +# 'carr_params': (30., 0.7, 1.), # NBW, zeta, k +# 'loop_freq': 1000./2, # 1000/coherent_ms +# 'carr_freq_b1': 0, # fll_aid +# 'carr_to_code': 1540. # carr_to_code +# } +# } +# ) + # 1;5 ms stages l1ca_stage_params_med = \ ({'coherent_ms': 1, 'loop_filter_params': {'code_params': (1., 0.7, 1.), # NBW, zeta, k 'carr_params': (10., 0.7, 1.), # NBW, zeta, k 'loop_freq': 1000., # 1000/coherent_ms - 'carr_freq_igain': 5., # fll_aid + 'carr_freq_b1': 5., # fll_aid 'carr_to_code': 1540. # carr_to_code } }, - {'coherent_ms': 5, - 'loop_filter_params': {'code_params': (1., 0.7, 1.), # NBW, zeta, k - 'carr_params': (50., 0.7, 1.), # NBW, zeta, k - 'loop_freq': 1000. / 5, # 1000/coherent_ms - 'carr_freq_igain': 0., # fll_aid + {'coherent_ms': 20, + 'loop_filter_params': {'code_params': (1., 0.707, 1.), # NBW, zeta, k + 'carr_params': (3, 0.707, 1.), # NBW, zeta, k + 'loop_freq': 1000. / 20, # 1000/coherent_ms + 'carr_freq_b1': 0., # fll_aid 'carr_to_code': 1540. # carr_to_code } } @@ -197,7 +332,7 @@ 'loop_filter_params': {'code_params': (1., 0.7, 1.), # NBW, zeta, k 'carr_params': (10., 0.7, 1.), # NBW, zeta, k 'loop_freq': 1000., # 1000/coherent_ms - 'carr_freq_igain': 5., # fll_aid + 'carr_freq_b1': 5., # fll_aid 'carr_to_code': 1540. # carr_to_code } }, @@ -205,7 +340,7 @@ 'loop_filter_params': {'code_params': (1., 0.7, 1.), # NBW, zeta, k 'carr_params': (62., 0.7, 1.), # NBW, zeta, k 'loop_freq': 1000. / 4, # 1000/coherent_ms - 'carr_freq_igain': 0., # fll_aid + 'carr_freq_b1': 0., # fll_aid 'carr_to_code': 1540. # carr_to_code } } @@ -217,7 +352,7 @@ 'loop_filter_params': {'code_params': (1., 0.7, 1.), # NBW, zeta, k 'carr_params': (10., 0.7, 1.), # NBW, zeta, k 'loop_freq': 1000., # 1000/coherent_ms - 'carr_freq_igain': 5., # fll_aid + 'carr_freq_b1': 5., # fll_aid 'carr_to_code': 1540. # carr_to_code } }, @@ -225,7 +360,7 @@ 'loop_filter_params': {'code_params': (1., 0.7, 1.), # NBW, zeta, k 'carr_params': (100., 0.7, 1.), # NBW, zeta, k 'loop_freq': 1000. / 2, # 1000/coherent_ms - 'carr_freq_igain': 0., # fll_aid + 'carr_freq_b1': 0., # fll_aid 'carr_to_code': 1540. # carr_to_code } } @@ -238,14 +373,890 @@ 'fast': l1ca_stage_params_fast, 'extrafast': l1ca_stage_params_extrafast} +ALIAS_DETECT_1ST = 1 +ALIAS_DETECT_2ND = 2 +ALIAS_DETECT_BOTH = 3 +RUN_LD = 4 +APPLY_CORR_1 = 5 +APPLY_CORR_2 = 6 +GET_CORR_1 = 7 +GET_CORR_2 = 8 +COMPENSATE_BIT_POLARITY = 9 +USE_COMPENSATED_BIT = 10 +PREPARE_BIT_COMPENSATION = 11 + +glo_fsm_states = \ + {'1ms': + {'no_bit_sync': + {'short_n_long': + {0: (511, 1, {'pre': (APPLY_CORR_1,), + 'post': (RUN_LD, GET_CORR_1, ALIAS_DETECT_1ST)}), + 1: (511, 0, {'pre': (APPLY_CORR_2,), + 'post': (RUN_LD, GET_CORR_2, ALIAS_DETECT_2ND)})}, + + 'ideal': + {0: (511, 1, {'pre': (APPLY_CORR_1,), + 'post': (RUN_LD, GET_CORR_1, ALIAS_DETECT_1ST)}), + 1: (511, 0, {'pre': (APPLY_CORR_1,), + 'post': (RUN_LD, GET_CORR_1, ALIAS_DETECT_2ND)})} + } + } + } + +gps_fsm_states = \ + {'1ms': + {'no_bit_sync': + {'short_n_long': + {0: (1023, 1, {'pre': (APPLY_CORR_1,), + 'post': (RUN_LD, GET_CORR_1, ALIAS_DETECT_1ST)}), + 1: (1023, 0, {'pre': (APPLY_CORR_2,), + 'post': (RUN_LD, GET_CORR_2, ALIAS_DETECT_2ND)})}, + + 'ideal': + {0: (1023, 1, {'pre': (APPLY_CORR_1,), + 'post': (RUN_LD, GET_CORR_1, ALIAS_DETECT_1ST)}), + 1: (1023, 0, {'pre': (APPLY_CORR_1,), + 'post': (RUN_LD, GET_CORR_1, ALIAS_DETECT_2ND)})} + }, + 'bit_sync': + {'short_n_long': + {0: (1023, 1, {'pre': (APPLY_CORR_1,), + 'post': (RUN_LD, ALIAS_DETECT_1ST, GET_CORR_1)}), + 1: (1023, 2, {'pre': (APPLY_CORR_2,), + 'post': (RUN_LD, ALIAS_DETECT_BOTH, GET_CORR_2)}), + 2: (1023, 3, {'pre': (APPLY_CORR_1,), + 'post': (RUN_LD, ALIAS_DETECT_BOTH, GET_CORR_1)}), + 3: (1023, 4, {'pre': (APPLY_CORR_2,), + 'post': (RUN_LD, ALIAS_DETECT_BOTH, GET_CORR_2)}), + 4: (1023, 5, {'pre': (APPLY_CORR_1,), + 'post': (RUN_LD, ALIAS_DETECT_BOTH, GET_CORR_1)}), + 5: (1023, 6, {'pre': (APPLY_CORR_2,), + 'post': (RUN_LD, ALIAS_DETECT_BOTH, GET_CORR_2)}), + 6: (1023, 7, {'pre': (APPLY_CORR_1,), + 'post': (RUN_LD, ALIAS_DETECT_BOTH, GET_CORR_1)}), + 7: (1023, 8, {'pre': (APPLY_CORR_2,), + 'post': (RUN_LD, ALIAS_DETECT_BOTH, GET_CORR_2)}), + 8: (1023, 9, {'pre': (APPLY_CORR_1,), + 'post': (RUN_LD, ALIAS_DETECT_BOTH, GET_CORR_1)}), + 9: (1023, 10, {'pre': (APPLY_CORR_2,), + 'post': (RUN_LD, ALIAS_DETECT_BOTH, GET_CORR_2)}), + 10: (1023, 11, {'pre': (APPLY_CORR_1,), + 'post': (RUN_LD, ALIAS_DETECT_BOTH, GET_CORR_1)}), + 11: (1023, 12, {'pre': (APPLY_CORR_2,), + 'post': (RUN_LD, ALIAS_DETECT_BOTH, GET_CORR_2)}), + 12: (1023, 13, {'pre': (APPLY_CORR_1,), + 'post': (RUN_LD, ALIAS_DETECT_BOTH, GET_CORR_1)}), + 13: (1023, 14, {'pre': (APPLY_CORR_2,), + 'post': (RUN_LD, ALIAS_DETECT_BOTH, GET_CORR_2)}), + 14: (1023, 15, {'pre': (APPLY_CORR_1,), + 'post': (RUN_LD, ALIAS_DETECT_BOTH, GET_CORR_1)}), + 15: (1023, 16, {'pre': (APPLY_CORR_2,), + 'post': (RUN_LD, ALIAS_DETECT_BOTH, GET_CORR_2)}), + 16: (1023, 17, {'pre': (APPLY_CORR_1,), + 'post': (RUN_LD, ALIAS_DETECT_BOTH, GET_CORR_1)}), + 17: (1023, 18, {'pre': (APPLY_CORR_2,), + 'post': (RUN_LD, ALIAS_DETECT_BOTH, GET_CORR_2)}), + 18: (1023, 19, {'pre': (APPLY_CORR_1,), + 'post': (RUN_LD, ALIAS_DETECT_BOTH, GET_CORR_1)}), + 19: (1023, 0, {'pre': (APPLY_CORR_2,), + 'post': (RUN_LD, ALIAS_DETECT_2ND, GET_CORR_2)})}, + 'ideal': + {0: (1023, 1, {'pre': (APPLY_CORR_1,), + 'post': (RUN_LD, ALIAS_DETECT_1ST, GET_CORR_1)}), + 1: (1023, 2, {'pre': (APPLY_CORR_1,), + 'post': (RUN_LD, ALIAS_DETECT_BOTH, GET_CORR_1)}), + 2: (1023, 3, {'pre': (APPLY_CORR_1,), + 'post': (RUN_LD, ALIAS_DETECT_BOTH, GET_CORR_1)}), + 3: (1023, 4, {'pre': (APPLY_CORR_1,), + 'post': (RUN_LD, ALIAS_DETECT_BOTH, GET_CORR_1)}), + 4: (1023, 5, {'pre': (APPLY_CORR_1,), + 'post': (RUN_LD, ALIAS_DETECT_BOTH, GET_CORR_1)}), + 5: (1023, 6, {'pre': (APPLY_CORR_1,), + 'post': (RUN_LD, ALIAS_DETECT_BOTH, GET_CORR_1)}), + 6: (1023, 7, {'pre': (APPLY_CORR_1,), + 'post': (RUN_LD, ALIAS_DETECT_BOTH, GET_CORR_1)}), + 7: (1023, 8, {'pre': (APPLY_CORR_1,), + 'post': (RUN_LD, ALIAS_DETECT_BOTH, GET_CORR_1)}), + 8: (1023, 9, {'pre': (APPLY_CORR_1,), + 'post': (RUN_LD, ALIAS_DETECT_BOTH, GET_CORR_1)}), + 9: (1023, 10, {'pre': (APPLY_CORR_1,), + 'post': (RUN_LD, ALIAS_DETECT_BOTH, GET_CORR_1)}), + 10: (1023, 11, {'pre': (APPLY_CORR_1,), + 'post': (RUN_LD, ALIAS_DETECT_BOTH, GET_CORR_1)}), + 11: (1023, 12, {'pre': (APPLY_CORR_1,), + 'post': (RUN_LD, ALIAS_DETECT_BOTH, GET_CORR_1)}), + 12: (1023, 13, {'pre': (APPLY_CORR_1,), + 'post': (RUN_LD, ALIAS_DETECT_BOTH, GET_CORR_1)}), + 13: (1023, 14, {'pre': (APPLY_CORR_1,), + 'post': (RUN_LD, ALIAS_DETECT_BOTH, GET_CORR_1)}), + 14: (1023, 15, {'pre': (APPLY_CORR_1,), + 'post': (RUN_LD, ALIAS_DETECT_BOTH, GET_CORR_1)}), + 15: (1023, 16, {'pre': (APPLY_CORR_1,), + 'post': (RUN_LD, ALIAS_DETECT_BOTH, GET_CORR_1)}), + 16: (1023, 17, {'pre': (APPLY_CORR_1,), + 'post': (RUN_LD, ALIAS_DETECT_BOTH, GET_CORR_1)}), + 17: (1023, 18, {'pre': (APPLY_CORR_1,), + 'post': (RUN_LD, ALIAS_DETECT_BOTH, GET_CORR_1)}), + 18: (1023, 19, {'pre': (APPLY_CORR_1,), + 'post': (RUN_LD, ALIAS_DETECT_BOTH, GET_CORR_1)}), + 19: (1023, 0, {'pre': (APPLY_CORR_1,), + 'post': (RUN_LD, ALIAS_DETECT_2ND, GET_CORR_1)})}, + } + }, + + '2ms': + {'bit_sync': + {'short_n_long': + {0: (1023, 1, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_1ST)}), + 1: (1023, 2, {'pre': (APPLY_CORR_1,), + 'post': (RUN_LD, ALIAS_DETECT_BOTH, GET_CORR_1)}), + 2: (1023, 3, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH)}), + 3: (1023, 4, {'pre': (APPLY_CORR_1,), + 'post': (RUN_LD, ALIAS_DETECT_BOTH, GET_CORR_1)}), + 4: (1023, 5, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH)}), + 5: (1023, 6, {'pre': (APPLY_CORR_1,), + 'post': (RUN_LD, ALIAS_DETECT_BOTH, GET_CORR_1)}), + 6: (1023, 7, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH)}), + 7: (1023, 8, {'pre': (APPLY_CORR_1,), + 'post': (RUN_LD, ALIAS_DETECT_BOTH, GET_CORR_1)}), + 8: (1023, 9, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH)}), + 9: (1023, 10, {'pre': (APPLY_CORR_1,), + 'post': (RUN_LD, ALIAS_DETECT_BOTH, GET_CORR_1)}), + 10: (1023, 11, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH)}), + 11: (1023, 12, {'pre': (APPLY_CORR_1,), + 'post': (RUN_LD, ALIAS_DETECT_BOTH, GET_CORR_1)}), + 12: (1023, 13, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH)}), + 13: (1023, 14, {'pre': (APPLY_CORR_1,), + 'post': (RUN_LD, ALIAS_DETECT_BOTH, GET_CORR_1)}), + 14: (1023, 15, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH)}), + 15: (1023, 16, {'pre': (APPLY_CORR_1,), + 'post': (RUN_LD, ALIAS_DETECT_BOTH, GET_CORR_1)}), + 16: (1023, 17, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH)}), + 17: (1023, 18, {'pre': (APPLY_CORR_1,), + 'post': (RUN_LD, ALIAS_DETECT_BOTH, GET_CORR_1)}), + 18: (1023, 19, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH)}), + 19: (1023, 0, {'pre': (APPLY_CORR_1,), + 'post': (RUN_LD, ALIAS_DETECT_2ND, GET_CORR_1)})}, + 'ideal': + {0: (1023, 1, {'pre': (APPLY_CORR_1,), + 'post': (RUN_LD, ALIAS_DETECT_1ST)}), + 1: (1023, 2, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH, GET_CORR_1)}), + 2: (1023, 3, {'pre': (APPLY_CORR_1,), + 'post': (RUN_LD, ALIAS_DETECT_BOTH)}), + 3: (1023, 4, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH, GET_CORR_1)}), + 4: (1023, 5, {'pre': (APPLY_CORR_1,), + 'post': (RUN_LD, ALIAS_DETECT_BOTH)}), + 5: (1023, 6, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH, GET_CORR_1)}), + 6: (1023, 7, {'pre': (APPLY_CORR_1,), + 'post': (RUN_LD, ALIAS_DETECT_BOTH)}), + 7: (1023, 8, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH, GET_CORR_1)}), + 8: (1023, 9, {'pre': (APPLY_CORR_1,), + 'post': (RUN_LD, ALIAS_DETECT_BOTH)}), + 9: (1023, 10, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH, GET_CORR_1)}), + 10: (1023, 11, {'pre': (APPLY_CORR_1,), + 'post': (RUN_LD, ALIAS_DETECT_BOTH)}), + 11: (1023, 12, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH, GET_CORR_1)}), + 12: (1023, 13, {'pre': (APPLY_CORR_1,), + 'post': (RUN_LD, ALIAS_DETECT_BOTH)}), + 13: (1023, 14, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH, GET_CORR_1)}), + 14: (1023, 15, {'pre': (APPLY_CORR_1,), + 'post': (RUN_LD, ALIAS_DETECT_BOTH)}), + 15: (1023, 16, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH, GET_CORR_1)}), + 16: (1023, 17, {'pre': (APPLY_CORR_1,), + 'post': (RUN_LD, ALIAS_DETECT_BOTH)}), + 17: (1023, 18, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH, GET_CORR_1)}), + 18: (1023, 19, {'pre': (APPLY_CORR_1,), + 'post': (RUN_LD, ALIAS_DETECT_BOTH)}), + 19: (1023, 0, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_2ND, GET_CORR_1)})}, + } + }, + + '4ms': + {'bit_sync': + {'short_n_long': + {0: (1023, 1, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_1ST)}), + 1: (1023, 2, {'pre': (APPLY_CORR_1,), + 'post': (RUN_LD, ALIAS_DETECT_BOTH)}), + 2: (1023, 3, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH)}), + 3: (1023, 4, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH, GET_CORR_1)}), + 4: (1023, 5, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH)}), + 5: (1023, 6, {'pre': (APPLY_CORR_1,), + 'post': (RUN_LD, ALIAS_DETECT_BOTH)}), + 6: (1023, 7, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH)}), + 7: (1023, 8, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH, GET_CORR_1)}), + 8: (1023, 9, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH)}), + 9: (1023, 10, {'pre': (APPLY_CORR_1,), + 'post': (RUN_LD, ALIAS_DETECT_BOTH)}), + 10: (1023, 11, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH)}), + 11: (1023, 12, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH, GET_CORR_1)}), + 12: (1023, 13, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH)}), + 13: (1023, 14, {'pre': (APPLY_CORR_1,), + 'post': (RUN_LD, ALIAS_DETECT_BOTH)}), + 14: (1023, 15, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH)}), + 15: (1023, 16, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH, GET_CORR_1)}), + 16: (1023, 17, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH)}), + 17: (1023, 18, {'pre': (APPLY_CORR_1,), + 'post': (RUN_LD, ALIAS_DETECT_BOTH)}), + 18: (1023, 19, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH)}), + 19: (1023, 0, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_2ND, GET_CORR_1)})}, + 'ideal': + {0: (1023, 1, {'pre': (APPLY_CORR_1,), + 'post': (RUN_LD, ALIAS_DETECT_1ST)}), + 1: (1023, 2, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH)}), + 2: (1023, 3, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH)}), + 3: (1023, 4, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH, GET_CORR_1)}), + 4: (1023, 5, {'pre': (APPLY_CORR_1,), + 'post': (RUN_LD, ALIAS_DETECT_BOTH)}), + 5: (1023, 6, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH)}), + 6: (1023, 7, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH)}), + 7: (1023, 8, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH, GET_CORR_1)}), + 8: (1023, 9, {'pre': (APPLY_CORR_1,), + 'post': (RUN_LD, ALIAS_DETECT_BOTH)}), + 9: (1023, 10, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH)}), + 10: (1023, 11, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH)}), + 11: (1023, 12, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH, GET_CORR_1)}), + 12: (1023, 13, {'pre': (APPLY_CORR_1,), + 'post': (RUN_LD, ALIAS_DETECT_BOTH)}), + 13: (1023, 14, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH)}), + 14: (1023, 15, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH)}), + 15: (1023, 16, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH, GET_CORR_1)}), + 16: (1023, 17, {'pre': (APPLY_CORR_1,), + 'post': (RUN_LD, ALIAS_DETECT_BOTH)}), + 17: (1023, 18, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH)}), + 18: (1023, 19, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH)}), + 19: (1023, 0, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_2ND, GET_CORR_1)})}, + } + }, + + '5ms': + {'bit_sync': + {'short_n_long': + {0: (1023, 1, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_1ST)}), + 1: (1023, 2, {'pre': (APPLY_CORR_1,), + 'post': (RUN_LD, ALIAS_DETECT_BOTH)}), + 2: (1023, 3, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH)}), + 3: (1023, 4, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH)}), + 4: (1023, 5, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH, GET_CORR_1)}), + 5: (1023, 6, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH)}), + 6: (1023, 7, {'pre': (APPLY_CORR_1,), + 'post': (RUN_LD, ALIAS_DETECT_BOTH)}), + 7: (1023, 8, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH)}), + 8: (1023, 9, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH)}), + 9: (1023, 10, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH, GET_CORR_1)}), + 10: (1023, 11, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH)}), + 11: (1023, 12, {'pre': (APPLY_CORR_1,), + 'post': (RUN_LD, ALIAS_DETECT_BOTH)}), + 12: (1023, 13, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH)}), + 13: (1023, 14, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH)}), + 14: (1023, 15, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH, GET_CORR_1)}), + 15: (1023, 16, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH)}), + 16: (1023, 17, {'pre': (APPLY_CORR_1,), + 'post': (RUN_LD, ALIAS_DETECT_BOTH)}), + 17: (1023, 18, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH)}), + 18: (1023, 19, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH)}), + 19: (1023, 0, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_2ND, GET_CORR_1)})}, + 'ideal': + {0: (1023, 1, {'pre': (APPLY_CORR_1,), + 'post': (RUN_LD, ALIAS_DETECT_1ST)}), + 1: (1023, 2, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH)}), + 2: (1023, 3, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH)}), + 3: (1023, 4, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH)}), + 4: (1023, 5, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH, GET_CORR_1)}), + 5: (1023, 6, {'pre': (APPLY_CORR_1,), + 'post': (RUN_LD, ALIAS_DETECT_BOTH)}), + 6: (1023, 7, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH)}), + 7: (1023, 8, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH)}), + 8: (1023, 9, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH)}), + 9: (1023, 10, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH, GET_CORR_1)}), + 10: (1023, 11, {'pre': (APPLY_CORR_1,), + 'post': (RUN_LD, ALIAS_DETECT_BOTH)}), + 11: (1023, 12, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH)}), + 12: (1023, 13, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH)}), + 13: (1023, 14, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH)}), + 14: (1023, 15, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH, GET_CORR_1)}), + 15: (1023, 16, {'pre': (APPLY_CORR_1,), + 'post': (RUN_LD, ALIAS_DETECT_BOTH)}), + 16: (1023, 17, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH)}), + 17: (1023, 18, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH)}), + 18: (1023, 19, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH)}), + 19: (1023, 0, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_2ND, GET_CORR_1)})}, + } + }, + + '10ms': + {'bit_sync': + {'short_n_long': + {0: (1023, 1, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_1ST)}), + 1: (1023, 2, {'pre': (APPLY_CORR_1,), + 'post': (RUN_LD, ALIAS_DETECT_BOTH)}), + 2: (1023, 3, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH)}), + 3: (1023, 4, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH)}), + 4: (1023, 5, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH)}), + 5: (1023, 6, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH)}), + 6: (1023, 7, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH)}), + 7: (1023, 8, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH)}), + 8: (1023, 9, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH)}), + 9: (1023, 10, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH, GET_CORR_1)}), + 10: (1023, 11, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH)}), + 11: (1023, 12, {'pre': (APPLY_CORR_1,), + 'post': (RUN_LD, ALIAS_DETECT_BOTH)}), + 12: (1023, 13, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH)}), + 13: (1023, 14, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH)}), + 14: (1023, 15, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH)}), + 15: (1023, 16, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH)}), + 16: (1023, 17, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH)}), + 17: (1023, 18, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH)}), + 18: (1023, 19, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH)}), + 19: (1023, 0, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_2ND, GET_CORR_1)})}, + 'ideal': + {0: (1023, 1, {'pre': (APPLY_CORR_1,), + 'post': (RUN_LD, ALIAS_DETECT_1ST)}), + 1: (1023, 2, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH)}), + 2: (1023, 3, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH)}), + 3: (1023, 4, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH)}), + 4: (1023, 5, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH)}), + 5: (1023, 6, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH)}), + 6: (1023, 7, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH)}), + 7: (1023, 8, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH)}), + 8: (1023, 9, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH)}), + 9: (1023, 10, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH, GET_CORR_1)}), + 10: (1023, 11, {'pre': (APPLY_CORR_1,), + 'post': (RUN_LD, ALIAS_DETECT_BOTH)}), + 11: (1023, 12, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH)}), + 12: (1023, 13, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH)}), + 13: (1023, 14, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH)}), + 14: (1023, 15, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH)}), + 15: (1023, 16, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH)}), + 16: (1023, 17, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH)}), + 17: (1023, 18, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH)}), + 18: (1023, 19, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH)}), + 19: (1023, 0, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_2ND, GET_CORR_1)})}, + } + }, + + '20ms': + {'bit_sync': + {'short_n_long': + {0: (1023, 1, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_1ST,)}), + 1: (1023, 2, {'pre': (APPLY_CORR_1,), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 2: (1023, 3, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 3: (1023, 4, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 4: (1023, 5, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 5: (1023, 6, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 6: (1023, 7, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 7: (1023, 8, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 8: (1023, 9, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 9: (1023, 10, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 10: (1023, 11, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 11: (1023, 12, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 12: (1023, 13, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 13: (1023, 14, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 14: (1023, 15, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 15: (1023, 16, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 16: (1023, 17, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 17: (1023, 18, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 18: (1023, 19, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 19: (1023, 0, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_2ND, GET_CORR_1)})}, + 'ideal': + {0: (1023, 1, {'pre': (APPLY_CORR_1,), + 'post': (RUN_LD, ALIAS_DETECT_1ST,)}), + 1: (1023, 2, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 2: (1023, 3, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 3: (1023, 4, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 4: (1023, 5, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 5: (1023, 6, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 6: (1023, 7, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 7: (1023, 8, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 8: (1023, 9, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 9: (1023, 10, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 10: (1023, 11, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 11: (1023, 12, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 12: (1023, 13, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 13: (1023, 14, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 14: (1023, 15, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 15: (1023, 16, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 16: (1023, 17, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 17: (1023, 18, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 18: (1023, 19, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 19: (1023, 0, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_2ND, GET_CORR_1)})}, + } + }, + + '40ms': + {'bit_sync': + {'short_n_long': + {0: (1023, 1, {'pre': (PREPARE_BIT_COMPENSATION,), + 'post': (RUN_LD, ALIAS_DETECT_1ST,)}), + 1: (1023, 2, {'pre': (APPLY_CORR_1,), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 2: (1023, 3, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 3: (1023, 4, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 4: (1023, 5, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 5: (1023, 6, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 6: (1023, 7, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 7: (1023, 8, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 8: (1023, 9, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 9: (1023, 10, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 10: (1023, 11, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 11: (1023, 12, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 12: (1023, 13, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 13: (1023, 14, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 14: (1023, 15, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 15: (1023, 16, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 16: (1023, 17, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 17: (1023, 18, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 18: (1023, 19, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_2ND,)}), + 19: (1023, 20, {'pre': (), + 'post': (RUN_LD, + ALIAS_DETECT_1ST, + COMPENSATE_BIT_POLARITY)}), + 20: (1023, 21, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 21: (1023, 22, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 22: (1023, 23, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 23: (1023, 24, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 24: (1023, 25, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 25: (1023, 26, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 26: (1023, 27, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 27: (1023, 28, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 28: (1023, 29, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 29: (1023, 30, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 30: (1023, 31, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 31: (1023, 32, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 32: (1023, 33, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 33: (1023, 34, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 34: (1023, 35, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 35: (1023, 36, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 36: (1023, 37, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 37: (1023, 38, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 38: (1023, 39, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 39: (1023, 0, {'pre': (), + 'post': (RUN_LD, + ALIAS_DETECT_2ND, + GET_CORR_1, + COMPENSATE_BIT_POLARITY, + USE_COMPENSATED_BIT)})}, + 'ideal': + {0: (1023, 1, {'pre': (APPLY_CORR_1,), + 'post': (RUN_LD, ALIAS_DETECT_1ST,)}), + 1: (1023, 2, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 2: (1023, 3, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 3: (1023, 4, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 4: (1023, 5, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 5: (1023, 6, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 6: (1023, 7, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 7: (1023, 8, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 8: (1023, 9, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 9: (1023, 10, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 10: (1023, 11, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 11: (1023, 12, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 12: (1023, 13, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 13: (1023, 14, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 14: (1023, 15, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 15: (1023, 16, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 16: (1023, 17, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 17: (1023, 18, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 18: (1023, 19, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 19: (1023, 0, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_2ND, GET_CORR_1)})}, + } + }, + + '80ms': + {'bit_sync': + {'short_n_long': + {0: (1023, 1, {'pre': (PREPARE_BIT_COMPENSATION,), + 'post': (RUN_LD, ALIAS_DETECT_1ST,)}), + 1: (1023, 2, {'pre': (APPLY_CORR_1,), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 2: (1023, 3, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 3: (1023, 4, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 4: (1023, 5, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 5: (1023, 6, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 6: (1023, 7, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 7: (1023, 8, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 8: (1023, 9, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 9: (1023, 10, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 10: (1023, 11, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 11: (1023, 12, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 12: (1023, 13, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 13: (1023, 14, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 14: (1023, 15, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 15: (1023, 16, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 16: (1023, 17, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 17: (1023, 18, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 18: (1023, 19, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_2ND,)}), + 19: (1023, 20, {'pre': (), + 'post': (RUN_LD, + ALIAS_DETECT_1ST, + COMPENSATE_BIT_POLARITY)}), + 20: (1023, 21, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 21: (1023, 22, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 22: (1023, 23, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 23: (1023, 24, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 24: (1023, 25, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 25: (1023, 26, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 26: (1023, 27, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 27: (1023, 28, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 28: (1023, 29, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 29: (1023, 30, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 30: (1023, 31, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 31: (1023, 32, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 32: (1023, 33, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 33: (1023, 34, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 34: (1023, 35, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 35: (1023, 36, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 36: (1023, 37, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 37: (1023, 38, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 38: (1023, 39, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 39: (1023, 40, {'pre': (), + 'post': (RUN_LD, + ALIAS_DETECT_2ND, + COMPENSATE_BIT_POLARITY,)}), + 40: (1023, 41, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_1ST,)}), + 41: (1023, 42, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 42: (1023, 43, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 43: (1023, 44, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 44: (1023, 45, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 45: (1023, 46, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 46: (1023, 47, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 47: (1023, 48, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 48: (1023, 49, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 49: (1023, 50, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 50: (1023, 51, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 51: (1023, 52, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 52: (1023, 53, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 53: (1023, 54, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 54: (1023, 55, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 55: (1023, 56, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 56: (1023, 57, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 57: (1023, 58, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 58: (1023, 59, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_2ND,)}), + 59: (1023, 60, {'pre': (), + 'post': (RUN_LD, + ALIAS_DETECT_1ST, + COMPENSATE_BIT_POLARITY)}), + 60: (1023, 61, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 61: (1023, 62, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 62: (1023, 63, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 63: (1023, 64, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 64: (1023, 65, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 65: (1023, 66, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 66: (1023, 67, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 67: (1023, 68, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 68: (1023, 69, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 69: (1023, 70, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 70: (1023, 71, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 71: (1023, 72, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 72: (1023, 73, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 73: (1023, 74, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 74: (1023, 75, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 75: (1023, 76, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 76: (1023, 77, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 77: (1023, 78, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 78: (1023, 79, {'pre': (), + 'post': (RUN_LD, ALIAS_DETECT_BOTH,)}), + 79: (1023, 0, {'pre': (), + 'post': (RUN_LD, + ALIAS_DETECT_2ND, + GET_CORR_1, + COMPENSATE_BIT_POLARITY, + USE_COMPENSATED_BIT)})}, + } + } + } + # pessimistic set l1ca_lock_detect_params_pess = {"k1": 0.10, "k2": 1.4, "lp": 200, "lo": 50} # normal set -l1ca_lock_detect_params_normal = {"k1": 0.05, "k2": 1.4, "lp": 150, "lo": 50} +l1ca_lock_detect_params_normal = {"k1": 0.2, "k2": .8, "lp": 150, "lo": 50} # optimal set -l1ca_lock_detect_params_opt = {"k1": 0.02, "k2": 1.1, "lp": 150, "lo": 50} +l1ca_lock_detect_params_opt = {"k1": 0.02, "k2": 1.1, "lp": 50, "lo": 150} # extra optimal set l1ca_lock_detect_params_extraopt = {"k1": 0.02, "k2": 0.8, "lp": 150, "lo": 50} @@ -253,6 +1264,19 @@ # disable lock detect l1ca_lock_detect_params_disable = {"k1": 0.02, "k2": 1e-6, "lp": 1, "lo": 1} +lock_detect_params_slow = {"k1": 0.005, "k2": 1.4, "lp": 200, "lo": 50} +lock_detect_params_fast = ( + (40, {"k1": 0.2, "k2": .80, "lp": 50, "lo": 50}), + (35, {"k1": 0.2, "k2": .83, "lp": 50, "lo": 50}), + (30, {"k1": 0.2, "k2": .86, "lp": 50, "lo": 50}), + (25, {"k1": 0.2, "k2": .89, "lp": 50, "lo": 50}), + (20, {"k1": 0.2, "k2": .92, "lp": 50, "lo": 50}), + (15, {"k1": 0.2, "k2": .95, "lp": 50, "lo": 50}), + (10, {"k1": 0.2, "k2": .98, "lp": 50, "lo": 50}), + (5, {"k1": 0.2, "k2": 1.0, "lp": 50, "lo": 50})) + +tracking_loop_stabilization_time_ms = 50 + # L2C 20ms lock detect profile # References: # - Understanding GPS: Principles and Applications. @@ -264,7 +1288,18 @@ 'lp': 50, # 1000ms worth of I/Q samples to reach pessimistic lock 'lo': 240} # 4800ms worth of I/Q samples to lower optimistic lock +glol1_lock_detect_params = l1ca_lock_detect_params_opt + +# The time interval, over which the alias detection is done. +# The alias detect algorithm averages the phase angle over this time [ms] alias_detect_interval_ms = 500 +# The correlator intermediate results are read with this timeout in [ms]. +# The intermediate results are the input for the alias lock detector. +alias_detect_slice_ms = 1 + # Default pipelining prediction coefficient pipelining_k = .9549 + +# Default coherent integration time for L2C tracker +l2c_coherent_integration_time_ms = 20 diff --git a/peregrine/glo_constants.py b/peregrine/glo_constants.py new file mode 100644 index 0000000..65ece04 --- /dev/null +++ b/peregrine/glo_constants.py @@ -0,0 +1,15 @@ +# -*- coding: utf-8 -*- +from gps_constants import c +# GLO system parameters +glo_l1 = 1.602e9 # Hz +glo_l2 = 1.246e9 # Hz +glo_code_len = 511 +glo_chip_rate = 0.511e6 # Hz +glo_l1_step = 0.5625e6 # Hz +glo_l2_step = 0.4375e6 # Hz + +glo_code_period = glo_code_len / glo_chip_rate +glo_code_wavelength = glo_code_period * c + +GLO_L1 = 'glo_l1' +GLO_L2 = 'glo_l2' diff --git a/peregrine/include/controlled_root.py b/peregrine/include/controlled_root.py new file mode 100644 index 0000000..466a5e2 --- /dev/null +++ b/peregrine/include/controlled_root.py @@ -0,0 +1,112 @@ +# -*- coding: utf-8 -*- +""" +Created on Thu Jul 14 14:29:34 2016 + +@author: tpaakki + +This script generates controlled-root loop parameters, based on +"Stephens, S. A., and J. C. Thomas, "Controlled-Root Formulation for +Digital Phase-Locked Loops," IEEE Trans. on Aerospace and Electronics + Systems, January 1995" + +""" +from math import factorial, exp +import cmath + +def controlled_root(N, T, BW): + # Input Parameters + # N [-] Loop Order + # T [s] Integration Time + # BW [Hz] Loop Bandwidth + # Output Parameters + # K [-] Loop constants + + K = [] + tol = 1.e-6 # Error tolerance + goal = BW*T # This is the BLT we want to solve + + # Few precomputed factorial parameters + if N > 1: + fac1 = factorial(N)/(factorial(1)*factorial(N-1)) # eq.(45) + if N > 2: + fac2 = factorial(N)/(factorial(2)*factorial(N-2)) # eq.(46) + fac3 = factorial(N-1)/(factorial(1)*factorial(N-1-1)) # eq.(46) + fac4 = factorial(N-2)/(factorial(1)*factorial(N-2-1)) # eq.(46) + + beta = 0.5 + step = 0.25 + done = True + ii = 1 + if N == 1: + while done: + z1 = exp(-beta) # eq.(50) + K1 = 1.-z1 # eq.(49) + blt = K1/(2.*(2.-K1)) # Table IV + err = goal-blt + if abs(err) <= tol: + K = [K1] + done = False + if err > 0.: + beta = beta + step + step = step / 2. + if err < 0.: + beta = beta - step + step = step / 2. + if ii > 30: + 'Error - did not converge' + done = False + ii = ii + 1; + if N == 2: + while done: + z1 = cmath.exp(-beta*(1.+1.j)) # eq.(50) + z2 = cmath.exp(-beta*(1.-1.j)) # eq.(50) + K1 = 1.-z1*z2 # eq.(49) + K1 = K1.real + K2 = fac1-K1-z1-z2 # eq.(45) + K2 = K2.real + blt = (2.*K1*K1+2.*K2+K1*K2)/(2.*K1*(4.-2*K1-K2)) # Table IV + err = goal-blt + if abs(err) <= tol: + K = K1, K2 + done = False + if err > 0.: + beta = beta + step + step = step / 2. + if err < 0.: + beta = beta - step + step = step / 2. + if ii > 30: + 'Error - did not converge' + done = False + ii = ii + 1; + if N == 3: + while done: + z1 = exp(-beta) # eq.(50) + z2 = cmath.exp(-beta*(1.+1.j)) # eq.(50) + z3 = cmath.exp(-beta*(1.-1.j)) # eq.(50) + K1 = 1-z1*z2*z3 # eq.(49) + K1 = K1.real + summ = z1*z2+z1*z3+z2*z3; + K2 = (fac2-fac3*K1-summ)/fac4 # eq.(46) + K2 = K2.real + K3 = fac1-K1-K2-z1-z2-z3 # eq.(45) + K3 = K3.real + blt = (4.*K1*K1*K2-4.*K1*K3+4.*K2*K2+2.*K1*K2*K2+4.*K1*K1*K3+4.*K2*K3 + +3*K1*K2*K3+K3*K3+K1*K3*K3)/(2.*(K1*K2-K3+K1*K3)*(8. + -4.*K1-2.*K2-K3)) # Table IV + err = goal-blt + if abs(err) <= tol: + K = K1, K2, K3 + done = False + if err > 0.: + beta = beta + step + step = step / 2. + if err < 0.: + beta = beta - step + step = step / 2. + if ii > 30: + 'Error - did not converge' + done = False + ii = ii + 1; + + return K diff --git a/peregrine/iqgen/generate.py b/peregrine/iqgen/generate.py index 7f236e3..94d9d28 100644 --- a/peregrine/iqgen/generate.py +++ b/peregrine/iqgen/generate.py @@ -25,7 +25,6 @@ import sys import traceback import logging -import scipy import scipy.constants import numpy import time @@ -434,8 +433,8 @@ def printSvInfo(sv_list, outputConfig, lpfFA_db, noiseParams, encoder): elif isinstance(_sv, GLOSatellite): band1 = outputConfig.GLONASS.L1 band2 = outputConfig.GLONASS.L2 - band1IncreaseDb = 60. - lpfFA_db[band1.INDEX] # GLONASS L1 - band2IncreaseDb = 60. - lpfFA_db[band2.INDEX] # GLONASS L2 + band1IncreaseDb = 57. - lpfFA_db[band1.INDEX] # GLONASS L1 + band2IncreaseDb = 57. - lpfFA_db[band2.INDEX] # GLONASS L2 signal1 = signals.GLONASS.L1S[_sv.prn] signal2 = signals.GLONASS.L2S[_sv.prn] _msg1 = _sv.getL1Message() @@ -546,8 +545,8 @@ def generateSamples(outputFile, # Print out parameters # logger.info( - "Generating samples, sample rate={} Hz, interval={} seconds".format( - outputConfig.SAMPLE_RATE_HZ, nSamples / outputConfig.SAMPLE_RATE_HZ)) + "Generating samples, sample rate={} Hz, interval={} seconds".format( + outputConfig.SAMPLE_RATE_HZ, nSamples / outputConfig.SAMPLE_RATE_HZ)) logger.debug("Jobs: %d" % threadCount) _count = 0l @@ -602,8 +601,8 @@ def generateSamples(outputFile, # Print out parameters logger.info( - "Generating samples, sample rate={} Hz, interval={} seconds".format( - outputConfig.SAMPLE_RATE_HZ, nSamples / outputConfig.SAMPLE_RATE_HZ)) + "Generating samples, sample rate={} Hz, interval={} seconds".format( + outputConfig.SAMPLE_RATE_HZ, nSamples / outputConfig.SAMPLE_RATE_HZ)) logger.debug("Jobs: %d" % threadCount) # Print out SV parameters printSvInfo(sv_list, outputConfig, lpfFA_db, noiseParams, encoder) diff --git a/peregrine/iqgen/iqgen_main.py b/peregrine/iqgen/iqgen_main.py index 5dd5852..ce0144a 100755 --- a/peregrine/iqgen/iqgen_main.py +++ b/peregrine/iqgen/iqgen_main.py @@ -85,6 +85,7 @@ from peregrine.iqgen.bits.tcxo_factory import factoryObject as tcxoFO from peregrine.log import default_logging_config +from peregrine import defaults logger = logging.getLogger(__name__) @@ -272,9 +273,9 @@ def doUpdate(self, sv, parser, namespace, values, option_string): raise ValueError("Signal band must be specified before doppler") elif isinstance(sv, GLOSatellite): if sv.isL1Enabled(): - frequency_hz = signals.GLONASS.L1S[sv.prn].CENTER_FREQUENCY_HZ + signal = signals.GLONASS.L1S[sv.prn] elif sv.isL2Enabled(): - frequency_hz = signals.GLONASS.L2S[sv.prn].CENTER_FREQUENCY_HZ + signal = signals.GLONASS.L2S[sv.prn] else: raise ValueError("Signal band must be specified before doppler") else: @@ -570,8 +571,7 @@ def __call__(self, parser, namespace, values, option_string=None): amplitudeGrp.add_argument('--amplitude-type', default="poly", choices=["poly", "sine"], - help= - "Configure amplitude type: polynomial or sine.", + help="Configure amplitude type: polynomial or sine.", action=UpdateAmplitudeType) amplitudeGrp.add_argument('--amplitude-units', default="snr-db", @@ -655,8 +655,7 @@ def __call__(self, parser, namespace, values, option_string=None): action=UpdateTcxoType) parser.add_argument('--group-delays', type=bool, - help= - "Enable/disable group delays simulation between bands") + help="Enable/disable group delays simulation between bands") parser.add_argument('--debug', type=argparse.FileType('wb'), help="Debug output file") @@ -666,7 +665,22 @@ def __call__(self, parser, namespace, values, option_string=None): help="Amount of data to generate, in seconds") parser.add_argument('--encoder', default="2bits", - choices=["1bit", "2bits"], + choices=["1bit", "2bits", + defaults.FORMAT_1BIT_X1_GPS_L1, + defaults.FORMAT_1BIT_X1_GPS_L2, + defaults.FORMAT_1BIT_X1_GLO_L1, + defaults.FORMAT_1BIT_X1_GLO_L2, + defaults.FORMAT_1BIT_X2_GPS_L1L2, + defaults.FORMAT_1BIT_X2_GLO_L1L2, + defaults.FORMAT_1BIT_X4_GPS_L1L2_GLO_L1L2, + defaults.FORMAT_2BITS_X1_GPS_L1, + defaults.FORMAT_2BITS_X1_GPS_L2, + defaults.FORMAT_2BITS_X1_GLO_L1, + defaults.FORMAT_2BITS_X1_GLO_L2, + defaults.FORMAT_2BITS_X2_GPS_L1L2, + defaults.FORMAT_2BITS_X2_GLO_L1L2, + defaults.FORMAT_2BITS_X4_GPS_L1L2_GLO_L1L2 + ], help="Output data format") parser.add_argument('--output', type=argparse.FileType('wb'), @@ -739,9 +753,9 @@ def printOutputConfig(outputConfig, args): print " GPS L1 IF: ", outputConfig.GPS.L1.INTERMEDIATE_FREQUENCY_HZ print " GPS L2 IF: ", outputConfig.GPS.L2.INTERMEDIATE_FREQUENCY_HZ print " GLONASS L1[0] IF:",\ - outputConfig.GLONASS.L1.INTERMEDIATE_FREQUENCIES_HZ[0] + outputConfig.GLONASS.L1.INTERMEDIATE_FREQUENCIES_HZ[0] print " GLONASS L2[0] IF:",\ - outputConfig.GLONASS.L2.INTERMEDIATE_FREQUENCIES_HZ[0] + outputConfig.GLONASS.L2.INTERMEDIATE_FREQUENCIES_HZ[0] print "Other parameters:" print " TCXO: ", args.tcxo print " noise sigma: ", args.noise_sigma @@ -801,8 +815,39 @@ def selectEncoder(encoderType, outputConfig, enabledBands): enabledGPS = enabledGPSL1 or enabledGPSL2 enabledGLONASS = enabledGLONASSL1 or enabledGLONASSL2 - # Configure data encoder - if encoderType == "1bit": + + # Explicitly defined encoders + if encoderType == defaults.FORMAT_1BIT_X1_GPS_L1: + encoder = GPSL1BitEncoder(outputConfig) + elif encoderType == defaults.FORMAT_1BIT_X1_GPS_L2: + encoder = GPSL2BitEncoder(outputConfig) + elif encoderType == defaults.FORMAT_1BIT_X2_GPS_L1L2: + encoder = GPSL1L2BitEncoder(outputConfig) + elif encoderType == defaults.FORMAT_1BIT_X1_GLO_L1: + encoder = GLONASSL1BitEncoder(outputConfig) + elif encoderType == defaults.FORMAT_1BIT_X1_GLO_L2: + encoder = GLONASSL2BitEncoder(outputConfig) + elif encoderType == defaults.FORMAT_1BIT_X2_GLO_L1L2: + encoder = GLONASSL1L2BitEncoder(outputConfig) + elif encoderType == defaults.FORMAT_1BIT_X4_GPS_L1L2_GLO_L1L2: + encoder = GPSGLONASSBitEncoder(outputConfig) + elif encoderType == defaults.FORMAT_2BITS_X1_GPS_L1: + encoder = GPSL1TwoBitsEncoder(outputConfig) + elif encoderType == defaults.FORMAT_2BITS_X1_GPS_L2: + encoder = GPSL2TwoBitsEncoder(outputConfig) + elif encoderType == defaults.FORMAT_2BITS_X2_GPS_L1L2: + encoder = GPSL1L2TwoBitsEncoder(outputConfig) + elif encoderType == defaults.FORMAT_2BITS_X1_GLO_L1: + encoder = GLONASSL1TwoBitsEncoder(outputConfig) + elif encoderType == defaults.FORMAT_2BITS_X1_GLO_L2: + encoder = GLONASSL2TwoBitsEncoder(outputConfig) + elif encoderType == defaults.FORMAT_2BITS_X2_GLO_L1L2: + encoder = GLONASSL1L2TwoBitsEncoder(outputConfig) + elif encoderType == defaults.FORMAT_2BITS_X4_GPS_L1L2_GLO_L1L2: + encoder = GPSGLONASSTwoBitsEncoder(outputConfig) + + # Encoder auto-detection + elif encoderType == "1bit": if enabledGPS and enabledGLONASS: encoder = GPSGLONASSBitEncoder(outputConfig) elif enabledGPS: diff --git a/peregrine/lock_detect.py b/peregrine/lock_detect.py new file mode 100644 index 0000000..7bd5eaa --- /dev/null +++ b/peregrine/lock_detect.py @@ -0,0 +1,140 @@ +# Copyright (C) 2016 Swift Navigation Inc. +# Contact: Adel Mamin +# +# This source is subject to the license found in the file 'LICENSE' which must +# be be distributed together with this source. All other rights reserved. +# +# THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +# EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. + +import numpy as np +from peregrine import defaults +from peregrine import gps_constants + + +class LockDetector(object): + """ + PLL lock detector implementation. + + """ + + def __init__(self, **kwargs): + """ + Initialize the lock detector parameters + + Parameters + ---------- + params : dictionary + k1 - 1st order IIR I & Q filter parameter + k2 - filtered in-phase divider + lp - pessimistic lock threshold + lo - optimistic lock threshold + + """ + + self.lpfi = 0 + self.lpfq = 0 + self.outo = False + self.outp = False + self.pcount1 = 0 + self.pcount2 = 0 + self.reinit(kwargs['k1'], + kwargs['k2'], + kwargs['lp'], + kwargs['lo']) + + def reinit(self, k1, k2, lp, lo): + """ + Adjust low-pass filter (LPF) coefficients + + Parameters + ---------- + params : dictionary + k1 - 1st order IIR I & Q filter parameter + k2 - filtered in-phase divider + lp - pessimistic lock threshold + lo - optimistic lock threshold + + """ + + self.k1 = k1 + self.k2 = k2 + self.lp = lp + self.lo = lo + + def _lpf_update(self, y, x): + """ + Low-pass filter (LPF) state update. + + Parameters + ---------- + y : float + old state + x : float + new input + lo - optimistic lock threshold + + Returns + ------- + out : float + Filtered output + + """ + + y += self.k1 * (x - y) + return y + + def update(self, I, Q, DT): + """ + Lock detector update. + + Parameters + ---------- + I : int + Prompt in-phase correlator output + Q : int + Prompt quadrature correlator output + DT : float + Time difference since the last update [ms] + + Returns + ------- + out : tuple + outo - optimistic lock detector output + outp - pessimistic lock detector output + pcount1 - the counter compared against the pessimistic lock threshold + pcount2 - the counter compared against the optimistic lock threshold + lpfi - filtered in-phase prompt correlator output + lpfq - filtered quadrature prompt correlator output + + """ + + # Calculated low-pass filtered prompt correlations + self.lpfi = self._lpf_update(self.lpfi, np.absolute(I) / DT) + self.lpfq = self._lpf_update(self.lpfq, np.absolute(Q) / DT) + + a = self.lpfi / self.k2 + b = self.lpfq + if a > b: + # In-phase > quadrature, looks like we're locked + self.outo = True + self.pcount2 = 0 + # Wait before raising the pessimistic indicator + if self.pcount1 > self.lp: + self.outp = True + else: + self.pcount1 += 1 + else: + # In-phase < quadrature, looks like we're not locked + self.outp = False + self.pcount1 = 0 + # Wait before lowering the optimistic indicator + if self.pcount2 > self.lo: + self.outo = False + else: + self.pcount2 += 1 + + return (self.outo, self.outp, + self.pcount1, self.pcount2, + self.lpfi, self.lpfq) diff --git a/peregrine/run.py b/peregrine/run.py index e99a06e..9a9e864 100755 --- a/peregrine/run.py +++ b/peregrine/run.py @@ -18,7 +18,8 @@ from operator import attrgetter from peregrine.samples import load_samples -from peregrine.acquisition import Acquisition, load_acq_results, save_acq_results +from peregrine.acquisition import Acquisition, load_acq_results,\ + save_acq_results from peregrine.navigation import navigation import peregrine.tracking as tracking from peregrine.log import default_logging_config @@ -27,6 +28,8 @@ from peregrine.tracking_file_utils import removeTrackingOutputFiles from peregrine.tracking_file_utils import TrackingResults from peregrine.tracking_file_utils import createTrackingDumpOutputFileName +import peregrine.glo_constants as glo +from peregrine.tracking_loop import TrackingLoop3, TrackingLoop3b, TrackingLoop2b class SaveConfigAction(argparse.Action): @@ -112,12 +115,10 @@ def populate_peregrine_cmd_line_arguments(parser): help="How many milliseconds to skip") inputCtrl.add_argument("-f", "--file-format", - choices=['piksi', 'int8', '1bit', '1bitrev', - '1bit_x2', '2bits', '2bits_x2', '2bits_x4'], + choices=defaults.file_encoding_profile.keys(), metavar='FORMAT', help="The format of the sample data file " - "('piksi', 'int8', '1bit', '1bitrev', " - "'1bit_x2', '2bits', '2bits_x2', '2bits_x4')") + "(%s)" % defaults.file_encoding_profile.keys()) inputCtrl.add_argument("--ms-to-process", metavar='MS', @@ -136,36 +137,16 @@ def populate_peregrine_cmd_line_arguments(parser): fpgaSim = parser.add_argument_group('FPGA simulation', 'FPGA delay control simulation') - fpgaExcl = fpgaSim.add_mutually_exclusive_group(required=False) - fpgaExcl.add_argument("--pipelining", - type=float, - nargs='?', - metavar='PIPELINING_K', - help="Use FPGA pipelining simulation. Supply optional " - " coefficient (%f)" % defaults.pipelining_k, - const=defaults.pipelining_k, - default=None) - - fpgaExcl.add_argument("--short-long-cycles", - type=float, - nargs='?', - metavar='PIPELINING_K', - help="Use FPGA short-long cycle simulation. Supply" - " optional pipelining coefficient (0.)", - const=0., - default=None) + + fpgaSim.add_argument("--short-long-cycles", + help="Use FPGA short-long cycle simulation.", + default=False, + action="store_true") signalParam = parser.add_argument_group('Signal tracking', 'Parameters for satellite vehicle' ' signal') - signalParam.add_argument('--l1ca-profile', - metavar='PROFILE', - help='L1 C/A stage profile. Controls coherent' - ' integration time and tuning parameters: %s.' % - str(defaults.l1ca_stage_profiles.keys()), - choices=defaults.l1ca_stage_profiles.keys()) - return signalParam @@ -183,6 +164,9 @@ def main(): parser.add_argument("-n", "--skip-navigation", help="use previously saved navigation results", action="store_true") + parser.add_argument("--skip-glonass", + help="skip glonass", + action="store_true") populate_peregrine_cmd_line_arguments(parser) @@ -206,16 +190,8 @@ def main(): else: raise NotImplementedError() - if args.l1ca_profile: - profile = defaults.l1ca_stage_profiles[args.l1ca_profile] - stage2_coherent_ms = profile[1]['coherent_ms'] - stage2_params = profile[1]['loop_filter_params'] - else: - stage2_coherent_ms = None - stage2_params = None - - if args.pipelining is not None: - tracker_options = {'mode': 'pipelining', 'k': args.pipelining} + if args.short_long_cycles: + tracker_options = {'mode': 'short-long-cycles'} else: tracker_options = None @@ -229,6 +205,8 @@ def main(): samples = {gps.L1CA: {'IF': freq_profile['GPS_L1_IF']}, gps.L2C: {'IF': freq_profile['GPS_L2_IF']}, + glo.GLO_L1: {'IF': freq_profile['GLO_L1_IF']}, + glo.GLO_L2: {'IF': freq_profile['GLO_L2_IF']}, 'samples_total': -1, 'sample_index': skip_samples} @@ -243,10 +221,29 @@ def main(): acq_results_file) sys.exit(1) else: - for signal in [gps.L1CA]: - - samplesPerCode = int(round(freq_profile['sampling_freq'] / - (gps.l1ca_chip_rate / gps.l1ca_code_length))) + encoding_profile = defaults.file_encoding_profile[args.file_format] + + acq_results = [] + for channel in encoding_profile: + if channel == defaults.sample_channel_GPS_L1: + signal = gps.L1CA + code_period = gps.l1ca_code_period + code_len = gps.l1ca_code_length + i_f = freq_profile['GPS_L1_IF'] + samplesPerCode = int(round(freq_profile['sampling_freq'] / + (gps.l1ca_chip_rate / gps.l1ca_code_length))) + elif channel == defaults.sample_channel_GLO_L1: + if args.skip_glonass: + continue + signal = glo.GLO_L1 + code_period = glo.glo_code_period + code_len = glo.glo_code_len + i_f = freq_profile['GLO_L1_IF'] + samplesPerCode = int(round(freq_profile['sampling_freq'] / + (glo.glo_chip_rate / glo.glo_code_len))) + else: + # No acquisition for other signals + continue # Get 11ms of acquisition samples for fine frequency estimation load_samples(samples=samples, @@ -257,13 +254,10 @@ def main(): acq = Acquisition(signal, samples[signal]['samples'], freq_profile['sampling_freq'], - freq_profile['GPS_L1_IF'], - gps.l1ca_code_period * freq_profile['sampling_freq'], - gps.l1ca_code_length) - # only one signal - L1CA is expected to be acquired at the moment - # TODO: add handling of acquisition results from GLONASS once GLONASS - # acquisition is supported. - acq_results = acq.acquisition(progress_bar_output=args.progress_bar) + i_f, + code_period * freq_profile['sampling_freq'], + code_len) + acq_results += acq.acquisition(progress_bar_output=args.progress_bar) print "Acquisition is over!" @@ -303,12 +297,11 @@ def main(): ms_to_track=ms_to_process, sampling_freq=freq_profile[ 'sampling_freq'], # [Hz] - stage2_coherent_ms=stage2_coherent_ms, - stage2_loop_filter_params=stage2_params, tracker_options=tracker_options, output_file=args.file, progress_bar_output=args.progress_bar, - check_l2c_mask=args.check_l2c_mask) + check_l2c_mask=args.check_l2c_mask, + loop_filter_class=TrackingLoop3) # The tracking channels are designed to support batch processing. # In the batch processing mode the data samples are provided in # batches (chunks) of 'defaults.processing_block_size' bytes size. diff --git a/peregrine/samples.py b/peregrine/samples.py index f2b077d..154ae99 100644 --- a/peregrine/samples.py +++ b/peregrine/samples.py @@ -14,6 +14,7 @@ import math import defaults from peregrine.gps_constants import L1CA, L2C +from peregrine.glo_constants import GLO_L1, GLO_L2 __all__ = ['load_samples', 'save_samples'] @@ -66,8 +67,7 @@ def __load_samples_n_bits(filename, num_samples, num_skip, n_bits, rounded_len = num_samples * sample_block_size bits = np.unpackbits(s_file) - samples = np.empty((n_rx, num_samples - sample_offset), - dtype=value_lookup.dtype) + samples = {} for rx in range(n_rx): # Construct multi-bit sample values @@ -78,7 +78,9 @@ def __load_samples_n_bits(filename, num_samples, num_skip, n_bits, # Generate sample values using value_lookup table chan = value_lookup[tmp] chan = chan[sample_offset:] - samples[channel_lookup[rx]][:] = chan + copy = np.empty(num_samples - sample_offset, dtype=value_lookup.dtype) + copy[:] = chan + samples[channel_lookup[rx]] = copy return samples @@ -139,9 +141,9 @@ def __load_samples_two_bits(filename, num_samples, num_skip, channel_lookup): def _load_samples(filename, - num_samples=defaults.processing_block_size, - num_skip=0, - file_format='piksi'): + num_samples=defaults.processing_block_size, + num_skip=0, + file_format='piksi'): """ Load sample data from a file. @@ -184,43 +186,55 @@ def _load_samples(filename, If `file_format` is unrecognised. """ - if file_format == 'int8': + + encoding_profile = defaults.file_encoding_profile[file_format] + + if file_format.startswith('int8'): with open(filename, 'rb') as f: f.seek(num_skip) - samples = np.zeros((1, num_samples), dtype=np.int8) - samples[:] = np.fromfile(f, dtype=np.int8, count=num_samples) - elif file_format == 'c8c8': + copy = np.empty(num_samples, dtype=np.int8) + copy[:] = np.fromfile(f, dtype=np.int8, count=num_samples) + samples = {} + samples[encoding_profile[0]] = copy + elif file_format.startswith('c8c8') and not file_format.startswith('c8c8_tayloe'): # Interleaved complex samples from two receivers, i.e. first four bytes are # I0 Q0 I1 Q1 s_file = np.memmap(filename, offset=num_skip, dtype=np.int8, mode='r') n_rx = 2 if num_samples > 0: s_file = s_file[:num_samples * 2 * n_rx] - samples = np.empty([n_rx, len(s_file) / (2 * n_rx)], dtype=np.complex64) + samples = {} for rx in range(n_rx): - samples[rx] = s_file[2 * rx::2 * n_rx] + s_file[2 * rx + 1::2 * n_rx] * 1j - elif file_format == 'c8c8_tayloe': + copy = np.empty(len(s_file) / (2 * n_rx), dtype=np.complex64) + copy[:] = s_file[2 * rx::2 * n_rx] + s_file[2 * rx + 1::2 * n_rx] * 1j + samples[encoding_profile[rx]] = copy + elif file_format.startswith('c8c8_tayloe'): # Interleaved complex samples from two receivers, i.e. first four bytes are - # I0 Q0 I1 Q1. Tayloe-upconverted to become purely real with fs=4fs0,fi=fs0 + # I0 Q0 I1 Q1. Tayloe-upconverted to become purely real with + # fs=4fs0,fi=fs0 s_file = np.memmap(filename, offset=num_skip, dtype=np.int8, mode='r') n_rx = 2 if num_samples > 0: s_file = s_file[:num_samples * 2 * n_rx] - samples = np.empty([n_rx, 4 * len(s_file) / (2 * n_rx)], dtype=np.int8) + samples = {} for rx in range(n_rx): - samples[rx][0::4] = s_file[2 * rx::2 * n_rx] - samples[rx][1::4] = -s_file[2 * rx + 1::2 * n_rx] - samples[rx][2::4] = -s_file[2 * rx::2 * n_rx] - samples[rx][3::4] = s_file[2 * rx + 1::2 * n_rx] - - elif file_format == 'piksinew': + copy = np.empty(4 * len(s_file) / (2 * n_rx), dtype=np.int8) + copy[0::4] = s_file[2 * rx::2 * n_rx] + copy[1::4] = -s_file[2 * rx + 1::2 * n_rx] + copy[2::4] = -s_file[2 * rx::2 * n_rx] + copy[3::4] = s_file[2 * rx + 1::2 * n_rx] + samples[encoding_profile[rx]] = copy + + elif file_format.startswith('piksinew'): packed = np.memmap(filename, offset=num_skip, dtype=np.uint8, mode='r') if num_samples > 0: packed = packed[:num_samples] - samples = np.empty((1, len(packed)), dtype=np.int8) - samples[0][:] = (packed >> 6) - 1 + copy = np.empty(len(packed), dtype=np.int8) + copy[:] = (packed >> 6) - 1 + samples = {} + samples[encoding_profile[0]] = copy - elif file_format == 'piksi': + elif file_format.startswith('piksi'): """ Piksi format is packed 3-bit sign-magnitude samples, 2 samples per byte. @@ -260,11 +274,13 @@ def _load_samples(filename, samples = samples[num_skip_samples:] if num_samples > 0: samples = samples[:num_samples] - tmp = np.ndarray((1, len(samples)), dtype=np.int8) - tmp[0][:] = samples - samples = tmp + copy = np.ndarray(len(samples), dtype=np.int8) + copy[:] = samples + result = {} + result[encoding_profile[0]] = copy + samples = result - elif file_format == '1bit' or file_format == '1bitrev': + elif file_format == '1bitrev': if num_samples > 0: num_skip_bytes = num_skip / 8 num_skip_samples = num_skip % 8 @@ -286,25 +302,20 @@ def _load_samples(filename, samples = samples[num_skip_samples:] if num_samples > 0: samples = samples[:num_samples] - tmp = np.ndarray((1, len(samples)), dtype=np.int8) - tmp[0][:] = samples - samples = tmp - - elif file_format == '1bit_x2': - # Interleaved single bit samples from two receivers: -1, +1 + result = {} + copy = np.ndarray(len(samples), dtype=np.int8) + copy[:] = samples + result[encoding_profile[0]] = copy + samples = result + + elif file_format.startswith('1bit'): + # Interleaved single bit samples from one, two or four receivers: -1, +1 samples = __load_samples_one_bit(filename, num_samples, num_skip, - defaults.file_encoding_1bit_x2) - elif file_format == '2bits': - # Two bit samples from one receiver: -3, -1, +1, +3 - samples = __load_samples_two_bits(filename, num_samples, num_skip, [0]) - elif file_format == '2bits_x2': - # Interleaved two bit samples from two receivers: -3, -1, +1, +3 + encoding_profile) + elif file_format.startswith('2bits'): + # Two bit samples from one, two or four receivers: -3, -1, +1, +3 samples = __load_samples_two_bits(filename, num_samples, num_skip, - defaults.file_encoding_2bits_x2) - elif file_format == '2bits_x4': - # Interleaved two bit samples from four receivers: -3, -1, +1, +3 - samples = __load_samples_two_bits(filename, num_samples, num_skip, - defaults.file_encoding_2bits_x4) + encoding_profile) else: raise ValueError("Unknown file type '%s'" % file_format) @@ -312,6 +323,7 @@ def _load_samples(filename, def __get_samples_total(filename, file_format, sample_index): + if file_format == 'int8': samples_block_size = 8 elif file_format == 'piksi': @@ -327,20 +339,23 @@ def __get_samples_total(filename, file_format, sample_index): """ samples_block_size = 4 - elif file_format == '1bit' or file_format == '1bitrev': - samples_block_size = 1 - elif file_format == '1bit_x2': + elif file_format.startswith('1bit_x4'): + # Interleaved single bit samples from four receivers: -1, +1 + samples_block_size = 4 + elif file_format.startswith('1bit_x2'): # Interleaved single bit samples from two receivers: -1, +1 samples_block_size = 2 - elif file_format == '2bits': - # Two bit samples from one receiver: -3, -1, +1, +3 - samples_block_size = 2 - elif file_format == '2bits_x2': - # Interleaved two bit samples from two receivers: -3, -1, +1, +3 - samples_block_size = 4 - elif file_format == '2bits_x4': + elif file_format.startswith('1bit'): + samples_block_size = 1 + elif file_format.startswith('2bits_x4'): # Interleaved two bit samples from four receivers: -3, -1, +1, +3 samples_block_size = 8 + elif file_format.startswith('2bits_x2'): + # Interleaved two bit samples from two receivers: -3, -1, +1, +3 + samples_block_size = 4 + elif file_format.startswith('2bits'): + # Two bit samples from one receiver: -3, -1, +1, +3 + samples_block_size = 2 else: raise ValueError("Unknown file type '%s'" % file_format) @@ -353,10 +368,58 @@ def __get_samples_total(filename, file_format, sample_index): return samples_total +def __update_dict(samples, sample_key, signal, signal_key): + ''' + Helper to populate sample map. The method attaches decoded signals from + a signal source into the result. Also the method removes unused result + entries. + + Parameters + ---------- + samples : map + Resulting map with bands + sample_key : string + Band name + signal : map + Map with decoded band id as keys and samples as entries. + signal_key : int + Band identifier key, which corresponds to band name. + ''' + + if sample_key in samples: + if signal_key in signal: + samples[sample_key]['samples'] = signal[signal_key] + else: + del samples[sample_key] + + def load_samples(samples, filename, num_samples=defaults.processing_block_size, file_format='piksi'): + ''' + Loads a block of samples according to parameters. + + Parameters + ---------- + samples : map + Map of band name as a key and a map of band parameters as values. The + following parameters must be present: + - 'samples_total' : long -- Total number of samples in file. + - Any combination of 'l1ca', 'l2c', 'glo_l1' and 'glo_l2' -- The band names + to load. + filename : string + Input file path. + num_samples : int + Number of samples to load. + file_format : string + Type of input file. + + Returns + ------- + samples : map + Updated band map. + ''' if samples['samples_total'] == -1: samples['samples_total'] = __get_samples_total(filename, @@ -366,9 +429,11 @@ def load_samples(samples, num_samples, samples['sample_index'], file_format) - samples[L1CA]['samples'] = signal[defaults.sample_channel_GPS_L1] - if len(signal) > 1: - samples[L2C]['samples'] = signal[defaults.sample_channel_GPS_L2] + + __update_dict(samples, L1CA, signal, defaults.sample_channel_GPS_L1) + __update_dict(samples, L2C, signal, defaults.sample_channel_GPS_L2) + __update_dict(samples, GLO_L1, signal, defaults.sample_channel_GLO_L1) + __update_dict(samples, GLO_L2, signal, defaults.sample_channel_GLO_L2) return samples diff --git a/peregrine/tracking.py b/peregrine/tracking.py index 3c4389f..e6aa04e 100644 --- a/peregrine/tracking.py +++ b/peregrine/tracking.py @@ -9,14 +9,14 @@ # WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. import numpy as np +import scipy.constants as constants import math import parallel_processing as pp import multiprocessing as mp import cPickle -from swiftnav.track import LockDetector +from peregrine import lock_detect from swiftnav.track import CN0Estimator -from swiftnav.track import AliasDetector from swiftnav.track import AidedTrackingLoop from swiftnav.correlate import track_correlate from swiftnav.nav_msg import NavMsg @@ -26,10 +26,15 @@ from swiftnav.signal import signal_from_code_index from peregrine import defaults from peregrine import gps_constants +from peregrine import glo_constants +from peregrine import alias_detector from peregrine.acquisition import AcquisitionResult +from peregrine.acquisition import GloAcquisitionResult from peregrine.include.generateCAcode import caCodes from peregrine.include.generateL2CMcode import L2CMCodes +from peregrine.include.generateGLOcode import GLOCode from peregrine.tracking_file_utils import createTrackingOutputFileNames +from peregrine.cn0 import CN0_Est_MM, CN0_Est_BL import logging import sys @@ -120,6 +125,91 @@ def _tracking_channel_factory(parameters): return TrackingChannelL1CA(parameters) if parameters['acq'].signal == gps_constants.L2C: return TrackingChannelL2C(parameters) + if parameters['acq'].signal == glo_constants.GLO_L1: + return TrackingChannelGLOL1(parameters) + + +def get_fsm_states(fsm_states, ms, short_n_long, bit_sync): + """ + Tracking loop FSM operation table getter. + The right FSM operation table is chosen based on the + set of input parameters. + + Parameters + ---------- + fsm_states : dictionary + Contains FSM operation tables for different modes + ms : integer + The integration time [ms] + short_n_long : Boolean + FPGA operation simulation flag. True - the simulation + is requested. + bit_sync : Boolean + Tells if bit sync is acquired. + + Returns + ------- + out : dictionary + The relevant tracking loop FSM operation table + + """ + + if ms == 1: + ms = '1ms' + elif ms == 2: + ms = '2ms' + elif ms == 4: + ms = '4ms' + elif ms == 5: + ms = '5ms' + elif ms == 10: + ms = '10ms' + elif ms == 20: + ms = '20ms' + elif ms == 40: + ms = '40ms' + elif ms == 80: + ms = '80ms' + else: + raise ValueError("Not implemented!") + + if short_n_long: + mode = 'short_n_long' + else: + mode = 'ideal' + + if bit_sync: + bit_sync_status = 'bit_sync' + else: + bit_sync_status = 'no_bit_sync' + + return fsm_states[ms][bit_sync_status][mode] + + +def get_lock_detector(cur_bw, lock_detect_set): + """ + Selects the relevant lock detector parameter set. + The selection is done based on the current PLL bandwidth. + + Parameters + ---------- + cur_bw : float + The current PLL bandwidth + lock_detect_set : tuple + The combination of PLL bandwidth and its lock detector + parameters set + + Returns + ------- + out : dictionary + The relevant lock detector parameters set + + """ + + for bw, params in lock_detect_set: + if cur_bw < bw: + continue + return params class TrackingChannel(object): @@ -129,8 +219,8 @@ class TrackingChannel(object): this class. See TrackingChannelL1CA or TrackingChannelL2C as examples. - Sub-classes can optionally implement :meth:'_run_preprocess', - :meth:'_run_postprocess' and :meth:'_get_result' methods. + Sub-classes can optionally implement :meth:'_run_track_profile_selection', + :meth:'_run_nav_data_decoding' and :meth:'_get_result' methods. The class is designed to support batch processing of sample data. This is to help processing of large data sample files without the need @@ -164,48 +254,107 @@ def __init__(self, params): self.signal = params['acq'].signal self.results_num = 500 - self.stage1 = True - self.lock_detect = LockDetector( + self.coherent_ms_index = 0 + self.fll_bw_index = 0 + self.pll_bw_index = 0 + coherent_ms = self.track_params['coherent_ms'][self.coherent_ms_index] + fll_bw = self.track_params['fll_bw'][self.fll_bw_index] + pll_bw = self.track_params['pll_bw'][self.pll_bw_index] + + loop_filter_params = self.loop_filter_params_template + carr_params = loop_filter_params['carr_params'] + loop_filter_params['carr_params'] = (pll_bw, + carr_params[1], + carr_params[2]) + loop_filter_params['loop_freq'] = 1000 / coherent_ms + loop_filter_params['carr_freq_b1'] = fll_bw + + self.track_profile = {'loop_filter_params': loop_filter_params, + 'coherent_ms': coherent_ms, + 'iq_ratio': 0} + + self.track_candidates = [] + self.stabilization_time = defaults.tracking_loop_stabilization_time_ms + self.coherent_ms = coherent_ms + self.alias_detector = alias_detector.AliasDetector() + + self.short_n_long = False + if params['tracker_options']: + mode = params['tracker_options']['mode'] + if mode == 'short-long-cycles': + self.short_n_long = True + + self.bit_sync_prev = self.bit_sync + + self.fsm_index = 0 + self.fsm_states = get_fsm_states(self.fsm_states_all, + ms=self.coherent_ms, + short_n_long=self.short_n_long, + bit_sync=self.bit_sync) + + self.lock_detect = lock_detect.LockDetector( k1=self.lock_detect_params["k1"], k2=self.lock_detect_params["k2"], lp=self.lock_detect_params["lp"], lo=self.lock_detect_params["lo"]) - self.alias_detect = AliasDetector( - acc_len=defaults.alias_detect_interval_ms / self.coherent_ms, - time_diff=1) + code_params = loop_filter_params['code_params'] + carr_params = loop_filter_params['carr_params'] + pll_bw = carr_params[0] + + lock_detect_params_fast = get_lock_detector(pll_bw, + defaults.lock_detect_params_fast) + + self.lock_detect_fast = lock_detect.LockDetector( + k1=lock_detect_params_fast["k1"], + k2=lock_detect_params_fast["k2"], + lp=lock_detect_params_fast["lp"], + lo=lock_detect_params_fast["lo"]) - self.cn0_est = CN0Estimator( + self.lock_detect_slow = lock_detect.LockDetector( + k1=defaults.lock_detect_params_slow["k1"], + k2=defaults.lock_detect_params_slow["k2"], + lp=defaults.lock_detect_params_slow["lp"], + lo=defaults.lock_detect_params_slow["lo"]) + + self.cn0_est = CN0_Est_MM( bw=1e3 / self.coherent_ms, cn0_0=self.cn0_0, cutoff_freq=0.1, - loop_freq=self.loop_filter_params["loop_freq"] + loop_freq=loop_filter_params["loop_freq"] ) self.loop_filter = self.loop_filter_class( - loop_freq=self.loop_filter_params['loop_freq'], - code_freq=self.code_freq_init, - code_bw=self.loop_filter_params['code_bw'], - code_zeta=self.loop_filter_params['code_zeta'], - code_k=self.loop_filter_params['code_k'], - carr_to_code=self.loop_filter_params['carr_to_code'], - carr_freq=self.acq.doppler, - carr_bw=self.loop_filter_params['carr_bw'], - carr_zeta=self.loop_filter_params['carr_zeta'], - carr_k=self.loop_filter_params['carr_k'], - carr_freq_b1=self.loop_filter_params['carr_freq_b1'], + loop_freq=loop_filter_params['loop_freq'], + code_freq=0, + code_bw=code_params[0], # code_bw' + code_zeta=code_params[1], # code_zeta + code_k=code_params[2], # code_k + carr_to_code=loop_filter_params['carr_to_code'], + carr_freq=self.acq.doppler * 2 * np.pi, + carr_bw=carr_params[0], # carr_bw, + carr_zeta=carr_params[1], # carr_zeta + carr_k=carr_params[2], # carr_k + carr_freq_b1=loop_filter_params['carr_freq_b1'], ) - self.next_code_freq = self.loop_filter.to_dict()['code_freq'] - self.next_carr_freq = self.loop_filter.to_dict()['carr_freq'] + self.code_freq_1 = self.code_freq_2 = self.corr_code_freq = \ + params['code_freq_init'] + + self.carr_freq_1 = self.carr_freq_2 = self.corr_carr_freq = \ + self.acq.doppler self.track_result = TrackResults(self.results_num, self.acq.prn, self.acq.signal) - self.alias_detect_init = 1 + self.track_profile_timer_ms = 0 + self.acc_timer_ms = 0 + self.bit_sync_timer_ms = 0 + self.acc_detected = False self.code_phase = 0.0 self.carr_phase = 0.0 + self.lock_detect_outp_prev = False self.samples_per_chip = int(round(self.sampling_freq / self.chipping_rate)) self.sample_index = params['sample_index'] self.sample_index += self.acq.sample_index @@ -215,22 +364,11 @@ def __init__(self, params): self.code_phase_acc = 0.0 self.samples_tracked = 0 self.i = 0 - - self.pipelining = False # Flag if pipelining is used - self.pipelining_k = 0. # Error prediction coefficient for pipelining - self.short_n_long = False # Short/Long cycle simulation - self.short_step = True # Short cycle - if self.tracker_options: - mode = self.tracker_options['mode'] - if mode == 'pipelining': - self.pipelining = True - self.pipelining_k = self.tracker_options['k'] - elif mode == 'short-long-cycles': - self.short_n_long = True - self.pipelining = True - self.pipelining_k = self.tracker_options['k'] - else: - raise ValueError("Invalid tracker mode %s" % str(mode)) + self.started = False + self.lock_detect_outo = 0 + self.lock_detect_outp = 0 + self.E = self.P = self.L = 0.j + self.acc_g = 0 def dump(self): """ @@ -249,6 +387,11 @@ def start(self): """ + if self.started: + return + + self.started = True + logger.info("[PRN: %d (%s)] Tracking is started. " "IF: %.1f, Doppler: %.1f, code phase: %.1f, " "sample index: %d" % @@ -276,17 +419,276 @@ def get_index(self): """ return self.sample_index - def _run_preprocess(self): + def _set_track_profile(self): """ - Customize the tracking run procedure in a subclass. - The method can be optionally redefined in a subclass to perform - a subclass specific actions to happen before correlator runs - next integration round. + Set the current track profile. + The current track profile determines the PLL BW, FLL BW and + the coherent integration time. """ - pass - def _run_postprocess(self): + coherent_ms = self.track_params['coherent_ms'][self.coherent_ms_index] + fll_bw = self.track_params['fll_bw'][self.fll_bw_index] + pll_bw = self.track_params['pll_bw'][self.pll_bw_index] + + loop_filter_params = self.loop_filter_params_template + carr_params = loop_filter_params['carr_params'] + loop_filter_params['carr_params'] = (pll_bw, + carr_params[1], + carr_params[2]) + loop_filter_params['loop_freq'] = 1000 / coherent_ms + loop_filter_params['carr_freq_b1'] = fll_bw + + self.track_profile = {'loop_filter_params': loop_filter_params, + 'coherent_ms': coherent_ms, + 'iq_ratio': 0} + + self.coherent_ms = coherent_ms + + # logger.info("[PRN: %d (%s)] coherent_ms=%d and PLL bw=%f FLL bw=%f" % + # (self.prn + 1, self.signal, self.coherent_ms, pll_bw, fll_bw)) + + lock_detect_params_fast = get_lock_detector(pll_bw, + defaults.lock_detect_params_fast) + + self.lock_detect_fast.reinit(k1=lock_detect_params_fast["k1"], + k2=lock_detect_params_fast["k2"], + lp=lock_detect_params_fast["lp"], + lo=lock_detect_params_fast["lo"]) + + self.loop_filter.retune(**loop_filter_params) + + self.cn0_est = CN0_Est_MM(bw=1e3 / self.coherent_ms, + cn0_0=self.track_result.cn0[self.i - 1], + cutoff_freq=10, + loop_freq=1e3 / self.coherent_ms) + + self.fsm_states = get_fsm_states(self.fsm_states_all, + ms=self.coherent_ms, + short_n_long=self.short_n_long, + bit_sync=self.bit_sync) + self.fsm_index = 0 + self.track_profile_timer_ms = 0 + + def _make_track_candidates(self): + """ + Create a list of track profile candidates. + The list content depends on the current track profile. + We are in the recovery stage, when this function is called. + See more details at + https://swiftnav.hackpad.com/High-sensitivity-tracking-FLL-PLL-profile-switching-design-HDpuFC1BygA + + """ + + fll_bw_index = self.fll_bw_index + # we try to minimize FLL BW first + if fll_bw_index == len(self.track_params['fll_bw']) - 1: + # FLL is already mith minimum BW + coherent_ms_index = self.coherent_ms_index + if coherent_ms_index < len(self.track_params['coherent_ms']) - 1: + coherent_ms_index += 1 + candidate = {'fll_bw_index': self.fll_bw_index, + 'pll_bw_index': self.pll_bw_index, + 'coherent_ms_index': coherent_ms_index} + self.track_candidates.append(candidate) + + pll_bw_index = self.pll_bw_index + if pll_bw_index < len(self.track_params['pll_bw']) - 1: + pll_bw_index += 1 + candidate = {'fll_bw_index': self.fll_bw_index, + 'pll_bw_index': pll_bw_index, + 'coherent_ms_index': self.coherent_ms_index} + self.track_candidates.append(candidate) + else: + fll_bw_index += 1 + candidate = {'fll_bw_index': fll_bw_index, + 'pll_bw_index': self.pll_bw_index, + 'coherent_ms_index': self.coherent_ms_index} + self.track_candidates.append(candidate) + + def _filter_track_candidates(self): + """ + Filter the track candidate list. + The track candidate list is created by _make_track_candidate() + function. + + Returns + ------- + res: list + The filtered track profiles candidates list + """ + + res = [] + for candidate in self.track_candidates: + pll_bw_index = candidate['pll_bw_index'] + pll_bw = self.track_params['pll_bw'][pll_bw_index] + + coherent_ms_index = candidate['coherent_ms_index'] + coherent_ms = self.track_params['coherent_ms'][coherent_ms_index] + + bw_time = pll_bw * coherent_ms * 1e-3 + + if pll_bw < 5: + continue + + if coherent_ms == 1: + if 30 <= pll_bw: + pass + else: + continue + + # elif coherent_ms == 2: + # if 12 <= pll_bw and pll_bw <= 30: + # pass + # else: + # continue + + # elif coherent_ms == 4: + # if 10 <= pll_bw and pll_bw <= 12: + # pass + # else: + # continue + + elif coherent_ms == 5: + if 5 <= pll_bw and pll_bw <= 30: + pass + else: + continue + + elif bw_time > 0.04: + if self.acc_detected: + if coherent_ms <= 10 and pll_bw <= 5: + pass + else: + continue + elif pll_bw == 5: + pass + else: + continue + + res.append(candidate) + + return res + + def _run_track_profile_selection(self): + """ + Runs tracking profile selection based on the + availability of bit sync, fast and normal locks. + + """ + + if self.bit_sync and not self.bit_sync_prev: + # we just got bit sync + self.bit_sync_timer_ms = 0 + self.bit_sync_prev = self.bit_sync + + if self.lock_detect_outp: + + if not self.lock_detect_fast_outp: + # we lost fast lock detector + if self.acc_detected: + # and we are facing dynamics + if self.fll_bw_index == 0 and \ + self.pll_bw_index == 0 and \ + self.coherent_ms_index == 0: + return + self.fll_bw_index = 0 + self.pll_bw_index = 0 + self.coherent_ms_index = 0 + else: + # we are in static scenario - just add FLL + if self.fll_bw_index == 0: + return + self.fll_bw_index = 0 + + self._set_track_profile() + return + + track_settled = self.track_profile_timer_ms >= self.stabilization_time + if not track_settled: + return + + # Detect dynamics + # do not assess dynamics in FLL mode as + # the PLL phase acceleration indicator looks to be scrued + fll_bw = self.track_params['fll_bw'][self.fll_bw_index] + if fll_bw == 0: + acc = self.loop_filter.to_dict()['phase_acc'] / (2 * np.pi) + self.acc_g = acc * constants.c / (self.carrier_freq * constants.g) + + if acc > 30: # [hz/sec] + self.acc_timer_ms = 0 + self.acc_detected = True + + if self.acc_timer_ms > 2000: + # clear acceleration flag as it is too old + self.acc_detected = False + + final_profile = \ + self.fll_bw_index == len(self.track_params['fll_bw']) - 1 and \ + self.pll_bw_index == len(self.track_params['pll_bw']) - 1 and \ + self.coherent_ms_index == len(self.track_params['coherent_ms']) - 1 + if final_profile: + return + + if len(self.track_candidates) == 0: + self._make_track_candidates() + self.track_candidates = self._filter_track_candidates() + if len(self.track_candidates) == 0: + return + + for i, track_profile in list(enumerate(self.track_candidates)): + coherent_ms_index = track_profile['coherent_ms_index'] + coherent_ms = self.track_params['coherent_ms'][coherent_ms_index] + bit_sync_required = (coherent_ms != 1) + + if bit_sync_required: + if not self.bit_sync: + continue + if int(self.bit_sync_timer_ms + 0.5) % 20 != 0: + # wait for bit edge + continue + + track_profile = self.track_candidates.pop(i) + + # switch to next track profile + + self.fll_bw_index = track_profile['fll_bw_index'] + self.pll_bw_index = track_profile['pll_bw_index'] + self.coherent_ms_index = track_profile['coherent_ms_index'] + + self.track_candidates = [] + self._set_track_profile() + break + + else: + if self.lock_detect_outp_prev: + # we just lost the lock + self.track_profile_timer_ms = 0 + + if self.acc_detected: + threshold = 50 + else: + threshold = 10000 + + if self.track_profile_timer_ms > threshold: + if self.fll_bw_index == 0 and \ + self.pll_bw_index == 0 and \ + self.coherent_ms_index == 0: + return + self.fll_bw_index = 0 + self.pll_bw_index = 0 + self.coherent_ms_index = 0 + else: + if self.fll_bw_index == 0: + return + self.fll_bw_index = 0 + + self._set_track_profile() + + self.lock_detect_outp_prev = self.lock_detect_outp + + def _run_nav_data_decoding(self): """ Customize the tracking run procedure in a subclass. The method can be optionally redefined in a subclass to perform @@ -310,12 +712,6 @@ def _get_result(self): """ return None - def _short_n_long_preprocess(self): - pass - - def _short_n_long_postprocess(self): - pass - def is_pickleable(self): """ Check if object is pickleable. @@ -349,7 +745,8 @@ def run(self, samples): Run tracking channel for the given batch of data. This method is an entry point for the tracking procedure. Subclasses normally will not redefine the method, but instead - redefine the customization methods '_run_preprocess', '_run_postprocess' + redefine the customization methods + '_run_track_profile_selection', '_run_nav_data_decoding' and '_get_result' to run signal specific tracking operations. Parameters @@ -363,7 +760,6 @@ def run(self, samples): which can be redefined in subclasses """ - self.start() self.samples = samples if self.sample_index < samples['sample_index']: @@ -380,112 +776,164 @@ def run(self, samples): while self.samples_tracked < self.samples_to_track and \ (sample_index + 2 * estimated_blksize) < samples_total: - self._run_preprocess() - - if self.pipelining: - # Pipelining and prediction - corr_code_freq = self.next_code_freq - corr_carr_freq = self.next_carr_freq - - self.next_code_freq = self.loop_filter.to_dict()['code_freq'] - self.next_carr_freq = self.loop_filter.to_dict()['carr_freq'] - - if self.short_n_long and not self.stage1 and not self.short_step: - # In case of short/long cycles, the correction applicable for the - # long cycle is smaller proportionally to the actual cycle size - pipelining_k = self.pipelining_k / (self.coherent_ms - 1) + cur_fsm_state = self.fsm_states[self.fsm_index] + flags_pre = cur_fsm_state[2]['pre'] + + if defaults.APPLY_CORR_1 in flags_pre: + self.corr_code_freq = self.code_freq_1 + self.corr_carr_freq = self.carr_freq_1 + elif defaults.APPLY_CORR_2 in flags_pre: + self.corr_code_freq = self.code_freq_2 + self.corr_carr_freq = self.carr_freq_2 + + if defaults.PREPARE_BIT_COMPENSATION in flags_pre: + comp_bit_E = 0 + comp_bit_P = 0 + comp_bit_L = 0 + + samples_ = samples[self.signal]['samples'][sample_index:] + + E_, P_, L_, blksize, self.code_phase, self.carr_phase = self.correlator( + samples_, + self.code_phase + cur_fsm_state[0], + self.corr_code_freq + self.chipping_rate, self.code_phase, + self.corr_carr_freq + self.IF, self.carr_phase, + self.prn_code, + self.sampling_freq, + self.signal + ) + + self.track_profile_timer_ms += 1e3 * blksize / self.sampling_freq + self.bit_sync_timer_ms += 1e3 * blksize / self.sampling_freq + self.acc_timer_ms += 1e3 * blksize / self.sampling_freq + + sample_index += blksize + samples_processed += blksize + self.carr_phase_acc += self.corr_carr_freq * blksize / self.sampling_freq + self.code_phase_acc += self.corr_code_freq * blksize / self.sampling_freq + estimated_blksize -= blksize + + self.E += E_ + self.P += P_ + self.L += L_ + + flags_post = cur_fsm_state[2]['post'] + self.fsm_index = cur_fsm_state[1] + + if defaults.COMPENSATE_BIT_POLARITY in flags_post: + if self.P.real < 0: + comp_bit_E += -self.E + comp_bit_P += -self.P + comp_bit_L += -self.L else: - pipelining_k = self.pipelining_k - - # There is an error between target frequency and actual one. Affect - # the target frequency according to the computed error - carr_freq_error = self.next_carr_freq - corr_carr_freq - self.next_carr_freq += carr_freq_error * pipelining_k - - code_freq_error = self.next_code_freq - corr_code_freq - self.next_code_freq += code_freq_error * pipelining_k - - else: - # Immediate correction simulation - self.next_code_freq = self.loop_filter.to_dict()['code_freq'] - self.next_carr_freq = self.loop_filter.to_dict()['carr_freq'] - - corr_code_freq = self.next_code_freq - corr_carr_freq = self.next_carr_freq - - coherent_iter, code_chips_to_integrate = self._short_n_long_preprocess() - - for _ in range(self.coherent_iter): - - if (sample_index + 2 * estimated_blksize) >= samples_total: - break - - samples_ = samples[self.signal]['samples'][sample_index:] - - E_, P_, L_, blksize, self.code_phase, self.carr_phase = self.correlator( - samples_, - code_chips_to_integrate, - corr_code_freq + self.chipping_rate, self.code_phase, - corr_carr_freq + self.IF, self.carr_phase, - self.prn_code, - self.sampling_freq, - self.signal - ) - - if blksize > estimated_blksize: - estimated_blksize = blksize - - sample_index += blksize - samples_processed += blksize - self.carr_phase_acc += corr_carr_freq * blksize / self.sampling_freq - self.code_phase_acc += corr_code_freq * blksize / self.sampling_freq - - self.E += E_ - self.P += P_ - self.L += L_ - - more_integration_needed = self._short_n_long_postprocess() - if more_integration_needed: + comp_bit_E += self.E + comp_bit_P += self.P + comp_bit_L += self.L + self.E = 0 + self.P = 0 + self.L = 0 + + if defaults.USE_COMPENSATED_BIT in flags_post: + self.E = comp_bit_E + self.P = comp_bit_P + self.L = comp_bit_L + + if defaults.RUN_LD in flags_post: + # Update PLL lock detector + self.lock_detect_outo, \ + self.lock_detect_outp, \ + lock_detect_pcount1, \ + lock_detect_pcount2, \ + self.lock_detect_lpfi, \ + self.lock_detect_lpfq = self.lock_detect.update(self.P.real, + self.P.imag, + 1) + # Update PLL fast lock detector + self.lock_detect_fast_outo, \ + self.lock_detect_fast_outp, \ + lock_detect_fast_pcount1, \ + lock_detect_fast_pcount2, \ + self.lock_detect_fast_lpfi, \ + self.lock_detect_fast_lpfq = self.lock_detect_fast.update(self.P.real, + self.P.imag, + 1) + + # Update PLL fast lock detector + self.lock_detect_slow_outo, \ + self.lock_detect_slow_outp, \ + lock_detect_slow_pcount1, \ + lock_detect_slow_pcount2, \ + self.lock_detect_slow_lpfi, \ + self.lock_detect_slow_lpfq = self.lock_detect_slow.update(self.P.real, + self.P.imag, + 1) + + if defaults.ALIAS_DETECT_1ST in flags_post or \ + defaults.ALIAS_DETECT_BOTH in flags_post: + self.alias_detector.first(P_) + + if defaults.ALIAS_DETECT_2ND in flags_post or \ + defaults.ALIAS_DETECT_BOTH in flags_post: + self.alias_detector.second(P_) + + err_hz = self.alias_detector.get_err_hz() + if abs(err_hz) > 0: + logger.info("[PRN: %d (%s)] False lock detected. " + "Error: %.1f Hz. Correcting..." % + (self.prn + 1, self.signal, -err_hz)) + self.loop_filter.adjust_freq(err_hz * 2 * np.pi) + + if not (defaults.GET_CORR_1 in flags_post) and \ + not (defaults.GET_CORR_2 in flags_post): continue - # Update PLL lock detector - lock_detect_outo, \ - lock_detect_outp, \ - lock_detect_pcount1, \ - lock_detect_pcount2, \ - lock_detect_lpfi, \ - lock_detect_lpfq = self.lock_detect.update(self.P.real, - self.P.imag, - coherent_iter) - - if lock_detect_outo: - if self.alias_detect_init: - self.alias_detect_init = 0 - self.alias_detect.reinit(defaults.alias_detect_interval_ms / - self.coherent_iter, - time_diff=1) - self.alias_detect.first(self.P.real, self.P.imag) - alias_detect_err_hz = \ - self.alias_detect.second(self.P.real, self.P.imag) * np.pi * \ - (1e3 / defaults.alias_detect_interval_ms) - self.alias_detect.first(self.P.real, self.P.imag) - else: - self.alias_detect_init = 1 - alias_detect_err_hz = 0 + phase_acc = self.loop_filter.to_dict()['phase_acc'] / (2 * np.pi) + self.track_result.acc_g[self.i] = self.acc_g + # run tracking loop self.loop_filter.update(self.E, self.P, self.L) + + if defaults.GET_CORR_1 in flags_post: + self.code_freq_1 = \ + self.loop_filter.to_dict()['code_freq'] / (2 * np.pi) + self.carr_freq_1 = \ + self.loop_filter.to_dict()['carr_freq'] / (2 * np.pi) + elif defaults.GET_CORR_2 in flags_post: + self.code_freq_2 = \ + self.loop_filter.to_dict()['code_freq'] / (2 * np.pi) + self.carr_freq_2 = \ + self.loop_filter.to_dict()['carr_freq'] / (2 * np.pi) + self.track_result.coherent_ms[self.i] = self.coherent_ms self.track_result.IF = self.IF self.track_result.carr_phase[self.i] = self.carr_phase self.track_result.carr_phase_acc[self.i] = self.carr_phase_acc self.track_result.carr_freq[self.i] = \ - self.loop_filter.to_dict()['carr_freq'] + self.IF + self.loop_filter.to_dict()['carr_freq'] / (2 * np.pi) self.track_result.code_phase[self.i] = self.code_phase self.track_result.code_phase_acc[self.i] = self.code_phase_acc self.track_result.code_freq[self.i] = \ - self.loop_filter.to_dict()['code_freq'] + self.chipping_rate + self.loop_filter.to_dict()['code_freq'] + + self.track_result.phase_err[self.i] = \ + self.loop_filter.to_dict()['phase_err'] + self.track_result.iq_ratio_min[self.i] = self.track_profile['iq_ratio'] + + self.track_result.code_err[self.i] = \ + self.loop_filter.to_dict()['code_err'] + + self.track_result.acceleration[self.i] = \ + self.loop_filter.to_dict()['phase_acc'] + + self.track_result.fll_bw[self.i] = self.loop_filter.to_dict()['fll_bw'] + self.track_result.pll_bw[self.i] = self.loop_filter.to_dict()['pll_bw'] + self.track_result.dll_bw[self.i] = self.loop_filter.to_dict()['dll_bw'] + + self.track_result.track_timer_ms[self.i] = self.track_profile_timer_ms + self.track_result.acc_timer_ms[self.i] = self.acc_timer_ms + self.track_result.acc_detected[self.i] = self.acc_detected # Record stuff for postprocessing self.track_result.absolute_sample[self.i] = self.sample_index + \ @@ -495,19 +943,48 @@ def run(self, samples): self.track_result.P[self.i] = self.P self.track_result.L[self.i] = self.L - self.track_result.cn0[self.i] = self.cn0_est.update( - self.P.real, self.P.imag) - - self.track_result.lock_detect_outo[self.i] = lock_detect_outo - self.track_result.lock_detect_outp[self.i] = lock_detect_outp + self.track_result.cn0[self.i], \ + self.track_result.snr[self.i], \ + self.track_result.snr_db[self.i] = \ + self.cn0_est.update(self.P.real, self.P.imag) + + self.track_result.lock_detect_outo[self.i] = self.lock_detect_outo + self.track_result.lock_detect_outp[self.i] = self.lock_detect_outp + self.track_result.lock_detect_fast_outo[self.i] = \ + self.lock_detect_fast_outo + self.track_result.lock_detect_fast_outp[self.i] = \ + self.lock_detect_fast_outp self.track_result.lock_detect_pcount1[self.i] = lock_detect_pcount1 self.track_result.lock_detect_pcount2[self.i] = lock_detect_pcount2 - self.track_result.lock_detect_lpfi[self.i] = lock_detect_lpfi - self.track_result.lock_detect_lpfq[self.i] = lock_detect_lpfq - self.track_result.alias_detect_err_hz[self.i] = alias_detect_err_hz + self.track_result.lock_detect_lpfi[self.i] = self.lock_detect_lpfi + self.track_result.lock_detect_lpfq[self.i] = self.lock_detect_lpfq + + self.track_result.lock_detect_fast_lpfi[self.i] = \ + self.lock_detect_fast_lpfi + self.track_result.lock_detect_fast_lpfq[self.i] = \ + self.lock_detect_fast_lpfq + + self.track_result.lock_detect_slow_lpfi[self.i] = \ + self.lock_detect_slow_lpfi + self.track_result.lock_detect_slow_lpfq[self.i] = \ + self.lock_detect_slow_lpfq - self._run_postprocess() + self.track_result.alias_detect_err_hz[self.i] = err_hz + + self._run_nav_data_decoding() + self._run_track_profile_selection() + + self.track_result.bit_sync[self.i] = self.bit_sync + + # Estimated blksize might change as a result of a change of + # the coherent integration time. + estimated_blksize = self.coherent_ms * self.sampling_freq / 1e3 + + if (sample_index + 2 * estimated_blksize) < samples_total: + self.track_result.more_samples[self.i] = 0 + else: + self.track_result.more_samples[self.i] = 1 self.samples_tracked = self.sample_index + samples_processed self.track_result.ms_tracked[self.i] = self.samples_tracked * 1e3 / \ @@ -517,6 +994,8 @@ def run(self, samples): if self.i >= self.results_num: self.dump() + self.E = self.P = self.L = 0.j + if self.i > 0: self.dump() @@ -547,15 +1026,22 @@ def __init__(self, params): cn0_0 += 10 * np.log10(defaults.L1CA_CHANNEL_BANDWIDTH_HZ) params['cn0_0'] = cn0_0 - params['coherent_ms'] = 1 params['IF'] = params['samples'][gps_constants.L1CA]['IF'] params['prn_code'] = caCodes[params['acq'].prn] params['code_freq_init'] = params['acq'].doppler * \ gps_constants.l1ca_chip_rate / gps_constants.l1 - params['loop_filter_params'] = defaults.l1ca_stage1_loop_filter_params + + params['track_params'] = defaults.l1ca_track_params + params['loop_filter_params_template'] = \ + defaults.l1ca_loop_filter_params_template + params['lock_detect_params'] = defaults.l1ca_lock_detect_params_opt params['chipping_rate'] = gps_constants.l1ca_chip_rate params['sample_index'] = params['samples']['sample_index'] + params['carrier_freq'] = gps_constants.l1 + params['fsm_states_all'] = defaults.gps_fsm_states + + self.bit_sync = False TrackingChannel.__init__(self, params) @@ -564,37 +1050,6 @@ def __init__(self, params): self.l2c_handover_acq = None self.l2c_handover_done = False - def _run_preprocess(self): - """ - Run L1C/A tracking loop preprocessor operation. - It runs before every coherent integration round. - - """ - - # For L1 C/A there are coherent and non-coherent tracking options. - if self.stage1 and \ - self.stage2_coherent_ms and \ - self.nav_bit_sync.bit_phase == self.nav_bit_sync.bit_phase_ref: - - logger.info("[PRN: %d (%s)] switching to stage2, coherent_ms=%d" % - (self.prn + 1, self.signal, self.stage2_coherent_ms)) - - self.stage1 = False - self.coherent_ms = self.stage2_coherent_ms - - self.loop_filter.retune(**self.stage2_loop_filter_params) - self.lock_detect.reinit( - k1=self.lock_detect_params["k1"] * self.coherent_ms, - k2=self.lock_detect_params["k2"], - lp=self.lock_detect_params["lp"], - lo=self.lock_detect_params["lo"]) - self.cn0_est = CN0Estimator(bw=1e3 / self.stage2_coherent_ms, - cn0_0=self.track_result.cn0[self.i - 1], - cutoff_freq=10, - loop_freq=1e3 / self.stage2_coherent_ms) - - self.coherent_iter = self.coherent_ms - def _get_result(self): """ Get L1C/A tracking results. @@ -613,36 +1068,7 @@ def _get_result(self): return self.l2c_handover_acq return None - def _short_n_long_preprocess(self): - if self.short_n_long and not self.stage1: - # When simulating short and long cycles, short step resets EPL - # registers, and long one adds up to them - if self.short_step: - self.E = self.P = self.L = 0.j - self.coherent_iter = 1 - else: - self.coherent_iter = self.coherent_ms - 1 - else: - self.E = self.P = self.L = 0.j - - self.code_chips_to_integrate = gps_constants.chips_per_code - - return self.coherent_iter, self.code_chips_to_integrate - - def _short_n_long_postprocess(self): - more_integration_needed = False - if not self.stage1 and self.short_n_long: - if self.short_step: - # In case of short step - go to next integration period - self.short_step = False - self.alias_detect.first(self.P.real, self.P.imag) - more_integration_needed = True - else: - # Next step is short cycle - self.short_step = True - return more_integration_needed - - def _run_postprocess(self): + def _run_nav_data_decoding(self): """ Run L1C/A coherent integration postprocessing. Runs navigation bit sync decoding operation and @@ -673,14 +1099,17 @@ def _run_postprocess(self): self.track_result.tow[self.i] = tow if tow >= 0 else ( self.track_result.tow[self.i - 1] + self.coherent_ms) + self.bit_sync = self.nav_bit_sync.bit_sync_acquired() + # Handover to L2C if possible if self.l2c_handover and not self.l2c_handover_acq and \ + gps_constants.L2C in self.samples and \ 'samples' in self.samples[gps_constants.L2C] and sync: chan_snr = self.track_result.cn0[self.i] chan_snr -= 10 * np.log10(defaults.L1CA_CHANNEL_BANDWIDTH_HZ) chan_snr = np.power(10, chan_snr / 10) l2c_doppler = self.loop_filter.to_dict( - )['carr_freq'] * gps_constants.l2 / gps_constants.l1 + )['carr_freq'] * gps_constants.l2 / gps_constants.l1 / (2 * np.pi) self.l2c_handover_acq = \ AcquisitionResult(self.prn, self.samples[gps_constants.L2C][ @@ -712,16 +1141,23 @@ def __init__(self, params): cn0_0 = 10 * np.log10(params['acq'].snr) cn0_0 += 10 * np.log10(defaults.L2C_CHANNEL_BANDWIDTH_HZ) params['cn0_0'] = cn0_0 - params['coherent_ms'] = 20 - params['coherent_iter'] = 1 - params['loop_filter_params'] = defaults.l2c_loop_filter_params + params['lock_detect_params'] = defaults.l2c_lock_detect_params_20ms params['IF'] = params['samples'][gps_constants.L2C]['IF'] params['prn_code'] = L2CMCodes[params['acq'].prn] params['code_freq_init'] = params['acq'].doppler * \ gps_constants.l2c_chip_rate / gps_constants.l2 + + params['track_params'] = defaults.l2c_track_params + params['loop_filter_params_template'] = \ + defaults.l2c_loop_filter_params_template + params['chipping_rate'] = gps_constants.l2c_chip_rate params['sample_index'] = 0 + params['carrier_freq'] = gps_constants.l2 + params['fsm_states_all'] = defaults.gps_fsm_states + + self.bit_sync = True TrackingChannel.__init__(self, params) @@ -738,47 +1174,9 @@ def is_pickleable(self): """ return False - def _short_n_long_preprocess(self): - if self.short_n_long: - # When simulating short and long cycles, short step resets EPL - # registers, and long one adds up to them - if self.short_step: - self.E = self.P = self.L = 0.j - # L2C CM code is only half of the PRN code length. - # The other half is CL code. Thus multiply by 2. - self.code_chips_to_integrate = \ - int(2 * defaults.l2c_short_step_chips) - else: - # L2C CM code is only half of the PRN code length. - # The other half is CL code. Thus multiply by 2. - self.code_chips_to_integrate = \ - 2 * gps_constants.l2_cm_chips_per_code - \ - self.code_chips_to_integrate + self.code_phase - code_chips_to_integrate = self.code_chips_to_integrate - else: - self.E = self.P = self.L = 0.j - code_chips_to_integrate = 2 * gps_constants.l2_cm_chips_per_code - - return self.coherent_iter, code_chips_to_integrate - - def _short_n_long_postprocess(self): - more_integration_needed = False - if self.short_n_long: - if self.short_step: - # In case of short step - go to next integration period - self.short_step = False - self.alias_detect.first(self.P.real, self.P.imag) - more_integration_needed = True - else: - # Next step is short cycle - self.short_step = True - - return more_integration_needed - - def _run_postprocess(self): + def _run_nav_data_decoding(self): """ - Run L2C coherent integration postprocessing. - Runs navigation bit sync decoding operation. + Runs L2C navigation bit sync decoding operation. """ @@ -803,6 +1201,118 @@ def _run_postprocess(self): self.coherent_ms +class TrackingChannelGLOL1(TrackingChannel): + """ + GLO L1 tracking channel. + """ + + def __init__(self, params): + """ + Initialize GLO L1 tracking channel with GLO L1 specific data. + + Parameters + ---------- + params : dictionary + GLO L1 tracking initialization parameters + + """ + + # Convert acquisition SNR to C/N0 + cn0_0 = 10 * np.log10(params['acq'].snr) + cn0_0 += 10 * np.log10(defaults.GLOL1_CHANNEL_BANDWIDTH_HZ) + + params['cn0_0'] = cn0_0 + params['IF'] = params['samples'][glo_constants.GLO_L1]['IF'] + params['prn_code'] = GLOCode + params['code_freq_init'] = params['acq'].doppler * \ + glo_constants.glo_chip_rate / glo_constants.glo_l1 + + params['track_params'] = defaults.glol1_track_params + params['loop_filter_params_template'] = \ + defaults.glol1_loop_filter_params_template + + params['lock_detect_params'] = defaults.glol1_lock_detect_params + params['chipping_rate'] = glo_constants.glo_chip_rate + params['sample_index'] = params['samples']['sample_index'] + params['carrier_freq'] = glo_constants.glo_l1 + + params['fsm_states_all'] = defaults.glo_fsm_states + + self.bit_sync = False + + TrackingChannel.__init__(self, params) + + self.glol2_handover_acq = None + self.glol2_handover_done = False + + # TODO add nav msg decoder (GLO L1) + + def is_pickleable(self): + """ + GLO L1 tracking channel object is not pickleable due to complexity + of serializing cnav_msg_decoder Cython object. + + out : bool + False - the GLO L1 tracking object is not pickleable + """ + return False + + def _get_result(self): + """ + Get GLO L1 tracking results. + The possible outcome of GLO L1 tracking operation is + the GLO L1 handover to GLO L2 in the form of an GloAcquisitionResult object. + + Returns + ------- + out : AcquisitionResult + GLO L2 acquisition result or None + + """ + + if self.glol2_handover_acq and not self.glol2_handover_done: + self.glol2_handover_done = True + return self.glol2_handover_acq + return None + + def _run_preprocess(self): + """ + Run GLONASS tracking loop preprocessor operation. + It runs before every coherent integration round. + + """ + + self.coherent_iter = self.coherent_ms + + def _run_nav_data_decoding(self): + """ + Run GLO L1 coherent integration postprocessing. + Runs navigation bit sync decoding operation and + GLO L1 to GLO L2 handover. + """ + + # Handover to L2 if possible + if self.glol2_handover and not self.glol2_handover_acq and \ + glo_constants.GLO_L2 in self.samples and \ + 'samples' in self.samples[glo_constants.GLO_L2]: # and sync: + chan_snr = self.track_result.cn0[self.i] + # chan_snr -= 10 * np.log10(defaults.GLOL1_CHANNEL_BANDWIDTH_HZ) + chan_snr = np.power(10, chan_snr / 10) + glol2_doppler = self.loop_filter.to_dict()['carr_freq'] * \ + glo_constants.glo_l2 / glo_constants.glo_l1 + self.glol2_handover_acq = \ + GloAcquisitionResult(self.prn, + self.samples[glo_constants.GLO_L2]['IF'] + + glol2_doppler, + glol2_doppler, # carrier doppler + self.track_result.code_phase[ + self.i], + chan_snr, + 'A', + glo_constants.GLO_L2, + self.track_result.absolute_sample[self.i]) + + class Tracker(object): """ Tracker class. @@ -817,11 +1327,10 @@ def __init__(self, sampling_freq, check_l2c_mask=False, l2c_handover=True, + glol2_handover=True, progress_bar_output='none', loop_filter_class=AidedTrackingLoop, correlator=track_correlate, - stage2_coherent_ms=None, - stage2_loop_filter_params=None, multi=False, tracker_options=None, output_file=None): @@ -850,10 +1359,6 @@ def __init__(self, The type of the loop filter class to be used by tracker channels correlator : class The correlator class to be used by tracker channels - stage2_coherent_ms : dictionary - Stage 2 coherent integration parameters set. - stage2_loop_filter_params : dictionary - Stage 2 loop filter parameters set. multi : bool Enable multi core CPU utilization tracker_options : dictionary @@ -866,16 +1371,16 @@ def __init__(self, """ + print "loop_filter_class = ", loop_filter_class self.samples = samples self.sampling_freq = sampling_freq self.ms_to_track = ms_to_track self.tracker_options = tracker_options self.output_file = output_file self.l2c_handover = l2c_handover + self.glol2_handover = glol2_handover self.check_l2c_mask = check_l2c_mask self.correlator = correlator - self.stage2_coherent_ms = stage2_coherent_ms - self.stage2_loop_filter_params = stage2_loop_filter_params if mp.cpu_count() > 1: self.multi = multi @@ -888,8 +1393,8 @@ def __init__(self, self.samples_to_track = self.ms_to_track * sampling_freq / 1e3 if samples['samples_total'] < self.samples_to_track: logger.warning( - "Samples set too short for requested tracking length (%.4fs)" - % (self.ms_to_track * 1e-3)) + "Samples set too short for requested tracking length (%.4fs)" + % (self.ms_to_track * 1e-3)) self.samples_to_track = samples['samples_total'] else: self.samples_to_track = samples['samples_total'] @@ -969,8 +1474,8 @@ def stop(self): if self.pbar: self.pbar.finish() res = map(lambda chan: chan.track_result.makeOutputFileNames( - chan.output_file), - self.tracking_channels) + chan.output_file), + self.tracking_channels) fn_analysis = map(lambda x: x[0], res) fn_results = map(lambda x: x[1], res) @@ -1016,10 +1521,9 @@ def _create_channel(self, acq): 'samples_to_track': self.samples_to_track, 'sampling_freq': self.sampling_freq, 'l2c_handover': l2c_handover, + 'glol2_handover': self.glol2_handover, 'show_progress': self.show_progress, 'correlator': self.correlator, - 'stage2_coherent_ms': self.stage2_coherent_ms, - 'stage2_loop_filter_params': self.stage2_loop_filter_params, 'multi': self.multi} return _tracking_channel_factory(parameters) @@ -1141,19 +1645,48 @@ def __init__(self, n_points, prn, signal): self.P = np.zeros(n_points, dtype=np.complex128) self.L = np.zeros(n_points, dtype=np.complex128) self.cn0 = np.zeros(n_points) + self.snr = np.zeros(n_points) + self.snr_db = np.zeros(n_points) self.lock_detect_outp = np.zeros(n_points) self.lock_detect_outo = np.zeros(n_points) + self.lock_detect_fast_outp = np.zeros(n_points) + self.lock_detect_fast_outo = np.zeros(n_points) self.lock_detect_pcount1 = np.zeros(n_points) self.lock_detect_pcount2 = np.zeros(n_points) + self.lock_detect_lpfi = np.zeros(n_points) self.lock_detect_lpfq = np.zeros(n_points) + + self.lock_detect_fast_lpfi = np.zeros(n_points) + self.lock_detect_fast_lpfq = np.zeros(n_points) + + self.lock_detect_slow_lpfi = np.zeros(n_points) + self.lock_detect_slow_lpfq = np.zeros(n_points) + self.alias_detect_err_hz = np.zeros(n_points) + self.phase_err = np.zeros(n_points) + self.iq_ratio_min = np.zeros(n_points) + self.code_err = np.zeros(n_points) + self.acceleration = np.zeros(n_points) + self.acc_g = np.zeros(n_points) + self.acc_detected = np.zeros(n_points) self.nav_msg = NavMsg() self.nav_msg_bit_phase_ref = np.zeros(n_points) self.nav_bit_sync = NBSMatchBit() if prn < 32 else NBSSBAS() self.tow = np.empty(n_points) self.tow[:] = np.NAN self.coherent_ms = np.zeros(n_points) + self.more_samples = np.zeros(n_points) + + self.bit_sync = np.zeros(n_points) + + self.fll_bw = np.zeros(n_points) + self.pll_bw = np.zeros(n_points) + self.dll_bw = np.zeros(n_points) + + self.track_timer_ms = np.zeros(n_points) + self.acc_timer_ms = np.zeros(n_points) + self.signal = signal self.ms_tracked = np.zeros(n_points) @@ -1191,46 +1724,83 @@ def dump(self, output_file, size): with open(fn_analysis, mode) as f1: if self.print_start: f1.write( - "sample_index,ms_tracked,coherent_ms,IF,doppler_phase,carr_doppler," - "code_phase,code_freq," - "CN0,E_I,E_Q,P_I,P_Q,L_I,L_Q," - "lock_detect_outp,lock_detect_outo," - "lock_detect_pcount1,lock_detect_pcount2," - "lock_detect_lpfi,lock_detect_lpfq,alias_detect_err_hz," - "code_phase_acc\n") + "sample,ms,coherent_ms,bs,fll_bw,pll_bw,dll_bw," + "track_ms,acc_ms," + "plock,plock_fast," + "i/q,i/q_fast,i/q_slow,i/q_raw,i/q_min," + "acc_g,acc_flag," + "phase_err,code_err,CN0,IF,doppler_phase," + "carr_doppler,code_phase,code_freq," + "SNR,SNR_DB,P_Mag,E_I,E_Q,P_I,P_Q,L_I,L_Q," + "lock_detect_pcount1,lock_detect_pcount2," + "lock_detect_lpfi,lock_detect_lpfq," + "alias_detect_err_hz," + "acceleration,code_phase_acc,more_samples\n") for i in range(size): f1.write("%s," % int(self.absolute_sample[i])) - f1.write("%s," % self.ms_tracked[i]) + f1.write("%.1f," % self.ms_tracked[i]) f1.write("%s," % self.coherent_ms[i]) - f1.write("%s," % self.IF) - f1.write("%s," % self.carr_phase[i]) - f1.write("%s," % (self.carr_freq[i] - - self.IF)) - f1.write("%s," % self.code_phase[i]) - f1.write("%s," % self.code_freq[i]) - f1.write("%s," % self.cn0[i]) - f1.write("%s," % self.E[i].real) - f1.write("%s," % self.E[i].imag) - f1.write("%s," % self.P[i].real) - f1.write("%s," % self.P[i].imag) - f1.write("%s," % self.L[i].real) - f1.write("%s," % self.L[i].imag) + + f1.write("%d," % self.bit_sync[i]) + + f1.write("%s," % self.fll_bw[i]) + f1.write("%s," % self.pll_bw[i]) + f1.write("%s," % self.dll_bw[i]) + + f1.write("%d," % self.track_timer_ms[i]) + f1.write("%d," % self.acc_timer_ms[i]) + f1.write("%s," % int(self.lock_detect_outp[i])) - f1.write("%s," % int(self.lock_detect_outo[i])) + f1.write("%s," % int(self.lock_detect_fast_outp[i])) + + f1.write("%.1f," % (self.lock_detect_lpfi[i] / self.lock_detect_lpfq[i])) + f1.write("%.1f," % (self.lock_detect_fast_lpfi[i] / + self.lock_detect_fast_lpfq[i])) + f1.write("%.1f," % (self.lock_detect_slow_lpfi[i] / + self.lock_detect_slow_lpfq[i])) + f1.write("%.1f," % np.absolute(self.P[i].real / self.P[i].imag)) + f1.write("%.1f," % self.iq_ratio_min[i]) + + f1.write("%.1f," % self.acc_g[i]) + f1.write("%d," % self.acc_detected[i]) + + f1.write("%.1f," % self.phase_err[i]) + f1.write("%.1f," % self.code_err[i]) + f1.write("%.1f," % self.cn0[i]) + + f1.write("%s," % self.IF) + f1.write("%.1f," % self.carr_phase[i]) + f1.write("%.1f," % self.carr_freq[i]) + f1.write("%.1f," % self.code_phase[i]) + f1.write("%.1f," % self.code_freq[i]) + f1.write("%.1f," % self.snr[i]) + f1.write("%.1f," % self.snr_db[i]) + f1.write("%.1f," % (np.absolute(self.P[i]) ** 2)) + f1.write("%.1f," % self.E[i].real) + f1.write("%.1f," % self.E[i].imag) + f1.write("%.1f," % self.P[i].real) + f1.write("%.1f," % self.P[i].imag) + f1.write("%.1f," % self.L[i].real) + f1.write("%.1f," % self.L[i].imag) f1.write("%s," % int(self.lock_detect_pcount1[i])) f1.write("%s," % int(self.lock_detect_pcount2[i])) f1.write("%s," % self.lock_detect_lpfi[i]) f1.write("%s," % self.lock_detect_lpfq[i]) f1.write("%s," % self.alias_detect_err_hz[i]) - f1.write("%s\n" % self.code_phase_acc[i]) + f1.write("%s," % self.acceleration[i]) + f1.write("%s," % self.code_phase_acc[i]) + f1.write("%s\n" % self.more_samples[i]) self.print_start = 0 return fn_analysis, fn_results def makeOutputFileNames(self, outputFileName): # mangle the output file names with the tracked signal name + prn = self.prn + if self.signal == gps_constants.L1CA or self.signal == gps_constants.L2C: + prn += 1 fn_analysis, fn_results = createTrackingOutputFileNames(outputFileName, - self.prn + 1, + prn, self.signal) return fn_analysis, fn_results @@ -1279,6 +1849,7 @@ def __init__(self): self.bit_phase = 0 self.bit_integrate = 0 self.synced = False + self.sync_acquired = False self.bits = [] self.bit_phase_ref = -1 # A new bit begins when bit_phase == bit_phase_ref self.count = 0 @@ -1294,10 +1865,14 @@ def update(self, corr, ms): bit = 1 if self.bit_integrate > 0 else 0 self.bits.append(bit) self.bit_integrate = 0 + self.sync_acquired = True return True, bit else: return False, None + def bit_sync_acquired(self): + return self.sync_acquired + def update_bit_sync(self, corr, ms): raise NotImplementedError diff --git a/peregrine/tracking_loop.py b/peregrine/tracking_loop.py new file mode 100644 index 0000000..c6790d2 --- /dev/null +++ b/peregrine/tracking_loop.py @@ -0,0 +1,550 @@ +# Copyright (C) 2016 Swift Navigation Inc. +# Contact: Adel Mamin +# +# This source is subject to the license found in the file 'LICENSE' which must +# be be distributed together with this source. All other rights reserved. +# +# THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +# EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. + +import numpy as np +from peregrine.include.controlled_root import controlled_root + + +def costas_discriminator(I, Q): + """ + Costas discriminator implementation. + + Parameters + ---------- + I : Prompt correlator in-phase output + Q : Prompt correlator quadrature output + + Returns + ------- + out : float + Phase error [radians] + + """ + + if I == 0: + # Technically, it should be +/- 0.25, but then we'd have to keep track + # of the previous sign do it right, so it's simple enough to just return + # the average of 0.25 and -0.25 in the face of that ambiguity, so zero. + return 0 + + return np.arctan(Q / I) + + +def frequency_discriminator(I, Q, prev_I, prev_Q): + """ + FLL discriminator + + Parameters + ---------- + I : Current prompt correlator in-phase output + Q : Current prompt correlator quadrature output + prev_I : Previous prompt correlator in-phase output + prev_Q : Previous prompt correlator quadrature output + + Returns + ------- + out : float + Phase difference [radians] + + """ + + dot = np.absolute(I * prev_I) + np.absolute(Q * prev_Q) + cross = prev_I * Q - I * prev_Q + return np.arctan2(cross, dot) + + +def dll_discriminator(E, P, L): + """ + DLL discriminator + + Parameters + ---------- + E : Early correlator output + P : Prompt correlator output + L : Late correlator output + + Returns + ------- + out : float + Code phase error + + """ + + E_mag = np.absolute(E) + L_mag = np.absolute(L) + if E_mag + L_mag == 0: + return 0 + + return 0.5 * (E_mag - L_mag) / (E_mag + L_mag) + + +class TrackingLoop3: + """ + Third order tracking loop initialization. + + For a full description of the loop filter parameters, see + :libswiftnav:`calc_loop_gains`. + + """ + + def __init__(self, **kwargs): + """ + Tracking loop initialization. + + Parameters + ---------- + code_params : dictionary + carr_freq - Carrier frequency [rad/s] + code_freq - Code frequency [rad/s] + carr_freq_b1 - FLL BW + carr_bw - PLL BW + code_bw - DLL BW + carr_to_code - carrier to code scaling factor + + """ + # Initial state + self.carr_freq = kwargs['carr_freq'] + self.code_freq = kwargs['code_freq'] + + self.code_vel = kwargs['code_freq'] + self.phase_acc = 0 + self.phase_vel = kwargs['carr_freq'] + self.phase_err = 0 + self.code_err = 0 + self.fll_bw = kwargs['carr_freq_b1'] + self.pll_bw = kwargs['carr_bw'] + self.dll_bw = kwargs['code_bw'] + + self.P_prev = 1 + 0j + + self.retune((kwargs['code_bw'], kwargs['code_zeta'], kwargs['code_k']), + (kwargs['carr_bw'], kwargs['carr_zeta'], kwargs['carr_k']), + kwargs['loop_freq'], + kwargs['carr_freq_b1'], + kwargs['carr_to_code']) + + def retune(self, code_params, carr_params, loop_freq, carr_freq_b1, carr_to_code): + """ + Retune the tracking loop. + + Parameters + ---------- + code_params : (float, float, float) + Code tracking loop parameter tuple, `(bw, zeta, k)`. + carr_params : (float, float, float) + Carrier tracking loop parameter tuple, `(bw, zeta, k)`. + loop_freq : float + The frequency with which loop updates are performed. + carr_freq_b1 : float + FLL aiding gain + carr_to_code : float + PLL to DLL aiding gain + + """ + code_bw, code_zeta, code_k = code_params + carr_bw, carr_zeta, carr_k = carr_params + + # Common parameters + self.T = 1. / loop_freq + + self.fll_bw = carr_freq_b1 + self.pll_bw = carr_bw + self.dll_bw = code_bw + + # FLL constants + freq_omega_0 = carr_freq_b1 / 0.53 + freq_a2 = 1.414 + + # a_2 * omega_0f + self.freq_c1 = freq_a2 * freq_omega_0 + self.freq_c2 = freq_omega_0 * freq_omega_0 + + # PLL constants + phase_omega_0 = carr_bw / 0.7845 + phase_a3 = 1.1 + phase_b3 = 2.4 + + self.phase_c1 = phase_b3 * phase_omega_0 + self.phase_c2 = phase_a3 * phase_omega_0 * phase_omega_0 + self.phase_c3 = phase_omega_0 * phase_omega_0 * phase_omega_0 + + # DLL constants + code_omega_0 = code_bw / 0.53 + code_a2 = 1.414 + + self.code_c1 = code_a2 * code_omega_0 + self.code_c2 = code_omega_0 * code_omega_0 + + self.carr_to_code = carr_to_code + + def update(self, E, P, L): + """ + Tracking loop update. + Based on the bilinear transform. + + Parameters + ---------- + E : [complex], :math:`I_E + Q_E j` + Complex Early Correlation + P : [complex], :math:`I_P + Q_P j` + Complex Prompt Correlation + L : [complex], :math:`I_L + Q_L j` + Complex Late Correlation + + Returns + ------- + out : (float, float) + The tuple (code_freq, carrier_freq). + + """ + + # Carrier loop + self.phase_err = costas_discriminator(P.real, P.imag) + freq_error = 0 + if self.freq_c1 != 0 and self.T != 0: + freq_error = frequency_discriminator( + P.real, P.imag, self.P_prev.real, self.P_prev.imag) / self.T + self.P_prev = P + + prev = self.phase_acc + self.phase_acc += freq_error * self.freq_c2 * \ + self.T + self.phase_err * self.phase_c3 * self.T + + sum = (self.phase_acc + prev) * 0.5 + sum += freq_error * self.freq_c1 + self.phase_err * self.phase_c2 + prev = self.phase_vel + self.phase_vel += sum * self.T + sum = (self.phase_vel + prev) * 0.5 + self.phase_err * self.phase_c1 + self.carr_freq = sum + + # Code loop + self.code_err = -dll_discriminator(E, P, L) + + prev = self.code_vel + self.code_vel += self.code_c2 * self.code_err * self.T + sum = (prev + self.code_vel) * 0.5 + self.code_c1 * self.code_err + + self.code_freq = sum + if self.carr_to_code > 0: + self.code_freq += self.carr_freq / self.carr_to_code + + return (self.code_freq, self.carr_freq) + + def adjust_freq(self, corr): + self.carr_freq += corr + self.phase_vel += corr + self.code_vel += corr / self.carr_to_code + self.code_freq += corr / self.carr_to_code + + def to_dict(self): + return {k: v for k, v in self.__dict__.items() + if not (k.startswith('__') and k.endswith('__'))} + + +class TrackingLoop3b: + """ + Third order tracking loop initialization. + + For a full description of the loop filter parameters, see + :libswiftnav:`calc_loop_gains`. + + """ + + def __init__(self, **kwargs): + """ + Tracking loop initialization. + + Parameters + ---------- + code_params : dictionary + carr_freq - Carrier frequency [rad/s] + code_freq - Code frequency [rad/s] + carr_freq_b1 - FLL BW + carr_bw - PLL BW + code_bw - DLL BW + carr_to_code - carrier to code scaling factor + + """ + + # Initial state + self.carr_freq = kwargs['carr_freq'] + self.code_freq = kwargs['code_freq'] + + self.code_vel = kwargs['code_freq'] + self.phase_acc = 0 + self.phase_vel = kwargs['carr_freq'] + self.phase_err = 0 + self.code_err = 0 + self.fll_bw = kwargs['carr_freq_b1'] + self.pll_bw = kwargs['carr_bw'] + self.dll_bw = kwargs['code_bw'] + + self.P_prev = 1 + 0j + + self.retune((kwargs['code_bw'], kwargs['code_zeta'], kwargs['code_k']), + (kwargs['carr_bw'], kwargs['carr_zeta'], kwargs['carr_k']), + kwargs['loop_freq'], + kwargs['carr_freq_b1'], + kwargs['carr_to_code']) + + def retune(self, code_params, carr_params, loop_freq, carr_freq_b1, carr_to_code): + """ + Retune the tracking loop. + + Parameters + ---------- + code_params : (float, float, float) + Code tracking loop parameter tuple, `(bw, zeta, k)`. + carr_params : (float, float, float) + Carrier tracking loop parameter tuple, `(bw, zeta, k)`. + loop_freq : float + The frequency with which loop updates are performed. + carr_freq_b1 : float + FLL aiding gain + carr_to_code : float + PLL to DLL aiding gain + + """ + code_bw, code_zeta, code_k = code_params + carr_bw, carr_zeta, carr_k = carr_params + + # Common parameters + self.T = 1. / loop_freq + + self.fll_bw = carr_freq_b1 + self.pll_bw = carr_bw + self.dll_bw = code_bw + + # FLL constants + freq_omega_0 = carr_freq_b1 / 0.53 + freq_a2 = 1.414 + + # a_2 * omega_0f + self.freq_c1 = freq_a2 * freq_omega_0 + self.freq_c2 = freq_omega_0 * freq_omega_0 + + # PLL constants + self.phase_c1, self.phase_c2, self.phase_c3 = controlled_root( + 3, 1. / loop_freq, carr_bw) + + # DLL constants + code_omega_0 = code_bw / 0.53 + code_a2 = 1.414 + + self.code_c1 = code_a2 * code_omega_0 + self.code_c2 = code_omega_0 * code_omega_0 + + self.carr_to_code = carr_to_code + + def update(self, E, P, L): + """ + Tracking loop update. + Based on the boxcar transform. + + + Parameters + ---------- + E : [complex], :math:`I_E + Q_E j` + Complex Early Correlation + P : [complex], :math:`I_P + Q_P j` + Complex Prompt Correlation + L : [complex], :math:`I_L + Q_L j` + Complex Late Correlation + + Returns + ------- + out : (float, float) + The tuple (code_freq, carrier_freq). + + """ + + # Carrier loop + self.phase_err = costas_discriminator(P.real, P.imag) + freq_error = 0 + if self.freq_c1 != 0 and self.T != 0: + freq_error = frequency_discriminator( + P.real, P.imag, self.P_prev.real, self.P_prev.imag) / self.T + self.P_prev = P + + self.phase_acc += self.phase_err * self.phase_c3 / self.T + + sum = self.phase_acc + self.phase_err * self.phase_c2 / self.T + self.phase_vel += sum + sum = self.phase_vel + self.phase_err * self.phase_c1 / self.T + self.carr_freq = sum + + # Code loop + self.code_err = -dll_discriminator(E, P, L) + + prev = self.code_vel + self.code_vel += self.code_c2 * self.code_err * self.T + sum = (prev + self.code_vel) * 0.5 + self.code_c1 * self.code_err + + self.code_freq = sum + if self.carr_to_code > 0: + self.code_freq += self.carr_freq / self.carr_to_code + + return (self.code_freq, self.carr_freq) + + def adjust_freq(self, corr): + self.carr_freq += corr + + def to_dict(self): + return {k: v for k, v in self.__dict__.items() + if not (k.startswith('__') and k.endswith('__'))} + + +class TrackingLoop2b: + """ + Second order tracking loop initialization. + + For a full description of the loop filter parameters, see + :libswiftnav:`calc_loop_gains`. + + """ + + def __init__(self, **kwargs): + """ + Tracking loop initialization. + + Parameters + ---------- + code_params : dictionary + carr_freq - Carrier frequency [rad/s] + code_freq - Code frequency [rad/s] + carr_freq_b1 - FLL BW + carr_bw - PLL BW + code_bw - DLL BW + carr_to_code - carrier to code scaling factor + + """ + + # Initial state + self.carr_freq = kwargs['carr_freq'] + self.code_freq = kwargs['code_freq'] + + self.code_vel = kwargs['code_freq'] + self.phase_vel = kwargs['carr_freq'] + + self.P_prev = 1 + 0j + + self.retune((kwargs['code_bw'], kwargs['code_zeta'], kwargs['code_k']), + (kwargs['carr_bw'], kwargs['carr_zeta'], kwargs['carr_k']), + kwargs['loop_freq'], + kwargs['carr_freq_b1'], + kwargs['carr_to_code']) + + def retune(self, code_params, carr_params, loop_freq, carr_freq_b1, carr_to_code): + """ + Retune the tracking loop. + + Parameters + ---------- + code_params : (float, float, float) + Code tracking loop parameter tuple, `(bw, zeta, k)`. + carr_params : (float, float, float) + Carrier tracking loop parameter tuple, `(bw, zeta, k)`. + loop_freq : float + The frequency with which loop updates are performed. + carr_freq_b1 : float + FLL aiding gain + carr_to_code : float + PLL to DLL aiding gain + + """ + code_bw, code_zeta, code_k = code_params + carr_bw, carr_zeta, carr_k = carr_params + + # Common parameters + self.T = 1. / loop_freq + + # FLL constants + freq_omega_0 = carr_freq_b1 / 0.53 + freq_a2 = 1.414 + + # a_2 * omega_0f + self.freq_c1 = freq_a2 * freq_omega_0 + self.freq_c2 = freq_omega_0 * freq_omega_0 + + # PLL constants + phase_omega_0 = carr_bw / 0.53 + phase_a2 = 1.414 + + self.phase_c1 = phase_a2 * phase_omega_0 + self.phase_c2 = phase_omega_0 * phase_omega_0 + # self.phase_c1, self.phase_c2 = controlled_root(2, 1 / loop_freq, carr_bw) + + # DLL constants + code_omega_0 = code_bw / 0.53 + code_a2 = 1.414 + + self.code_c1 = code_a2 * code_omega_0 + self.code_c2 = code_omega_0 * code_omega_0 + + self.carr_to_code = carr_to_code + + # self.code_vel = 0 + # self.phase_vel = 0 + + def update(self, E, P, L): + """ + Tracking loop update. + Uses boxcar transform. + + Parameters + ---------- + E : [complex], :math:`I_E + Q_E j` + Complex Early Correlation + P : [complex], :math:`I_P + Q_P j` + Complex Prompt Correlation + L : [complex], :math:`I_L + Q_L j` + Complex Late Correlation + + Returns + ------- + out : (float, float) + The tuple (code_freq, carrier_freq). + + """ + + # Carrier loop + self.phase_err = costas_discriminator(P.real, P.imag) + freq_error = 0 + if self.freq_c1 != 0: + freq_error = frequency_discriminator( + P.real, P.imag, self.P_prev.real, self.P_prev.imag) / self.T + + self.P_prev = P + + self.phase_vel += freq_error * self.freq_c2 * \ + self.T + self.phase_err * self.phase_c2 * self.T + + self.carr_freq = self.phase_vel + freq_error * \ + self.freq_c1 + self.phase_err * self.phase_c1 + + # Code loop + code_error = -dll_discriminator(E, P, L) + + prev = self.code_vel + self.code_vel += self.code_c2 * code_error * self.T + sum = (prev + self.code_vel) * 0.5 + self.code_c1 * code_error + + self.code_freq = sum + if self.carr_to_code > 0: + self.code_freq += self.carr_freq / self.carr_to_code + + return (self.code_freq, self.carr_freq) + + def adjust_freq(self, corr): + self.carr_freq += corr + + def to_dict(self): + return {k: v for k, v in self.__dict__.items() + if not (k.startswith('__') and k.endswith('__'))} diff --git a/tests/test_acquisition.py b/tests/test_acquisition.py index 3c0d526..49bcc56 100644 --- a/tests/test_acquisition.py +++ b/tests/test_acquisition.py @@ -8,12 +8,13 @@ # WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. from test_common import generate_sample_file, \ - run_peregrine,\ - propagate_code_phase, \ - get_sampling_freq + run_peregrine,\ + propagate_code_phase, \ + get_sampling_freq import os import peregrine.acquisition as acq +from peregrine import defaults def get_acq_result_file_name(sample_file): @@ -37,15 +38,15 @@ def run_acq_test(init_doppler, init_code_phase, for prn in prns: samples_filename = generate_sample_file(prn, init_doppler, - init_code_phase, - file_format, freq_profile) + init_code_phase, + file_format, freq_profile) run_peregrine(samples_filename, file_format, freq_profile, skip_param, skip_val) code_phase = propagate_code_phase(init_code_phase, - get_sampling_freq(freq_profile), - skip_param, skip_val) + get_sampling_freq(freq_profile), + skip_param, skip_val) if skip_val == 0: check_acq_results(samples_filename, prn, init_doppler, code_phase) @@ -57,7 +58,7 @@ def run_acq_test(init_doppler, init_code_phase, def check_acq_results(filename, prn, doppler, code_phase): acq_results = acq.load_acq_results( - get_acq_result_file_name(filename)) + get_acq_result_file_name(filename)) acq_results = sorted(acq_results, lambda x, y: -1 if x.snr > y.snr else 1) @@ -79,7 +80,7 @@ def check_acq_results(filename, prn, doppler, code_phase): assert code_phase_diff < 1.0 -#def test_acquisition(): +# def test_acquisition(): # """ # Test GPS L1C/A acquisition # """ @@ -97,21 +98,21 @@ def test_acqusition_prn1_m1000(): """ Test GPS L1C/A acquisition """ - run_acq_test(-1000., 0., [1], '2bits') + run_acq_test(-1000., 0., [1], defaults.FORMAT_2BITS_X1_GPS_L1) def test_acqusition_prn32_0(): """ Test GPS L1C/A acquisition """ - run_acq_test(0., 0., [32], '2bits') + run_acq_test(0., 0., [32], defaults.FORMAT_2BITS_X1_GPS_L1) def test_acqusition_prn5_p1000(): """ Test GPS L1C/A acquisition """ - run_acq_test(1000., 0., [5], '2bits') + run_acq_test(1000., 0., [5], defaults.FORMAT_2BITS_X1_GPS_L1) def test_skip_params(): @@ -122,5 +123,5 @@ def test_skip_params(): --skip_ms """ - run_acq_test(1000, 0, [1], '1bit', skip_samples=1000) - run_acq_test(1000, 0, [1], '1bit', skip_ms=50) + run_acq_test(1000, 0, [1], defaults.FORMAT_1BIT_X1_GPS_L1, skip_samples=1000) + run_acq_test(1000, 0, [1], defaults.FORMAT_1BIT_X1_GPS_L1, skip_ms=50) diff --git a/tests/test_alias_detector.py b/tests/test_alias_detector.py new file mode 100644 index 0000000..34de7dd --- /dev/null +++ b/tests/test_alias_detector.py @@ -0,0 +1,43 @@ +# Copyright (C) 2016 Swift Navigation Inc. +# +# This source is subject to the license found in the file 'LICENSE' which must +# be be distributed together with this source. All other rights reserved. +# +# THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +# EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. + +import numpy as np +from peregrine import alias_detector + +def get_error(ad, expected_err_hz): + angle = expected_err_hz * 2 * np.pi * 0.001 + rotation = np.exp(1j * angle) + + ad.reinit() + + iq = rotation + ad.first(iq) + + for i in range(ad.acc_len): + ad.second(iq) + ad.first(iq) + iq *= rotation + iq /= abs(iq) + + return ad.get_err_hz() + +def test_alias_detect(): + ''' + Alias lock detector test + ''' + + ad_l1ca = alias_detector.AliasDetector() + ad_l2c = alias_detector.AliasDetector() + + meas_err_hz = [-75, -80, -25, -30, 0, 20, 25, 65, 75] + true_err_hz = [-75, -75, -25, -25, 0, 25, 25, 75, 75] + + for ad in [ad_l1ca, ad_l2c]: + for i, err_hz in enumerate(meas_err_hz): + assert true_err_hz[i] == get_error(ad, err_hz) diff --git a/tests/test_common.py b/tests/test_common.py index 135e309..d2502c6 100644 --- a/tests/test_common.py +++ b/tests/test_common.py @@ -12,22 +12,24 @@ import peregrine.iqgen.iqgen_main as iqgen import peregrine.defaults as defaults import peregrine.gps_constants as gps +import peregrine.glo_constants as glo import numpy as np from mock import patch def fileformat_to_bands(file_format): - if file_format == '1bit': - bands = ['l1ca'] - elif file_format == '1bit_x2': - bands = ['l1ca', 'l2c'] - elif file_format == '2bits': - bands = ['l1ca'] - elif file_format == '2bits_x2': - bands = ['l1ca', 'l2c'] - elif file_format == '2bits_x4': - bands = ['l1ca', 'l2c'] + profile = defaults.file_encoding_profile[file_format] + bands = [] + for p in profile: + if p == defaults.sample_channel_GPS_L1: + bands += [gps.L1CA] + elif p == defaults.sample_channel_GPS_L2: + bands += [gps.L2C] + elif p == defaults.sample_channel_GLO_L1: + bands += [glo.GLO_L1] + elif p == defaults.sample_channel_GLO_L2: + bands += [glo.GLO_L2] return bands @@ -67,8 +69,8 @@ def generate_2bits_x4_sample_file(filename): samples[channel_lookup[rx]][:] = chan # Store the result back to the same file packed = np.zeros(num_samples, dtype=np.uint8) - packed = samples[3][::] << 6 - packed |= samples[0][::] & 3 + packed[:] = samples[3] << 6 + packed |= samples[0] & 3 with open(filename, 'wb') as f: packed.tofile(f) @@ -77,17 +79,17 @@ def generate_2bits_x4_sample_file(filename): def generate_piksi_sample_file(filename): samples_lookup = [ - 0b11111100, - 0b11011000, - 0b10110100, - 0b10010000, - 0b00000000, - 0b00100100, - 0b01001000, - 0b01101100 + 0b11111100, + 0b11011000, + 0b10110100, + 0b10010000, + 0b00000000, + 0b00100100, + 0b01001000, + 0b01101100 ] samples_lookup_values = [ - -7, -7, -5, -5, -3, -3, -1, -1, 1, 1, 3, 3, 5, 5, 7, 7 + -7, -7, -5, -5, -3, -3, -1, -1, 1, 1, 3, 3, 5, 5, 7, 7 ] num_samples = int(1e6) packed = np.zeros(num_samples, dtype=np.uint8) @@ -104,23 +106,53 @@ def generate_sample_file(gps_sv_prn, init_doppler, freq_profile, generate=.1): sample_file = 'iqgen-data-samples.bin' params = ['iqgen_main'] - params += ['--gps-sv', str(gps_sv_prn)] - - if file_format == '1bit': - encoder = '1bit' - params += ['--bands', 'l1ca'] - elif file_format == '1bit_x2': - encoder = '1bit' - params += ['--bands', 'l1ca+l2c'] - elif file_format == '2bits': - encoder = '2bits' - params += ['--bands', 'l1ca'] - elif file_format == '2bits_x2': - encoder = '2bits' - params += ['--bands', 'l1ca+l2c'] - elif file_format == '2bits_x4': - encoder = '2bits' - params += ['--bands', 'l1ca+l2c'] + bands = fileformat_to_bands(file_format) + + if gps.L1CA in bands or gps.L2C in bands: + params += ['--gps-sv', str(gps_sv_prn)] + + if file_format == defaults.FORMAT_1BIT_X1_GPS_L1: + encoder = '1bit' + params += ['--bands', 'l1ca'] + elif file_format == defaults.FORMAT_1BIT_X2_GPS_L1L2: + encoder = '1bit' + params += ['--bands', 'l1ca+l2c'] + elif file_format == defaults.FORMAT_2BITS_X1_GPS_L1: + encoder = '2bits' + params += ['--bands', 'l1ca'] + elif file_format == defaults.FORMAT_2BITS_X2_GPS_L1L2: + encoder = '2bits' + params += ['--bands', 'l1ca+l2c'] + elif file_format == defaults.FORMAT_2BITS_X4_GPS_L1L2_GLO_L1L2: + encoder = '2bits' + params += ['--bands', 'l1ca+l2c'] + else: + assert False + elif glo.GLO_L1 in bands or glo.GLO_L2 in bands: + params += ['--glo-sv', str(gps_sv_prn)] + + if file_format == defaults.FORMAT_1BIT_X1_GLO_L1: + encoder = '1bit' + params += ['--bands', 'l1'] + elif file_format == defaults.FORMAT_1BIT_X1_GLO_L2: + encoder = '1bit' + params += ['--bands', 'l2'] + elif file_format == defaults.FORMAT_1BIT_X2_GLO_L1L2: + encoder = '1bit' + params += ['--bands', 'l1+l2'] + elif file_format == defaults.FORMAT_2BITS_X1_GLO_L1: + encoder = '2bits' + params += ['--bands', 'l1'] + elif file_format == defaults.FORMAT_2BITS_X2_GLO_L1L2: + encoder = '2bits' + params += ['--bands', 'l1+l2'] + elif file_format == defaults.FORMAT_2BITS_X4_GPS_L1L2_GLO_L1L2: + encoder = '2bits' + params += ['--bands', 'l1+l2'] + else: + assert False + else: + assert False params += ['--encoder', encoder] params += ['--doppler-type', 'const'] @@ -149,16 +181,15 @@ def run_peregrine(file_name, file_format, freq_profile, skip_param, skip_val, skip_tracking=True, skip_navigation=True, - pipelining=None, - short_long_cycles=None): + short_long_cycles=False): parameters = [ - 'peregrine', - '--file', file_name, - '--file-format', file_format, - '--profile', freq_profile, - skip_param, str(skip_val), - '--progress-bar', 'stdout' + 'peregrine', + '--file', file_name, + '--file-format', file_format, + '--profile', freq_profile, + skip_param, str(skip_val), + '--progress-bar', 'stdout' ] if skip_tracking: parameters += ['-t'] @@ -166,11 +197,8 @@ def run_peregrine(file_name, file_format, freq_profile, if skip_navigation: parameters += ['-n'] - if pipelining: - parameters += ['--pipelining', str(pipelining)] - if short_long_cycles: - parameters += ['--short-long-cycles', str(short_long_cycles)] + parameters += ['--short-long-cycles'] # Replace argv with args to skip tracking and navigation. with patch.object(sys, 'argv', parameters): diff --git a/tests/test_file_format.py b/tests/test_file_format.py index 36f7a95..a94794c 100644 --- a/tests/test_file_format.py +++ b/tests/test_file_format.py @@ -13,28 +13,28 @@ def test_file_format_1bit(): """ Test different file formats: '1bit' """ - run_acq_test(1000, 0, [1], '1bit') + run_acq_test(1000, 0, [1], defaults.FORMAT_1BIT_X1_GPS_L1) def test_file_format_2bits(): """ Test different file formats: '2bits' """ - run_acq_test(1000, 0, [1], '2bits') + run_acq_test(1000, 0, [1], defaults.FORMAT_2BITS_X1_GPS_L1) def test_file_format_1bitx2(): """ Test different file formats: '1bit_x2' """ - run_acq_test(1000, 0, [1], '1bit_x2') + run_acq_test(1000, 0, [1], defaults.FORMAT_1BIT_X2_GPS_L1L2) def test_file_format_2bitsx2(): """ Test different file formats: '2bits_x2' """ - run_acq_test(1000, 0, [1], '2bits_x2') + run_acq_test(1000, 0, [1], defaults.FORMAT_2BITS_X2_GPS_L1L2) def test_file_formats(): @@ -45,9 +45,9 @@ def test_file_formats(): # test 'piksi' format val = generate_piksi_sample_file(SAMPLE_FILE_NAME) samples = { - 'samples_total': -1, - 'sample_index': 0, - L1CA: {} + 'samples_total': -1, + 'sample_index': 0, + L1CA: {} } for num in [-1, defaults.processing_block_size]: load_samples(samples, SAMPLE_FILE_NAME, num, 'piksi') diff --git a/tests/test_freq_profiles.py b/tests/test_freq_profiles.py index b343c19..7786935 100644 --- a/tests/test_freq_profiles.py +++ b/tests/test_freq_profiles.py @@ -8,21 +8,22 @@ # WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. from test_acquisition import run_acq_test +from peregrine import defaults def test_custom_rate(): - run_acq_test(-4000, 0, [1], '2bits', 'custom_rate') + run_acq_test(-4000, 0, [1], defaults.FORMAT_2BITS_X1_GPS_L1, 'custom_rate') def test_low_rate(): - run_acq_test(-2000, 0, [2], '2bits', 'low_rate') + run_acq_test(-2000, 0, [2], defaults.FORMAT_2BITS_X1_GPS_L1, 'low_rate') def test_normal_rate(): - run_acq_test(2000, 0, [3], '2bits', 'normal_rate') + run_acq_test(2000, 0, [3], defaults.FORMAT_2BITS_X1_GPS_L1, 'normal_rate') # Takes long time to complete in Travis CI test and # therefore fails -#def test_high_rate(): -# run_acq_test(2000, 0, [4], '2bits', 'high_rate') +# def test_high_rate(): +# run_acq_test(2000, 0, [4], defaults.FORMAT_2BITS_X1_GPS_L1, 'high_rate') diff --git a/tests/test_run.py b/tests/test_run.py index 53128fa..d9d5bab 100644 --- a/tests/test_run.py +++ b/tests/test_run.py @@ -7,34 +7,178 @@ # EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED # WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +import peregrine.run +import peregrine.iqgen.iqgen_main as iqgen +import sys import os +import peregrine.acquisition as acq +import peregrine.gps_constants as gps +import peregrine.glo_constants as glo -from test_common import generate_sample_file, \ - run_peregrine +from mock import patch +SAMPLES_PATH = 'tests/test_data/' +# todo: the gpsl1ca_ci_samples.piksi_format.acq_results +# should replace the old file with the same name at the +# remote server, where the script takes it from. +# For now, let's use the local version. +#RES_PATH = SAMPLES_PATH + '/results/' +RES_PATH = 'tests/' -def test_tracking(): - prn = 15 - init_doppler = 1234 - init_code_phase = 0 - file_format = '2bits_x2' +SAMPLES_FNAME = 'gpsl1ca_ci_samples.piksi_format' + +SAMPLES = SAMPLES_PATH + SAMPLES_FNAME + +OLD_TRK_RES = RES_PATH + SAMPLES_FNAME + '.track_results' +OLD_NAV_RES = RES_PATH + SAMPLES_FNAME + '.nav_results' + +# run.py deposits results in same location as samples +NEW_TRK_RES = SAMPLES_PATH + SAMPLES_FNAME + '.track_results' +NEW_NAV_RES = SAMPLES_PATH + SAMPLES_FNAME + '.nav_results' + + +def generate_sample_file(gps_sv_prn, glo_ch, init_doppler, init_code_phase): + sample_file = 'iqgen-data-samples.bin' freq_profile = 'low_rate' - skip_param = '--skip-ms' - skip_val = 0 - samples_filename = generate_sample_file(prn, init_doppler, - init_code_phase, - file_format, freq_profile) - - run_peregrine(samples_filename, file_format, freq_profile, - skip_param, skip_val, False) - - # Comparison not working on Travis at the moment, needs further debugging. - # Simply make sure tracking runs successfully for now. - #with open(NEW_TRK_RES, 'rb') as f: - # new_trk_results = cPickle.load(f) - #with open(OLD_TRK_RES, 'rb') as f: - # old_trk_results = cPickle.load(f) - #assert new_trk_results == old_trk_results - - # Clean-up. - os.remove(samples_filename) + params = ['iqgen_main'] + # GPS + params += ['--gps-sv', str(gps_sv_prn)] + params += ['--bands', 'l1ca'] + params += ['--doppler-type', 'const'] + params += ['--doppler-value', str(init_doppler)] + params += ['--tec', '0'] + params += ['--distance', '0'] + params += ['--chip-delay', str(init_code_phase)] + params += ['--amplitude-type', 'poly'] + params += ['--amplitude-units', 'snr-db'] + params += ['--amplitude-a0', '-17'] + # GLO + params += ['--glo-sv', str(glo_ch)] + params += ['--bands', 'l1'] + params += ['--doppler-type', 'const'] + params += ['--doppler-value', str(init_doppler)] + params += ['--tec', '0'] + params += ['--distance', '0'] + params += ['--message-type', 'crc'] + params += ['--chip-delay', str(init_code_phase)] + params += ['--amplitude-type', 'poly'] + params += ['--amplitude-units', 'snr-db'] + params += ['--amplitude-a0', '-17'] + # common + params += ['--generate', '1'] + params += ['--encoder', '2bits'] + params += ['--output', sample_file] + params += ['--profile', freq_profile] + params += ['-j', '4'] + print params + with patch.object(sys, 'argv', params): + iqgen.main() + + return {'sample_file': sample_file, + 'file_format': '2bits_x4', + 'freq_profile': 'low_rate'} + + +def get_acq_result_file_name(sample_file): + return sample_file + '.acq_results' + + +def run_acq_test(init_doppler, init_code_phase): + for ch in [-1, 0, 1]: + prn = (ch + 8) * 2 + + samples = generate_sample_file(prn, ch, init_doppler, init_code_phase) + + # Replace argv with args to skip tracking and navigation. + with patch.object(sys, 'argv', + ['peregrine', + '--file', samples['sample_file'], + '--file-format', samples['file_format'], + '--profile', samples['freq_profile'], + '-t', '-n']): + + try: + peregrine.run.main() + except SystemExit: + # Thrown if track and nav results files are not present and we + # supplied command line args to skip tracking and navigation. + pass + + acq_results = acq.load_acq_results( + get_acq_result_file_name(samples['sample_file'])) + + glo_res = [] + gps_res = [] + for res in acq_results: + if isinstance(res, acq.GloAcquisitionResult): + glo_res.append(res) + else: + gps_res.append(res) + glo_res = sorted(glo_res, lambda x, y: -1 if x.snr > y.snr else 1) + gps_res = sorted(gps_res, lambda x, y: -1 if x.snr > y.snr else 1) + + def check_result(res): + assert len(res) != 0 + + result = res[0] + print "result = ", result + if isinstance(result, acq.GloAcquisitionResult): + assert (result.prn) == ch + code_length = glo.glo_code_len + else: + assert (result.prn + 1) == prn + code_length = gps.l1ca_code_length + + # check doppler phase estimation + doppler_diff = abs(abs(result.doppler) - abs(init_doppler)) + print "doppler_diff = ", doppler_diff + assert doppler_diff < 200.0 + + # check code phase estimation + code_phase = result.code_phase + if code_phase > code_length / 2: + code_phase = code_phase - code_length + code_phase_diff = abs(abs(code_phase) - abs(init_code_phase)) + print "code_phase_diff = ", code_phase_diff + assert code_phase_diff < 1.0 + + check_result(glo_res) + check_result(gps_res) + + # Clean-up. + os.remove(get_acq_result_file_name(samples['sample_file'])) + os.remove(samples['sample_file']) + + +def test_acquisition(): + run_acq_test(775, 0) + +# def test_tracking(): + +# # Replace argv with args to skip acquisition and navigation. +# with patch.object(sys, 'argv', ['peregrine', SAMPLES, '-a', '-n']): + +# # Copy reference acq results to use in order to skip acquisition. +# copyfile(OLD_ACQ_RES, NEW_ACQ_RES) + +# try: +# peregrine.run.main() +# except SystemExit: +# # Thrown if nav results file is not present and we supplied +# # command line arg to skip navigation. +# pass + +# # Comparison not working on Travis at the moment, needs further debugging. +# # Simply make sure tracking runs successfully for now. +# #with open(NEW_TRK_RES, 'rb') as f: +# # new_trk_results = cPickle.load(f) +# #with open(OLD_TRK_RES, 'rb') as f: +# # old_trk_results = cPickle.load(f) +# #assert new_trk_results == old_trk_results + +# # Clean-up. +# os.remove(NEW_ACQ_RES) +# #os.remove(NEW_TRK_RES) + +# if __name__ == '__main__': +# test_acquisition() diff --git a/tests/test_tracking.py b/tests/test_tracking.py index c1709a6..a9ad22c 100644 --- a/tests/test_tracking.py +++ b/tests/test_tracking.py @@ -10,13 +10,15 @@ # WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. from peregrine.gps_constants import l1, l2, L1CA, L2C +from peregrine.glo_constants import GLO_L1 from test_common import generate_sample_file, fileformat_to_bands,\ - get_skip_params, run_peregrine + get_skip_params, run_peregrine from test_acquisition import get_acq_result_file_name from peregrine.analysis import tracking_loop from peregrine.tracking import TrackingLoop, NavBitSync, NavBitSyncSBAS,\ - NBSLibSwiftNav, NBSSBAS, NBSMatchBit,\ - NBSHistogram, NBSMatchEdge + NBSLibSwiftNav, NBSSBAS, NBSMatchBit,\ + NBSHistogram, NBSMatchEdge +from peregrine import defaults import cPickle import csv @@ -28,23 +30,21 @@ def run_tracking_loop(prn, signal, dopp, phase, file_name, file_format, freq_profile, skip_val, norun=False, l2chandover=False, - pipelining=None, short_long_cycles=None): + short_long_cycles=False): parameters = [ - 'tracking_loop', - '-P', str(prn), - '-p', str(phase), - '-d', str(dopp), - '-S', signal, - '--file', file_name, - '--file-format', file_format, - '--profile', freq_profile, - '--skip-samples', str(skip_val) + 'tracking_loop', + '-P', str(prn), + '-p', str(phase), + '-d', str(dopp), + '-S', signal, + '--file', file_name, + '--file-format', file_format, + '--profile', freq_profile, + '--skip-samples', str(skip_val) ] - if pipelining: - parameters += ['--pipelining', str(pipelining)] - elif short_long_cycles: - parameters += ['--short-long-cycles', str(short_long_cycles)] + if short_long_cycles: + parameters += ['--short-long-cycles'] if norun: parameters.append('--no-run') @@ -61,24 +61,24 @@ def run_tracking_loop(prn, signal, dopp, phase, file_name, file_format, def get_track_result_file_name(sample_file, prn, band): sample_file, sample_file_extension = os.path.splitext(sample_file) return (sample_file + (".PRN-%d.%s" % (prn, band)) + - sample_file_extension + '.track_results', - "track.PRN-%d.%s.csv" % (prn, band)) + sample_file_extension + '.track_results', + "track.PRN-%d.%s.csv" % (prn, band)) def get_peregrine_tr_res_file_name(sample_file, prn, band): - per_fn, tr_loop_fn = get_track_result_file_name(sample_file, prn, band) + per_fn, _ = get_track_result_file_name(sample_file, prn, band) return per_fn def get_tr_loop_res_file_name(sample_file, prn, band): - per_fn, tr_loop_fn = get_track_result_file_name(sample_file, prn, band) + _, tr_loop_fn = get_track_result_file_name(sample_file, prn, band) return tr_loop_fn def run_track_test(samples_file, expected_lock_ratio, init_doppler, init_code_phase, prn, file_format, freq_profile, skip_samples=None, skip_ms=None, - pipelining=None, short_long_cycles=None): + short_long_cycles=False): bands = fileformat_to_bands(file_format) @@ -86,7 +86,7 @@ def run_track_test(samples_file, expected_lock_ratio, init_doppler, run_peregrine(samples_file, file_format, freq_profile, skip_param, skip_val, skip_tracking=False, - pipelining=pipelining, short_long_cycles=short_long_cycles) + short_long_cycles=short_long_cycles) for band in bands: dopp_ratio = 1 @@ -94,18 +94,17 @@ def run_track_test(samples_file, expected_lock_ratio, init_doppler, dopp_ratio = l2 / l1 run_tracking_loop(prn, band, init_doppler * dopp_ratio, init_code_phase, samples_file, file_format, freq_profile, 0, - pipelining=pipelining, short_long_cycles=short_long_cycles) - #code_phase = propagate_code_phase(init_code_phase, - #get_sampling_freq(freq_profile), - #skip_param, skip_val) + # code_phase = propagate_code_phase(init_code_phase, + # get_sampling_freq(freq_profile), + # skip_param, skip_val) check_per_track_results(expected_lock_ratio, samples_file, prn, bands, - pipelining, short_long_cycles) + short_long_cycles) check_tr_loop_track(expected_lock_ratio, samples_file, prn, bands, - pipelining, short_long_cycles) + short_long_cycles) # Clean-up. os.remove(get_acq_result_file_name(samples_file)) @@ -115,7 +114,7 @@ def run_track_test(samples_file, expected_lock_ratio, init_doppler, def check_per_track_results(expected_lock_ratio, filename, prn, bands, - pipelining, short_long_cycles): + short_long_cycles): ret = {} print "Peregrine tracking:" for band in bands: @@ -126,7 +125,10 @@ def check_per_track_results(expected_lock_ratio, filename, prn, bands, while True: try: track_results = cPickle.load(f) - assert (track_results.prn + 1) == prn + if band == L1CA or band == L2C: + assert (track_results.prn + 1) == prn + else: + assert (track_results.prn) == prn assert track_results.status == 'T' assert track_results == track_results lock_detect_outp_sum += (track_results.lock_detect_outp == 1).sum() @@ -137,14 +139,14 @@ def check_per_track_results(expected_lock_ratio, filename, prn, bands, print "band =", band lock_ratio = float(lock_detect_outp_sum) / lock_detect_outp_len print "lock_ratio =", lock_ratio - if (not short_long_cycles and not pipelining) or band != L2C: + if not short_long_cycles or band != L2C: assert lock_ratio >= expected_lock_ratio ret[band]['lock_ratio'] = lock_ratio return ret def check_tr_loop_track(expected_lock_ratio, filename, prn, bands, - pipelining, short_long_cycles): + short_long_cycles): ret = {} print "Tracking loop:" for band in bands: @@ -161,13 +163,13 @@ def check_tr_loop_track(expected_lock_ratio, filename, prn, bands, print "band =", band lock_ratio = float(lock_detect_outp_sum) / lock_detect_outp_len print "lock_ratio =", lock_ratio - if (not short_long_cycles and not pipelining) or band != L2C: + if not short_long_cycles or band != L2C: assert lock_ratio >= expected_lock_ratio ret[band]['lock_ratio'] = lock_ratio return ret -def test_tracking(): +def test_tracking_gps(): """ Test GPS L1C/A and L2C tracking """ @@ -175,7 +177,7 @@ def test_tracking(): prn = 1 init_doppler = 555 init_code_phase = 0 - file_format = '2bits_x2' + file_format = defaults.FORMAT_2BITS_X2_GPS_L1L2 freq_profile = 'low_rate' samples = generate_sample_file(prn, init_doppler, @@ -183,16 +185,16 @@ def test_tracking(): file_format, freq_profile, generate=5) run_track_test(samples, 0.6, init_doppler, init_code_phase, prn, file_format, - freq_profile) + freq_profile) run_track_test(samples, 0.3, init_doppler, init_code_phase, prn, file_format, - freq_profile, pipelining=0.5) + freq_profile) run_track_test(samples, 0.3, init_doppler, init_code_phase, prn, file_format, - freq_profile, short_long_cycles=0.5) + freq_profile, short_long_cycles=True) os.remove(samples) # test --no-run - run_tracking_loop(1, L1CA, 0, 0, 'dummy', '2bits_x2', 'low_rate', 0, + run_tracking_loop(1, L1CA, 0, 0, 'dummy', file_format, 'low_rate', 0, norun=True) # Test with different initial code phases @@ -233,5 +235,35 @@ def test_tracking(): assert NBSMatchEdge() +def test_tracking_glo(): + """ + Test GLO L1 tracking + """ + + prn = 0 + init_doppler = 555 + init_code_phase = 0 + file_format = defaults.FORMAT_2BITS_X1_GLO_L1 + freq_profile = 'low_rate' + + samples = generate_sample_file(prn, init_doppler, + init_code_phase, + file_format, freq_profile, generate=5) + + run_track_test(samples, 0.6, init_doppler, init_code_phase, prn, file_format, + freq_profile) + run_track_test(samples, 0.3, init_doppler, init_code_phase, prn, file_format, + freq_profile) + run_track_test(samples, 0.3, init_doppler, init_code_phase, prn, file_format, + freq_profile, short_long_cycles=True) + + os.remove(samples) + + # test --no-run + run_tracking_loop(0, GLO_L1, 0, 0, 'dummy', file_format, 'low_rate', 0, + norun=True) + + if __name__ == '__main__': - test_tracking() + test_tracking_gps() + test_tracking_glo()