From d9e56292f8fd35d37aa045bfed61d60ad13aba20 Mon Sep 17 00:00:00 2001 From: Pasi Miettinen Date: Thu, 16 Jun 2016 12:38:10 +0300 Subject: [PATCH 01/12] Remove call to start() which was done for debugging purposes --- peregrine/tracking.py | 1 - 1 file changed, 1 deletion(-) diff --git a/peregrine/tracking.py b/peregrine/tracking.py index 3c4389f..7934ea8 100644 --- a/peregrine/tracking.py +++ b/peregrine/tracking.py @@ -363,7 +363,6 @@ def run(self, samples): which can be redefined in subclasses """ - self.start() self.samples = samples if self.sample_index < samples['sample_index']: From 4b59d6e847ed993c444e89db78d8cb1484ee5e87 Mon Sep 17 00:00:00 2001 From: Adel Mamin Date: Tue, 21 Jun 2016 12:09:23 +0300 Subject: [PATCH 02/12] Fix L1CA and L2C alias lock detectors --- libswiftnav | 2 +- peregrine/alias_detector.py | 136 +++++++++++++++++++++++++++++++ peregrine/defaults.py | 13 ++- peregrine/tracking.py | 158 +++++++++++++++++++----------------- 4 files changed, 230 insertions(+), 79 deletions(-) create mode 100644 peregrine/alias_detector.py diff --git a/libswiftnav b/libswiftnav index 8bbc35e..d3e254c 160000 --- a/libswiftnav +++ b/libswiftnav @@ -1 +1 @@ -Subproject commit 8bbc35e7292b639c7f4be636ef3ccb1227312c8b +Subproject commit d3e254cf0db3e0b6d3fadd0b1fb15304acff9ef3 diff --git a/peregrine/alias_detector.py b/peregrine/alias_detector.py new file mode 100644 index 0000000..f348727 --- /dev/null +++ b/peregrine/alias_detector.py @@ -0,0 +1,136 @@ +# 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 swiftnav.track import AliasDetector as AD +from peregrine import defaults +from peregrine import gps_constants + + +class AliasDetector(object): + + def __init__(self, coherent_ms): + """ + Initialize the parameters, which are common across different + types of tracking channels. + + Parameters + ---------- + coherent_ms : int + Coherent integration time [ms]. + + + """ + self.first_step = True + self.err_hz = 0. + self.coherent_ms = coherent_ms + self.iterations_num = coherent_ms / defaults.alias_detect_slice_ms + + def reinit(self, coherent_ms): + """ + Customize the alias detect reinitialization in a subclass. + The method can be optionally redefined in a subclass to perform + a subclass specific actions to happen when alias detect + reinitialization procedure happens. + + Parameters + ---------- + coherent_ms : int + Coherent integration time [ms]. + + """ + self.err_hz = 0 + self.coherent_ms = coherent_ms + self.alias_detect.reinit(self.integration_rounds, time_diff=2e-3) + + def preprocess(self): + """ + Customize the alias detect 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. + + """ + self.first_step = True + return self.iterations_num, self.chips_num + + def postprocess(self, P): + """ + Customize the alias detect run procedure in a subclass. + The method can be optionally redefined in a subclass to perform + a subclass specific actions to happen after correlator runs + next integration round. + + Parameters + ---------- + P : prompt I/Q + The prompt I/Q samples from correlator. + code_phase : current code phase [chips] + + """ + + def postprocess(self, P): + if self.first_step: + self.alias_detect.first(P.real, P.imag) + else: + self.err_hz = self.alias_detect.second(P.real, P.imag) + 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 + + self.first_step = not self.first_step + + return self.chips_num + + def get_err_hz(self): + """ + Customize the alias detect get error procedure in a subclass. + + """ + return self.err_hz + + +class AliasDetectorL1CA(AliasDetector): + + def __init__(self, coherent_ms): + + AliasDetector.__init__(self, coherent_ms) + + self.chips_num = gps_constants.chips_per_code + self.integration_rounds = defaults.alias_detect_interval_ms / \ + (defaults.alias_detect_slice_ms * 2) + + self.alias_detect = AD(acc_len=self.integration_rounds, time_diff=2e-3) + + +class AliasDetectorL2C(AliasDetector): + + def __init__(self, coherent_ms): + + AliasDetector.__init__(self, coherent_ms) + + self.chips_num = 2 * gps_constants.l2_cm_chips_per_code / coherent_ms + self.integration_rounds = defaults.alias_detect_interval_ms / \ + (defaults.alias_detect_slice_ms * 2) + + self.alias_detect = AD(acc_len=self.integration_rounds, time_diff=2e-3) diff --git a/peregrine/defaults.py b/peregrine/defaults.py index 3620e9c..7caf99d 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 @@ -21,7 +21,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 @@ -264,7 +264,16 @@ 'lp': 50, # 1000ms worth of I/Q samples to reach pessimistic lock 'lo': 240} # 4800ms worth of I/Q samples to lower optimistic lock +# 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/tracking.py b/peregrine/tracking.py index 7934ea8..fb5755b 100644 --- a/peregrine/tracking.py +++ b/peregrine/tracking.py @@ -16,7 +16,6 @@ from swiftnav.track import LockDetector 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,6 +25,7 @@ from swiftnav.signal import signal_from_code_index from peregrine import defaults from peregrine import gps_constants +from peregrine import alias_detector from peregrine.acquisition import AcquisitionResult from peregrine.include.generateCAcode import caCodes from peregrine.include.generateL2CMcode import L2CMCodes @@ -172,10 +172,6 @@ def __init__(self, params): 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) - self.cn0_est = CN0Estimator( bw=1e3 / self.coherent_ms, cn0_0=self.cn0_0, @@ -203,7 +199,6 @@ def __init__(self, params): self.track_result = TrackResults(self.results_num, self.acq.prn, self.acq.signal) - self.alias_detect_init = 1 self.code_phase = 0.0 self.carr_phase = 0.0 self.samples_per_chip = int(round(self.sampling_freq / self.chipping_rate)) @@ -215,6 +210,9 @@ def __init__(self, params): self.code_phase_acc = 0.0 self.samples_tracked = 0 self.i = 0 + self.started = False + self.lock_detect_outo = 0 + self.lock_detect_outp = 0 self.pipelining = False # Flag if pipelining is used self.pipelining_k = 0. # Error prediction coefficient for pipelining @@ -249,6 +247,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" % @@ -412,18 +415,26 @@ def run(self, samples): 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() + if self.short_n_long: + coherent_iter, code_chips_to_integrate = self._short_n_long_preprocess() + else: + coherent_iter, code_chips_to_integrate = \ + self.alias_detector.preprocess() + self.E = self.P = self.L = 0.j - for _ in range(self.coherent_iter): + # 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: + continue - if (sample_index + 2 * estimated_blksize) >= samples_total: - break + for _ in range(coherent_iter): samples_ = samples[self.signal]['samples'][sample_index:] E_, P_, L_, blksize, self.code_phase, self.carr_phase = self.correlator( samples_, - code_chips_to_integrate, + self.code_phase + code_chips_to_integrate, corr_code_freq + self.chipping_rate, self.code_phase, corr_carr_freq + self.IF, self.carr_phase, self.prn_code, @@ -431,9 +442,6 @@ def run(self, samples): 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 @@ -443,34 +451,35 @@ def run(self, samples): self.P += P_ self.L += L_ - more_integration_needed = self._short_n_long_postprocess() - if more_integration_needed: - continue + if self.short_n_long: + continue + + if self.lock_detect_outo: + code_chips_to_integrate = self.alias_detector.postprocess(P_) + else: + self.alias_detector.reinit(self.coherent_ms) + + if self.short_n_long: + more_integration_needed = self._short_n_long_postprocess() + if more_integration_needed: + continue + + 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) # 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 + self.lock_detect_outo, \ + self.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) self.loop_filter.update(self.E, self.P, self.L) self.track_result.coherent_ms[self.i] = self.coherent_ms @@ -497,14 +506,14 @@ def run(self, samples): 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.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_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.alias_detect_err_hz[self.i] = err_hz self._run_postprocess() @@ -555,6 +564,8 @@ def __init__(self, params): 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['alias_detector'] = \ + alias_detector.AliasDetectorL1CA(params['coherent_ms']) TrackingChannel.__init__(self, params) @@ -581,6 +592,8 @@ def _run_preprocess(self): self.stage1 = False self.coherent_ms = self.stage2_coherent_ms + self.alias_detector.reinit(self.coherent_ms) + self.loop_filter.retune(**self.stage2_loop_filter_params) self.lock_detect.reinit( k1=self.lock_detect_params["k1"] * self.coherent_ms, @@ -613,7 +626,9 @@ def _get_result(self): return None def _short_n_long_preprocess(self): - if self.short_n_long and not self.stage1: + if self.stage1: + self.E = self.P = self.L = 0.j + else: # When simulating short and long cycles, short step resets EPL # registers, and long one adds up to them if self.short_step: @@ -621,8 +636,6 @@ def _short_n_long_preprocess(self): 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 @@ -630,11 +643,10 @@ def _short_n_long_preprocess(self): def _short_n_long_postprocess(self): more_integration_needed = False - if not self.stage1 and self.short_n_long: + if not self.stage1: 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 @@ -711,7 +723,7 @@ 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_ms'] = defaults.l2c_coherent_integration_time_ms params['coherent_iter'] = 1 params['loop_filter_params'] = defaults.l2c_loop_filter_params params['lock_detect_params'] = defaults.l2c_lock_detect_params_20ms @@ -721,6 +733,8 @@ def __init__(self, params): gps_constants.l2c_chip_rate / gps_constants.l2 params['chipping_rate'] = gps_constants.l2c_chip_rate params['sample_index'] = 0 + params['alias_detector'] = \ + alias_detector.AliasDetectorL2C(params['coherent_ms']) TrackingChannel.__init__(self, params) @@ -738,39 +752,31 @@ 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: + # 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 - code_chips_to_integrate = 2 * gps_constants.l2_cm_chips_per_code + # 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 + code_chips_to_integrate = self.code_chips_to_integrate 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 + if self.short_step: + self.short_step = False + more_integration_needed = True + else: + self.short_step = True return more_integration_needed From da7eaaba0bf495cea415b77d36174d6b6e130f4f Mon Sep 17 00:00:00 2001 From: Adel Mamin Date: Tue, 21 Jun 2016 14:10:07 +0300 Subject: [PATCH 03/12] Fix code formatting --- peregrine/tracking.py | 46 +++++++++++++++++++++---------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/peregrine/tracking.py b/peregrine/tracking.py index fb5755b..e2dc838 100644 --- a/peregrine/tracking.py +++ b/peregrine/tracking.py @@ -419,7 +419,7 @@ def run(self, samples): coherent_iter, code_chips_to_integrate = self._short_n_long_preprocess() else: coherent_iter, code_chips_to_integrate = \ - self.alias_detector.preprocess() + self.alias_detector.preprocess() self.E = self.P = self.L = 0.j # Estimated blksize might change as a result of a change of @@ -473,13 +473,13 @@ def run(self, samples): # Update PLL lock detector self.lock_detect_outo, \ - self.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) + self.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) self.loop_filter.update(self.E, self.P, self.L) self.track_result.coherent_ms[self.i] = self.coherent_ms @@ -565,7 +565,7 @@ def __init__(self, params): params['chipping_rate'] = gps_constants.l1ca_chip_rate params['sample_index'] = params['samples']['sample_index'] params['alias_detector'] = \ - alias_detector.AliasDetectorL1CA(params['coherent_ms']) + alias_detector.AliasDetectorL1CA(params['coherent_ms']) TrackingChannel.__init__(self, params) @@ -759,13 +759,13 @@ def _short_n_long_preprocess(self): # 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) + 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 + 2 * gps_constants.l2_cm_chips_per_code - \ + self.code_chips_to_integrate code_chips_to_integrate = self.code_chips_to_integrate return self.coherent_iter, code_chips_to_integrate @@ -893,8 +893,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'] @@ -974,8 +974,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) @@ -1196,13 +1196,13 @@ 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_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") for i in range(size): f1.write("%s," % int(self.absolute_sample[i])) f1.write("%s," % self.ms_tracked[i]) From f9c7ef99fb1e0adcf05cb5049d5c7383a571af7d Mon Sep 17 00:00:00 2001 From: Adel Mamin Date: Tue, 21 Jun 2016 21:15:47 +0300 Subject: [PATCH 04/12] Address code review comments --- libswiftnav | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libswiftnav b/libswiftnav index d3e254c..8bbc35e 160000 --- a/libswiftnav +++ b/libswiftnav @@ -1 +1 @@ -Subproject commit d3e254cf0db3e0b6d3fadd0b1fb15304acff9ef3 +Subproject commit 8bbc35e7292b639c7f4be636ef3ccb1227312c8b From 8ea1d215d29a6eb30450d670f2a910ebb2a37abc Mon Sep 17 00:00:00 2001 From: Pasi Miettinen Date: Mon, 18 Apr 2016 10:48:20 +0300 Subject: [PATCH 05/12] Add GLO acquisition support Conflicts: peregrine/defaults.py peregrine/gps_constants.py peregrine/run.py tests/test_run.py --- peregrine/acquisition.py | 130 +++++++++++++++++------- peregrine/defaults.py | 7 +- peregrine/glo_constants.py | 13 +++ peregrine/run.py | 40 +++++--- peregrine/samples.py | 3 + tests/test_run.py | 196 ++++++++++++++++++++++++++++++++----- 6 files changed, 312 insertions(+), 77 deletions(-) create mode 100644 peregrine/glo_constants.py 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/defaults.py b/peregrine/defaults.py index 7caf99d..80e9b83 100644 --- a/peregrine/defaults.py +++ b/peregrine/defaults.py @@ -84,18 +84,21 @@ freq_profile_low_rate = { 'GPS_L1_IF': 1026375.0, 'GPS_L2_IF': 7.4e5, + 'GLO_L1_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, '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'], 'sampling_freq': 99.375e6} freq_profile_lookup = { diff --git a/peregrine/glo_constants.py b/peregrine/glo_constants.py new file mode 100644 index 0000000..d35319d --- /dev/null +++ b/peregrine/glo_constants.py @@ -0,0 +1,13 @@ +# -*- 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_code_period = glo_code_len / glo_chip_rate +glo_code_wavelength = glo_code_period * c + +GLO_L1 = 'glo_l1' diff --git a/peregrine/run.py b/peregrine/run.py index e99a06e..d47dc56 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,7 @@ 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 class SaveConfigAction(argparse.Action): @@ -54,6 +56,7 @@ def __call__(self, parser, namespace, file_hnd, option_string=None): file_hnd.close() + def populate_peregrine_cmd_line_arguments(parser): if sys.stdout.isatty(): progress_bar_default = 'stdout' @@ -183,6 +186,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) @@ -229,6 +235,7 @@ 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']}, 'samples_total': -1, 'sample_index': skip_samples} @@ -243,10 +250,22 @@ 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))) + acq_results = [] + for signal in [gps.L1CA, glo.GLO_L1]: + if 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))) + else: + if args.skip_glonass: + continue + 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))) # Get 11ms of acquisition samples for fine frequency estimation load_samples(samples=samples, @@ -257,13 +276,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!" diff --git a/peregrine/samples.py b/peregrine/samples.py index f2b077d..972e024 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 __all__ = ['load_samples', 'save_samples'] @@ -369,6 +370,8 @@ def load_samples(samples, samples[L1CA]['samples'] = signal[defaults.sample_channel_GPS_L1] if len(signal) > 1: samples[L2C]['samples'] = signal[defaults.sample_channel_GPS_L2] + if len(signal) > 2: + samples[GLO_L1]['samples'] = signal[defaults.sample_channel_GLO_L1] return samples 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() From 0ed5bb32a741243e37954fa0fd5db0994b2bbba1 Mon Sep 17 00:00:00 2001 From: Valeri Atamaniouk Date: Mon, 1 Aug 2016 17:15:50 +0300 Subject: [PATCH 06/12] glonass: added file formats with GLO signals for acquisition Added GLONASS-specific file formats for processing. --- peregrine/analysis/tracking_loop.py | 3 + peregrine/defaults.py | 82 +++++++++++++++-- peregrine/glo_constants.py | 1 + peregrine/run.py | 27 +++--- peregrine/samples.py | 135 ++++++++++++++++------------ 5 files changed, 172 insertions(+), 76 deletions(-) diff --git a/peregrine/analysis/tracking_loop.py b/peregrine/analysis/tracking_loop.py index 1832821..6cc7329 100755 --- a/peregrine/analysis/tracking_loop.py +++ b/peregrine/analysis/tracking_loop.py @@ -18,6 +18,7 @@ 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 from peregrine.run import populate_peregrine_cmd_line_arguments @@ -129,6 +130,8 @@ def main(): 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} diff --git a/peregrine/defaults.py b/peregrine/defaults.py index 80e9b83..34daeae 100644 --- a/peregrine/defaults.py +++ b/peregrine/defaults.py @@ -34,11 +34,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,21 +77,74 @@ # 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 @@ -85,6 +152,7 @@ '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 @@ -92,6 +160,7 @@ 'GPS_L1_IF': 10263750.0, 'GPS_L2_IF': 7.4e6, 'GLO_L1_IF': 12e6, + 'GLO_L2_IF': 12e6, 'sampling_freq': 24.84375e6} # 'high_rate' frequencies profile @@ -99,6 +168,7 @@ '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 = { diff --git a/peregrine/glo_constants.py b/peregrine/glo_constants.py index d35319d..176af07 100644 --- a/peregrine/glo_constants.py +++ b/peregrine/glo_constants.py @@ -11,3 +11,4 @@ glo_code_wavelength = glo_code_period * c GLO_L1 = 'glo_l1' +GLO_L2 = 'glo_l2' diff --git a/peregrine/run.py b/peregrine/run.py index d47dc56..1d6fa9a 100755 --- a/peregrine/run.py +++ b/peregrine/run.py @@ -19,7 +19,7 @@ from peregrine.samples import load_samples from peregrine.acquisition import Acquisition, load_acq_results,\ - save_acq_results + save_acq_results from peregrine.navigation import navigation import peregrine.tracking as tracking from peregrine.log import default_logging_config @@ -56,7 +56,6 @@ def __call__(self, parser, namespace, file_hnd, option_string=None): file_hnd.close() - def populate_peregrine_cmd_line_arguments(parser): if sys.stdout.isatty(): progress_bar_default = 'stdout' @@ -115,12 +114,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', @@ -236,6 +233,7 @@ 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} @@ -250,22 +248,29 @@ def main(): acq_results_file) sys.exit(1) else: + encoding_profile = defaults.file_encoding_profile[args.file_format] + acq_results = [] - for signal in [gps.L1CA, glo.GLO_L1]: - if signal == gps.L1CA: + 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))) - else: + (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))) + (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, diff --git a/peregrine/samples.py b/peregrine/samples.py index 972e024..c2043d8 100644 --- a/peregrine/samples.py +++ b/peregrine/samples.py @@ -14,7 +14,7 @@ import math import defaults from peregrine.gps_constants import L1CA, L2C -from peregrine.glo_constants import GLO_L1 +from peregrine.glo_constants import GLO_L1, GLO_L2 __all__ = ['load_samples', 'save_samples'] @@ -67,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 @@ -79,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 @@ -140,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. @@ -185,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. @@ -261,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 @@ -287,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 - 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 + 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_x4) + encoding_profile) else: raise ValueError("Unknown file type '%s'" % file_format) @@ -313,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': @@ -328,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) @@ -367,11 +381,14 @@ def load_samples(samples, num_samples, samples['sample_index'], file_format) - samples[L1CA]['samples'] = signal[defaults.sample_channel_GPS_L1] - if len(signal) > 1: + if L1CA in samples and defaults.sample_channel_GPS_L1 in signal: + samples[L1CA]['samples'] = signal[defaults.sample_channel_GPS_L1] + if L2C in samples and defaults.sample_channel_GPS_L2 in signal: samples[L2C]['samples'] = signal[defaults.sample_channel_GPS_L2] - if len(signal) > 2: + if GLO_L1 in samples and defaults.sample_channel_GLO_L1 in signal: samples[GLO_L1]['samples'] = signal[defaults.sample_channel_GLO_L1] + if GLO_L2 in samples and defaults.sample_channel_GLO_L2 in signal: + samples[GLO_L2]['samples'] = signal[defaults.sample_channel_GLO_L2] return samples From 011be82d58630c88772d08803605303483e413c5 Mon Sep 17 00:00:00 2001 From: Valeri Atamaniouk Date: Wed, 3 Aug 2016 14:50:42 +0300 Subject: [PATCH 07/12] iqgen: Added support for explicit file encoding types Added support for file encoding types compatible with main peregrine tools. --- peregrine/iqgen/iqgen_main.py | 53 +++++++++++++++++++++++++++++++++-- 1 file changed, 50 insertions(+), 3 deletions(-) diff --git a/peregrine/iqgen/iqgen_main.py b/peregrine/iqgen/iqgen_main.py index 5dd5852..f96647c 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__) @@ -666,7 +667,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'), @@ -801,8 +817,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: From 31b3a6a5a8bbd984f02ae18314e54f364db311ca Mon Sep 17 00:00:00 2001 From: Pasi Miettinen Date: Thu, 21 Apr 2016 10:11:42 +0300 Subject: [PATCH 08/12] Add GLO tracking support --- peregrine/alias_detector.py | 14 ++++ peregrine/defaults.py | 3 +- peregrine/samples.py | 61 ++++++++++++++-- peregrine/tracking.py | 142 ++++++++++++++++++++++++++++++++++++ 4 files changed, 211 insertions(+), 9 deletions(-) diff --git a/peregrine/alias_detector.py b/peregrine/alias_detector.py index f348727..e809c74 100644 --- a/peregrine/alias_detector.py +++ b/peregrine/alias_detector.py @@ -12,6 +12,7 @@ from swiftnav.track import AliasDetector as AD from peregrine import defaults from peregrine import gps_constants +from peregrine import glo_constants class AliasDetector(object): @@ -134,3 +135,16 @@ def __init__(self, coherent_ms): (defaults.alias_detect_slice_ms * 2) self.alias_detect = AD(acc_len=self.integration_rounds, time_diff=2e-3) + + +class AliasDetectorGLO(AliasDetector): + + def __init__(self, coherent_ms): + + super(AliasDetectorGLO, self).__init__(coherent_ms) + + self.chips_num = glo_constants.glo_code_len + self.integration_rounds = defaults.alias_detect_interval_ms / \ + (defaults.alias_detect_slice_ms * 2) + + self.alias_detect = AD(acc_len=self.integration_rounds, time_diff=2e-3) diff --git a/peregrine/defaults.py b/peregrine/defaults.py index 34daeae..e02a8e9 100644 --- a/peregrine/defaults.py +++ b/peregrine/defaults.py @@ -160,7 +160,7 @@ 'GPS_L1_IF': 10263750.0, 'GPS_L2_IF': 7.4e6, 'GLO_L1_IF': 12e6, - 'GLO_L2_IF': 12e6, + 'GLO_L2_IF': 5.6e6, 'sampling_freq': 24.84375e6} # 'high_rate' frequencies profile @@ -179,6 +179,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] diff --git a/peregrine/samples.py b/peregrine/samples.py index c2043d8..154ae99 100644 --- a/peregrine/samples.py +++ b/peregrine/samples.py @@ -368,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, @@ -381,14 +429,11 @@ def load_samples(samples, num_samples, samples['sample_index'], file_format) - if L1CA in samples and defaults.sample_channel_GPS_L1 in signal: - samples[L1CA]['samples'] = signal[defaults.sample_channel_GPS_L1] - if L2C in samples and defaults.sample_channel_GPS_L2 in signal: - samples[L2C]['samples'] = signal[defaults.sample_channel_GPS_L2] - if GLO_L1 in samples and defaults.sample_channel_GLO_L1 in signal: - samples[GLO_L1]['samples'] = signal[defaults.sample_channel_GLO_L1] - if GLO_L2 in samples and defaults.sample_channel_GLO_L2 in signal: - samples[GLO_L2]['samples'] = signal[defaults.sample_channel_GLO_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 e2dc838..8fa85a3 100644 --- a/peregrine/tracking.py +++ b/peregrine/tracking.py @@ -25,10 +25,13 @@ 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 import logging @@ -120,6 +123,8 @@ 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) class TrackingChannel(object): @@ -686,6 +691,7 @@ def _run_postprocess(self): # 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) @@ -808,6 +814,139 @@ 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['coherent_ms'] = 1 + params['coherent_iter'] = 1 + params['loop_filter_params'] = defaults.l1ca_stage1_loop_filter_params + params['lock_detect_params'] = defaults.l1ca_lock_detect_params_opt + 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['chipping_rate'] = glo_constants.glo_chip_rate + params['sample_index'] = 0 + params['alias_detector'] = \ + alias_detector.AliasDetectorGLO(params['coherent_ms']) + + 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 _short_n_long_preprocess(self): + if self.stage1: + self.E = self.P = self.L = 0.j + else: + # 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 + + self.code_chips_to_integrate = glo_constants.glo_code_len + + return self.coherent_iter, self.code_chips_to_integrate + + def _short_n_long_postprocess(self): + more_integration_needed = False + if not self.stage1: + if self.short_step: + # In case of short step - go to next integration period + self.short_step = False + more_integration_needed = True + else: + # Next step is short cycle + self.short_step = True + return more_integration_needed + + def _run_postprocess(self): + """ + Run GLO L1 coherent integration postprocessing. + Runs navigation bit sync decoding operation and + GLO L1 to GLO L2 handover. + """ + + # Handover to L2C 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. @@ -822,6 +961,7 @@ 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, @@ -877,6 +1017,7 @@ def __init__(self, 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 @@ -1021,6 +1162,7 @@ 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, From 89ace5d44b83e8ce069553f4e073a0669018a7c6 Mon Sep 17 00:00:00 2001 From: Valeri Atamaniouk Date: Tue, 2 Aug 2016 13:47:05 +0300 Subject: [PATCH 09/12] iqgen: fixes for GLO signal generation. Updated GLONASS C/N0 estimation and parameter handling. --- peregrine/iqgen/generate.py | 13 ++++++------- peregrine/iqgen/iqgen_main.py | 14 ++++++-------- 2 files changed, 12 insertions(+), 15 deletions(-) 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 f96647c..ce0144a 100755 --- a/peregrine/iqgen/iqgen_main.py +++ b/peregrine/iqgen/iqgen_main.py @@ -273,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: @@ -571,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", @@ -656,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") @@ -755,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 From 0ca3c2fa2f2e4b1398d927ad46bfc3fb5f5346e6 Mon Sep 17 00:00:00 2001 From: Valeri Atamaniouk Date: Wed, 3 Aug 2016 15:58:28 +0300 Subject: [PATCH 10/12] tracking: fixed GLONASS result file name mangling Fixed handling of GLONASS tracking result file names. --- peregrine/glo_constants.py | 1 + peregrine/tracking.py | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/peregrine/glo_constants.py b/peregrine/glo_constants.py index 176af07..65ece04 100644 --- a/peregrine/glo_constants.py +++ b/peregrine/glo_constants.py @@ -6,6 +6,7 @@ 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 diff --git a/peregrine/tracking.py b/peregrine/tracking.py index 8fa85a3..154797d 100644 --- a/peregrine/tracking.py +++ b/peregrine/tracking.py @@ -1376,8 +1376,11 @@ def dump(self, output_file, size): 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 From 7af2714ff275cf423f2a16695436e9e62ebe0b65 Mon Sep 17 00:00:00 2001 From: Valeri Atamaniouk Date: Wed, 3 Aug 2016 15:59:13 +0300 Subject: [PATCH 11/12] tracking: added support for tracking GLONASS SVs Added support for tracking analysys of GLONASS signals. --- peregrine/analysis/tracking_loop.py | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/peregrine/analysis/tracking_loop.py b/peregrine/analysis/tracking_loop.py index 6cc7329..e639679 100755 --- a/peregrine/analysis/tracking_loop.py +++ b/peregrine/analysis/tracking_loop.py @@ -18,7 +18,7 @@ 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 +from peregrine.glo_constants import GLO_L1, GLO_L2, glo_l1_step, glo_l2_step from peregrine.run import populate_peregrine_cmd_line_arguments @@ -41,9 +41,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", @@ -79,13 +79,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() @@ -98,7 +110,6 @@ 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) @@ -148,8 +159,10 @@ 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 From 0f8ae7c19b529c83450bef9bb5a26a1d2d2402c3 Mon Sep 17 00:00:00 2001 From: Valeri Atamaniouk Date: Wed, 3 Aug 2016 14:36:59 +0300 Subject: [PATCH 12/12] Updated unit tests Updated hanlding of encoding names, added GLONASS tracking test. --- tests/test_acquisition.py | 29 ++++----- tests/test_common.py | 120 +++++++++++++++++++++++------------- tests/test_file_format.py | 14 ++--- tests/test_freq_profiles.py | 11 ++-- tests/test_tracking.py | 89 ++++++++++++++++++-------- 5 files changed, 166 insertions(+), 97 deletions(-) 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_common.py b/tests/test_common.py index 135e309..6166df7 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'] @@ -153,12 +185,12 @@ def run_peregrine(file_name, file_format, freq_profile, short_long_cycles=None): 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'] 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_tracking.py b/tests/test_tracking.py index c1709a6..c58776e 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 @@ -30,15 +32,15 @@ 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): 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: @@ -61,17 +63,17 @@ 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 @@ -97,9 +99,9 @@ def run_track_test(samples_file, expected_lock_ratio, init_doppler, 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) @@ -126,7 +128,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() @@ -167,7 +172,7 @@ def check_tr_loop_track(expected_lock_ratio, filename, prn, bands, return ret -def test_tracking(): +def test_tracking_gps(): """ Test GPS L1C/A and L2C tracking """ @@ -175,7 +180,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 +188,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, pipelining=0.5) 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=0.5) 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 +238,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, pipelining=0.5) + run_track_test(samples, 0.3, init_doppler, init_code_phase, prn, file_format, + freq_profile, short_long_cycles=0.5) + + 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()