From 8a681a92043715f4dbaf19228c587edf20ae04b3 Mon Sep 17 00:00:00 2001 From: Colin Beighley Date: Fri, 19 Feb 2016 12:55:02 -0800 Subject: [PATCH 01/57] Change loop filter params from list to dict per Adel's comments --- peregrine/tracking.py | 48 +++++++++++++++++++++++++++---------------- 1 file changed, 30 insertions(+), 18 deletions(-) diff --git a/peregrine/tracking.py b/peregrine/tracking.py index a94c216..71304b4 100644 --- a/peregrine/tracking.py +++ b/peregrine/tracking.py @@ -29,6 +29,22 @@ except ImportError: _progressbar_available = False +default_stage1_loop_filter_params = { + 'code_loop': { + 'bw': 1, + 'zeta': 0.7, + 'k': 1, + }, + 'carr_loop': { + 'bw': 25, + 'zeta': 0.7, + 'k': 1, + }, + 'loop_freq': 1e3, + 'carr_freq_b1': 5, + 'carr_to_code': 1540, +} + class TrackingLoop(object): """ @@ -81,6 +97,7 @@ def update(self, e, p, l): """ raise NotImplementedError() + def track(samples, channels, ms_to_track=None, sampling_freq=defaults.sampling_freq, @@ -88,13 +105,8 @@ def track(samples, channels, IF=defaults.IF, show_progress=True, loop_filter_class=swiftnav.track.AidedTrackingLoop, - stage1_loop_filter_params=( - (1, 0.7, 1), # Code loop NBW, zeta, k - (25, 0.7, 1), # Carrier loop NBW, zeta, k - 1e3, # Loop frequency - 5, # Carrier loop aiding_igain - 1540 - ), + stage1_loop_filter_params=\ + default_stage1_loop_filter_params, correlator=swiftnav.correlate.track_correlate_, stage2_coherent_ms=None, stage2_loop_filter_params=None, @@ -165,17 +177,17 @@ def do_channel(chan, n=None, q_progress=None): gps_constants.chip_rate / gps_constants.l1 carr_freq_init = chan.carr_freq - IF loop_filter = loop_filter_class( - loop_freq = stage1_loop_filter_params[2], - code_freq = code_freq_init, - code_bw = stage1_loop_filter_params[0][0], - code_zeta = stage1_loop_filter_params[0][1], - code_k = stage1_loop_filter_params[0][2], - carr_to_code = stage1_loop_filter_params[4], - carr_freq = carr_freq_init, - carr_bw = stage1_loop_filter_params[1][0], - carr_zeta = stage1_loop_filter_params[1][1], - carr_k = stage1_loop_filter_params[1][2], - carr_freq_b1 = stage1_loop_filter_params[3], + loop_freq = stage1_loop_filter_params['loop_freq'], + code_freq = code_freq_init, + code_bw = stage1_loop_filter_params['code_loop_bw'], + code_zeta = stage1_loop_filter_params['code_loop_zeta'], + code_k = stage1_loop_filter_params['code_loop_k'], + carr_to_code = stage1_loop_filter_params['carr_to_code'], + carr_freq = carr_freq_init, + carr_bw = stage1_loop_filter_params['carr_loop_bw'], + carr_zeta = stage1_loop_filter_params['carr_loop_zeta'], + carr_k = stage1_loop_filter_params['carr_loop_k'], + carr_freq_b1 = stage1_loop_filter_params['carr_freq_b1'], ) code_phase = 0.0 carr_phase = 0.0 From 7bd08ea75f0198b2b5bd84199b98b928456447d7 Mon Sep 17 00:00:00 2001 From: Colin Beighley Date: Fri, 19 Feb 2016 12:57:54 -0800 Subject: [PATCH 02/57] Fix dict --- peregrine/tracking.py | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/peregrine/tracking.py b/peregrine/tracking.py index 71304b4..6830df6 100644 --- a/peregrine/tracking.py +++ b/peregrine/tracking.py @@ -30,16 +30,12 @@ _progressbar_available = False default_stage1_loop_filter_params = { - 'code_loop': { - 'bw': 1, - 'zeta': 0.7, - 'k': 1, - }, - 'carr_loop': { - 'bw': 25, - 'zeta': 0.7, - 'k': 1, - }, + 'code_loop_bw': 1, + 'code_loop_zeta': 0.7, + 'code_loop_k': 1, + 'carr_loop_bw': 25, + 'carr_loop_zeta': 0.7, + 'carr_loop_k': 1, 'loop_freq': 1e3, 'carr_freq_b1': 5, 'carr_to_code': 1540, From 1c0b2ab5f89d5b2fd04b1fab527ce59cd738863c Mon Sep 17 00:00:00 2001 From: Dmitry Tatarinov Date: Thu, 21 Jan 2016 20:49:43 +0200 Subject: [PATCH 03/57] Add L2CM code generation functionality New file generateL2CMcode.py added. Use function genereteL2CMcode(PRN) to generate the sequence. New func returns a code and end state of shift register for testing purposes. --- peregrine/include/generateL2CMcode.py | 99 +++++++++++++++++++++++++++ peregrine/stream_usrp.py | 55 +++++++++++---- tests/test_generateL2CMcode.py | 42 ++++++++++++ 3 files changed, 181 insertions(+), 15 deletions(-) create mode 100644 peregrine/include/generateL2CMcode.py create mode 100644 tests/test_generateL2CMcode.py diff --git a/peregrine/include/generateL2CMcode.py b/peregrine/include/generateL2CMcode.py new file mode 100644 index 0000000..ad83dbd --- /dev/null +++ b/peregrine/include/generateL2CMcode.py @@ -0,0 +1,99 @@ +# Copyright (C) 2016 Swift Navigation Inc. +# +# Contact: Dmitry Tatarinov +# 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 + +def generateL2CMcode(PRN): + """ + The function generates PRN sequence for a particular SV. + In the sequence '0' is represented as '1', 1 as -1 + + INPUT: SV number from 0 (SV1) to 31 (SV32) + OUTPUT: + - PRN seequence array of 10230, + - end state of shift register for testing purpuses + """ + #--- Sanity sheck for PRN number ------------------------------------------ + if PRN < 0 or PRN > 31: + raise ValueError('PRN number(',PRN,') is not in range [0..31]') + + #--- Initial states for shift register for each PRN[1..32], --------------- + # see IS-GPS-200H, Table 3-IIa + initL2CM = [\ + 0742417664, #PRN 1 + 0756014035, + 0002747144, + 0066265724, + 0601403471, + 0703232733, + 0124510070, + 0617316361, + 0047541621, + 0733031046, + 0713512145, + 0024437606, + 0021264003, + 0230655351, + 0001314400, + 0222021506, + 0540264026, + 0205521705, + 0064022144, + 0120161274, + 0044023533, + 0724744327, + 0045743577, + 0741201660, + 0700274134, + 0010247261, + 0713433445, + 0737324162, + 0311627434, + 0710452007, + 0722462133, + 0050172213 #PRN 32 + ] + + #--- Init L2CM PRN and shift reg ------------------------------------------ + L2CM_PRN = np.zeros(10230, np.int8) + + #--- Load Shift register -------------------------------------------------- + shift_cm = initL2CM[PRN] + + #--- Generate L2CM PRN chips ---------------------------------------------- + for i in range(10230): + + out = shift_cm & 1 + if out == 1: + L2CM_PRN[i] = -1 #-1 to represent '1' + else: + L2CM_PRN[i] = 1 # 1 to represent '0' + + shift_reg_out = shift_cm + + shift_cm ^= out << 3 + shift_cm ^= out << 4 + shift_cm ^= out << 5 + shift_cm ^= out << 6 + shift_cm ^= out << 9 + shift_cm ^= out << 11 + shift_cm ^= out << 13 + shift_cm ^= out << 16 + shift_cm ^= out << 19 + shift_cm ^= out << 21 + shift_cm ^= out << 24 + shift_cm = (shift_cm >> 1) | (out << 26) + + return (L2CM_PRN, shift_reg_out) + +L2CMCodes = np.empty((32,10230), dtype=np.int8) +for PRN in range(32): + L2CMCodes[PRN][:] = generateL2CMcode(PRN)[0] + diff --git a/peregrine/stream_usrp.py b/peregrine/stream_usrp.py index 9abeae3..29832c5 100755 --- a/peregrine/stream_usrp.py +++ b/peregrine/stream_usrp.py @@ -10,7 +10,6 @@ from gnuradio import blocks from gnuradio import gr from gnuradio import uhd -from gnuradio import gru import time import argparse import sys @@ -24,6 +23,7 @@ def __init__(self, filenames, dev_addrs, dual, gr.top_block.__init__(self) if mix: raise NotImplementedError("TODO: Hilbert remix mode not implemented.") + if dual: channels = [0, 1] else: @@ -58,6 +58,7 @@ def __init__(self, filenames, dev_addrs, dual, if noise or onebit or not iq: raise NotImplementedError("TODO: RX channel-interleaved mode only " "supported for noiseless 8-bit complex.") + BLOCK_N = 16*1024*1024 demux = blocks.vector_to_streams(2, len(uhd_sinks)) self.connect(blocks.file_source(2*len(uhd_sinks)*BLOCK_N, filenames[0], False), @@ -131,23 +132,49 @@ def __init__(self, filenames, dev_addrs, dual, # No external PPS/10 MHz. Just set each clock and accept some skew. t = time.time() [sink.set_time_now(uhd.time_spec(time.time())) for sink in uhd_sinks] - if len(uhd_sinks) > 1 or dual: + if len(uhd_sinks) > 1: print "Uncabled; loosely synced only. Initial skew ~ %.1f ms" % ( (time.time()-t) * 1000) t_start = uhd.time_spec(time.time() + 1.5) [sink.set_start_time(t_start) for sink in uhd_sinks] print "ready" - # setup message handler - self.async_msgq = gr.msg_queue(0) - self.async_src = uhd.amsg_source("", self.async_msgq) - self.async_rcv = gru.msgq_runner(self.async_msgq, self.async_callback) - def async_callback(self, msg): - md = self.async_src.msg_to_async_metadata_t(msg) - print "Channel: %i Time: %f Event: %i" % (md.channel, md.time_spec.get_real_secs(), md.event_code) - self.error_code= md.event_code - self.stop() +# This function should behave exactly as MAIN, except it errors out +# as soon as any of the USRP errors are encountered. It should be run in +# a fashion like this: +# PYTHONPATH=. python -c "import peregrine.stream_usrp; peregrine.stream_usrp.main_capture_errors()" +# ... -1 -u name=MyB210 -d -g30 peregrine/sample_2015_09_11_18-47-11.1bit peregrine/a + +def main_capture_errors(): + args = sys.argv + args.pop(0) + args.insert(0,"peregrine/stream_usrp.py") + print args + proc = subprocess.Popen(args, + stderr=subprocess.PIPE) + out_str = "" + while proc.poll() == None: + errchar = proc.stderr.read(1) + if errchar == 'U': + print "Stream_usrp exiting due to Underflow at time {0}".format(str(datetime.datetime.now())) + proc.kill() + sys.exit(2) + if errchar == 'L': + print "Stream_usrp exiting due to Undeflow at time {0}".format(str(dateime.datetime.now())) + proc.kill() + sys.exit(3) + if errchar == "\n": + sys.stderr.write(out_str) + out_str = "" + else: + out_str += errchar + # Sleep for a second before exiting if it's not one of the cases we handle specially + time.sleep(1) + out_str += proc.stderr.read() + if out_str != "": + sys.stderr.write(out_str) + return proc.returncode def main(): if gr.enable_realtime_scheduling() != gr.RT_OK: @@ -179,7 +206,7 @@ def main(): help="Center frequency (%(default).0f)") parser.add_argument("-d", dest="dual", action='store_true', help="Using dual USRP devices") - parser.add_argument("-o", dest="outfile", default=None, + parser.add_argument("-o", dest="outfile", default="out.txt", help="Route Python stdout/stderr to this file") args = parser.parse_args() @@ -210,9 +237,7 @@ def main(): sync_pps=args.pps) tb.start() tb.wait() - if args.outfile: - stdout.close() - sys.exit(tb.error_code) + stdout.close() if __name__ == '__main__': main() diff --git a/tests/test_generateL2CMcode.py b/tests/test_generateL2CMcode.py new file mode 100644 index 0000000..b682449 --- /dev/null +++ b/tests/test_generateL2CMcode.py @@ -0,0 +1,42 @@ +import pytest +from peregrine.include.generateL2CMcode import generateL2CMcode + +end_shift_regs_test = [\ + 0552566002, + 0034445034, + 0723443711, + 0511222013, + 0463055213, + 0667044524, + 0652322653, + 0505703344, + 0520302775, + 0244205506, + 0236174002, + 0654305531, + 0435070571, + 0630431251, + 0234043417, + 0535540745, + 0043056734, + 0731304103, + 0412120105, + 0365636111, + 0143324657, + 0110766462, + 0602405203, + 0177735650, + 0630177560, + 0653467107, + 0406576630, + 0221777100, + 0773266673, + 0100010710, + 0431037132, + 0624127475 +] + +def test_generateL2CMcode(): + for i in range(32): + assert (end_shift_regs_test[i] == generateL2CMcode(i)[1]) + From 6996b8bbc40e1e2808147c5b1078ef525cf42bf8 Mon Sep 17 00:00:00 2001 From: Adel Mamin Date: Thu, 11 Feb 2016 15:28:38 +0200 Subject: [PATCH 04/57] Add L2C CM tracking loop to Peregrine --- peregrine/acquisition.py | 14 ++- peregrine/analysis/print_res.py | 40 +++++++ peregrine/analysis/tracking_loop.py | 157 ++++++++++++++++++++++++++++ peregrine/gps_constants.py | 1 + peregrine/run.py | 2 + peregrine/tracking.py | 70 ++++++++----- 6 files changed, 250 insertions(+), 34 deletions(-) create mode 100644 peregrine/analysis/print_res.py create mode 100644 peregrine/analysis/tracking_loop.py diff --git a/peregrine/acquisition.py b/peregrine/acquisition.py index 01da32d..4726c74 100644 --- a/peregrine/acquisition.py +++ b/peregrine/acquisition.py @@ -531,22 +531,26 @@ class AcquisitionResult: * `'A'` : The satellite has been successfully acquired. * `'-'` : The acquisition was not successful, the SNR was below the acquisition threshold. - + signal : {'l1ca', 'l2c'} + The type of the signal: L1C/A or L2C """ - __slots__ = ('prn', 'carr_freq', 'doppler', 'code_phase', 'snr', 'status') + __slots__ = ('prn', 'carr_freq', 'doppler', \ + 'code_phase', 'snr', 'status', 'signal') - def __init__(self, prn, carr_freq, doppler, code_phase, snr, status): + def __init__(self, prn, carr_freq, doppler, code_phase, snr, status, signal): self.prn = prn self.snr = snr self.carr_freq = carr_freq self.doppler = doppler self.code_phase = code_phase self.status = status + self.signal = signal def __str__(self): - return "PRN %2d SNR %6.2f @ CP %6.1f, %+8.2f Hz %s" % \ - (self.prn + 1, self.snr, self.code_phase, self.doppler, self.status) + return "PRN %2d (%s) SNR %6.2f @ CP %6.1f, %+8.2f Hz %s" % \ + (self.prn + 1, self.signal, self.snr, self.code_phase, \ + self.doppler, self.status) def __repr__(self): return "" % self.__str__() diff --git a/peregrine/analysis/print_res.py b/peregrine/analysis/print_res.py new file mode 100644 index 0000000..f7c415a --- /dev/null +++ b/peregrine/analysis/print_res.py @@ -0,0 +1,40 @@ +from scipy import signal +import numpy as np +import matplotlib.pyplot as plt +from StringIO import StringIO +import argparse + +def main(): + parser = argparse.ArgumentParser() + + parser.add_argument("-f", "--file", default="tracking_res.csv", + help="the input CSV file to process") + + parser.add_argument("-p", "--par-to-print", default="CN0", + help="parameter to print") + + parser.add_argument("-s", "--time-step", default="0.1", + help="time step [s]") + + args = parser.parse_args() + + fig = plt.figure() + #plt.title('Carrier tracking loop filter frequency response') + ax1 = fig.add_subplot(111) + + plt.ylabel(args.par_to_print, color='b') + plt.xlabel('time [%s s]' % float(args.time_step)) + + data = np.genfromtxt(args.file, dtype=float, delimiter=',', names=True) + + plt.plot(np.array(range(len(data[args.par_to_print]))), + np.array(data[args.par_to_print]), 'r.') + + plt.legend(loc='upper right') + + plt.grid() + plt.axis('tight') + plt.show() + +if __name__ == '__main__': + main() diff --git a/peregrine/analysis/tracking_loop.py b/peregrine/analysis/tracking_loop.py new file mode 100644 index 0000000..b3f351c --- /dev/null +++ b/peregrine/analysis/tracking_loop.py @@ -0,0 +1,157 @@ +# 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 sys +import argparse +import gps_constants +from peregrine.samples import load_samples +from peregrine.acquisition import AcquisitionResult +import defaults +from peregrine.log import default_logging_config +from peregrine.tracking import track + +from initSettings import initSettings + +def main(): + default_logging_config() + + # Initialize constants, settings + settings = initSettings() + + parser = argparse.ArgumentParser() + parser.add_argument("file", + help="the sample data file to process") + + parser.add_argument("-f", "--file-format", + help="the format of the sample data file " + "(e.g. 'piksi', 'int8', '1bit', '1bitrev')") + + parser.add_argument("-t", "--ms-to-track", + help="the number of milliseconds to process. ") + + parser.add_argument("-I", "--IF", + help="intermediate frequency [Hz]. ") + + parser.add_argument("-s", "--sampling-freq", + help="sampling frequency [Hz]. "); + + parser.add_argument("-P", "--prn", + help="PRN to track. ") + + parser.add_argument("-p", "--code-phase", + help="code phase [chips]. ") + + parser.add_argument("-d", "--carr-doppler", + help="carrier Doppler frequency [Hz]. ") + + parser.add_argument("-o", "--output-file", default="track.csv", + help="Track results file name. " + "Default: %s" % "track.csv") + + parser.add_argument("-S", "--signal", + help="Signal type (l1ca / l2c)") + + args = parser.parse_args() + settings.fileName = args.file + + samplesPerCode = int(round(settings.samplingFreq / + (settings.codeFreqBasis / settings.codeLength))) + + IF = float(args.IF) + carr_doppler = float(args.carr_doppler) + code_phase = float(args.code_phase) + prn = int(args.prn) - 1 + + ms_to_track = int(args.ms_to_track) + sampling_freq = float(args.sampling_freq) # [Hz] + + if args.signal == "l1ca": + acq_result = AcquisitionResult(prn = prn, + snr = 25, # dB + carr_freq = IF + carr_doppler, + doppler = carr_doppler, + code_phase = code_phase, + status = 'A', + signal = 'l1ca') + + loop_filter_params = { + "loop_freq" : 1e3, # loop frequency [Hz] + "code_bw" : 1, # Code loop NBW + "code_zeta" : 0.707, # Code loop zeta + "code_k" : 1, # Code loop k + "carr_to_code" : 0, # Carrier-to-code freq ratio (carrier aiding) + "carr_bw" : 25, # Carrier loop NBW + "carr_zeta" : 0.707, # Carrier loop zeta + "carr_k" : 1, # Carrier loop k + "carr_freq_b1" : 5} # Carrier loop aiding_igain + else: # L2C signal clause + acq_result = AcquisitionResult(prn = prn, + snr = 25, # dB + carr_freq = IF + carr_doppler, + doppler = carr_doppler, + code_phase = code_phase, + status = 'A', + signal = 'l2c') + loop_filter_params = { + "loop_freq" : 50, # loop frequency [Hz] + "code_bw" : 1, # Code loop NBW + "code_zeta" : 0.707, # Code loop zeta + "code_k" : 1, # Code loop k + "carr_to_code" : 0, # Carrier-to-code freq ratio (carrier aiding) + "carr_bw" : 20, # Carrier loop NBW + "carr_zeta" : 0.707, # Carrier loop zeta + "carr_k" : 1, # Carrier loop k + "carr_freq_b1" : 5} # Carrier loop aiding_igain + + print "==================== Tracking parameters =============================" + print "File: %s" % args.file + print "File format: %s" % args.file_format + print "PRN to track [1-32]: %s" % args.prn + print "Time to process [ms]: %s" % args.ms_to_track + print "IF [Hz]: %s" % args.IF + print "Sampling frequency [Hz]: %s" % args.sampling_freq + print "Initial carrier Doppler frequency [Hz]: %s" % carr_doppler + print "Initial code phase [chips]: %s" % code_phase + print "Track results file name: %s" % args.output_file + print "Signal: %s" % args.signal + print "======================================================================" + + samples_num = int(args.sampling_freq) * 1e-3 * ms_to_track + signal = load_samples(args.file, + int(samples_num), + 0, # skip samples + file_format = args.file_format) + + track_results = track(samples = signal, + signal = args.signal, + channels = [acq_result], + stage1_loop_filter_params = loop_filter_params, + ms_to_track = ms_to_track, + sampling_freq = sampling_freq, # [Hz] + chipping_rate = defaults.chipping_rate, + IF = IF) + + with open(args.output_file, 'w') as f1: + f1.write("doppler_phase,carr_doppler,code_phase,code_freq,CN0,E_I,E_Q,P_I,P_Q,L_I,L_Q\n") + for i in range(len(track_results[0].carr_phase)): + f1.write("%s," % track_results[0].carr_phase[i]) + f1.write("%s," % (track_results[0].carr_freq[i] - IF)) + f1.write("%s," % track_results[0].code_phase[i]) + f1.write("%s," % track_results[0].code_freq[i]) + f1.write("%s," % track_results[0].cn0[i]) + f1.write("%s," % track_results[0].E[i].real) + f1.write("%s," % track_results[0].E[i].imag) + f1.write("%s," % track_results[0].P[i].real) + f1.write("%s," % track_results[0].P[i].imag) + f1.write("%s," % track_results[0].L[i].real) + f1.write("%s\n" % track_results[0].L[i].imag) + +if __name__ == '__main__': + main() diff --git a/peregrine/gps_constants.py b/peregrine/gps_constants.py index 789a89e..fb3f252 100644 --- a/peregrine/gps_constants.py +++ b/peregrine/gps_constants.py @@ -11,6 +11,7 @@ # GPS system parameters: l1 = 1.57542e9 # Hz +l2 = 1.22760e9 # Hz chips_per_code = 1023 chip_rate = 1.023e6 # Hz nominal_range = 26000e3 # m diff --git a/peregrine/run.py b/peregrine/run.py index 7e7eb86..cbf2618 100755 --- a/peregrine/run.py +++ b/peregrine/run.py @@ -70,6 +70,8 @@ def main(): acq = Acquisition(acq_samples) acq_results = acq.acquisition() + print "Acquisition is over!" + try: save_acq_results(acq_results_file, acq_results) logging.debug("Saving acquisition results as '%s'" % acq_results_file) diff --git a/peregrine/tracking.py b/peregrine/tracking.py index 6830df6..2c2b21b 100644 --- a/peregrine/tracking.py +++ b/peregrine/tracking.py @@ -1,4 +1,5 @@ -# Copyright (C) 2012 Swift Navigation Inc. +# Copyright (C) 2012,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. @@ -9,6 +10,7 @@ import numpy as np from include.generateCAcode import caCodes +from include.generateL2CMcode import L2CMCodes import gps_constants import progressbar import math @@ -95,25 +97,22 @@ def update(self, e, p, l): def track(samples, channels, + signal, # L1C/A or L2C + stage1_loop_filter_params, ms_to_track=None, sampling_freq=defaults.sampling_freq, chipping_rate=defaults.chipping_rate, IF=defaults.IF, show_progress=True, loop_filter_class=swiftnav.track.AidedTrackingLoop, - stage1_loop_filter_params=\ - default_stage1_loop_filter_params, - correlator=swiftnav.correlate.track_correlate_, + correlator=swiftnav.correlate.track_correlate, stage2_coherent_ms=None, stage2_loop_filter_params=None, multi=True): n_channels = len(channels) - # Add 22ms for safety, the corellator might try to access data a bit past - # just the number of milliseconds specified. - # TODO: Fix the correlator so this isn't an issue. - samples_length_ms = int(1e3 * len(samples) / sampling_freq - 22) + samples_length_ms = int(1e3 * len(samples) / sampling_freq) if ms_to_track is None: ms_to_track = samples_length_ms @@ -162,37 +161,42 @@ def do_channel(chan, n=None, q_progress=None): cn0_0 = 10 * np.log10(chan.snr) cn0_0 += 10 * np.log10(1000) # Channel bandwidth cn0_est = swiftnav.track.CN0Estimator( - bw=1e3, - cn0_0=cn0_0, - cutoff_freq=10, - loop_freq=1e3 - ) + bw=1e3, + cn0_0=cn0_0, + cutoff_freq=10, + loop_freq = stage1_loop_filter_params["loop_freq"] + ) # Estimate initial code freq via aiding from acq carrier freq - code_freq_init = (chan.carr_freq - IF) * \ - gps_constants.chip_rate / gps_constants.l1 + if chan.signal == 'l1ca': + code_freq_init = (chan.carr_freq - IF) * \ + gps_constants.chip_rate / gps_constants.l1 + else: # l2c clause + code_freq_init = (chan.carr_freq - IF) * \ + gps_constants.chip_rate / gps_constants.l2 + carr_freq_init = chan.carr_freq - IF loop_filter = loop_filter_class( loop_freq = stage1_loop_filter_params['loop_freq'], code_freq = code_freq_init, - code_bw = stage1_loop_filter_params['code_loop_bw'], - code_zeta = stage1_loop_filter_params['code_loop_zeta'], - code_k = stage1_loop_filter_params['code_loop_k'], + code_bw = stage1_loop_filter_params['code_bw'], + code_zeta = stage1_loop_filter_params['code_zeta'], + code_k = stage1_loop_filter_params['code_k'], carr_to_code = stage1_loop_filter_params['carr_to_code'], carr_freq = carr_freq_init, - carr_bw = stage1_loop_filter_params['carr_loop_bw'], - carr_zeta = stage1_loop_filter_params['carr_loop_zeta'], - carr_k = stage1_loop_filter_params['carr_loop_k'], + carr_bw = stage1_loop_filter_params['carr_bw'], + carr_zeta = stage1_loop_filter_params['carr_zeta'], + carr_k = stage1_loop_filter_params['carr_k'], carr_freq_b1 = stage1_loop_filter_params['carr_freq_b1'], ) code_phase = 0.0 carr_phase = 0.0 - # Get a vector with the C/A code sampled 1x/chip - ca_code = caCodes[chan.prn] - - # Add wrapping to either end to be able to do early/late - ca_code = np.concatenate(([ca_code[1022]], ca_code, [ca_code[0]])) + # Get a vector with the C/A / L2C code sampled 1x/chip + if chan.signal == "l1ca": + prn_code = caCodes[chan.prn] + else: + prn_code = L2CMCodes[chan.prn] # Number of samples to seek ahead in file samples_per_chip = int(round(sampling_freq / chipping_rate)) @@ -228,14 +232,19 @@ def do_channel(chan, n=None, q_progress=None): coherent_ms = 1 if stage1 else stage2_coherent_ms for j in range(coherent_ms): + + if sample_index >= len(samples): + break; + samples_ = samples[sample_index:] E_, P_, L_, blksize, code_phase, carr_phase = correlator( samples_, loop_filter.to_dict()['code_freq'] + chipping_rate, code_phase, loop_filter.to_dict()['carr_freq'] + IF, carr_phase, - ca_code, - sampling_freq + prn_code, + sampling_freq, + signal ) sample_index += blksize carr_phase_acc += loop_filter.to_dict()['carr_freq'] * blksize / sampling_freq @@ -271,7 +280,10 @@ def do_channel(chan, n=None, q_progress=None): track_result.cn0[i] = cn0_est.update(P.real, P.imag) i += 1 - ms_tracked += coherent_ms + if signal == "l1ca": + ms_tracked += coherent_ms + else: # L2C clause + ms_tracked += 20 if q_progress and (i % 200 == 0): p = 1.0 * ms_tracked / ms_to_track From e40ff58f4d551f7c238975b215f1ce69a0168f91 Mon Sep 17 00:00:00 2001 From: Adel Mamin Date: Tue, 16 Feb 2016 15:25:48 +0200 Subject: [PATCH 05/57] Fix bug in acquisition result generation code: add missing signal type --- peregrine/acquisition.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/peregrine/acquisition.py b/peregrine/acquisition.py index 4726c74..427f6a2 100644 --- a/peregrine/acquisition.py +++ b/peregrine/acquisition.py @@ -471,7 +471,8 @@ def progress_callback(freq_num, num_freqs): carr_freq - self.IF, code_phase, snr, - status) + status, + 'l1ca') # If the acquisition was successful, log it if (snr > threshold): From e2e2aada94f0e6200af922789153849e143bf24f Mon Sep 17 00:00:00 2001 From: Adel Mamin Date: Tue, 16 Feb 2016 16:06:46 +0200 Subject: [PATCH 06/57] Fix bug in track() invocation --- peregrine/analysis/tracking_loop.py | 22 ---------------------- peregrine/defaults.py | 22 ++++++++++++++++++++++ peregrine/tracking.py | 27 ++++++++++++++++----------- 3 files changed, 38 insertions(+), 33 deletions(-) diff --git a/peregrine/analysis/tracking_loop.py b/peregrine/analysis/tracking_loop.py index b3f351c..7c43793 100644 --- a/peregrine/analysis/tracking_loop.py +++ b/peregrine/analysis/tracking_loop.py @@ -80,17 +80,6 @@ def main(): code_phase = code_phase, status = 'A', signal = 'l1ca') - - loop_filter_params = { - "loop_freq" : 1e3, # loop frequency [Hz] - "code_bw" : 1, # Code loop NBW - "code_zeta" : 0.707, # Code loop zeta - "code_k" : 1, # Code loop k - "carr_to_code" : 0, # Carrier-to-code freq ratio (carrier aiding) - "carr_bw" : 25, # Carrier loop NBW - "carr_zeta" : 0.707, # Carrier loop zeta - "carr_k" : 1, # Carrier loop k - "carr_freq_b1" : 5} # Carrier loop aiding_igain else: # L2C signal clause acq_result = AcquisitionResult(prn = prn, snr = 25, # dB @@ -99,16 +88,6 @@ def main(): code_phase = code_phase, status = 'A', signal = 'l2c') - loop_filter_params = { - "loop_freq" : 50, # loop frequency [Hz] - "code_bw" : 1, # Code loop NBW - "code_zeta" : 0.707, # Code loop zeta - "code_k" : 1, # Code loop k - "carr_to_code" : 0, # Carrier-to-code freq ratio (carrier aiding) - "carr_bw" : 20, # Carrier loop NBW - "carr_zeta" : 0.707, # Carrier loop zeta - "carr_k" : 1, # Carrier loop k - "carr_freq_b1" : 5} # Carrier loop aiding_igain print "==================== Tracking parameters =============================" print "File: %s" % args.file @@ -132,7 +111,6 @@ def main(): track_results = track(samples = signal, signal = args.signal, channels = [acq_result], - stage1_loop_filter_params = loop_filter_params, ms_to_track = ms_to_track, sampling_freq = sampling_freq, # [Hz] chipping_rate = defaults.chipping_rate, diff --git a/peregrine/defaults.py b/peregrine/defaults.py index 8fccec7..f23d566 100644 --- a/peregrine/defaults.py +++ b/peregrine/defaults.py @@ -18,3 +18,25 @@ code_period = code_length / chipping_rate samples_per_code = code_period * sampling_freq +l1ca_stage1_loop_filter_params = { + "loop_freq" : 1e3, # loop frequency [Hz] + "code_bw" : 1, # Code loop NBW + "code_zeta" : 0.707, # Code loop zeta + "code_k" : 1, # Code loop k + "carr_to_code" : 0, # Carrier-to-code freq ratio (carrier aiding) + "carr_bw" : 25, # Carrier loop NBW + "carr_zeta" : 0.707, # Carrier loop zeta + "carr_k" : 1, # Carrier loop k + "carr_freq_b1" : 5} # Carrier loop aiding_igain + +l2c_loop_filter_params = { + "loop_freq" : 50, # loop frequency [Hz] + "code_bw" : 1, # Code loop NBW + "code_zeta" : 0.707, # Code loop zeta + "code_k" : 1, # Code loop k + "carr_to_code" : 0, # Carrier-to-code freq ratio (carrier aiding) + "carr_bw" : 20, # Carrier loop NBW + "carr_zeta" : 0.707, # Carrier loop zeta + "carr_k" : 1, # Carrier loop k + "carr_freq_b1" : 5} # Carrier loop aiding_igain + diff --git a/peregrine/tracking.py b/peregrine/tracking.py index 2c2b21b..4555cb6 100644 --- a/peregrine/tracking.py +++ b/peregrine/tracking.py @@ -98,7 +98,6 @@ def update(self, e, p, l): def track(samples, channels, signal, # L1C/A or L2C - stage1_loop_filter_params, ms_to_track=None, sampling_freq=defaults.sampling_freq, chipping_rate=defaults.chipping_rate, @@ -160,11 +159,17 @@ def do_channel(chan, n=None, q_progress=None): # Convert acquisition SNR to C/N0 cn0_0 = 10 * np.log10(chan.snr) cn0_0 += 10 * np.log10(1000) # Channel bandwidth + + if chan.signal == "l1ca": + loop_filter_params = defaults.l1ca_stage1_loop_filter_params + else: # L2C signal clause + loop_filter_params = defaults.l2c_loop_filter_params + cn0_est = swiftnav.track.CN0Estimator( bw=1e3, cn0_0=cn0_0, cutoff_freq=10, - loop_freq = stage1_loop_filter_params["loop_freq"] + loop_freq = loop_filter_params["loop_freq"] ) # Estimate initial code freq via aiding from acq carrier freq @@ -177,17 +182,17 @@ def do_channel(chan, n=None, q_progress=None): carr_freq_init = chan.carr_freq - IF loop_filter = loop_filter_class( - loop_freq = stage1_loop_filter_params['loop_freq'], + loop_freq = loop_filter_params['loop_freq'], code_freq = code_freq_init, - code_bw = stage1_loop_filter_params['code_bw'], - code_zeta = stage1_loop_filter_params['code_zeta'], - code_k = stage1_loop_filter_params['code_k'], - carr_to_code = stage1_loop_filter_params['carr_to_code'], + code_bw = loop_filter_parpams['code_bw'], + code_zeta = loop_filter_params['code_zeta'], + code_k = loop_filter_params['code_k'], + carr_to_code = loop_filter_params['carr_to_code'], carr_freq = carr_freq_init, - carr_bw = stage1_loop_filter_params['carr_bw'], - carr_zeta = stage1_loop_filter_params['carr_zeta'], - carr_k = stage1_loop_filter_params['carr_k'], - carr_freq_b1 = stage1_loop_filter_params['carr_freq_b1'], + carr_bw = loop_filter_params['carr_bw'], + carr_zeta = loop_filter_params['carr_zeta'], + carr_k = loop_filter_params['carr_k'], + carr_freq_b1 = loop_filter_params['carr_freq_b1'], ) code_phase = 0.0 carr_phase = 0.0 From 94ba6d2d5553fa0f03281776b31e11e13551b2a8 Mon Sep 17 00:00:00 2001 From: Valeri Atamaniouk Date: Tue, 16 Feb 2016 11:11:44 +0200 Subject: [PATCH 07/57] peregrine: added support for two codes input Added support for loading file with two diferent codes (bands). Added 1bit_x2 file format, updated tracking loop analysys. --- .gitignore | 6 ++++ peregrine/analysis/samples.py | 24 ++++++------- peregrine/analysis/tracking_loop.py | 39 ++++++++++++--------- peregrine/run.py | 10 +++--- peregrine/samples.py | 54 ++++++++++++++++++++--------- 5 files changed, 84 insertions(+), 49 deletions(-) diff --git a/.gitignore b/.gitignore index 0b17b6a..0409b13 100644 --- a/.gitignore +++ b/.gitignore @@ -50,3 +50,9 @@ pip-delete-this-directory.txt tests/peregrine_ci_test_data.tar.gz tests/test_data/ tests/test_data_old* + +# Eclipse project files +.pydevproject +.project +.cproject +.settings/ diff --git a/peregrine/analysis/samples.py b/peregrine/analysis/samples.py index efeb553..0ca1821 100755 --- a/peregrine/analysis/samples.py +++ b/peregrine/analysis/samples.py @@ -59,7 +59,7 @@ def hist(samples, ax=None, value_range=None, bin_width=1.0, max_len=ANALYSIS_MAX """ if max_len is not None and len(samples) > max_len: - logger.debug( "Truncating to %d samples." % max_len) + logger.debug("Truncating to %d samples." % max_len) samples = samples[:max_len] if ax is None: @@ -72,8 +72,8 @@ def hist(samples, ax=None, value_range=None, bin_width=1.0, max_len=ANALYSIS_MAX max_val = np.max(samples) min_val = np.min(samples) - n_bins = 1 + np.round(float(max_val) - float(min_val) / bin_width) - bins = np.linspace(min_val-bin_width/2.0, max_val+bin_width/2.0, n_bins+1) + n_bins = 1 + np.round(float(max_val) - float(min_val) / bin_width) + bins = np.linspace(min_val - bin_width / 2.0, max_val + bin_width / 2.0, n_bins + 1) ticks = np.linspace(min_val, max_val, n_bins) @@ -83,11 +83,11 @@ def hist(samples, ax=None, value_range=None, bin_width=1.0, max_len=ANALYSIS_MAX ax.set_xlabel('Sample value') if len(ticks) < 22: ax.set_xticks(ticks) - ax.set_xbound(min_val-bin_width, max_val+bin_width) + ax.set_xbound(min_val - bin_width, max_val + bin_width) ax.set_ylabel('Count') y_min, y_max = ax.get_ybound() - ax.set_ybound(0, y_max*1.1) - ax.ticklabel_format(style='sci', scilimits=(0,0), axis='y') + ax.set_ybound(0, y_max * 1.1) + ax.ticklabel_format(style='sci', scilimits=(0, 0), axis='y') return ax @@ -118,7 +118,7 @@ def psd(samples, sampling_freq=None, ax=None, max_len=ANALYSIS_MAX_LEN): """ if max_len is not None and len(samples) > max_len: - logger.debug( "Truncating to %d samples." % max_len) + logger.debug("Truncating to %d samples." % max_len) samples = samples[:max_len] if ax is None: @@ -132,7 +132,7 @@ def psd(samples, sampling_freq=None, ax=None, max_len=ANALYSIS_MAX_LEN): ax.set_ylabel('Power Spectral Density ($f_s \cdot \mathrm{Hz}^{-1}$)') sampling_freq = 1.0 else: - ax.ticklabel_format(style='sci', scilimits=(0,0), axis='x') + ax.ticklabel_format(style='sci', scilimits=(0, 0), axis='x') ax.set_xlabel('Frequency (Hz)') ax.set_ylabel('Power Spectral Density ($\mathrm{Hz}^{-1}$)') @@ -143,7 +143,7 @@ def psd(samples, sampling_freq=None, ax=None, max_len=ANALYSIS_MAX_LEN): noverlap=1024) ax.semilogy(freqs, Pxx, color='black') - ax.set_xbound(0, sampling_freq/2.0) + ax.set_xbound(0, sampling_freq / 2.0) return ax @@ -172,8 +172,8 @@ def summary(samples, sampling_freq=None, max_len=ANALYSIS_MAX_LEN): ax1 = fig.add_subplot(121) ax2 = fig.add_subplot(122) - hist(samples, ax=ax1, max_len=max_len) - psd(samples, sampling_freq, ax=ax2, max_len=max_len) + hist(samples[0], ax=ax1, max_len=max_len) + psd(samples[0], sampling_freq, ax=ax2, max_len=max_len) fig.set_size_inches(10, 4, forward=True) fig.tight_layout() @@ -193,7 +193,7 @@ def main(): + "'int8', '1bit', '1bitrev' or 'piksi' (default)") args = parser.parse_args() - samples = peregrine.samples.load_samples(args.file, args.num_samples, file_format = args.format) + samples = peregrine.samples.load_samples(args.file, args.num_samples, file_format=args.format) summary(samples) plt.show() diff --git a/peregrine/analysis/tracking_loop.py b/peregrine/analysis/tracking_loop.py index 7c43793..8fade99 100644 --- a/peregrine/analysis/tracking_loop.py +++ b/peregrine/analysis/tracking_loop.py @@ -8,16 +8,14 @@ # EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED # WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. -import sys import argparse -import gps_constants from peregrine.samples import load_samples from peregrine.acquisition import AcquisitionResult -import defaults +from peregrine import defaults from peregrine.log import default_logging_config from peregrine.tracking import track -from initSettings import initSettings +from peregrine.initSettings import initSettings def main(): default_logging_config() @@ -56,6 +54,7 @@ def main(): "Default: %s" % "track.csv") parser.add_argument("-S", "--signal", + choices=["l1ca", "l2c"], help="Signal type (l1ca / l2c)") args = parser.parse_args() @@ -70,7 +69,7 @@ def main(): prn = int(args.prn) - 1 ms_to_track = int(args.ms_to_track) - sampling_freq = float(args.sampling_freq) # [Hz] + sampling_freq = float(args.sampling_freq) # [Hz] if args.signal == "l1ca": acq_result = AcquisitionResult(prn = prn, @@ -103,18 +102,26 @@ def main(): print "======================================================================" samples_num = int(args.sampling_freq) * 1e-3 * ms_to_track - signal = load_samples(args.file, + signals = load_samples(args.file, int(samples_num), - 0, # skip samples - file_format = args.file_format) - - track_results = track(samples = signal, - signal = args.signal, - channels = [acq_result], - ms_to_track = ms_to_track, - sampling_freq = sampling_freq, # [Hz] - chipping_rate = defaults.chipping_rate, - IF = IF) + 0, # skip samples + file_format=args.file_format) + + index = 0 + if len(signals) > 1: + if args.signal == 'l1ca': + index = 0 + else: + index = 1 + pass + + track_results = track(samples=signals[index], + signal=args.signal, + channels=[acq_result], + ms_to_track=ms_to_track, + sampling_freq=sampling_freq, # [Hz] + chipping_rate=defaults.chipping_rate, + IF=IF) with open(args.output_file, 'w') as f1: f1.write("doppler_phase,carr_doppler,code_phase,code_freq,CN0,E_I,E_Q,P_I,P_Q,L_I,L_Q\n") diff --git a/peregrine/run.py b/peregrine/run.py index cbf2618..62574e9 100755 --- a/peregrine/run.py +++ b/peregrine/run.py @@ -64,10 +64,10 @@ def main(): sys.exit(1) else: # Get 11ms of acquisition samples for fine frequency estimation - acq_samples = load_samples(args.file, 11*samplesPerCode, + acq_samples = load_samples(args.file, 11 * samplesPerCode, settings.skipNumberOfBytes, file_format=args.file_format) - acq = Acquisition(acq_samples) + acq = Acquisition(acq_samples[0]) acq_results = acq.acquisition() print "Acquisition is over!" @@ -101,10 +101,10 @@ def main(): sys.exit(1) else: signal = load_samples(args.file, - int(settings.samplingFreq*1e-3*(settings.msToProcess+22)), + int(settings.samplingFreq * 1e-3 * (settings.msToProcess + 22)), settings.skipNumberOfBytes, file_format=args.file_format) - track_results = track(signal, acq_results, settings.msToProcess) + track_results = track(signal[0], acq_results, settings.msToProcess) try: with open(track_results_file, 'wb') as f: cPickle.dump(track_results, f, protocol=cPickle.HIGHEST_PROTOCOL) @@ -127,7 +127,7 @@ def main(): nav_results[0][2][0], nav_results[0][2][1], nav_results[0][2][2]) with open(nav_results_file, 'wb') as f: cPickle.dump(nav_results, f, protocol=cPickle.HIGHEST_PROTOCOL) - print "and %d more are cPickled in '%s'." % (len(nav_results)-1, nav_results_file) + print "and %d more are cPickled in '%s'." % (len(nav_results) - 1, nav_results_file) else: print "No navigation results." diff --git a/peregrine/samples.py b/peregrine/samples.py index cc5fefc..413f992 100644 --- a/peregrine/samples.py +++ b/peregrine/samples.py @@ -42,8 +42,9 @@ def load_samples(filename, num_samples=-1, num_skip=0, file_format='piksi'): Returns ------- - out : :class:`numpy.ndarray`, shape(`num_samples`,) - The sample data as a numpy array. + out : :class:`numpy.ndarray`, shape(bands, `num_samples`,) + The sample data as a two-dimensional numpy array. The first dimension + separates codes (bands). Raises ------ @@ -58,37 +59,38 @@ def load_samples(filename, num_samples=-1, num_skip=0, file_format='piksi'): if file_format == 'int8': with open(filename, 'rb') as f: f.seek(num_skip) - samples = np.fromfile(f, dtype=np.int8, count=num_samples) + samples = np.zeros((1, num_samples), dtype=np.int8) + samples[:] = np.fromfile(f, dtype=np.int8, count=num_samples) elif file_format == 'c8c8': # 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) + s_file = s_file[:num_samples * 2 * n_rx] + samples = np.empty([n_rx, len(s_file) / (2 * n_rx)], dtype=np.complex64) for rx in range(n_rx): - samples[rx] = s_file[2 * rx : : 2 * n_rx] + s_file[2*rx + 1 :: 2 * n_rx]*1j + samples[rx] = s_file[2 * rx : : 2 * n_rx] + s_file[2 * rx + 1 :: 2 * n_rx] * 1j elif file_format == '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 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) + s_file = s_file[:num_samples * 2 * n_rx] + samples = np.empty([n_rx, 4 * len(s_file) / (2 * n_rx)], dtype=np.int8) for rx in range(n_rx): - samples[rx][0::4] = s_file[2 * rx : : 2 * 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] + samples[rx][3::4] = s_file[2 * rx + 1 : : 2 * n_rx] elif file_format == 'piksinew': packed = np.memmap(filename, offset=num_skip, dtype=np.uint8, mode='r') if num_samples > 0: packed = packed[:num_samples] - samples = np.empty(len(packed), dtype=np.int8) - samples[:] = (packed >> 6) - 1 + samples = np.empty((1, len(packed)), dtype=np.int8) + samples[0][:] = (packed >> 6) - 1 elif file_format == 'piksi': """ @@ -105,7 +107,7 @@ def load_samples(filename, num_samples=-1, num_skip=0, file_format='piksi'): if num_samples > 0: num_skip_bytes = num_skip / 2 num_skip_samples = num_skip % 2 - num_bytes = num_samples/2 + 1 + num_bytes = num_samples / 2 + 1 else: # Read whole file num_skip_bytes = 0 @@ -122,17 +124,20 @@ def load_samples(filename, num_samples=-1, num_skip=0, file_format='piksi'): samples[::2] = (packed >> 5) samples[1::2] = (packed >> 2) & 7 # Sign-magnitude to two's complement mapping - samples = (1-2*(samples>>2)) * (2*(samples&3)+1) + samples = (1 - 2 * (samples >> 2)) * (2 * (samples & 3) + 1) 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' or file_format == '1bitrev': if num_samples > 0: num_skip_bytes = num_skip / 8 num_skip_samples = num_skip % 8 - num_bytes = num_samples/8 + 1 + num_bytes = num_samples / 8 + 1 else: # Read whole file num_skip_bytes = 0 @@ -146,11 +151,28 @@ def load_samples(filename, num_samples=-1, num_skip=0, file_format='piksi'): samples *= 2 samples -= 1 if file_format == '1bitrev': - samples = np.reshape(samples,(-1,8))[:,::-1].flatten(); + samples = np.reshape(samples, (-1, 8))[:, ::-1].flatten(); 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 + s_file = np.memmap(filename, offset=num_skip, dtype=np.uint8, mode='r') + n_rx = 2 + print "Num samples", num_samples + if num_samples > 0: + print "Trimming file to ", num_samples, (num_samples * n_rx + 7) / 8, "from", len(s_file) + s_file = s_file[:(num_samples * n_rx + 7) / 8] + bits = np.unpackbits(s_file) + print "Unpacked: ", len(bits), len(bits) / n_rx + samples = np.empty((n_rx, num_samples), dtype=np.int8) + lookup = np.asarray((1, -1), dtype=np.int8) + for rx in range(n_rx): + samples[rx][:] = lookup[bits[rx:n_rx * (num_samples + 1):n_rx]] else: raise ValueError("Unknown file type '%s'" % file_format) From d2c51034da675d52c75ad071b0ee339f92e16f34 Mon Sep 17 00:00:00 2001 From: Valeri Atamaniouk Date: Tue, 16 Feb 2016 14:53:39 +0200 Subject: [PATCH 08/57] encoding: added support for two-bit samples Added decoding support for two-bit samples. Added '2bits' and '2bits_x2' file formats. --- peregrine/samples.py | 130 ++++++++++++++++++++++++++++++++++++++----- 1 file changed, 117 insertions(+), 13 deletions(-) diff --git a/peregrine/samples.py b/peregrine/samples.py index 413f992..71b9253 100644 --- a/peregrine/samples.py +++ b/peregrine/samples.py @@ -13,6 +13,115 @@ __all__ = ['load_samples', 'save_samples'] +def __load_samples_n_bits(filename, num_samples, num_skip, n_rx, n_bits, lookup): + ''' + Helper method to load two-bit samples from a file. + + Parameters + ---------- + filename : string + Filename of sample data file. + num_samples : int + Number of samples to read, ``-1`` means the whole file. + num_skip : int + Number of samples to discard from the beginning of the file. + n_rx : int + Number of interleaved streams in the source file + n_bits : int + Number of bits per sample + lookup : array-like + Array to map values + + Returns + ------- + out : :class:`numpy.ndarray`, shape(`n_rx`, `num_samples`,) + The sample data as a two-dimensional numpy array. The first dimension + separates codes (bands). The second dimention contains samples indexed + with the `lookup` table. + ''' + sample_block_size = n_bits * n_rx + byte_offset = num_skip / (8 / sample_block_size) + sample_offset = num_skip % (8 / sample_block_size) + s_file = np.memmap(filename, offset=byte_offset, dtype=np.uint8, mode='r') + + if num_samples > 0: + # Number of samples is defined: trim the source from start and end + s_file = s_file[sample_offset * sample_block_size: + (num_samples * sample_block_size + 7) / 8] + else: + # Number of samples is not defined: trim the source from start only + # compute actual number of samples + s_file = s_file[sample_offset * sample_block_size:] + num_samples = len(s_file) * 8 / sample_block_size + + # Compute total data block size to ignore bits in the tail. + rounded_len = num_samples * sample_block_size + + bits = np.unpackbits(s_file) + samples = np.empty((n_rx, num_samples), dtype=lookup.dtype) + + for rx in range(n_rx): + # Construct multi-bit sample values + tmp = bits[rx * n_bits:rounded_len:sample_block_size] + for bit in range(1, n_bits): + tmp <<= 1 + tmp += bits[rx * n_bits + bit:rounded_len:sample_block_size] + # Generate sample values using lookup table + samples[rx][:] = lookup[tmp] + return samples + +def __load_samples_one_bit(filename, num_samples, num_skip, n_rx): + ''' + Helper method to load single-bit samples from a file. + + Parameters + ---------- + filename : string + Filename of sample data file. + num_samples : int + Number of samples to read, ``-1`` means the whole file. + num_skip : int + Number of samples to discard from the beginning of the file. + n_rx : int + Number of interleaved streams in the source file + + Returns + ------- + out : :class:`numpy.ndarray`, shape(`n_rx`, `num_samples`,) + The sample data as a two-dimensional numpy array. The first dimension + separates codes (bands). The second dimention contains samples with one + of the values: -1, 1 + ''' + lookup = np.asarray((1, -1), dtype=np.int8) + return __load_samples_n_bits(filename, num_samples, num_skip, n_rx, 1, lookup) + +def __load_samples_two_bits(filename, num_samples, num_skip, n_rx): + ''' + Helper method to load two-bit samples from a file. + + Parameters + ---------- + filename : string + Filename of sample data file. + num_samples : int + Number of samples to read, ``-1`` means the whole file. + num_skip : int + Number of samples to discard from the beginning of the file. + n_rx : int + Number of interleaved streams in the source file + + Returns + ------- + out : :class:`numpy.ndarray`, shape(`n_rx`, `num_samples`,) + The sample data as a two-dimensional numpy array. The first dimension + separates codes (bands). The second dimention contains samples with one + of the values: -3, -1, 1, 3 + ''' + # Interleaved two bit samples from two receivers. First bit is a sign of the + # sample, and the second bit is the amplitude value: 1 or 3. + lookup = np.asarray((-1, -3, 1, 3), dtype=np.int8) + return __load_samples_n_bits(filename, num_samples, num_skip, n_rx, 2, lookup) + def load_samples(filename, num_samples=-1, num_skip=0, file_format='piksi'): """ Load sample data from a file. @@ -160,19 +269,14 @@ def load_samples(filename, num_samples=-1, num_skip=0, file_format='piksi'): samples = tmp elif file_format == '1bit_x2': - # Interleaved single bit samples from two receivers - s_file = np.memmap(filename, offset=num_skip, dtype=np.uint8, mode='r') - n_rx = 2 - print "Num samples", num_samples - if num_samples > 0: - print "Trimming file to ", num_samples, (num_samples * n_rx + 7) / 8, "from", len(s_file) - s_file = s_file[:(num_samples * n_rx + 7) / 8] - bits = np.unpackbits(s_file) - print "Unpacked: ", len(bits), len(bits) / n_rx - samples = np.empty((n_rx, num_samples), dtype=np.int8) - lookup = np.asarray((1, -1), dtype=np.int8) - for rx in range(n_rx): - samples[rx][:] = lookup[bits[rx:n_rx * (num_samples + 1):n_rx]] + # Interleaved single bit samples from two receivers: -1, +1 + samples = __load_samples_one_bit(filename, num_samples, num_skip, 2) + elif file_format == '2bits': + # Two bit samples from one receiver: -3, -1, +1, +3 + samples = __load_samples_two_bits(filename, num_samples, num_skip, 1) + 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, 2) else: raise ValueError("Unknown file type '%s'" % file_format) From dad9582ca48c13a8e87b23f5727228f4ab66d118 Mon Sep 17 00:00:00 2001 From: Adel Mamin Date: Wed, 17 Feb 2016 13:55:30 +0200 Subject: [PATCH 09/57] Fix build env --- .gitmodules | 2 +- deps.sh | 4 ++-- libswiftnav | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.gitmodules b/.gitmodules index 540aac8..16ea5a7 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ [submodule "libswiftnav"] path = libswiftnav - url = https://github.com/swift-nav/libswiftnav + url = https://github.com/adel-mamin/libswiftnav diff --git a/deps.sh b/deps.sh index 33b4f15..3f0bc26 100755 --- a/deps.sh +++ b/deps.sh @@ -6,7 +6,7 @@ function install_deps_ubuntu_maybe () { # Sudo'd version of travis installation instructions sudo apt-get update -qq sudo apt-get install python-software-properties - sudo add-apt-repository --yes ppa:kalakris/cmake + #sudo add-apt-repository --yes ppa:kalakris/cmake sudo apt-get update -qq sudo apt-get -y install cmake \ check \ @@ -17,8 +17,8 @@ function install_deps_ubuntu_maybe () { python-numpy \ python-dev \ cython \ - python-cython \ python-dev + sudo pip install -U cython git submodule update --init # Build libswiftnav cd libswiftnav/ diff --git a/libswiftnav b/libswiftnav index 2c0d0a7..8a10026 160000 --- a/libswiftnav +++ b/libswiftnav @@ -1 +1 @@ -Subproject commit 2c0d0a776ebdb252ccf94da316c1bf5183b939b0 +Subproject commit 8a10026a694cd373fed1ecd6ea12d0d295f85641 From 9b5dbdf816a91cac9f9d90871c287854e91b68e2 Mon Sep 17 00:00:00 2001 From: Adel Mamin Date: Wed, 17 Feb 2016 14:52:40 +0200 Subject: [PATCH 10/57] Remove signal parameter from track() --- peregrine/analysis/tracking_loop.py | 1 - peregrine/tracking.py | 5 ++--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/peregrine/analysis/tracking_loop.py b/peregrine/analysis/tracking_loop.py index 8fade99..db173bb 100644 --- a/peregrine/analysis/tracking_loop.py +++ b/peregrine/analysis/tracking_loop.py @@ -116,7 +116,6 @@ def main(): pass track_results = track(samples=signals[index], - signal=args.signal, channels=[acq_result], ms_to_track=ms_to_track, sampling_freq=sampling_freq, # [Hz] diff --git a/peregrine/tracking.py b/peregrine/tracking.py index 4555cb6..6209ffb 100644 --- a/peregrine/tracking.py +++ b/peregrine/tracking.py @@ -97,7 +97,6 @@ def update(self, e, p, l): def track(samples, channels, - signal, # L1C/A or L2C ms_to_track=None, sampling_freq=defaults.sampling_freq, chipping_rate=defaults.chipping_rate, @@ -249,7 +248,7 @@ def do_channel(chan, n=None, q_progress=None): loop_filter.to_dict()['carr_freq'] + IF, carr_phase, prn_code, sampling_freq, - signal + chan.signal ) sample_index += blksize carr_phase_acc += loop_filter.to_dict()['carr_freq'] * blksize / sampling_freq @@ -285,7 +284,7 @@ def do_channel(chan, n=None, q_progress=None): track_result.cn0[i] = cn0_est.update(P.real, P.imag) i += 1 - if signal == "l1ca": + if chan.signal == "l1ca": ms_tracked += coherent_ms else: # L2C clause ms_tracked += 20 From bc70d0236b18869ef7002a6b68727dc025d41682 Mon Sep 17 00:00:00 2001 From: Adel Mamin Date: Wed, 17 Feb 2016 16:47:17 +0200 Subject: [PATCH 11/57] Fix Peregrine deps.sh --- deps.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/deps.sh b/deps.sh index 3f0bc26..37a3d5e 100755 --- a/deps.sh +++ b/deps.sh @@ -17,7 +17,8 @@ function install_deps_ubuntu_maybe () { python-numpy \ python-dev \ cython \ - python-dev + python-dev \ + python-matplotlib sudo pip install -U cython git submodule update --init # Build libswiftnav From d1d030802dcaadf5c8db78a749b26eca71809fe0 Mon Sep 17 00:00:00 2001 From: Adel Mamin Date: Wed, 17 Feb 2016 16:48:08 +0200 Subject: [PATCH 12/57] Reduce carrier track loop bandwidth to reduce power leak into prompt Q brach --- peregrine/defaults.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/peregrine/defaults.py b/peregrine/defaults.py index f23d566..e5ac5c0 100644 --- a/peregrine/defaults.py +++ b/peregrine/defaults.py @@ -35,7 +35,7 @@ "code_zeta" : 0.707, # Code loop zeta "code_k" : 1, # Code loop k "carr_to_code" : 0, # Carrier-to-code freq ratio (carrier aiding) - "carr_bw" : 20, # Carrier loop NBW + "carr_bw" : 13, # Carrier loop NBW "carr_zeta" : 0.707, # Carrier loop zeta "carr_k" : 1, # Carrier loop k "carr_freq_b1" : 5} # Carrier loop aiding_igain From 6f03e54084a9ece69ded291bc73569d766259562 Mon Sep 17 00:00:00 2001 From: Adel Mamin Date: Wed, 17 Feb 2016 18:28:36 +0200 Subject: [PATCH 13/57] Add lock PLL lock detectors --- peregrine/analysis/tracking_loop.py | 6 ++-- peregrine/defaults.py | 19 ++++++++++++ peregrine/tracking.py | 45 ++++++++++++++++++++--------- 3 files changed, 54 insertions(+), 16 deletions(-) diff --git a/peregrine/analysis/tracking_loop.py b/peregrine/analysis/tracking_loop.py index db173bb..64e492d 100644 --- a/peregrine/analysis/tracking_loop.py +++ b/peregrine/analysis/tracking_loop.py @@ -123,7 +123,7 @@ def main(): IF=IF) with open(args.output_file, 'w') as f1: - f1.write("doppler_phase,carr_doppler,code_phase,code_freq,CN0,E_I,E_Q,P_I,P_Q,L_I,L_Q\n") + f1.write("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\n") for i in range(len(track_results[0].carr_phase)): f1.write("%s," % track_results[0].carr_phase[i]) f1.write("%s," % (track_results[0].carr_freq[i] - IF)) @@ -135,7 +135,9 @@ def main(): f1.write("%s," % track_results[0].P[i].real) f1.write("%s," % track_results[0].P[i].imag) f1.write("%s," % track_results[0].L[i].real) - f1.write("%s\n" % track_results[0].L[i].imag) + f1.write("%s," % track_results[0].L[i].imag) + f1.write("%s," % track_results[0].lock_detect_outp[i]) + f1.write("%s\n" % track_results[0].lock_detect_outo[i]) if __name__ == '__main__': main() diff --git a/peregrine/defaults.py b/peregrine/defaults.py index e5ac5c0..89a4844 100644 --- a/peregrine/defaults.py +++ b/peregrine/defaults.py @@ -40,3 +40,22 @@ "carr_k" : 1, # Carrier loop k "carr_freq_b1" : 5} # Carrier loop aiding_igain +# pessimistic set +l1ca_lock_detect_params_pess = {"k1" : 0.10, "k2" : 1.4, "lp" : 200, "lo" : 50 } +l2c_lock_detect_params_pess = {"k1" : 0.10, "k2" : 1.4, "lp" : 200, "lo" : 50 } + +# normal set +l1ca_lock_detect_params_normal = {"k1" : 0.05, "k2" : 1.4, "lp" : 150, "lo" : 50 } +l2c_lock_detect_params_normal = {"k1" : 0.05, "k2" : 1.4, "lp" : 150, "lo" : 50 } + +# optimal set +l1ca_lock_detect_params_opt = {"k1" : 0.02, "k2" : 1.1, "lp" : 150, "lo" : 50 } +l2c_lock_detect_params_opt = {"k1" : 0.02, "k2" : 1.1, "lp" : 150, "lo" : 50 } + +# extra optimal set +l1ca_lock_detect_params_extraopt = {"k1" : 0.02, "k2" : 0.8, "lp" : 150, "lo" : 50 } +l2c_lock_detect_params_extraopt = {"k1" : 0.02, "k2" : 0.8, "lp" : 150, "lo" : 50 } + +# disable lock detect +l1ca_lock_detect_params_disable = {"k1" : 0.02, "k2" : 1e-6, "lp" : 1, "lo" : 1 } +l2c_lock_detect_params_disable = {"k1" : 0.02, "k2" : 1e-6, "lp" : 1, "lo" : 1 } diff --git a/peregrine/tracking.py b/peregrine/tracking.py index 6209ffb..685a6e7 100644 --- a/peregrine/tracking.py +++ b/peregrine/tracking.py @@ -161,8 +161,14 @@ def do_channel(chan, n=None, q_progress=None): if chan.signal == "l1ca": loop_filter_params = defaults.l1ca_stage1_loop_filter_params + lock_detect_params = defaults.l1ca_lock_detect_params_opt + prn_code = caCodes[chan.prn] + coherent_ms = 1 else: # L2C signal clause loop_filter_params = defaults.l2c_loop_filter_params + lock_detect_params = defaults.l2c_lock_detect_params_opt + prn_code = L2CMCodes[chan.prn] + coherent_ms = 20 cn0_est = swiftnav.track.CN0Estimator( bw=1e3, @@ -171,6 +177,12 @@ def do_channel(chan, n=None, q_progress=None): loop_freq = loop_filter_params["loop_freq"] ) + lock_detect = swiftnav.track.LockDetector( + k1 = lock_detect_params["k1"] * coherent_ms, + k2 = lock_detect_params["k2"], + lp = lock_detect_params["lp"], + lo = lock_detect_params["lo"]); + # Estimate initial code freq via aiding from acq carrier freq if chan.signal == 'l1ca': code_freq_init = (chan.carr_freq - IF) * \ @@ -196,12 +208,6 @@ def do_channel(chan, n=None, q_progress=None): code_phase = 0.0 carr_phase = 0.0 - # Get a vector with the C/A / L2C code sampled 1x/chip - if chan.signal == "l1ca": - prn_code = caCodes[chan.prn] - else: - prn_code = L2CMCodes[chan.prn] - # Number of samples to seek ahead in file samples_per_chip = int(round(sampling_freq / chipping_rate)) @@ -224,18 +230,21 @@ def do_channel(chan, n=None, q_progress=None): E = 0+0.j; P = 0+0.j; L = 0+0.j - if stage1 and stage2_coherent_ms and track_result.nav_msg.bit_phase == track_result.nav_msg.bit_phase_ref: + if stage1 and \ + chan.signal == 'l1ca' and \ + stage2_coherent_ms and \ + track_result.nav_msg.bit_phase == track_result.nav_msg.bit_phase_ref: + #print "PRN %02d transition to stage 2 at %d ms" % (chan.prn+1, ms_tracked) stage1 = False + coherent_ms = stage2_coherent_ms loop_filter.retune(*stage2_loop_filter_params) cn0_est = swiftnav.track.CN0Estimator(bw=1e3/stage2_coherent_ms, cn0_0=track_result.cn0[i-1], cutoff_freq=10, loop_freq=1e3/stage2_coherent_ms) - coherent_ms = 1 if stage1 else stage2_coherent_ms - - for j in range(coherent_ms): + for _ in range(coherent_ms): if sample_index >= len(samples): break; @@ -256,6 +265,13 @@ def do_channel(chan, n=None, q_progress=None): E += E_; P += P_; L += L_ + if chan.signal == 'l2c': # only one 20 ms coherent integration time for L2C + break + + # Update PLL lock detector + lock_detect_outo, lock_detect_outp = lock_detect.update(P.real, P.imag, + coherent_ms) + loop_filter.update(E, P, L) track_result.coherent_ms[i] = coherent_ms @@ -282,12 +298,11 @@ def do_channel(chan, n=None, q_progress=None): track_result.L[i] = L track_result.cn0[i] = cn0_est.update(P.real, P.imag) + track_result.lock_detect_outo[i] = lock_detect_outo + track_result.lock_detect_outp[i] = lock_detect_outp i += 1 - if chan.signal == "l1ca": - ms_tracked += coherent_ms - else: # L2C clause - ms_tracked += 20 + ms_tracked += coherent_ms if q_progress and (i % 200 == 0): p = 1.0 * ms_tracked / ms_to_track @@ -332,6 +347,8 @@ def __init__(self, n_points, prn): self.P = np.zeros(n_points, dtype=np.complex128) self.L = np.zeros(n_points, dtype=np.complex128) self.cn0 = np.zeros(n_points) + self.lock_detect_outp = np.zeros(n_points) + self.lock_detect_outo = np.zeros(n_points) self.nav_msg = swiftnav.nav_msg.NavMsg() self.nav_msg_bit_phase_ref = np.zeros(n_points) self.nav_bit_sync = NBSMatchBit() if prn < 32 else NBSSBAS() From 9abe52c49d778a595aeea5dea022565d28c46461 Mon Sep 17 00:00:00 2001 From: Valeri Atamaniouk Date: Wed, 17 Feb 2016 16:31:50 +0200 Subject: [PATCH 14/57] peregrine: cnav message decoding added Added GPS CNAV message decoding for L2C. Updated tracking loop to follow L1 C/A and L2 C tracking logic differences. Removed L1 C/A message and decoder objects from tracking results. --- peregrine/analysis/tracking_loop.py | 40 ++--- peregrine/gps_constants.py | 3 + peregrine/parallel_processing.py | 120 ++++++++------- peregrine/tracking.py | 221 ++++++++++++++++++---------- 4 files changed, 233 insertions(+), 151 deletions(-) diff --git a/peregrine/analysis/tracking_loop.py b/peregrine/analysis/tracking_loop.py index 64e492d..cdcdab9 100644 --- a/peregrine/analysis/tracking_loop.py +++ b/peregrine/analysis/tracking_loop.py @@ -14,7 +14,7 @@ from peregrine import defaults from peregrine.log import default_logging_config from peregrine.tracking import track - +from peregrine.gps_constants import L1CA, L2C from peregrine.initSettings import initSettings def main(): @@ -25,37 +25,37 @@ def main(): parser = argparse.ArgumentParser() parser.add_argument("file", - help="the sample data file to process") + help = "the sample data file to process") parser.add_argument("-f", "--file-format", - help="the format of the sample data file " + help = "the format of the sample data file " "(e.g. 'piksi', 'int8', '1bit', '1bitrev')") parser.add_argument("-t", "--ms-to-track", - help="the number of milliseconds to process. ") + help = "the number of milliseconds to process. ") parser.add_argument("-I", "--IF", - help="intermediate frequency [Hz]. ") + help = "intermediate frequency [Hz]. ") parser.add_argument("-s", "--sampling-freq", - help="sampling frequency [Hz]. "); + help = "sampling frequency [Hz]. "); parser.add_argument("-P", "--prn", - help="PRN to track. ") + help = "PRN to track. ") parser.add_argument("-p", "--code-phase", - help="code phase [chips]. ") + help = "code phase [chips]. ") parser.add_argument("-d", "--carr-doppler", - help="carrier Doppler frequency [Hz]. ") + help = "carrier Doppler frequency [Hz]. ") - parser.add_argument("-o", "--output-file", default="track.csv", - help="Track results file name. " + parser.add_argument("-o", "--output-file", default = "track.csv", + help = "Track results file name. " "Default: %s" % "track.csv") parser.add_argument("-S", "--signal", - choices=["l1ca", "l2c"], - help="Signal type (l1ca / l2c)") + choices = [L1CA, L2C], + help = "Signal type (l1ca / l2c)") args = parser.parse_args() settings.fileName = args.file @@ -105,7 +105,7 @@ def main(): signals = load_samples(args.file, int(samples_num), 0, # skip samples - file_format=args.file_format) + file_format = args.file_format) index = 0 if len(signals) > 1: @@ -115,12 +115,12 @@ def main(): index = 1 pass - track_results = track(samples=signals[index], - channels=[acq_result], - ms_to_track=ms_to_track, - sampling_freq=sampling_freq, # [Hz] - chipping_rate=defaults.chipping_rate, - IF=IF) + track_results = track(samples = signals[index], + channels = [acq_result], + ms_to_track = ms_to_track, + sampling_freq = sampling_freq, # [Hz] + chipping_rate = defaults.chipping_rate, + IF = IF) with open(args.output_file, 'w') as f1: f1.write("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\n") diff --git a/peregrine/gps_constants.py b/peregrine/gps_constants.py index fb3f252..dcf2601 100644 --- a/peregrine/gps_constants.py +++ b/peregrine/gps_constants.py @@ -19,3 +19,6 @@ # Useful derived quantities: code_period = chips_per_code / chip_rate code_wavelength = code_period * c + +L1CA = 'l1ca' +L2C = 'l2c' diff --git a/peregrine/parallel_processing.py b/peregrine/parallel_processing.py index 44238bb..6826856 100644 --- a/peregrine/parallel_processing.py +++ b/peregrine/parallel_processing.py @@ -4,70 +4,80 @@ import progressbar as pb import multiprocessing as mp import time +import sys +import traceback + def spawn(f): - def worker(q_in, q_out, q_progress): - while True: - i,x = q_in.get() - if i is None: - break - try: - if q_progress: - q_out.put((i,f(x, q_progress=q_progress))) - else: - q_out.put((i,f(x))) - except Exception as err: - print "Subprocess raised exception:" - print err - q_out.put(None) - return worker + def worker(q_in, q_out, q_progress): + while True: + i, x = q_in.get() + if i is None: + break + try: + if q_progress: + res = f(x, q_progress=q_progress) + q_out.put((i, res)) + else: + res = f(x) + q_out.put((i, res)) + except: + print "Subprocess raised exception:" + exType, exValue, exTraceback = sys.exc_info() + traceback.print_exception( + exType, exValue, exTraceback, file=sys.stdout) + q_out.put(None) + return worker -def parmap(f, X, nprocs = mp.cpu_count(), show_progress=True, func_progress=False): - q_in = mp.Queue() - q_out = mp.Queue() - if func_progress: - q_progress = mp.Queue(100) - else: - q_progress = None - proc = [mp.Process(target=spawn(f),args=(q_in, q_out, q_progress)) for _ in range(nprocs)] +def parmap(f, X, nprocs=mp.cpu_count(), show_progress=True, func_progress=False): + q_in = mp.Queue() + q_out = mp.Queue() + if func_progress: + q_progress = mp.Queue(100) + else: + q_progress = None - for p in proc: - p.daemon = True - p.start() + proc = [mp.Process(target=spawn(f), args=(q_in, q_out, q_progress)) + for _ in range(nprocs)] - if show_progress: - pbar = pb.ProgressBar(widgets=[pb.Percentage(), ' ', pb.ETA()], maxval=len(X)).start() + for p in proc: + p.daemon = True + p.start() - [q_in.put((i, x)) for i, x in enumerate(X)] + if show_progress: + pbar = pb.ProgressBar( + widgets=[pb.Percentage(), ' ', pb.ETA()], maxval=len(X)).start() - [q_in.put((None,None)) for _ in range(nprocs)] + [q_in.put((i, x)) for i, x in enumerate(X)] - n_done = 0 - progress = 0 - res = [] - t0 = time.time() - while n_done < len(X): - if func_progress: - time.sleep(0.02) - else: - res.append(q_out.get()) - n_done += 1 - while not q_out.empty(): - res.append(q_out.get()) - n_done += 1 - if q_progress: - while not q_progress.empty(): - progress_increment = q_progress.get_nowait() - progress += progress_increment - else: - progress = n_done - if show_progress and progress <= len(X): - pbar.update(progress) + [q_in.put((None, None)) for _ in range(nprocs)] + + n_done = 0 + progress = 0 + res = [] + t0 = time.time() + while n_done < len(X): + if func_progress: + time.sleep(0.02) + else: + res.append(q_out.get()) + n_done += 1 + while not q_out.empty(): + res.append(q_out.get()) + n_done += 1 + if q_progress: + while not q_progress.empty(): + progress_increment = q_progress.get_nowait() + progress += progress_increment + else: + progress = n_done + if show_progress and progress <= len(X): + pbar.update(progress) - if show_progress: - pbar.finish() + if show_progress: + pbar.finish() - [p.join() for p in proc] + [p.join() for p in proc] - return [x for i,x in sorted(res)] + return [x for i, x in sorted(res)] diff --git a/peregrine/tracking.py b/peregrine/tracking.py index 685a6e7..f2124c8 100644 --- a/peregrine/tracking.py +++ b/peregrine/tracking.py @@ -19,6 +19,7 @@ import swiftnav.track import swiftnav.correlate import swiftnav.nav_msg +import swiftnav.cnav_msg import defaults import logging @@ -117,18 +118,18 @@ def track(samples, channels, if samples_length_ms < ms_to_track: logger.warning("Samples set too short for requested tracking length (%.4fs)" - % (ms_to_track * 1e-3)) + % (ms_to_track * 1e-3)) ms_to_track = samples_length_ms logger.info("Tracking %.4fs of data (%d samples)" % - (ms_to_track * 1e-3, ms_to_track * 1e-3 * sampling_freq)) + (ms_to_track * 1e-3, ms_to_track * 1e-3 * sampling_freq)) # Make sure we have an integer number of points num_points = int(math.floor(ms_to_track)) logger.info("Tracking starting") logger.debug("Tracking %d channels, PRNs %s" % - (n_channels, [chan.prn+1 for chan in channels])) + (n_channels, [chan.prn + 1 for chan in channels])) # If progressbar is not available, disable show_progress. if show_progress and not _progressbar_available: @@ -145,7 +146,7 @@ def track(samples, channels, progressbar.ETA(), ' ', progressbar.Bar()] pbar = progressbar.ProgressBar(widgets=widgets, - maxval=n_channels*num_points, + maxval=n_channels * num_points, attr={'nchan': n_channels}) pbar.start() else: @@ -153,29 +154,42 @@ def track(samples, channels, # Run tracking for each channel def do_channel(chan, n=None, q_progress=None): + isL1CA = (chan.signal == gps_constants.L1CA) + isL2C = (chan.signal == gps_constants.L2C) + + if not isL1CA and not isL2C: + NotImplementedError("Signal type '%s' is not supported" % chan.signal) + track_result = TrackResults(num_points, chan.prn) # Convert acquisition SNR to C/N0 cn0_0 = 10 * np.log10(chan.snr) - cn0_0 += 10 * np.log10(1000) # Channel bandwidth + cn0_0 += 10 * np.log10(1000) # Channel bandwidth - if chan.signal == "l1ca": + if isL1CA: loop_filter_params = defaults.l1ca_stage1_loop_filter_params lock_detect_params = defaults.l1ca_lock_detect_params_opt prn_code = caCodes[chan.prn] coherent_ms = 1 - else: # L2C signal clause + nav_msg = swiftnav.nav_msg.NavMsg() + nav_msg_bit_phase_ref = np.zeros(num_points) + nav_bit_sync = NBSMatchBit() if chan.prn < 30 else NBSSBAS() + elif isL2C: loop_filter_params = defaults.l2c_loop_filter_params lock_detect_params = defaults.l2c_lock_detect_params_opt prn_code = L2CMCodes[chan.prn] coherent_ms = 20 + cnav_msg = swiftnav.cnav_msg.CNavMsg() + cnav_msg_decoder = swiftnav.cnav_msg.CNavMsgDecoder() + else: + raise NotImplementedError() cn0_est = swiftnav.track.CN0Estimator( - bw=1e3, - cn0_0=cn0_0, - cutoff_freq=10, - loop_freq = loop_filter_params["loop_freq"] - ) + bw=1e3, + cn0_0=cn0_0, + cutoff_freq=10, + loop_freq=loop_filter_params["loop_freq"] + ) lock_detect = swiftnav.track.LockDetector( k1 = lock_detect_params["k1"] * coherent_ms, @@ -184,26 +198,28 @@ def do_channel(chan, n=None, q_progress=None): lo = lock_detect_params["lo"]); # Estimate initial code freq via aiding from acq carrier freq - if chan.signal == 'l1ca': + if isL1CA: code_freq_init = (chan.carr_freq - IF) * \ - gps_constants.chip_rate / gps_constants.l1 - else: # l2c clause + gps_constants.chip_rate / gps_constants.l1 + elif isL2C: code_freq_init = (chan.carr_freq - IF) * \ - gps_constants.chip_rate / gps_constants.l2 + gps_constants.chip_rate / gps_constants.l2 + else: + raise NotImplementedError() carr_freq_init = chan.carr_freq - IF loop_filter = loop_filter_class( - loop_freq = loop_filter_params['loop_freq'], - code_freq = code_freq_init, - code_bw = loop_filter_parpams['code_bw'], - code_zeta = loop_filter_params['code_zeta'], - code_k = loop_filter_params['code_k'], - carr_to_code = loop_filter_params['carr_to_code'], - carr_freq = carr_freq_init, - carr_bw = loop_filter_params['carr_bw'], - carr_zeta = loop_filter_params['carr_zeta'], - carr_k = loop_filter_params['carr_k'], - carr_freq_b1 = loop_filter_params['carr_freq_b1'], + loop_freq=loop_filter_params['loop_freq'], + code_freq=code_freq_init, + code_bw=loop_filter_params['code_bw'], + code_zeta=loop_filter_params['code_zeta'], + code_k=loop_filter_params['code_k'], + carr_to_code=loop_filter_params['carr_to_code'], + carr_freq=carr_freq_init, + carr_bw=loop_filter_params['carr_bw'], + carr_zeta=loop_filter_params['carr_zeta'], + carr_k=loop_filter_params['carr_k'], + carr_freq_b1=loop_filter_params['carr_freq_b1'], ) code_phase = 0.0 carr_phase = 0.0 @@ -226,44 +242,59 @@ def do_channel(chan, n=None, q_progress=None): # Process the specified number of ms while ms_tracked < ms_to_track: if pbar: - pbar.update(ms_tracked + n * num_points, attr={'chan': n+1}) - - E = 0+0.j; P = 0+0.j; L = 0+0.j - - if stage1 and \ - chan.signal == 'l1ca' and \ - stage2_coherent_ms and \ - track_result.nav_msg.bit_phase == track_result.nav_msg.bit_phase_ref: - - #print "PRN %02d transition to stage 2 at %d ms" % (chan.prn+1, ms_tracked) - stage1 = False - coherent_ms = stage2_coherent_ms - loop_filter.retune(*stage2_loop_filter_params) - cn0_est = swiftnav.track.CN0Estimator(bw=1e3/stage2_coherent_ms, - cn0_0=track_result.cn0[i-1], - cutoff_freq=10, - loop_freq=1e3/stage2_coherent_ms) - - for _ in range(coherent_ms): + pbar.update(ms_tracked + n * num_points, attr={'chan': n + 1}) + + if isL1CA: + # For L1 C/A there are coherent and non-coherent tracking options. + if stage1 and \ + stage2_coherent_ms and \ + nav_msg.bit_phase == nav_msg.bit_phase_ref: + # print "PRN %02d transition to stage 2 at %d ms" % (chan.prn+1, + # ms_tracked) + stage1 = False + coherent_ms = stage2_coherent_ms + loop_filter.retune(*stage2_loop_filter_params) + cn0_est = swiftnav.track.CN0Estimator(bw=1e3 / stage2_coherent_ms, + cn0_0=track_result.cn0[i - 1], + cutoff_freq=10, + loop_freq=1e3 / stage2_coherent_ms) + + coherent_iter = coherent_ms + elif isL2C: + # L2 C is always tracked coherently + coherent_ms = 20 + coherent_iter = 1 + else: + raise NotImplementedError() + + E = 0 + 0.j + P = 0 + 0.j + L = 0 + 0.j + + for _ in range(coherent_iter): if sample_index >= len(samples): - break; + break samples_ = samples[sample_index:] E_, P_, L_, blksize, code_phase, carr_phase = correlator( - samples_, - loop_filter.to_dict()['code_freq'] + chipping_rate, code_phase, - loop_filter.to_dict()['carr_freq'] + IF, carr_phase, - prn_code, - sampling_freq, - chan.signal + samples_, + loop_filter.to_dict()['code_freq'] + chipping_rate, code_phase, + loop_filter.to_dict()['carr_freq'] + IF, carr_phase, + prn_code, + sampling_freq, + chan.signal ) sample_index += blksize - carr_phase_acc += loop_filter.to_dict()['carr_freq'] * blksize / sampling_freq - code_phase_acc += loop_filter.to_dict()['code_freq'] * blksize / sampling_freq + carr_phase_acc += loop_filter.to_dict()['carr_freq'] * \ + blksize / sampling_freq + code_phase_acc += loop_filter.to_dict()['code_freq'] * \ + blksize / sampling_freq - E += E_; P += P_; L += L_ + E += E_ + P += P_ + L += L_ if chan.signal == 'l2c': # only one 20 ms coherent integration time for L2C break @@ -275,12 +306,30 @@ def do_channel(chan, n=None, q_progress=None): loop_filter.update(E, P, L) track_result.coherent_ms[i] = coherent_ms - track_result.nav_bit_sync.update(np.real(P), coherent_ms) - - # TODO - Is this the correct way to call nav_msg.update? - tow = track_result.nav_msg.update(np.real(P) >= 0) - track_result.nav_msg_bit_phase_ref[i] = track_result.nav_msg.bit_phase_ref - track_result.tow[i] = tow or (track_result.tow[i-1] + coherent_ms) + if isL1CA: + nav_bit_sync.update(np.real(P), coherent_ms) + + # TODO - Is this the correct way to call nav_msg.update? + tow = nav_msg.update(np.real(P) >= 0) + nav_msg_bit_phase_ref[i] = nav_msg.bit_phase_ref + track_result.tow[i] = tow or (track_result.tow[i - 1] + coherent_ms) + elif isL2C: + symbol = 0xFF if np.real(P) >= 0 else 0x00 + res, delay = cnav_msg_decoder.decode(symbol, cnav_msg) + if res: + logger.debug("CNAV message decoded: prn=%d msg_id=%d tow=%d alert=%d delay=%d" % + (cnav_msg.getPrn(), + cnav_msg.getMsgId(), + cnav_msg.getTow(), + cnav_msg.getAlert(), + delay)) + tow = cnav_msg.getTow() * 6000 + delay * 20 + logger.debug("Current L2C ToW %d", tow) + track_result.tow[i] = tow + else: + track_result.tow[i] = track_result.tow[i - 1] + coherent_ms + else: + raise NotImplementedError() track_result.carr_phase[i] = carr_phase track_result.carr_phase_acc[i] = carr_phase_acc @@ -288,7 +337,8 @@ def do_channel(chan, n=None, q_progress=None): track_result.code_phase[i] = code_phase track_result.code_phase_acc[i] = code_phase_acc - track_result.code_freq[i] = loop_filter.to_dict()['code_freq'] + chipping_rate + track_result.code_freq[ + i] = loop_filter.to_dict()['code_freq'] + chipping_rate # Record stuff for postprocessing track_result.absolute_sample[i] = sample_index @@ -302,7 +352,10 @@ def do_channel(chan, n=None, q_progress=None): track_result.lock_detect_outp[i] = lock_detect_outp i += 1 - ms_tracked += coherent_ms + if isL1CA or isL2C: + ms_tracked += coherent_ms + else: + raise NotImplementedError() if q_progress and (i % 200 == 0): p = 1.0 * ms_tracked / ms_to_track @@ -319,10 +372,11 @@ def do_channel(chan, n=None, q_progress=None): return track_result if multi: - track_results=pp.parmap(do_channel, channels, - show_progress=show_progress, func_progress=show_progress) + track_results = pp.parmap(do_channel, channels, + show_progress=show_progress, func_progress=show_progress) else: - track_results=map(lambda (n, chan): do_channel(chan, n=n), enumerate(channels)) + track_results = map( + lambda (n, chan): do_channel(chan, n=n), enumerate(channels)) if pbar: pbar.finish() @@ -333,6 +387,7 @@ def do_channel(chan, n=None, q_progress=None): class TrackResults: + def __init__(self, n_points, prn): self.status = '-' self.prn = prn @@ -355,6 +410,8 @@ def __init__(self, n_points, prn): self.tow = np.empty(n_points) self.tow[:] = np.NAN self.coherent_ms = np.zeros(n_points) + # self.cnav_msg = swiftnav.cnav_msg.CNavMsg() + # self.cnav_msg_decoder = swiftnav.cnav_msg.CNavMsgDecoder() def resize(self, n_points): for k in dir(self): @@ -395,14 +452,15 @@ def _equal(self, other): class NavBitSync: + def __init__(self): self.bit_phase = 0 self.bit_integrate = 0 - self.synced=False - self.bits=[] - self.bit_phase_ref=-1 # A new bit begins when bit_phase == bit_phase_ref + self.synced = False + self.bits = [] + self.bit_phase_ref = -1 # A new bit begins when bit_phase == bit_phase_ref self.count = 0 - + def update(self, corr, ms): self.bit_phase += ms self.bit_phase %= 20 @@ -455,12 +513,13 @@ def _equal(self, other): return True class NavBitSyncSBAS: + def __init__(self): self.bit_phase = 0 self.bit_integrate = 0 - self.synced=False - self.bits=[] - self.bit_phase_ref=-1 # A new bit begins when bit_phase == bit_phase_ref + self.synced = False + self.bits = [] + self.bit_phase_ref = -1 # A new bit begins when bit_phase == bit_phase_ref self.count = 0 def update(self, corr, ms): @@ -482,6 +541,7 @@ def bitstring(self): class NBSSBAS(NavBitSyncSBAS): + def __init__(self, thres=200): NavBitSyncSBAS.__init__(self) self.hist = np.zeros(2) @@ -505,7 +565,9 @@ def update_bit_sync(self, corr, ms): self.synced = True self.bit_phase_ref = np.argmax(self.hist) + class NBSLibSwiftNav(NavBitSync): + def __init__(self): NavBitSync.__init__(self) self.nav_msg = swiftnav.nav_msg.NavMsg() @@ -515,7 +577,9 @@ def update_bit_sync(self, corr, ms): self.bit_phase_ref = self.nav_msg.bit_phase_ref self.synced = self.bit_phase_ref >= 0 + class NBSMatchBit(NavBitSync): + def __init__(self, thres=22): NavBitSync.__init__(self) self.hist = np.zeros(20) @@ -539,7 +603,9 @@ def update_bit_sync(self, corr, ms): self.synced = True self.bit_phase_ref = np.argmax(self.hist) + class NBSHistogram(NavBitSync): + def __init__(self, thres=10): NavBitSync.__init__(self) self.bit_phase_count = 0 @@ -559,8 +625,11 @@ def update_bit_sync(self, corr, ms): self.hist = np.zeros(20) self.bit_phase_count = 0 + class NBSMatchEdge(NavBitSync): - # TODO: This isn't quite right - might get wrong answer with long leading run of same bits, depending on initial phase + # TODO: This isn't quite right - might get wrong answer with long leading + # run of same bits, depending on initial phase + def __init__(self, thres=100000): NavBitSync.__init__(self) self.hist = np.zeros(20) @@ -570,7 +639,7 @@ def __init__(self, thres=100000): def update_bit_sync(self, corr, ms): bp40 = self.bit_phase % 40 - self.acc += corr - 2*self.prev[(bp40 - 20) % 40] + self.prev[bp40] + self.acc += corr - 2 * self.prev[(bp40 - 20) % 40] + self.prev[bp40] self.prev[bp40] = corr if self.bit_phase >= 40: # Accumulator valid From ecaa6ccca14c40049c05eef3ce9957badb7a2006 Mon Sep 17 00:00:00 2001 From: Valeri Atamaniouk Date: Fri, 19 Feb 2016 13:43:50 +0200 Subject: [PATCH 15/57] peregine: fix l1ca bit sync and frame sync Updated handling of GPS L1 C/A bit sync and frame sync operation. --- peregrine/tracking.py | 51 +++++++++++++++++++++++++++++-------------- 1 file changed, 35 insertions(+), 16 deletions(-) diff --git a/peregrine/tracking.py b/peregrine/tracking.py index f2124c8..c1920af 100644 --- a/peregrine/tracking.py +++ b/peregrine/tracking.py @@ -20,6 +20,7 @@ import swiftnav.correlate import swiftnav.nav_msg import swiftnav.cnav_msg +import swiftnav.ephemeris import defaults import logging @@ -192,10 +193,10 @@ def do_channel(chan, n=None, q_progress=None): ) lock_detect = swiftnav.track.LockDetector( - k1 = lock_detect_params["k1"] * coherent_ms, - k2 = lock_detect_params["k2"], - lp = lock_detect_params["lp"], - lo = lock_detect_params["lo"]); + k1=lock_detect_params["k1"] * coherent_ms, + k2=lock_detect_params["k2"], + lp=lock_detect_params["lp"], + lo=lock_detect_params["lo"]) # Estimate initial code freq via aiding from acq carrier freq if isL1CA: @@ -248,9 +249,8 @@ def do_channel(chan, n=None, q_progress=None): # For L1 C/A there are coherent and non-coherent tracking options. if stage1 and \ stage2_coherent_ms and \ - nav_msg.bit_phase == nav_msg.bit_phase_ref: - # print "PRN %02d transition to stage 2 at %d ms" % (chan.prn+1, - # ms_tracked) + nav_bit_sync.bit_phase == nav_bit_sync.bit_phase_ref: + stage1 = False coherent_ms = stage2_coherent_ms loop_filter.retune(*stage2_loop_filter_params) @@ -296,23 +296,38 @@ def do_channel(chan, n=None, q_progress=None): P += P_ L += L_ - if chan.signal == 'l2c': # only one 20 ms coherent integration time for L2C + # only one 20 ms coherent integration time for L2C + if chan.signal == 'l2c': break # Update PLL lock detector lock_detect_outo, lock_detect_outp = lock_detect.update(P.real, P.imag, - coherent_ms) + coherent_ms) loop_filter.update(E, P, L) track_result.coherent_ms[i] = coherent_ms if isL1CA: - nav_bit_sync.update(np.real(P), coherent_ms) - - # TODO - Is this the correct way to call nav_msg.update? - tow = nav_msg.update(np.real(P) >= 0) + sync, bit = nav_bit_sync.update(np.real(P), coherent_ms) + if sync: + tow = nav_msg.update(bit) + if tow >= 0: + logger.info("L1 C/A ToW %d" % tow) + if nav_msg.subframe_ready(): + eph = swiftnav.ephemeris.Ephemeris() + res = nav_msg.process_subframe(eph) + if res < 0: + logger.error("Subframe decoding error %d", res) + elif res > 0: + logger.info("Subframe decoded") + else: + # Subframe decoding is in progress + pass + else: + tow = -1 nav_msg_bit_phase_ref[i] = nav_msg.bit_phase_ref - track_result.tow[i] = tow or (track_result.tow[i - 1] + coherent_ms) + track_result.tow[i] = tow if tow >= 0 else ( + track_result.tow[i - 1] + coherent_ms) elif isL2C: symbol = 0xFF if np.real(P) >= 0 else 0x00 res, delay = cnav_msg_decoder.decode(symbol, cnav_msg) @@ -324,7 +339,7 @@ def do_channel(chan, n=None, q_progress=None): cnav_msg.getAlert(), delay)) tow = cnav_msg.getTow() * 6000 + delay * 20 - logger.debug("Current L2C ToW %d", tow) + logger.debug("L2C ToW %d", tow) track_result.tow[i] = tow else: track_result.tow[i] = track_result.tow[i - 1] + coherent_ms @@ -469,8 +484,12 @@ def update(self, corr, ms): if not self.synced: self.update_bit_sync(corr, ms) if self.bit_phase == self.bit_phase_ref: - self.bits.append(1 if self.bit_integrate > 0 else 0) + bit = 1 if self.bit_integrate > 0 else 0 + self.bits.append(bit) self.bit_integrate = 0 + return True, bit + else: + return False, None def update_bit_sync(self, corr, ms): raise NotImplementedError From 7c2c7af2c88d76c1cb1edde85da9b089c133ad96 Mon Sep 17 00:00:00 2001 From: Adel Mamin Date: Thu, 18 Feb 2016 18:46:41 +0200 Subject: [PATCH 16/57] Add Kaplan 20m integration time parameters for L2C lock detector --- peregrine/defaults.py | 74 ++++++++++++++++++++++++------------------- peregrine/tracking.py | 2 +- 2 files changed, 42 insertions(+), 34 deletions(-) diff --git a/peregrine/defaults.py b/peregrine/defaults.py index 89a4844..a1bd68f 100644 --- a/peregrine/defaults.py +++ b/peregrine/defaults.py @@ -1,4 +1,5 @@ # Copyright (C) 2014 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. @@ -7,55 +8,62 @@ # EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED # WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. -ms_to_track = 37*1e3 +ms_to_track = 37 * 1e3 skip_samples = 1000 file_format = 'piksi' -IF = 4.092e6 # Hz -sampling_freq = 16.368e6 # Hz -chipping_rate = 1.023e6 # Hz -code_length = 1023 # chips +IF = 4.092e6 # Hz +sampling_freq = 16.368e6 # Hz +chipping_rate = 1.023e6 # Hz +code_length = 1023 # chips code_period = code_length / chipping_rate samples_per_code = code_period * sampling_freq l1ca_stage1_loop_filter_params = { - "loop_freq" : 1e3, # loop frequency [Hz] - "code_bw" : 1, # Code loop NBW - "code_zeta" : 0.707, # Code loop zeta - "code_k" : 1, # Code loop k - "carr_to_code" : 0, # Carrier-to-code freq ratio (carrier aiding) - "carr_bw" : 25, # Carrier loop NBW - "carr_zeta" : 0.707, # Carrier loop zeta - "carr_k" : 1, # Carrier loop k - "carr_freq_b1" : 5} # Carrier loop aiding_igain + "loop_freq": 1e3, # loop frequency [Hz] + "code_bw": 1, # Code loop NBW + "code_zeta": 0.707, # Code loop zeta + "code_k": 1, # Code loop k + "carr_to_code": 0, # Carrier-to-code freq ratio (carrier aiding) + "carr_bw": 25, # Carrier loop NBW + "carr_zeta": 0.707, # Carrier loop zeta + "carr_k": 1, # Carrier loop k + "carr_freq_b1": 5} # Carrier loop aiding_igain l2c_loop_filter_params = { - "loop_freq" : 50, # loop frequency [Hz] - "code_bw" : 1, # Code loop NBW - "code_zeta" : 0.707, # Code loop zeta - "code_k" : 1, # Code loop k - "carr_to_code" : 0, # Carrier-to-code freq ratio (carrier aiding) - "carr_bw" : 13, # Carrier loop NBW - "carr_zeta" : 0.707, # Carrier loop zeta - "carr_k" : 1, # Carrier loop k - "carr_freq_b1" : 5} # Carrier loop aiding_igain + "loop_freq": 50, # loop frequency [Hz] + "code_bw": 1, # Code loop NBW + "code_zeta": 0.707, # Code loop zeta + "code_k": 1, # Code loop k + "carr_to_code": 0, # Carrier-to-code freq ratio (carrier aiding) + "carr_bw": 13, # Carrier loop NBW + "carr_zeta": 0.707, # Carrier loop zeta + "carr_k": 1, # Carrier loop k + "carr_freq_b1": 5} # Carrier loop aiding_igain # pessimistic set -l1ca_lock_detect_params_pess = {"k1" : 0.10, "k2" : 1.4, "lp" : 200, "lo" : 50 } -l2c_lock_detect_params_pess = {"k1" : 0.10, "k2" : 1.4, "lp" : 200, "lo" : 50 } +l1ca_lock_detect_params_pess = {"k1": 0.10, "k2": 1.4, "lp": 200, "lo": 50} # normal set -l1ca_lock_detect_params_normal = {"k1" : 0.05, "k2" : 1.4, "lp" : 150, "lo" : 50 } -l2c_lock_detect_params_normal = {"k1" : 0.05, "k2" : 1.4, "lp" : 150, "lo" : 50 } +l1ca_lock_detect_params_normal = {"k1": 0.05, "k2": 1.4, "lp": 150, "lo": 50} # optimal set -l1ca_lock_detect_params_opt = {"k1" : 0.02, "k2" : 1.1, "lp" : 150, "lo" : 50 } -l2c_lock_detect_params_opt = {"k1" : 0.02, "k2" : 1.1, "lp" : 150, "lo" : 50 } +l1ca_lock_detect_params_opt = {"k1": 0.02, "k2": 1.1, "lp": 150, "lo": 50} # extra optimal set -l1ca_lock_detect_params_extraopt = {"k1" : 0.02, "k2" : 0.8, "lp" : 150, "lo" : 50 } -l2c_lock_detect_params_extraopt = {"k1" : 0.02, "k2" : 0.8, "lp" : 150, "lo" : 50 } +l1ca_lock_detect_params_extraopt = {"k1": 0.02, "k2": 0.8, "lp": 150, "lo": 50} # disable lock detect -l1ca_lock_detect_params_disable = {"k1" : 0.02, "k2" : 1e-6, "lp" : 1, "lo" : 1 } -l2c_lock_detect_params_disable = {"k1" : 0.02, "k2" : 1e-6, "lp" : 1, "lo" : 1 } +l1ca_lock_detect_params_disable = {"k1": 0.02, "k2": 1e-6, "lp": 1, "lo": 1} + + +# L2C 20ms lock detect profile +# References: +# - Understanding GPS: Principles and Applications. +# Elliott D. Kaplan. Artech House, 2006. 2nd edition +# p.235 +l2c_lock_detect_params_20ms = { + 'k1': 0.0247, # LPF with -3dB at ~0.4 Hz + 'k2': 1.5, # use ~26 degrees I/Q phase angle as a threshold + 'lp': 50, # 1000ms worth of I/Q samples to reach pessimistic lock + 'lo': 240} # 4800ms worth of I/Q samples to lower optimistic lock diff --git a/peregrine/tracking.py b/peregrine/tracking.py index c1920af..0383c65 100644 --- a/peregrine/tracking.py +++ b/peregrine/tracking.py @@ -177,7 +177,7 @@ def do_channel(chan, n=None, q_progress=None): nav_bit_sync = NBSMatchBit() if chan.prn < 30 else NBSSBAS() elif isL2C: loop_filter_params = defaults.l2c_loop_filter_params - lock_detect_params = defaults.l2c_lock_detect_params_opt + lock_detect_params = defaults.l2c_lock_detect_params_20ms prn_code = L2CMCodes[chan.prn] coherent_ms = 20 cnav_msg = swiftnav.cnav_msg.CNavMsg() From bd6d9ce92c3010c8cfc45f9942b44a81a127e9b7 Mon Sep 17 00:00:00 2001 From: Adel Mamin Date: Mon, 22 Feb 2016 10:19:53 +0200 Subject: [PATCH 17/57] Add alias lock detectors to Peregrine Conflicts: peregrine/tracking.py --- peregrine/analysis/tracking_loop.py | 16 ++++++-- peregrine/defaults.py | 38 +++++++++--------- peregrine/tracking.py | 62 +++++++++++++++++++++++------ 3 files changed, 83 insertions(+), 33 deletions(-) diff --git a/peregrine/analysis/tracking_loop.py b/peregrine/analysis/tracking_loop.py index cdcdab9..a16aa9d 100644 --- a/peregrine/analysis/tracking_loop.py +++ b/peregrine/analysis/tracking_loop.py @@ -29,7 +29,8 @@ def main(): parser.add_argument("-f", "--file-format", help = "the format of the sample data file " - "(e.g. 'piksi', 'int8', '1bit', '1bitrev')") + "('piksi', 'int8', '1bit', '1bitrev', " + "'1bit_x2', '2bits', '2bits_x2')") parser.add_argument("-t", "--ms-to-track", help = "the number of milliseconds to process. ") @@ -123,7 +124,11 @@ def main(): IF = IF) with open(args.output_file, 'w') as f1: - f1.write("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\n") + f1.write("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\n") for i in range(len(track_results[0].carr_phase)): f1.write("%s," % track_results[0].carr_phase[i]) f1.write("%s," % (track_results[0].carr_freq[i] - IF)) @@ -137,7 +142,12 @@ def main(): f1.write("%s," % track_results[0].L[i].real) f1.write("%s," % track_results[0].L[i].imag) f1.write("%s," % track_results[0].lock_detect_outp[i]) - f1.write("%s\n" % track_results[0].lock_detect_outo[i]) + f1.write("%s," % track_results[0].lock_detect_outo[i]) + f1.write("%s," % track_results[0].lock_detect_pcount1[i]) + f1.write("%s," % track_results[0].lock_detect_pcount2[i]) + f1.write("%s," % track_results[0].lock_detect_lpfi[i]) + f1.write("%s," % track_results[0].lock_detect_lpfq[i]) + f1.write("%s\n" % track_results[0].alias_detect_err_hz[i]) if __name__ == '__main__': main() diff --git a/peregrine/defaults.py b/peregrine/defaults.py index a1bd68f..8d3a308 100644 --- a/peregrine/defaults.py +++ b/peregrine/defaults.py @@ -20,26 +20,26 @@ samples_per_code = code_period * sampling_freq l1ca_stage1_loop_filter_params = { - "loop_freq": 1e3, # loop frequency [Hz] - "code_bw": 1, # Code loop NBW - "code_zeta": 0.707, # Code loop zeta - "code_k": 1, # Code loop k - "carr_to_code": 0, # Carrier-to-code freq ratio (carrier aiding) - "carr_bw": 25, # Carrier loop NBW - "carr_zeta": 0.707, # Carrier loop zeta - "carr_k": 1, # Carrier loop k - "carr_freq_b1": 5} # Carrier loop aiding_igain + "loop_freq": 1e3, # loop frequency [Hz] + "code_bw": 1, # Code loop NBW + "code_zeta": 0.7, # Code loop zeta + "code_k": 1, # Code loop k + "carr_to_code": 1540, # Carrier-to-code freq ratio (carrier aiding) + "carr_bw": 10, # Carrier loop NBW + "carr_zeta": 0.7, # Carrier loop zeta + "carr_k": 1, # Carrier loop k + "carr_freq_b1": 5} # Carrier loop aiding_igain l2c_loop_filter_params = { - "loop_freq": 50, # loop frequency [Hz] - "code_bw": 1, # Code loop NBW - "code_zeta": 0.707, # Code loop zeta - "code_k": 1, # Code loop k - "carr_to_code": 0, # Carrier-to-code freq ratio (carrier aiding) - "carr_bw": 13, # Carrier loop NBW - "carr_zeta": 0.707, # Carrier loop zeta - "carr_k": 1, # Carrier loop k - "carr_freq_b1": 5} # Carrier loop aiding_igain + "loop_freq": 50, # loop frequency [Hz] + "code_bw": 1, # Code loop NBW + "code_zeta": 0.707, # Code loop zeta + "code_k": 1, # Code loop k + "carr_to_code": 1200, # Carrier-to-code freq ratio (carrier aiding) + "carr_bw": 13, # Carrier loop NBW + "carr_zeta": 0.707, # Carrier loop zeta + "carr_k": 1, # Carrier loop k + "carr_freq_b1": 5} # Carrier loop aiding_igain # pessimistic set l1ca_lock_detect_params_pess = {"k1": 0.10, "k2": 1.4, "lp": 200, "lo": 50} @@ -67,3 +67,5 @@ 'k2': 1.5, # use ~26 degrees I/Q phase angle as a threshold 'lp': 50, # 1000ms worth of I/Q samples to reach pessimistic lock 'lo': 240} # 4800ms worth of I/Q samples to lower optimistic lock + +alias_detect_interval_ms = 500 diff --git a/peregrine/tracking.py b/peregrine/tracking.py index 0383c65..c63d39c 100644 --- a/peregrine/tracking.py +++ b/peregrine/tracking.py @@ -170,6 +170,11 @@ def do_channel(chan, n=None, q_progress=None): if isL1CA: loop_filter_params = defaults.l1ca_stage1_loop_filter_params lock_detect_params = defaults.l1ca_lock_detect_params_opt + lock_detect = swiftnav.track.LockDetector( + k1 = lock_detect_params["k1"], + k2 = lock_detect_params["k2"], + lp = lock_detect_params["lp"], + lo = lock_detect_params["lo"]) prn_code = caCodes[chan.prn] coherent_ms = 1 nav_msg = swiftnav.nav_msg.NavMsg() @@ -178,6 +183,11 @@ def do_channel(chan, n=None, q_progress=None): elif isL2C: loop_filter_params = defaults.l2c_loop_filter_params lock_detect_params = defaults.l2c_lock_detect_params_20ms + lock_detect = swiftnav.track.LockDetector( + k1 = lock_detect_params["k1"], + k2 = lock_detect_params["k2"], + lp = lock_detect_params["lp"], + lo = lock_detect_params["lo"]) prn_code = L2CMCodes[chan.prn] coherent_ms = 20 cnav_msg = swiftnav.cnav_msg.CNavMsg() @@ -185,6 +195,12 @@ def do_channel(chan, n=None, q_progress=None): else: raise NotImplementedError() + alias_detect_init = 1 # require alias_detect_first() call + # or alias_detect.reinit() call or both + alias_detect = swiftnav.track.AliasDetector( + acc_len = defaults.alias_detect_interval_ms / coherent_ms, + time_diff = 1) + cn0_est = swiftnav.track.CN0Estimator( bw=1e3, cn0_0=cn0_0, @@ -192,12 +208,6 @@ def do_channel(chan, n=None, q_progress=None): loop_freq=loop_filter_params["loop_freq"] ) - lock_detect = swiftnav.track.LockDetector( - k1=lock_detect_params["k1"] * coherent_ms, - k2=lock_detect_params["k2"], - lp=lock_detect_params["lp"], - lo=lock_detect_params["lo"]) - # Estimate initial code freq via aiding from acq carrier freq if isL1CA: code_freq_init = (chan.carr_freq - IF) * \ @@ -215,7 +225,7 @@ def do_channel(chan, n=None, q_progress=None): code_bw=loop_filter_params['code_bw'], code_zeta=loop_filter_params['code_zeta'], code_k=loop_filter_params['code_k'], - carr_to_code=loop_filter_params['carr_to_code'], + carr_to_code=0, # the provided code frequency accounts for Doppler carr_freq=carr_freq_init, carr_bw=loop_filter_params['carr_bw'], carr_zeta=loop_filter_params['carr_zeta'], @@ -254,6 +264,11 @@ def do_channel(chan, n=None, q_progress=None): stage1 = False coherent_ms = stage2_coherent_ms loop_filter.retune(*stage2_loop_filter_params) + lock_detect.reinit( + k1 = lock_detect_params["k1"] * coherent_ms, + k2 = lock_detect_params["k2"], + lp = lock_detect_params["lp"], + lo = lock_detect_params["lo"]); cn0_est = swiftnav.track.CN0Estimator(bw=1e3 / stage2_coherent_ms, cn0_0=track_result.cn0[i - 1], cutoff_freq=10, @@ -296,14 +311,25 @@ def do_channel(chan, n=None, q_progress=None): P += P_ L += L_ - # only one 20 ms coherent integration time for L2C - if chan.signal == 'l2c': - break - # Update PLL lock detector - lock_detect_outo, lock_detect_outp = lock_detect.update(P.real, P.imag, + lock_detect_outo, lock_detect_outp, \ + lock_detect_pcount1, lock_detect_pcount2, \ + lock_detect_lpfi, lock_detect_lpfq = lock_detect.update(P.real, P.imag, coherent_ms) + if lock_detect_outo: + if alias_detect_init: + alias_detect_init = 0 + alias_detect.reinit(defaults.alias_detect_interval_ms / coherent_ms, + time_diff = 1) + alias_detect.first(P.real, P.imag) + alias_detect_err_hz = alias_detect.second(P.real, P.imag) * np.pi * \ + (1e3 / defaults.alias_detect_interval_ms) + alias_detect.first(P.real, P.imag) + else: + alias_detect_init = 1 + alias_detect_err_hz = 0 + loop_filter.update(E, P, L) track_result.coherent_ms[i] = coherent_ms @@ -363,8 +389,15 @@ def do_channel(chan, n=None, q_progress=None): track_result.L[i] = L track_result.cn0[i] = cn0_est.update(P.real, P.imag) + track_result.lock_detect_outo[i] = lock_detect_outo track_result.lock_detect_outp[i] = lock_detect_outp + track_result.lock_detect_pcount1[i] = lock_detect_pcount1 + track_result.lock_detect_pcount2[i] = lock_detect_pcount2 + track_result.lock_detect_lpfi[i] = lock_detect_lpfi + track_result.lock_detect_lpfq[i] = lock_detect_lpfq + + track_result.alias_detect_err_hz[i] = alias_detect_err_hz i += 1 if isL1CA or isL2C: @@ -419,6 +452,11 @@ def __init__(self, n_points, prn): self.cn0 = np.zeros(n_points) self.lock_detect_outp = np.zeros(n_points) self.lock_detect_outo = np.zeros(n_points) + self.lock_detect_pcount1 = np.zeros(n_points) + self.lock_detect_pcount2 = np.zeros(n_points) + self.lock_detect_lpfi = np.zeros(n_points) + self.lock_detect_lpfq = np.zeros(n_points) + self.alias_detect_err_hz = np.zeros(n_points) self.nav_msg = swiftnav.nav_msg.NavMsg() self.nav_msg_bit_phase_ref = np.zeros(n_points) self.nav_bit_sync = NBSMatchBit() if prn < 32 else NBSSBAS() From 44c0066a3f50e9dae88bf3c94075ce5a49542ebc Mon Sep 17 00:00:00 2001 From: Adel Mamin Date: Tue, 23 Feb 2016 10:52:38 +0200 Subject: [PATCH 18/57] Add --ms-to-process parameter to Peregrine --- peregrine/run.py | 7 ++++++- peregrine/samples.py | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/peregrine/run.py b/peregrine/run.py index 62574e9..027d8b0 100755 --- a/peregrine/run.py +++ b/peregrine/run.py @@ -43,11 +43,16 @@ def main(): parser.add_argument("-n", "--skip-navigation", help="use previously saved navigation results", action="store_true") + parser.add_argument("--ms-to-process", + help="milliseconds to process", + default = settings.msToProcess) parser.add_argument("-f", "--file-format", default=defaults.file_format, help="the format of the sample data file " - "(e.g. 'piksi', 'int8', '1bit', '1bitrev')") + "('piksi', 'int8', '1bit', '1bitrev', " + "'1bit_x2', '2bits', '2bits_x2')") args = parser.parse_args() settings.fileName = args.file + settings.msToProcess = int(args.ms_to_process) - 22 samplesPerCode = int(round(settings.samplingFreq / (settings.codeFreqBasis / settings.codeLength))) diff --git a/peregrine/samples.py b/peregrine/samples.py index 71b9253..a1907c4 100644 --- a/peregrine/samples.py +++ b/peregrine/samples.py @@ -280,7 +280,7 @@ def load_samples(filename, num_samples=-1, num_skip=0, file_format='piksi'): else: raise ValueError("Unknown file type '%s'" % file_format) - if len(samples.T) < num_samples: + if len(samples[0]) < num_samples: raise EOFError("Failed to read %d samples from file '%s'" % (num_samples, filename)) From 01000ae9f7934c5bcf88f416ea09a7efe734ccb2 Mon Sep 17 00:00:00 2001 From: Adel Mamin Date: Wed, 24 Feb 2016 09:48:20 +0200 Subject: [PATCH 19/57] Fix nav_bit_sync initialization for GPS SVs PRNs 31 and 32 --- peregrine/tracking.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/peregrine/tracking.py b/peregrine/tracking.py index c63d39c..8f0bc35 100644 --- a/peregrine/tracking.py +++ b/peregrine/tracking.py @@ -179,7 +179,7 @@ def do_channel(chan, n=None, q_progress=None): coherent_ms = 1 nav_msg = swiftnav.nav_msg.NavMsg() nav_msg_bit_phase_ref = np.zeros(num_points) - nav_bit_sync = NBSMatchBit() if chan.prn < 30 else NBSSBAS() + nav_bit_sync = NBSMatchBit() if chan.prn < 32 else NBSSBAS() elif isL2C: loop_filter_params = defaults.l2c_loop_filter_params lock_detect_params = defaults.l2c_lock_detect_params_20ms From e3ce68fd471bddc54181b9a599bd25b8dd8b5579 Mon Sep 17 00:00:00 2001 From: Perttu Salmela Date: Mon, 22 Feb 2016 00:14:49 +0200 Subject: [PATCH 20/57] Handover to L2C on bit synch --- peregrine/acquisition.py | 4 ++- peregrine/run.py | 2 +- peregrine/tracking.py | 55 +++++++++++++++++++++++++++++++++++----- 3 files changed, 53 insertions(+), 8 deletions(-) diff --git a/peregrine/acquisition.py b/peregrine/acquisition.py index 427f6a2..e6a2581 100644 --- a/peregrine/acquisition.py +++ b/peregrine/acquisition.py @@ -534,12 +534,13 @@ class AcquisitionResult: acquisition threshold. signal : {'l1ca', 'l2c'} The type of the signal: L1C/A or L2C + sample_index : Index of sample when acquisition succeeded """ __slots__ = ('prn', 'carr_freq', 'doppler', \ 'code_phase', 'snr', 'status', 'signal') - def __init__(self, prn, carr_freq, doppler, code_phase, snr, status, signal): + def __init__(self, prn, carr_freq, doppler, code_phase, snr, status, signal, sample_index=None): self.prn = prn self.snr = snr self.carr_freq = carr_freq @@ -547,6 +548,7 @@ def __init__(self, prn, carr_freq, doppler, code_phase, snr, status, signal): self.code_phase = code_phase self.status = status self.signal = signal + self.sample_index = sample_index def __str__(self): return "PRN %2d (%s) SNR %6.2f @ CP %6.1f, %+8.2f Hz %s" % \ diff --git a/peregrine/run.py b/peregrine/run.py index 027d8b0..043695d 100755 --- a/peregrine/run.py +++ b/peregrine/run.py @@ -109,7 +109,7 @@ def main(): int(settings.samplingFreq * 1e-3 * (settings.msToProcess + 22)), settings.skipNumberOfBytes, file_format=args.file_format) - track_results = track(signal[0], acq_results, settings.msToProcess) + track_results,l2c_track_results = track(signal[0], acq_results, settings.msToProcess) try: with open(track_results_file, 'wb') as f: cPickle.dump(track_results, f, protocol=cPickle.HIGHEST_PROTOCOL) diff --git a/peregrine/tracking.py b/peregrine/tracking.py index 8f0bc35..01c5723 100644 --- a/peregrine/tracking.py +++ b/peregrine/tracking.py @@ -22,6 +22,7 @@ import swiftnav.cnav_msg import swiftnav.ephemeris import defaults +from peregrine.acquisition import AcquisitionResult import logging logger = logging.getLogger(__name__) @@ -162,6 +163,11 @@ def do_channel(chan, n=None, q_progress=None): NotImplementedError("Signal type '%s' is not supported" % chan.signal) track_result = TrackResults(num_points, chan.prn) + l2c_handover_chan = AcquisitionResult(chan.prn, 0,0,0,0,'-','l2c') + + # Do not track if acquisition or handover failed + if chan.status == '-': + return track_result, l2c_handover_chan # Convert acquisition SNR to C/N0 cn0_0 = 10 * np.log10(chan.snr) @@ -250,6 +256,17 @@ def do_channel(chan, n=None, q_progress=None): progress = 0 ms_tracked = 0 i = 0 + # For L2C, proceed in steps of full milliseconds up to the ms when + # handover succeeded. Do not set sample_index := chan.sample_index + # since the sub ms part of sample_index presents code phase. + # Therefore, skip just full ms steps to preserve code phase. + if chan.signal == 'l2c': + samples_per_ms = defaults.sampling_freq * defaults.code_period + skip_ms = int((chan.sample_index - sample_index)/samples_per_ms) + skip_samples = skip_ms * samples_per_ms + sample_index += skip_samples + ms_tracked += skip_ms + # Process the specified number of ms while ms_tracked < ms_to_track: if pbar: @@ -399,6 +416,19 @@ def do_channel(chan, n=None, q_progress=None): track_result.alias_detect_err_hz[i] = alias_detect_err_hz + # Handover to L2C if possible + if chan.signal == "l1ca" and l2c_handover_chan.status == '-' and nav_bit_sync.bit_phase_ref != -1: + chan_snr = track_result.cn0[i] + chan_snr -= 10 * np.log10(1000) # Channel bandwidth + chan_snr = np.power(10, chan_snr / 10) + l2c_handover_chan = AcquisitionResult(track_result.prn, + track_result.carr_freq[i], + chan.doppler, + track_result.code_phase[i], + chan_snr, + 'A', + 'l2c', + track_result.absolute_sample[i]) i += 1 if isL1CA or isL2C: ms_tracked += coherent_ms @@ -417,21 +447,34 @@ def do_channel(chan, n=None, q_progress=None): if q_progress: q_progress.put(1.0 - progress) - return track_result + return track_result, l2c_handover_chan + # Run L1CA + if multi: + track_handover_results=pp.parmap(do_channel, channels, + show_progress=show_progress, func_progress=show_progress) + else: + track_handover_results=map(lambda (n, chan): do_channel(chan, n=n), enumerate(channels)) + # Extract track and handover results + l1ca_track_results = map(lambda x : x[0], track_handover_results) + l2c_handover_channels = map(lambda x : x[1], track_handover_results) + + # Run L2C + logger.info("Start L2C tracking") if multi: - track_results = pp.parmap(do_channel, channels, - show_progress=show_progress, func_progress=show_progress) + track_handover_results=pp.parmap(do_channel, l2c_handover_channels, + show_progress=show_progress, func_progress=show_progress) else: - track_results = map( - lambda (n, chan): do_channel(chan, n=n), enumerate(channels)) + track_handover_results=map(lambda (n, chan): do_channel(chan, n=n), enumerate(l2c_handover_channels)) + # Extract track results, handover results are unused + l2c_track_results = map(lambda x : x[0], track_handover_results) if pbar: pbar.finish() logger.info("Tracking finished") - return track_results + return l1ca_track_results, l2c_track_results class TrackResults: From 6fb939e7a1750a8ac26ca31893d3fc5e892b3b98 Mon Sep 17 00:00:00 2001 From: Perttu Salmela Date: Mon, 22 Feb 2016 21:08:38 +0200 Subject: [PATCH 21/57] Code review changes of handover to L2C --- peregrine/analysis/tracking_loop.py | 6 ++- peregrine/defaults.py | 3 ++ peregrine/run.py | 2 +- peregrine/tracking.py | 57 ++++++++++++++++------------- 4 files changed, 39 insertions(+), 29 deletions(-) diff --git a/peregrine/analysis/tracking_loop.py b/peregrine/analysis/tracking_loop.py index a16aa9d..458fabd 100644 --- a/peregrine/analysis/tracking_loop.py +++ b/peregrine/analysis/tracking_loop.py @@ -79,7 +79,8 @@ def main(): doppler = carr_doppler, code_phase = code_phase, status = 'A', - signal = 'l1ca') + signal = 'l1ca', + sample_index = 0) else: # L2C signal clause acq_result = AcquisitionResult(prn = prn, snr = 25, # dB @@ -87,7 +88,8 @@ def main(): doppler = carr_doppler, code_phase = code_phase, status = 'A', - signal = 'l2c') + signal = 'l2c', + sample_index = 0) print "==================== Tracking parameters =============================" print "File: %s" % args.file diff --git a/peregrine/defaults.py b/peregrine/defaults.py index 8d3a308..7dd7809 100644 --- a/peregrine/defaults.py +++ b/peregrine/defaults.py @@ -19,6 +19,9 @@ code_period = code_length / chipping_rate samples_per_code = code_period * sampling_freq +L1CA_CHANNEL_BANDWIDTH_HZ=1000 +L2C_CHANNEL_BANDWIDTH_HZ=1000 + l1ca_stage1_loop_filter_params = { "loop_freq": 1e3, # loop frequency [Hz] "code_bw": 1, # Code loop NBW diff --git a/peregrine/run.py b/peregrine/run.py index 043695d..027d8b0 100755 --- a/peregrine/run.py +++ b/peregrine/run.py @@ -109,7 +109,7 @@ def main(): int(settings.samplingFreq * 1e-3 * (settings.msToProcess + 22)), settings.skipNumberOfBytes, file_format=args.file_format) - track_results,l2c_track_results = track(signal[0], acq_results, settings.msToProcess) + track_results = track(signal[0], acq_results, settings.msToProcess) try: with open(track_results_file, 'wb') as f: cPickle.dump(track_results, f, protocol=cPickle.HIGHEST_PROTOCOL) diff --git a/peregrine/tracking.py b/peregrine/tracking.py index 01c5723..6904648 100644 --- a/peregrine/tracking.py +++ b/peregrine/tracking.py @@ -162,17 +162,13 @@ def do_channel(chan, n=None, q_progress=None): if not isL1CA and not isL2C: NotImplementedError("Signal type '%s' is not supported" % chan.signal) - track_result = TrackResults(num_points, chan.prn) - l2c_handover_chan = AcquisitionResult(chan.prn, 0,0,0,0,'-','l2c') + track_result = TrackResults(num_points, chan.prn, chan.signal) + l2c_handover_chan = AcquisitionResult(chan.prn, 0, 0, 0, 0, '-', 'l2c') # Do not track if acquisition or handover failed if chan.status == '-': return track_result, l2c_handover_chan - # Convert acquisition SNR to C/N0 - cn0_0 = 10 * np.log10(chan.snr) - cn0_0 += 10 * np.log10(1000) # Channel bandwidth - if isL1CA: loop_filter_params = defaults.l1ca_stage1_loop_filter_params lock_detect_params = defaults.l1ca_lock_detect_params_opt @@ -186,6 +182,9 @@ def do_channel(chan, n=None, q_progress=None): nav_msg = swiftnav.nav_msg.NavMsg() nav_msg_bit_phase_ref = np.zeros(num_points) nav_bit_sync = NBSMatchBit() if chan.prn < 32 else NBSSBAS() + # Convert acquisition SNR to C/N0 + cn0_0 = 10 * np.log10(chan.snr) + cn0_0 += 10 * np.log10(defaults.L1CA_CHANNEL_BANDWIDTH_HZ) elif isL2C: loop_filter_params = defaults.l2c_loop_filter_params lock_detect_params = defaults.l2c_lock_detect_params_20ms @@ -198,6 +197,9 @@ def do_channel(chan, n=None, q_progress=None): coherent_ms = 20 cnav_msg = swiftnav.cnav_msg.CNavMsg() cnav_msg_decoder = swiftnav.cnav_msg.CNavMsgDecoder() + # Convert acquisition SNR to C/N0 + cn0_0 = 10 * np.log10(chan.snr) + cn0_0 += 10 * np.log10(defaults.L2C_CHANNEL_BANDWIDTH_HZ) else: raise NotImplementedError() @@ -260,12 +262,12 @@ def do_channel(chan, n=None, q_progress=None): # handover succeeded. Do not set sample_index := chan.sample_index # since the sub ms part of sample_index presents code phase. # Therefore, skip just full ms steps to preserve code phase. - if chan.signal == 'l2c': - samples_per_ms = defaults.sampling_freq * defaults.code_period - skip_ms = int((chan.sample_index - sample_index)/samples_per_ms) + if isL2C: + samples_per_ms = sampling_freq * defaults.code_period + skip_ms = int((chan.sample_index - sample_index) / samples_per_ms) skip_samples = skip_ms * samples_per_ms sample_index += skip_samples - ms_tracked += skip_ms + ms_tracked += skip_ms # Process the specified number of ms while ms_tracked < ms_to_track: @@ -417,15 +419,15 @@ def do_channel(chan, n=None, q_progress=None): track_result.alias_detect_err_hz[i] = alias_detect_err_hz # Handover to L2C if possible - if chan.signal == "l1ca" and l2c_handover_chan.status == '-' and nav_bit_sync.bit_phase_ref != -1: + if isL1CA and l2c_handover_chan.status == '-' and nav_bit_sync.bit_phase_ref != -1: chan_snr = track_result.cn0[i] - chan_snr -= 10 * np.log10(1000) # Channel bandwidth + chan_snr -= 10 * np.log10(defaults.L1CA_CHANNEL_BANDWIDTH_HZ) chan_snr = np.power(10, chan_snr / 10) l2c_handover_chan = AcquisitionResult(track_result.prn, track_result.carr_freq[i], chan.doppler, track_result.code_phase[i], - chan_snr, + chan_snr, 'A', 'l2c', track_result.absolute_sample[i]) @@ -451,35 +453,37 @@ def do_channel(chan, n=None, q_progress=None): # Run L1CA if multi: - track_handover_results=pp.parmap(do_channel, channels, - show_progress=show_progress, func_progress=show_progress) + track_handover_results = pp.parmap(do_channel, channels, + show_progress=show_progress, func_progress=show_progress) else: - track_handover_results=map(lambda (n, chan): do_channel(chan, n=n), enumerate(channels)) + track_handover_results = map( + lambda (n, chan): do_channel(chan, n=n), enumerate(channels)) # Extract track and handover results - l1ca_track_results = map(lambda x : x[0], track_handover_results) - l2c_handover_channels = map(lambda x : x[1], track_handover_results) - + l1ca_track_results = map(lambda x: x[0], track_handover_results) + l2c_handover_channels = map(lambda x: x[1], track_handover_results) + # Run L2C logger.info("Start L2C tracking") if multi: - track_handover_results=pp.parmap(do_channel, l2c_handover_channels, - show_progress=show_progress, func_progress=show_progress) + track_handover_results = pp.parmap(do_channel, l2c_handover_channels, + show_progress=show_progress, func_progress=show_progress) else: - track_handover_results=map(lambda (n, chan): do_channel(chan, n=n), enumerate(l2c_handover_channels)) - # Extract track results, handover results are unused - l2c_track_results = map(lambda x : x[0], track_handover_results) + track_handover_results = map(lambda (n, chan): do_channel( + chan, n=n), enumerate(l2c_handover_channels)) + # Extract track results, handover results are unused + l2c_track_results = map(lambda x: x[0], track_handover_results) if pbar: pbar.finish() logger.info("Tracking finished") - return l1ca_track_results, l2c_track_results + return l1ca_track_results + l2c_track_results class TrackResults: - def __init__(self, n_points, prn): + def __init__(self, n_points, prn, signal): self.status = '-' self.prn = prn self.absolute_sample = np.zeros(n_points) @@ -508,6 +512,7 @@ def __init__(self, n_points, prn): self.coherent_ms = np.zeros(n_points) # self.cnav_msg = swiftnav.cnav_msg.CNavMsg() # self.cnav_msg_decoder = swiftnav.cnav_msg.CNavMsgDecoder() + self.signal = signal def resize(self, n_points): for k in dir(self): From c0f4f3ee5c4be9c19d956f420b38b4d64c8e06e6 Mon Sep 17 00:00:00 2001 From: Adel Mamin Date: Wed, 24 Feb 2016 10:46:02 +0200 Subject: [PATCH 22/57] Added more verbose tracking log --- peregrine/tracking.py | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/peregrine/tracking.py b/peregrine/tracking.py index 6904648..1b53ee0 100644 --- a/peregrine/tracking.py +++ b/peregrine/tracking.py @@ -169,6 +169,9 @@ def do_channel(chan, n=None, q_progress=None): if chan.status == '-': return track_result, l2c_handover_chan + logger.info("[PRN: %d (%s)] Tracking is started" % + (chan.prn + 1, chan.signal)) + if isL1CA: loop_filter_params = defaults.l1ca_stage1_loop_filter_params lock_detect_params = defaults.l1ca_lock_detect_params_opt @@ -357,14 +360,17 @@ def do_channel(chan, n=None, q_progress=None): if sync: tow = nav_msg.update(bit) if tow >= 0: - logger.info("L1 C/A ToW %d" % tow) + logger.info("[PRN: %d (%s)] ToW %d" % + (chan.prn + 1, chan.signal, tow)) if nav_msg.subframe_ready(): eph = swiftnav.ephemeris.Ephemeris() res = nav_msg.process_subframe(eph) if res < 0: - logger.error("Subframe decoding error %d", res) + logger.error("[PRN: %d (%s)] Subframe decoding error %d" % + (chan.prn + 1, chan.signal, res)) elif res > 0: - logger.info("Subframe decoded") + logger.info("[PRN: %d (%s)] Subframe decoded" % + (chan.prn + 1, chan.signal) ) else: # Subframe decoding is in progress pass @@ -377,14 +383,17 @@ def do_channel(chan, n=None, q_progress=None): symbol = 0xFF if np.real(P) >= 0 else 0x00 res, delay = cnav_msg_decoder.decode(symbol, cnav_msg) if res: - logger.debug("CNAV message decoded: prn=%d msg_id=%d tow=%d alert=%d delay=%d" % - (cnav_msg.getPrn(), + logger.debug("[PRN: %d (%s)] CNAV message decoded: " + "prn=%d msg_id=%d tow=%d alert=%d delay=%d" % + (chan.prn + 1, + chan.signal, + cnav_msg.getPrn(), cnav_msg.getMsgId(), cnav_msg.getTow(), cnav_msg.getAlert(), delay)) tow = cnav_msg.getTow() * 6000 + delay * 20 - logger.debug("L2C ToW %d", tow) + logger.debug("[PRN: %d (%s)] ToW %d", (chan.prn + 1, chan.signal, tow)) track_result.tow[i] = tow else: track_result.tow[i] = track_result.tow[i - 1] + coherent_ms From 98ecfada0d8c8b73689138e4032c60e9e5a4da0b Mon Sep 17 00:00:00 2001 From: Adel Mamin Date: Wed, 24 Feb 2016 12:03:14 +0200 Subject: [PATCH 23/57] Dump regular Peregrine tracking results to files for analysis --- peregrine/analysis/tracking_loop.py | 59 +++++++++++++++++------------ peregrine/run.py | 3 ++ peregrine/tracking.py | 6 +++ 3 files changed, 43 insertions(+), 25 deletions(-) diff --git a/peregrine/analysis/tracking_loop.py b/peregrine/analysis/tracking_loop.py index 458fabd..c8febe4 100644 --- a/peregrine/analysis/tracking_loop.py +++ b/peregrine/analysis/tracking_loop.py @@ -8,6 +8,7 @@ # EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED # WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +import os import argparse from peregrine.samples import load_samples from peregrine.acquisition import AcquisitionResult @@ -17,6 +18,38 @@ from peregrine.gps_constants import L1CA, L2C from peregrine.initSettings import initSettings +def dump_tracking_results_for_analysis(output_file, track_results): + output_filename, output_file_extension = os.path.splitext(output_file) + + for j in range(len(track_results)): + filename = output_filename + (".index-%s" % j) + output_file_extension + with open(filename, 'w') as f1: + f1.write("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\n") + for i in range(len(track_results[j].carr_phase)): + f1.write("%s," % track_results[j].carr_phase[i]) + f1.write("%s," % (track_results[j].carr_freq[i] - + track_results[j].IF)) + f1.write("%s," % track_results[j].code_phase[i]) + f1.write("%s," % track_results[j].code_freq[i]) + f1.write("%s," % track_results[j].cn0[i]) + f1.write("%s," % track_results[j].E[i].real) + f1.write("%s," % track_results[j].E[i].imag) + f1.write("%s," % track_results[j].P[i].real) + f1.write("%s," % track_results[j].P[i].imag) + f1.write("%s," % track_results[j].L[i].real) + f1.write("%s," % track_results[j].L[i].imag) + f1.write("%s," % track_results[j].lock_detect_outp[i]) + f1.write("%s," % track_results[j].lock_detect_outo[i]) + f1.write("%s," % track_results[j].lock_detect_pcount1[i]) + f1.write("%s," % track_results[j].lock_detect_pcount2[i]) + f1.write("%s," % track_results[j].lock_detect_lpfi[i]) + f1.write("%s," % track_results[j].lock_detect_lpfq[i]) + f1.write("%s\n" % track_results[j].alias_detect_err_hz[i]) + def main(): default_logging_config() @@ -125,31 +158,7 @@ def main(): chipping_rate = defaults.chipping_rate, IF = IF) - with open(args.output_file, 'w') as f1: - f1.write("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\n") - for i in range(len(track_results[0].carr_phase)): - f1.write("%s," % track_results[0].carr_phase[i]) - f1.write("%s," % (track_results[0].carr_freq[i] - IF)) - f1.write("%s," % track_results[0].code_phase[i]) - f1.write("%s," % track_results[0].code_freq[i]) - f1.write("%s," % track_results[0].cn0[i]) - f1.write("%s," % track_results[0].E[i].real) - f1.write("%s," % track_results[0].E[i].imag) - f1.write("%s," % track_results[0].P[i].real) - f1.write("%s," % track_results[0].P[i].imag) - f1.write("%s," % track_results[0].L[i].real) - f1.write("%s," % track_results[0].L[i].imag) - f1.write("%s," % track_results[0].lock_detect_outp[i]) - f1.write("%s," % track_results[0].lock_detect_outo[i]) - f1.write("%s," % track_results[0].lock_detect_pcount1[i]) - f1.write("%s," % track_results[0].lock_detect_pcount2[i]) - f1.write("%s," % track_results[0].lock_detect_lpfi[i]) - f1.write("%s," % track_results[0].lock_detect_lpfq[i]) - f1.write("%s\n" % track_results[0].alias_detect_err_hz[i]) + dump_tracking_results_for_analysis(args.output_file, track_results) if __name__ == '__main__': main() diff --git a/peregrine/run.py b/peregrine/run.py index 027d8b0..13f4cde 100755 --- a/peregrine/run.py +++ b/peregrine/run.py @@ -21,6 +21,7 @@ from peregrine.navigation import navigation from peregrine.tracking import track from peregrine.log import default_logging_config +from peregrine.analysis.tracking_loop import dump_tracking_results_for_analysis import defaults from initSettings import initSettings @@ -114,6 +115,8 @@ def main(): with open(track_results_file, 'wb') as f: cPickle.dump(track_results, f, protocol=cPickle.HIGHEST_PROTOCOL) logging.debug("Saving tracking results as '%s'" % track_results_file) + logging.debug("Saving tracking results for analysis") + dump_tracking_results_for_analysis(track_results_file, track_results) except IOError: logging.error("Couldn't save tracking results file '%s'.", track_results_file) diff --git a/peregrine/tracking.py b/peregrine/tracking.py index 1b53ee0..de81768 100644 --- a/peregrine/tracking.py +++ b/peregrine/tracking.py @@ -400,6 +400,11 @@ def do_channel(chan, n=None, q_progress=None): else: raise NotImplementedError() + track_result.IF = IF # IF is not going to change from channel to channel, + # but it is easier to propagate the actual IF to + # the tracking results file for further analysis + # this way as IF might be different from defaults.IF, + # if tracking loop is run via tracking_loop.py entry track_result.carr_phase[i] = carr_phase track_result.carr_phase_acc[i] = carr_phase_acc track_result.carr_freq[i] = loop_filter.to_dict()['carr_freq'] + IF @@ -495,6 +500,7 @@ class TrackResults: def __init__(self, n_points, prn, signal): self.status = '-' self.prn = prn + self.IF = defaults.IF self.absolute_sample = np.zeros(n_points) self.code_phase = np.zeros(n_points) self.code_phase_acc = np.zeros(n_points) From c6e54c9bd21088435468bc1c9ee529c91d7a030d Mon Sep 17 00:00:00 2001 From: Adel Mamin Date: Wed, 24 Feb 2016 15:46:27 +0200 Subject: [PATCH 24/57] Fix samples channel selection issue --- peregrine/acquisition.py | 6 ++- peregrine/analysis/tracking_loop.py | 59 +++++++++++++++++------------ peregrine/run.py | 2 +- peregrine/tracking.py | 18 ++++++--- 4 files changed, 52 insertions(+), 33 deletions(-) diff --git a/peregrine/acquisition.py b/peregrine/acquisition.py index e6a2581..4f0d0c3 100644 --- a/peregrine/acquisition.py +++ b/peregrine/acquisition.py @@ -534,13 +534,16 @@ class AcquisitionResult: acquisition threshold. signal : {'l1ca', 'l2c'} The type of the signal: L1C/A or L2C + sample_channel : IQ channel index sample_index : Index of sample when acquisition succeeded """ __slots__ = ('prn', 'carr_freq', 'doppler', \ 'code_phase', 'snr', 'status', 'signal') - def __init__(self, prn, carr_freq, doppler, code_phase, snr, status, signal, sample_index=None): + def __init__(self, prn, carr_freq, doppler, code_phase, snr, status, signal, + sample_channel = 0, + sample_index = None): self.prn = prn self.snr = snr self.carr_freq = carr_freq @@ -548,6 +551,7 @@ def __init__(self, prn, carr_freq, doppler, code_phase, snr, status, signal, sam self.code_phase = code_phase self.status = status self.signal = signal + self.sample_channel = sample_channel self.sample_index = sample_index def __str__(self): diff --git a/peregrine/analysis/tracking_loop.py b/peregrine/analysis/tracking_loop.py index c8febe4..e9711ec 100644 --- a/peregrine/analysis/tracking_loop.py +++ b/peregrine/analysis/tracking_loop.py @@ -22,7 +22,14 @@ def dump_tracking_results_for_analysis(output_file, track_results): output_filename, output_file_extension = os.path.splitext(output_file) for j in range(len(track_results)): - filename = output_filename + (".index-%s" % j) + output_file_extension + + if len(track_results) > 1: + # mangle the result file name with the tracked signal name + filename = output_filename + (".%s" % track_results[j].signal) + \ + output_file_extension + else: + pass + with open(filename, 'w') as f1: f1.write("doppler_phase,carr_doppler,code_phase,code_freq," "CN0,E_I,E_Q,P_I,P_Q,L_I,L_Q," @@ -105,25 +112,6 @@ def main(): ms_to_track = int(args.ms_to_track) sampling_freq = float(args.sampling_freq) # [Hz] - if args.signal == "l1ca": - acq_result = AcquisitionResult(prn = prn, - snr = 25, # dB - carr_freq = IF + carr_doppler, - doppler = carr_doppler, - code_phase = code_phase, - status = 'A', - signal = 'l1ca', - sample_index = 0) - else: # L2C signal clause - acq_result = AcquisitionResult(prn = prn, - snr = 25, # dB - carr_freq = IF + carr_doppler, - doppler = carr_doppler, - code_phase = code_phase, - status = 'A', - signal = 'l2c', - sample_index = 0) - print "==================== Tracking parameters =============================" print "File: %s" % args.file print "File format: %s" % args.file_format @@ -143,22 +131,43 @@ def main(): 0, # skip samples file_format = args.file_format) - index = 0 + channel = 0 if len(signals) > 1: if args.signal == 'l1ca': - index = 0 + channel = 0 else: - index = 1 + channel = 1 pass - track_results = track(samples = signals[index], + if args.signal == "l1ca": + acq_result = AcquisitionResult(prn = prn, + snr = 25, # dB + carr_freq = IF + carr_doppler, + doppler = carr_doppler, + code_phase = code_phase, + status = 'A', + signal = 'l1ca', + sample_channel = channel, + sample_index = 0) + else: # L2C signal clause + acq_result = AcquisitionResult(prn = prn, + snr = 25, # dB + carr_freq = IF + carr_doppler, + doppler = carr_doppler, + code_phase = code_phase, + status = 'A', + signal = 'l2c', + sample_channel = channel, + sample_index = 0) + + track_results = track(samples = signals, channels = [acq_result], ms_to_track = ms_to_track, sampling_freq = sampling_freq, # [Hz] chipping_rate = defaults.chipping_rate, IF = IF) - dump_tracking_results_for_analysis(args.output_file, track_results) + dump_tracking_results_for_analysis(args.output_file, track_results[0]) if __name__ == '__main__': main() diff --git a/peregrine/run.py b/peregrine/run.py index 13f4cde..9fe5433 100755 --- a/peregrine/run.py +++ b/peregrine/run.py @@ -110,7 +110,7 @@ def main(): int(settings.samplingFreq * 1e-3 * (settings.msToProcess + 22)), settings.skipNumberOfBytes, file_format=args.file_format) - track_results = track(signal[0], acq_results, settings.msToProcess) + track_results = track(signal, acq_results, settings.msToProcess) try: with open(track_results_file, 'wb') as f: cPickle.dump(track_results, f, protocol=cPickle.HIGHEST_PROTOCOL) diff --git a/peregrine/tracking.py b/peregrine/tracking.py index de81768..d44781e 100644 --- a/peregrine/tracking.py +++ b/peregrine/tracking.py @@ -113,7 +113,7 @@ def track(samples, channels, n_channels = len(channels) - samples_length_ms = int(1e3 * len(samples) / sampling_freq) + samples_length_ms = int(1e3 * len(samples[0]) / sampling_freq) if ms_to_track is None: ms_to_track = samples_length_ms @@ -169,8 +169,13 @@ def do_channel(chan, n=None, q_progress=None): if chan.status == '-': return track_result, l2c_handover_chan - logger.info("[PRN: %d (%s)] Tracking is started" % - (chan.prn + 1, chan.signal)) + logger.info("[PRN: %d (%s)] Tracking is started." + "Doppler: %f, code phase: %f, " + "sample channel: %d sample index: %d" % + (chan.prn + 1, chan.signal, + chan.doppler, chan.code_phase, + chan.sample_channel, + chan.sample_index if chan.sample_index else 0)) if isL1CA: loop_filter_params = defaults.l1ca_stage1_loop_filter_params @@ -310,10 +315,10 @@ def do_channel(chan, n=None, q_progress=None): for _ in range(coherent_iter): - if sample_index >= len(samples): + if sample_index >= len(samples[chan.sample_channel]): break - samples_ = samples[sample_index:] + samples_ = samples[chan.sample_channel][sample_index:] E_, P_, L_, blksize, code_phase, carr_phase = correlator( samples_, @@ -439,11 +444,12 @@ def do_channel(chan, n=None, q_progress=None): chan_snr = np.power(10, chan_snr / 10) l2c_handover_chan = AcquisitionResult(track_result.prn, track_result.carr_freq[i], - chan.doppler, + loop_filter.to_dict()['carr_freq'], track_result.code_phase[i], chan_snr, 'A', 'l2c', + 1, # samples' channel index track_result.absolute_sample[i]) i += 1 if isL1CA or isL2C: From b058adf1836d61121ce40b3e332290d9ff139caf Mon Sep 17 00:00:00 2001 From: Adel Mamin Date: Wed, 24 Feb 2016 16:46:18 +0200 Subject: [PATCH 25/57] Fix bit sync recognition in L2C handover --- peregrine/tracking.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/peregrine/tracking.py b/peregrine/tracking.py index d44781e..fb2d521 100644 --- a/peregrine/tracking.py +++ b/peregrine/tracking.py @@ -398,7 +398,7 @@ def do_channel(chan, n=None, q_progress=None): cnav_msg.getAlert(), delay)) tow = cnav_msg.getTow() * 6000 + delay * 20 - logger.debug("[PRN: %d (%s)] ToW %d", (chan.prn + 1, chan.signal, tow)) + logger.debug("[PRN: %d (%s)] ToW %d" % (chan.prn + 1, chan.signal, tow)) track_result.tow[i] = tow else: track_result.tow[i] = track_result.tow[i - 1] + coherent_ms @@ -438,13 +438,13 @@ def do_channel(chan, n=None, q_progress=None): track_result.alias_detect_err_hz[i] = alias_detect_err_hz # Handover to L2C if possible - if isL1CA and l2c_handover_chan.status == '-' and nav_bit_sync.bit_phase_ref != -1: + if isL1CA and l2c_handover_chan.status == '-' and sync: chan_snr = track_result.cn0[i] chan_snr -= 10 * np.log10(defaults.L1CA_CHANNEL_BANDWIDTH_HZ) chan_snr = np.power(10, chan_snr / 10) l2c_handover_chan = AcquisitionResult(track_result.prn, - track_result.carr_freq[i], - loop_filter.to_dict()['carr_freq'], + track_result.carr_freq[i], # IF + carrier doppler + loop_filter.to_dict()['carr_freq'], # carrier doppler track_result.code_phase[i], chan_snr, 'A', From e874f49b3de77eecbdb81ea0da3a6169f4bed898 Mon Sep 17 00:00:00 2001 From: Adel Mamin Date: Wed, 24 Feb 2016 17:05:36 +0200 Subject: [PATCH 26/57] Fix L2C doppler estimation in L2C handover --- peregrine/tracking.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/peregrine/tracking.py b/peregrine/tracking.py index fb2d521..5d19340 100644 --- a/peregrine/tracking.py +++ b/peregrine/tracking.py @@ -442,9 +442,10 @@ def do_channel(chan, n=None, q_progress=None): chan_snr = track_result.cn0[i] chan_snr -= 10 * np.log10(defaults.L1CA_CHANNEL_BANDWIDTH_HZ) chan_snr = np.power(10, chan_snr / 10) + l2c_doppler = loop_filter.to_dict()['carr_freq'] * gps_constants.l2 / gps_constants.l1 l2c_handover_chan = AcquisitionResult(track_result.prn, - track_result.carr_freq[i], # IF + carrier doppler - loop_filter.to_dict()['carr_freq'], # carrier doppler + IF + l2c_doppler, + l2c_doppler, # carrier doppler track_result.code_phase[i], chan_snr, 'A', From 9d839a6e972c2795cedce8620d6d5e5845b3ac0b Mon Sep 17 00:00:00 2001 From: Adel Mamin Date: Wed, 24 Feb 2016 19:58:11 +0200 Subject: [PATCH 27/57] Add frequency profiles --- peregrine/acquisition.py | 6 +-- peregrine/analysis/tracking_loop.py | 80 ++++++++++++++++------------- peregrine/defaults.py | 18 +++++-- peregrine/initSettings.py | 16 +++--- peregrine/run.py | 32 +++++++++--- peregrine/tracking.py | 27 +++++----- 6 files changed, 107 insertions(+), 72 deletions(-) diff --git a/peregrine/acquisition.py b/peregrine/acquisition.py index 4f0d0c3..028da5d 100644 --- a/peregrine/acquisition.py +++ b/peregrine/acquisition.py @@ -76,9 +76,9 @@ class Acquisition: def __init__(self, samples, - sampling_freq=defaults.sampling_freq, - IF=defaults.IF, - samples_per_code=defaults.samples_per_code, + sampling_freq, + IF, + samples_per_code, code_length=defaults.code_length, n_codes_integrate=4, offsets = None, diff --git a/peregrine/analysis/tracking_loop.py b/peregrine/analysis/tracking_loop.py index e9711ec..e21a8b0 100644 --- a/peregrine/analysis/tracking_loop.py +++ b/peregrine/analysis/tracking_loop.py @@ -75,11 +75,10 @@ def main(): parser.add_argument("-t", "--ms-to-track", help = "the number of milliseconds to process. ") - parser.add_argument("-I", "--IF", - help = "intermediate frequency [Hz]. ") - - parser.add_argument("-s", "--sampling-freq", - help = "sampling frequency [Hz]. "); + parser.add_argument("--profile", + help="L1C/A & L2C IF + sampling frequency profile" + "('peregrine', 'low_rate')", + default = defaults.frequencies_profile) parser.add_argument("-P", "--prn", help = "PRN to track. ") @@ -99,12 +98,31 @@ def main(): help = "Signal type (l1ca / l2c)") args = parser.parse_args() + + if args.profile == 'peregrine': + freq_profile = defaults.freq_profile_peregrine + elif args.profile == 'low_rate': + freq_profile = defaults.freq_profile_low + else: + raise NotImplementedError() + + isL1CA = (args.signal == L1CA) + isL2C = (args.signal == L2C) + + if isL1CA: + signal = L1CA + IF = freq_profile['L1_IF'] + elif isL2C: + signal = L2C + IF = freq_profile['L2_IF'] + else: + raise NotImplementedError() + settings.fileName = args.file samplesPerCode = int(round(settings.samplingFreq / (settings.codeFreqBasis / settings.codeLength))) - IF = float(args.IF) carr_doppler = float(args.carr_doppler) code_phase = float(args.code_phase) prn = int(args.prn) - 1 @@ -117,8 +135,8 @@ def main(): print "File format: %s" % args.file_format print "PRN to track [1-32]: %s" % args.prn print "Time to process [ms]: %s" % args.ms_to_track - print "IF [Hz]: %s" % args.IF - print "Sampling frequency [Hz]: %s" % args.sampling_freq + print "IF [Hz]: %f" % IF + print "Sampling frequency [Hz]: %f" % freq_profile['sampling_freq'] print "Initial carrier Doppler frequency [Hz]: %s" % carr_doppler print "Initial code phase [chips]: %s" % code_phase print "Track results file name: %s" % args.output_file @@ -127,45 +145,33 @@ def main(): samples_num = int(args.sampling_freq) * 1e-3 * ms_to_track signals = load_samples(args.file, - int(samples_num), - 0, # skip samples - file_format = args.file_format) + int(samples_num), + 0, # skip samples + file_format = args.file_format) channel = 0 if len(signals) > 1: - if args.signal == 'l1ca': + if isL1CA: channel = 0 else: channel = 1 pass - if args.signal == "l1ca": - acq_result = AcquisitionResult(prn = prn, - snr = 25, # dB - carr_freq = IF + carr_doppler, - doppler = carr_doppler, - code_phase = code_phase, - status = 'A', - signal = 'l1ca', - sample_channel = channel, - sample_index = 0) - else: # L2C signal clause - acq_result = AcquisitionResult(prn = prn, - snr = 25, # dB - carr_freq = IF + carr_doppler, - doppler = carr_doppler, - code_phase = code_phase, - status = 'A', - signal = 'l2c', - sample_channel = channel, - sample_index = 0) - - track_results = track(samples = signals, + acq_result = AcquisitionResult(prn = prn, + snr = 25, # dB + carr_freq = IF + carr_doppler, + doppler = carr_doppler, + code_phase = code_phase, + status = 'A', + signal = L2C, + sample_channel = channel, + sample_index = 0) + + track_results = track(samples = [ {'data': signals[channel], 'IF': IF} ], channels = [acq_result], ms_to_track = ms_to_track, - sampling_freq = sampling_freq, # [Hz] - chipping_rate = defaults.chipping_rate, - IF = IF) + sampling_freq = freq_profile['sampling_freq'], # [Hz] + chipping_rate = defaults.chipping_rate) dump_tracking_results_for_analysis(args.output_file, track_results[0]) diff --git a/peregrine/defaults.py b/peregrine/defaults.py index 7dd7809..bea1396 100644 --- a/peregrine/defaults.py +++ b/peregrine/defaults.py @@ -11,13 +11,25 @@ ms_to_track = 37 * 1e3 skip_samples = 1000 file_format = 'piksi' -IF = 4.092e6 # Hz -sampling_freq = 16.368e6 # Hz + chipping_rate = 1.023e6 # Hz code_length = 1023 # chips code_period = code_length / chipping_rate -samples_per_code = code_period * sampling_freq + +# 'peregrine' frequencies profile +freq_profile_peregrine = { + 'L1_IF': 4.092e6, + 'L2_IF': 4.092e6, + 'sampling_freq': 16.368e6, + 'samples_per_l1ca_code': code_period * 16.368e6} + +# 'low_rate' frequencies profile +freq_profile_low = { + 'L1_IF': 1450000.0, + 'L2_IF': 750000.0, + 'sampling_freq': 2484375.0, + 'samples_per_l1ca_code': code_period * 2484375.0} L1CA_CHANNEL_BANDWIDTH_HZ=1000 L2C_CHANNEL_BANDWIDTH_HZ=1000 diff --git a/peregrine/initSettings.py b/peregrine/initSettings.py index c17cf16..aca99e7 100644 --- a/peregrine/initSettings.py +++ b/peregrine/initSettings.py @@ -10,18 +10,18 @@ import defaults class initSettings: - def __init__(self): - + def __init__(self, freq_profile): self.msToProcess = 39000 # Number of ms of samples to perform tracking over (ms) self.skipNumberOfBytes = 0 # Skip bytes in sample file before loading samples for acquisition (bytes) - self.IF = defaults.IF # Intermediate frequency of signal in sample file (Hz) - self.samplingFreq = defaults.sampling_freq # Sampling frequency of sample file (Hz) + self.L1_IF = freq_profile['L1_IF'] # L1 intermediate frequency of signal in sample file (Hz) + self.L2_IF = freq_profile['L2_IF'] # L2 intermediate frequency of signal in sample file (Hz) + self.samplingFreq = freq_profile['sampling_freq'] # Sampling frequency of sample file (Hz) self.codeFreqBasis = defaults.chipping_rate # Frequency of chipping code (Hz) self.codeLength = defaults.code_length # Length of chipping code (chips) - self.acqThreshold = 21.0 # SNR (unitless) - self.acqSanityCheck = True # Check for sats known to be below the horizon - self.navSanityMaxResid = 25.0 # Meters per SV, normalized nav residuals (meters) - self.abortIfInsane = True # Abort the whole attempt if sanity check fails + self.acqThreshold = 21.0 # SNR (unitless) + self.acqSanityCheck = True # Check for sats known to be below the horizon + self.navSanityMaxResid = 25.0 # meters per SV, normalized nav residuals + self.abortIfInsane = True # Abort the whole attempt if sanity check fails self.useCache = True self.cacheDir = 'cache' self.ephemMaxAge = 4 * 3600.0 # Reject an ephemeris entry if older than this diff --git a/peregrine/run.py b/peregrine/run.py index 9fe5433..3ee92a2 100755 --- a/peregrine/run.py +++ b/peregrine/run.py @@ -29,9 +29,6 @@ def main(): default_logging_config() - # Initialize constants, settings - settings = initSettings() - parser = argparse.ArgumentParser() parser.add_argument("file", help="the sample data file to process") @@ -46,12 +43,25 @@ def main(): action="store_true") parser.add_argument("--ms-to-process", help="milliseconds to process", - default = settings.msToProcess) + required = True) + parser.add_argument("--profile", + help="L1C/A & L2C IF + sampling frequency profile" + "('peregrine', 'low_rate')", + default = 'peregrine') parser.add_argument("-f", "--file-format", default=defaults.file_format, help="the format of the sample data file " "('piksi', 'int8', '1bit', '1bitrev', " "'1bit_x2', '2bits', '2bits_x2')") args = parser.parse_args() + + if args.profile == 'peregrine': + freq_profile = defaults.freq_profile_peregrine + elif args.profile == 'low_rate': + freq_profile = defaults.freq_profile_low + else: + raise NotImplementedError() + + settings = initSettings(freq_profile) settings.fileName = args.file settings.msToProcess = int(args.ms_to_process) - 22 @@ -73,7 +83,10 @@ def main(): acq_samples = load_samples(args.file, 11 * samplesPerCode, settings.skipNumberOfBytes, file_format=args.file_format) - acq = Acquisition(acq_samples[0]) + acq = Acquisition(acq_samples[0], + freq_profile['sampling_freq'], + freq_profile['L1_IF'], + freq_profile['samples_per_l1ca_code']) acq_results = acq.acquisition() print "Acquisition is over!" @@ -110,7 +123,14 @@ def main(): int(settings.samplingFreq * 1e-3 * (settings.msToProcess + 22)), settings.skipNumberOfBytes, file_format=args.file_format) - track_results = track(signal, acq_results, settings.msToProcess) + if len(signal) > 1: + samples = [ {'data': signal[0], 'IF': freq_profile['L1_IF']}, + {'data': signal[1], 'IF': freq_profile['L2_IF']} ] + else: + samples = [ {data: signal[0], 'IF': freq_profile['L1_IF']} ] + + track_results = track( samples, acq_results, + settings.msToProcess, freq_profile['sampling_freq']) try: with open(track_results_file, 'wb') as f: cPickle.dump(track_results, f, protocol=cPickle.HIGHEST_PROTOCOL) diff --git a/peregrine/tracking.py b/peregrine/tracking.py index 5d19340..b7905d9 100644 --- a/peregrine/tracking.py +++ b/peregrine/tracking.py @@ -100,10 +100,9 @@ def update(self, e, p, l): def track(samples, channels, - ms_to_track=None, - sampling_freq=defaults.sampling_freq, + ms_to_track, + sampling_freq, chipping_rate=defaults.chipping_rate, - IF=defaults.IF, show_progress=True, loop_filter_class=swiftnav.track.AidedTrackingLoop, correlator=swiftnav.correlate.track_correlate, @@ -113,7 +112,7 @@ def track(samples, channels, n_channels = len(channels) - samples_length_ms = int(1e3 * len(samples[0]) / sampling_freq) + samples_length_ms = int(1e3 * len(samples[0]['data']) / sampling_freq) if ms_to_track is None: ms_to_track = samples_length_ms @@ -169,11 +168,13 @@ def do_channel(chan, n=None, q_progress=None): if chan.status == '-': return track_result, l2c_handover_chan + IF = samples[chan.sample_channel]['IF'] + logger.info("[PRN: %d (%s)] Tracking is started." - "Doppler: %f, code phase: %f, " + "IF: %f, Doppler: %f, code phase: %f, " "sample channel: %d sample index: %d" % (chan.prn + 1, chan.signal, - chan.doppler, chan.code_phase, + IF, chan.doppler, chan.code_phase, chan.sample_channel, chan.sample_index if chan.sample_index else 0)) @@ -315,10 +316,10 @@ def do_channel(chan, n=None, q_progress=None): for _ in range(coherent_iter): - if sample_index >= len(samples[chan.sample_channel]): + if sample_index >= len(samples[chan.sample_channel]['data']): break - samples_ = samples[chan.sample_channel][sample_index:] + samples_ = samples[chan.sample_channel]['data'][sample_index:] E_, P_, L_, blksize, code_phase, carr_phase = correlator( samples_, @@ -405,11 +406,7 @@ def do_channel(chan, n=None, q_progress=None): else: raise NotImplementedError() - track_result.IF = IF # IF is not going to change from channel to channel, - # but it is easier to propagate the actual IF to - # the tracking results file for further analysis - # this way as IF might be different from defaults.IF, - # if tracking loop is run via tracking_loop.py entry + track_result.IF = IF track_result.carr_phase[i] = carr_phase track_result.carr_phase_acc[i] = carr_phase_acc track_result.carr_freq[i] = loop_filter.to_dict()['carr_freq'] + IF @@ -444,7 +441,7 @@ def do_channel(chan, n=None, q_progress=None): chan_snr = np.power(10, chan_snr / 10) l2c_doppler = loop_filter.to_dict()['carr_freq'] * gps_constants.l2 / gps_constants.l1 l2c_handover_chan = AcquisitionResult(track_result.prn, - IF + l2c_doppler, + samples[chan.sample_channel]['IF'] + l2c_doppler, l2c_doppler, # carrier doppler track_result.code_phase[i], chan_snr, @@ -507,7 +504,7 @@ class TrackResults: def __init__(self, n_points, prn, signal): self.status = '-' self.prn = prn - self.IF = defaults.IF + self.IF = 0 self.absolute_sample = np.zeros(n_points) self.code_phase = np.zeros(n_points) self.code_phase_acc = np.zeros(n_points) From 39360aecefa59ee7b782f184468b2eb7c081a423 Mon Sep 17 00:00:00 2001 From: Adel Mamin Date: Thu, 25 Feb 2016 10:12:02 +0200 Subject: [PATCH 28/57] Add --profile argument support for tracking_loop and regular peregrine --- peregrine/analysis/tracking_loop.py | 42 +++++++++++++++++++---------- peregrine/tracking.py | 23 +++++++++------- 2 files changed, 41 insertions(+), 24 deletions(-) diff --git a/peregrine/analysis/tracking_loop.py b/peregrine/analysis/tracking_loop.py index e21a8b0..5a71787 100644 --- a/peregrine/analysis/tracking_loop.py +++ b/peregrine/analysis/tracking_loop.py @@ -28,7 +28,7 @@ def dump_tracking_results_for_analysis(output_file, track_results): filename = output_filename + (".%s" % track_results[j].signal) + \ output_file_extension else: - pass + filename = output_file with open(filename, 'w') as f1: f1.write("doppler_phase,carr_doppler,code_phase,code_freq," @@ -60,9 +60,6 @@ def dump_tracking_results_for_analysis(output_file, track_results): def main(): default_logging_config() - # Initialize constants, settings - settings = initSettings() - parser = argparse.ArgumentParser() parser.add_argument("file", help = "the sample data file to process") @@ -75,10 +72,16 @@ def main(): parser.add_argument("-t", "--ms-to-track", help = "the number of milliseconds to process. ") + parser.add_argument("-I", "--IF", + help = "intermediate frequency [Hz]. ") + + parser.add_argument("-s", "--sampling-freq", + help = "sampling frequency [Hz]. "); + parser.add_argument("--profile", help="L1C/A & L2C IF + sampling frequency profile" "('peregrine', 'low_rate')", - default = defaults.frequencies_profile) + default = 'peregrine') parser.add_argument("-P", "--prn", help = "PRN to track. ") @@ -118,9 +121,20 @@ def main(): else: raise NotImplementedError() + if args.IF is not None: + IF = float(args.IF) + + if args.sampling_freq is not None: + sampling_freq = float(args.sampling_freq) # [Hz] + else: + sampling_freq = freq_profile['sampling_freq'] # [Hz] + + # Initialize constants, settings + settings = initSettings(freq_profile) + settings.fileName = args.file - samplesPerCode = int(round(settings.samplingFreq / + samplesPerCode = int(round(sampling_freq / (settings.codeFreqBasis / settings.codeLength))) carr_doppler = float(args.carr_doppler) @@ -128,7 +142,6 @@ def main(): prn = int(args.prn) - 1 ms_to_track = int(args.ms_to_track) - sampling_freq = float(args.sampling_freq) # [Hz] print "==================== Tracking parameters =============================" print "File: %s" % args.file @@ -136,14 +149,14 @@ def main(): print "PRN to track [1-32]: %s" % args.prn print "Time to process [ms]: %s" % args.ms_to_track print "IF [Hz]: %f" % IF - print "Sampling frequency [Hz]: %f" % freq_profile['sampling_freq'] + print "Sampling frequency [Hz]: %f" % sampling_freq print "Initial carrier Doppler frequency [Hz]: %s" % carr_doppler print "Initial code phase [chips]: %s" % code_phase print "Track results file name: %s" % args.output_file print "Signal: %s" % args.signal print "======================================================================" - samples_num = int(args.sampling_freq) * 1e-3 * ms_to_track + samples_num = sampling_freq * 1e-3 * ms_to_track signals = load_samples(args.file, int(samples_num), 0, # skip samples @@ -163,17 +176,18 @@ def main(): doppler = carr_doppler, code_phase = code_phase, status = 'A', - signal = L2C, + signal = signal, sample_channel = channel, sample_index = 0) - track_results = track(samples = [ {'data': signals[channel], 'IF': IF} ], + track_results = track(samples = [ {'data': signals[0], 'IF': IF}, + {'data': signals[1], 'IF': IF} ], channels = [acq_result], ms_to_track = ms_to_track, - sampling_freq = freq_profile['sampling_freq'], # [Hz] - chipping_rate = defaults.chipping_rate) + sampling_freq = sampling_freq, # [Hz] + l2c_handover = False) - dump_tracking_results_for_analysis(args.output_file, track_results[0]) + dump_tracking_results_for_analysis(args.output_file, track_results) if __name__ == '__main__': main() diff --git a/peregrine/tracking.py b/peregrine/tracking.py index b7905d9..13771dd 100644 --- a/peregrine/tracking.py +++ b/peregrine/tracking.py @@ -103,6 +103,7 @@ def track(samples, channels, ms_to_track, sampling_freq, chipping_rate=defaults.chipping_rate, + l2c_handover = True, show_progress=True, loop_filter_class=swiftnav.track.AidedTrackingLoop, correlator=swiftnav.correlate.track_correlate, @@ -170,8 +171,8 @@ def do_channel(chan, n=None, q_progress=None): IF = samples[chan.sample_channel]['IF'] - logger.info("[PRN: %d (%s)] Tracking is started." - "IF: %f, Doppler: %f, code phase: %f, " + logger.info("[PRN: %d (%s)] Tracking is started. " + "IF: %.1f, Doppler: %.1f, code phase: %.1f, " "sample channel: %d sample index: %d" % (chan.prn + 1, chan.signal, IF, chan.doppler, chan.code_phase, @@ -481,15 +482,17 @@ def do_channel(chan, n=None, q_progress=None): l2c_handover_channels = map(lambda x: x[1], track_handover_results) # Run L2C - logger.info("Start L2C tracking") - if multi: - track_handover_results = pp.parmap(do_channel, l2c_handover_channels, - show_progress=show_progress, func_progress=show_progress) + if l2c_handover: + if multi: + track_handover_results = pp.parmap(do_channel, l2c_handover_channels, + show_progress=show_progress, func_progress=show_progress) + else: + track_handover_results = map(lambda (n, chan): do_channel( + chan, n=n), enumerate(l2c_handover_channels)) + # Extract track results, handover results are unused + l2c_track_results = map(lambda x: x[0], track_handover_results) else: - track_handover_results = map(lambda (n, chan): do_channel( - chan, n=n), enumerate(l2c_handover_channels)) - # Extract track results, handover results are unused - l2c_track_results = map(lambda x: x[0], track_handover_results) + l2c_track_results = [] if pbar: pbar.finish() From 05db932612f04a0e0c93a919a08a863edc341719 Mon Sep 17 00:00:00 2001 From: Adel Mamin Date: Thu, 25 Feb 2016 13:37:40 +0200 Subject: [PATCH 29/57] Fix sample offset handling in loading.py --- peregrine/samples.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/peregrine/samples.py b/peregrine/samples.py index a1907c4..d458faf 100644 --- a/peregrine/samples.py +++ b/peregrine/samples.py @@ -46,12 +46,12 @@ def __load_samples_n_bits(filename, num_samples, num_skip, n_rx, n_bits, lookup) if num_samples > 0: # Number of samples is defined: trim the source from start and end - s_file = s_file[sample_offset * sample_block_size: + s_file = s_file[(sample_offset * sample_block_size + 7) / 8: (num_samples * sample_block_size + 7) / 8] else: # Number of samples is not defined: trim the source from start only # compute actual number of samples - s_file = s_file[sample_offset * sample_block_size:] + s_file = s_file[(sample_offset * sample_block_size + 7) / 8:] num_samples = len(s_file) * 8 / sample_block_size # Compute total data block size to ignore bits in the tail. From 77580c6750ca7409426a8c2d5ef765458f3b834e Mon Sep 17 00:00:00 2001 From: Adel Mamin Date: Thu, 25 Feb 2016 14:52:59 +0200 Subject: [PATCH 30/57] Fix L2C doppler handover issue when L2C IF is different from L1C/A IF --- peregrine/analysis/tracking_loop.py | 3 ++- peregrine/tracking.py | 9 +++------ 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/peregrine/analysis/tracking_loop.py b/peregrine/analysis/tracking_loop.py index 5a71787..104a229 100644 --- a/peregrine/analysis/tracking_loop.py +++ b/peregrine/analysis/tracking_loop.py @@ -31,12 +31,13 @@ def dump_tracking_results_for_analysis(output_file, track_results): filename = output_file with open(filename, 'w') as f1: - f1.write("doppler_phase,carr_doppler,code_phase,code_freq," + f1.write("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\n") for i in range(len(track_results[j].carr_phase)): + f1.write("%s," % track_results[j].IF) f1.write("%s," % track_results[j].carr_phase[i]) f1.write("%s," % (track_results[j].carr_freq[i] - track_results[j].IF)) diff --git a/peregrine/tracking.py b/peregrine/tracking.py index 13771dd..81e31d4 100644 --- a/peregrine/tracking.py +++ b/peregrine/tracking.py @@ -228,15 +228,12 @@ def do_channel(chan, n=None, q_progress=None): # Estimate initial code freq via aiding from acq carrier freq if isL1CA: - code_freq_init = (chan.carr_freq - IF) * \ - gps_constants.chip_rate / gps_constants.l1 + code_freq_init = chan.doppler * gps_constants.chip_rate / gps_constants.l1 elif isL2C: - code_freq_init = (chan.carr_freq - IF) * \ - gps_constants.chip_rate / gps_constants.l2 + code_freq_init = chan.doppler * gps_constants.chip_rate / gps_constants.l2 else: raise NotImplementedError() - carr_freq_init = chan.carr_freq - IF loop_filter = loop_filter_class( loop_freq=loop_filter_params['loop_freq'], code_freq=code_freq_init, @@ -244,7 +241,7 @@ def do_channel(chan, n=None, q_progress=None): code_zeta=loop_filter_params['code_zeta'], code_k=loop_filter_params['code_k'], carr_to_code=0, # the provided code frequency accounts for Doppler - carr_freq=carr_freq_init, + carr_freq = chan.doppler, carr_bw=loop_filter_params['carr_bw'], carr_zeta=loop_filter_params['carr_zeta'], carr_k=loop_filter_params['carr_k'], From e260c414949a726d5bbcdf86d491d35621834ac2 Mon Sep 17 00:00:00 2001 From: Adel Mamin Date: Thu, 25 Feb 2016 15:23:11 +0200 Subject: [PATCH 31/57] Add 'piksi_v3' frequency profile --- peregrine/analysis/tracking_loop.py | 6 +++--- peregrine/defaults.py | 22 +++++++++++++--------- peregrine/initSettings.py | 12 ++++++------ peregrine/run.py | 12 ++++++------ 4 files changed, 28 insertions(+), 24 deletions(-) diff --git a/peregrine/analysis/tracking_loop.py b/peregrine/analysis/tracking_loop.py index 104a229..7d09fe2 100644 --- a/peregrine/analysis/tracking_loop.py +++ b/peregrine/analysis/tracking_loop.py @@ -106,7 +106,7 @@ def main(): if args.profile == 'peregrine': freq_profile = defaults.freq_profile_peregrine elif args.profile == 'low_rate': - freq_profile = defaults.freq_profile_low + freq_profile = defaults.freq_profile_low_rate else: raise NotImplementedError() @@ -115,10 +115,10 @@ def main(): if isL1CA: signal = L1CA - IF = freq_profile['L1_IF'] + IF = freq_profile['GPS_L1_IF'] elif isL2C: signal = L2C - IF = freq_profile['L2_IF'] + IF = freq_profile['GPS_L2_IF'] else: raise NotImplementedError() diff --git a/peregrine/defaults.py b/peregrine/defaults.py index bea1396..f41cee6 100644 --- a/peregrine/defaults.py +++ b/peregrine/defaults.py @@ -19,17 +19,21 @@ # 'peregrine' frequencies profile freq_profile_peregrine = { - 'L1_IF': 4.092e6, - 'L2_IF': 4.092e6, - 'sampling_freq': 16.368e6, - 'samples_per_l1ca_code': code_period * 16.368e6} + 'GPS_L1_IF': 4.092e6, + 'GPS_L2_IF': 4.092e6, + 'sampling_freq': 16.368e6 } # 'low_rate' frequencies profile -freq_profile_low = { - 'L1_IF': 1450000.0, - 'L2_IF': 750000.0, - 'sampling_freq': 2484375.0, - 'samples_per_l1ca_code': code_period * 2484375.0} +freq_profile_low_rate = { + 'GPS_L1_IF': 1450000.0, + 'GPS_L2_IF': 750000.0, + 'sampling_freq': 2484375.0 } + +# 'piksi_v3' frequencies profile +freq_profile_piksi_v3 = { + 'GPS_L1_IF': 14.58e6, + 'GPS_L2_IF': 7.4e6, + 'sampling_freq': 24.84375e6 } L1CA_CHANNEL_BANDWIDTH_HZ=1000 L2C_CHANNEL_BANDWIDTH_HZ=1000 diff --git a/peregrine/initSettings.py b/peregrine/initSettings.py index aca99e7..4d7d209 100644 --- a/peregrine/initSettings.py +++ b/peregrine/initSettings.py @@ -13,15 +13,15 @@ class initSettings: def __init__(self, freq_profile): self.msToProcess = 39000 # Number of ms of samples to perform tracking over (ms) self.skipNumberOfBytes = 0 # Skip bytes in sample file before loading samples for acquisition (bytes) - self.L1_IF = freq_profile['L1_IF'] # L1 intermediate frequency of signal in sample file (Hz) - self.L2_IF = freq_profile['L2_IF'] # L2 intermediate frequency of signal in sample file (Hz) + self.L1_IF = freq_profile['GPS_L1_IF'] # L1 intermediate frequency of signal in sample file (Hz) + self.L2_IF = freq_profile['GPS_L2_IF'] # L2 intermediate frequency of signal in sample file (Hz) self.samplingFreq = freq_profile['sampling_freq'] # Sampling frequency of sample file (Hz) self.codeFreqBasis = defaults.chipping_rate # Frequency of chipping code (Hz) self.codeLength = defaults.code_length # Length of chipping code (chips) - self.acqThreshold = 21.0 # SNR (unitless) - self.acqSanityCheck = True # Check for sats known to be below the horizon - self.navSanityMaxResid = 25.0 # meters per SV, normalized nav residuals - self.abortIfInsane = True # Abort the whole attempt if sanity check fails + self.acqThreshold = 21.0 # SNR (unitless) + self.acqSanityCheck = True # Check for sats known to be below the horizon + self.navSanityMaxResid = 25.0 # meters per SV, normalized nav residuals + self.abortIfInsane = True # Abort the whole attempt if sanity check fails self.useCache = True self.cacheDir = 'cache' self.ephemMaxAge = 4 * 3600.0 # Reject an ephemeris entry if older than this diff --git a/peregrine/run.py b/peregrine/run.py index 3ee92a2..972d110 100755 --- a/peregrine/run.py +++ b/peregrine/run.py @@ -57,7 +57,7 @@ def main(): if args.profile == 'peregrine': freq_profile = defaults.freq_profile_peregrine elif args.profile == 'low_rate': - freq_profile = defaults.freq_profile_low + freq_profile = defaults.freq_profile_low_rate else: raise NotImplementedError() @@ -85,8 +85,8 @@ def main(): file_format=args.file_format) acq = Acquisition(acq_samples[0], freq_profile['sampling_freq'], - freq_profile['L1_IF'], - freq_profile['samples_per_l1ca_code']) + freq_profile['GPS_L1_IF'], + defaults.code_period * freq_profile['sampling_freq']) acq_results = acq.acquisition() print "Acquisition is over!" @@ -124,10 +124,10 @@ def main(): settings.skipNumberOfBytes, file_format=args.file_format) if len(signal) > 1: - samples = [ {'data': signal[0], 'IF': freq_profile['L1_IF']}, - {'data': signal[1], 'IF': freq_profile['L2_IF']} ] + samples = [ {'data': signal[0], 'IF': freq_profile['GPS_L1_IF']}, + {'data': signal[1], 'IF': freq_profile['GPS_L2_IF']} ] else: - samples = [ {data: signal[0], 'IF': freq_profile['L1_IF']} ] + samples = [ {data: signal[0], 'IF': freq_profile['GPS_L1_IF']} ] track_results = track( samples, acq_results, settings.msToProcess, freq_profile['sampling_freq']) From a8e49c3dbeb056034d1c5938395db0de4a93ab50 Mon Sep 17 00:00:00 2001 From: Adel Mamin Date: Thu, 25 Feb 2016 15:54:01 +0200 Subject: [PATCH 32/57] Add automatic data length recoginition. --ms-to-process/--ms-to-track support for -1 --- peregrine/analysis/tracking_loop.py | 25 +++++++++++++++++-------- peregrine/defaults.py | 4 ++-- peregrine/run.py | 19 +++++++++++++++---- 3 files changed, 34 insertions(+), 14 deletions(-) diff --git a/peregrine/analysis/tracking_loop.py b/peregrine/analysis/tracking_loop.py index 7d09fe2..7f69965 100644 --- a/peregrine/analysis/tracking_loop.py +++ b/peregrine/analysis/tracking_loop.py @@ -71,7 +71,9 @@ def main(): "'1bit_x2', '2bits', '2bits_x2')") parser.add_argument("-t", "--ms-to-track", - help = "the number of milliseconds to process. ") + help = "the number of milliseconds to track." + "(-1: use all available data", + default = "-1") parser.add_argument("-I", "--IF", help = "intermediate frequency [Hz]. ") @@ -144,11 +146,24 @@ def main(): ms_to_track = int(args.ms_to_track) + if ms_to_track > 0: + samples_num = sampling_freq * 1e-3 * ms_to_track + else: + samples_num = -1 # all available samples + signals = load_samples(args.file, + int(samples_num), + 0, # skip samples + file_format = args.file_format) + + if ms_to_track < 0: + # use all available data + ms_to_track = int(1e3 * len(signals[0]) / sampling_freq) + print "==================== Tracking parameters =============================" print "File: %s" % args.file print "File format: %s" % args.file_format print "PRN to track [1-32]: %s" % args.prn - print "Time to process [ms]: %s" % args.ms_to_track + print "Time to process [ms]: %s" % ms_to_track print "IF [Hz]: %f" % IF print "Sampling frequency [Hz]: %f" % sampling_freq print "Initial carrier Doppler frequency [Hz]: %s" % carr_doppler @@ -157,12 +172,6 @@ def main(): print "Signal: %s" % args.signal print "======================================================================" - samples_num = sampling_freq * 1e-3 * ms_to_track - signals = load_samples(args.file, - int(samples_num), - 0, # skip samples - file_format = args.file_format) - channel = 0 if len(signals) > 1: if isL1CA: diff --git a/peregrine/defaults.py b/peregrine/defaults.py index f41cee6..443980b 100644 --- a/peregrine/defaults.py +++ b/peregrine/defaults.py @@ -35,8 +35,8 @@ 'GPS_L2_IF': 7.4e6, 'sampling_freq': 24.84375e6 } -L1CA_CHANNEL_BANDWIDTH_HZ=1000 -L2C_CHANNEL_BANDWIDTH_HZ=1000 +L1CA_CHANNEL_BANDWIDTH_HZ = 1000 +L2C_CHANNEL_BANDWIDTH_HZ = 1000 l1ca_stage1_loop_filter_params = { "loop_freq": 1e3, # loop frequency [Hz] diff --git a/peregrine/run.py b/peregrine/run.py index 972d110..3d5f503 100755 --- a/peregrine/run.py +++ b/peregrine/run.py @@ -42,8 +42,9 @@ def main(): help="use previously saved navigation results", action="store_true") parser.add_argument("--ms-to-process", - help="milliseconds to process", - required = True) + help = "the number of milliseconds to process." + "(-1: use all available data", + default = "-1") parser.add_argument("--profile", help="L1C/A & L2C IF + sampling frequency profile" "('peregrine', 'low_rate')", @@ -63,7 +64,12 @@ def main(): settings = initSettings(freq_profile) settings.fileName = args.file - settings.msToProcess = int(args.ms_to_process) - 22 + + ms_to_process = int(args.ms_to_process) + if ms_to_process > 0: + samples_num = freq_profile['sampling_freq'] * 1e-3 * ms_to_process + else: + samples_num = -1 # all available samples samplesPerCode = int(round(settings.samplingFreq / (settings.codeFreqBasis / settings.codeLength))) @@ -120,9 +126,14 @@ def main(): sys.exit(1) else: signal = load_samples(args.file, - int(settings.samplingFreq * 1e-3 * (settings.msToProcess + 22)), + int(samples_num), settings.skipNumberOfBytes, file_format=args.file_format) + if ms_to_process < 0: + ms_to_process = int(1e3 * len(signal[0]) / freq_profile['sampling_freq']) + + settings.msToProcess = ms_to_process - 22 + if len(signal) > 1: samples = [ {'data': signal[0], 'IF': freq_profile['GPS_L1_IF']}, {'data': signal[1], 'IF': freq_profile['GPS_L2_IF']} ] From e1c5b09f1a20f569301e4e54e9ea50b9ca035fb1 Mon Sep 17 00:00:00 2001 From: Adel Mamin Date: Thu, 25 Feb 2016 18:37:27 +0200 Subject: [PATCH 33/57] Add '2bits_x4' samples data file format --- peregrine/analysis/tracking_loop.py | 6 +++-- peregrine/defaults.py | 21 ++++++++++++++++ peregrine/run.py | 6 +++-- peregrine/samples.py | 37 ++++++++++++++++++++++------- 4 files changed, 57 insertions(+), 13 deletions(-) diff --git a/peregrine/analysis/tracking_loop.py b/peregrine/analysis/tracking_loop.py index 7f69965..b0be4eb 100644 --- a/peregrine/analysis/tracking_loop.py +++ b/peregrine/analysis/tracking_loop.py @@ -68,7 +68,7 @@ def main(): parser.add_argument("-f", "--file-format", help = "the format of the sample data file " "('piksi', 'int8', '1bit', '1bitrev', " - "'1bit_x2', '2bits', '2bits_x2')") + "'1bit_x2', '2bits', '2bits_x2', '2bits_x4')") parser.add_argument("-t", "--ms-to-track", help = "the number of milliseconds to track." @@ -83,7 +83,7 @@ def main(): parser.add_argument("--profile", help="L1C/A & L2C IF + sampling frequency profile" - "('peregrine', 'low_rate')", + "('peregrine', 'low_rate', 'piksi_v3')", default = 'peregrine') parser.add_argument("-P", "--prn", @@ -109,6 +109,8 @@ def main(): freq_profile = defaults.freq_profile_peregrine elif args.profile == 'low_rate': freq_profile = defaults.freq_profile_low_rate + elif args.profile == 'piksi_v3': + freq_profile = defaults.freq_profile_piksi_v3 else: raise NotImplementedError() diff --git a/peregrine/defaults.py b/peregrine/defaults.py index 443980b..aab1215 100644 --- a/peregrine/defaults.py +++ b/peregrine/defaults.py @@ -17,6 +17,27 @@ code_period = code_length / chipping_rate +file_encoding_1bit_x2 = { + 'GPS_L1_IF': 0, + 'GPS_L2_IF': 1 } + +file_encoding_2bits_x2 = { + 'GPS_L1_IF': 0, + 'GPS_L2_IF': 1 } + +# encoding is taken from here: +# https://swiftnav.hackpad.com/Initial-Testing-NT1065-XKgGd5aVF6W +file_encoding_2bits_x4 = { + 'GPS_L1': 0, + 'GPS_L2': 1, + 'GLO_L1': 2, + 'GLO_L2': 3 } + +file_encoding_profile = { + '1bit_x2': file_encoding_1bit_x2, + '2bits_x2': file_encoding_2bits_x2, + '2bits_x4': file_encoding_2bits_x4 } + # 'peregrine' frequencies profile freq_profile_peregrine = { 'GPS_L1_IF': 4.092e6, diff --git a/peregrine/run.py b/peregrine/run.py index 3d5f503..7327263 100755 --- a/peregrine/run.py +++ b/peregrine/run.py @@ -47,18 +47,20 @@ def main(): default = "-1") parser.add_argument("--profile", help="L1C/A & L2C IF + sampling frequency profile" - "('peregrine', 'low_rate')", + "('peregrine', 'low_rate', 'piksi_v3')", default = 'peregrine') parser.add_argument("-f", "--file-format", default=defaults.file_format, help="the format of the sample data file " "('piksi', 'int8', '1bit', '1bitrev', " - "'1bit_x2', '2bits', '2bits_x2')") + "'1bit_x2', '2bits', '2bits_x2', '2bits_x4')") args = parser.parse_args() if args.profile == 'peregrine': freq_profile = defaults.freq_profile_peregrine elif args.profile == 'low_rate': freq_profile = defaults.freq_profile_low_rate + elif args.profile == 'piksi_v3': + freq_profile = defaults.freq_profile_piksi_v3 else: raise NotImplementedError() diff --git a/peregrine/samples.py b/peregrine/samples.py index d458faf..8f5408a 100644 --- a/peregrine/samples.py +++ b/peregrine/samples.py @@ -10,10 +10,12 @@ """Functions for handling sample data and sample data files.""" import numpy as np +import defaults __all__ = ['load_samples', 'save_samples'] -def __load_samples_n_bits(filename, num_samples, num_skip, n_rx, n_bits, lookup): +def __load_samples_n_bits(filename, num_samples, num_skip, n_rx, n_bits, + value_lookup, channel_lookup = None): ''' Helper method to load two-bit samples from a file. @@ -29,7 +31,9 @@ def __load_samples_n_bits(filename, num_samples, num_skip, n_rx, n_bits, lookup) Number of interleaved streams in the source file n_bits : int Number of bits per sample - lookup : array-like + channel_lookup : array-like + Array to map channels + value_lookup : array-like Array to map values Returns @@ -37,8 +41,11 @@ def __load_samples_n_bits(filename, num_samples, num_skip, n_rx, n_bits, lookup) out : :class:`numpy.ndarray`, shape(`n_rx`, `num_samples`,) The sample data as a two-dimensional numpy array. The first dimension separates codes (bands). The second dimention contains samples indexed - with the `lookup` table. + with the `value_lookup` table. ''' + if not channel_lookup: + channel_lookup = range(n_rx) + sample_block_size = n_bits * n_rx byte_offset = num_skip / (8 / sample_block_size) sample_offset = num_skip % (8 / sample_block_size) @@ -58,7 +65,7 @@ def __load_samples_n_bits(filename, num_samples, num_skip, n_rx, n_bits, lookup) rounded_len = num_samples * sample_block_size bits = np.unpackbits(s_file) - samples = np.empty((n_rx, num_samples), dtype=lookup.dtype) + samples = np.empty((n_rx, num_samples), dtype=value_lookup.dtype) for rx in range(n_rx): # Construct multi-bit sample values @@ -66,8 +73,8 @@ def __load_samples_n_bits(filename, num_samples, num_skip, n_rx, n_bits, lookup) for bit in range(1, n_bits): tmp <<= 1 tmp += bits[rx * n_bits + bit:rounded_len:sample_block_size] - # Generate sample values using lookup table - samples[rx][:] = lookup[tmp] + # Generate sample values using value_lookup table + samples[channel_lookup[rx]][:] = value_lookup[tmp] return samples def __load_samples_one_bit(filename, num_samples, num_skip, n_rx): @@ -95,7 +102,8 @@ def __load_samples_one_bit(filename, num_samples, num_skip, n_rx): lookup = np.asarray((1, -1), dtype=np.int8) return __load_samples_n_bits(filename, num_samples, num_skip, n_rx, 1, lookup) -def __load_samples_two_bits(filename, num_samples, num_skip, n_rx): +def __load_samples_two_bits(filename, num_samples, num_skip, n_rx, + channel_lookup = None): ''' Helper method to load two-bit samples from a file. @@ -109,6 +117,8 @@ def __load_samples_two_bits(filename, num_samples, num_skip, n_rx): Number of samples to discard from the beginning of the file. n_rx : int Number of interleaved streams in the source file + channel_lookup : array-like + Array to map channels Returns ------- @@ -119,8 +129,9 @@ def __load_samples_two_bits(filename, num_samples, num_skip, n_rx): ''' # Interleaved two bit samples from two receivers. First bit is a sign of the # sample, and the second bit is the amplitude value: 1 or 3. - lookup = np.asarray((-1, -3, 1, 3), dtype=np.int8) - return __load_samples_n_bits(filename, num_samples, num_skip, n_rx, 2, lookup) + value_lookup = np.asarray((-1, -3, 1, 3), dtype=np.int8) + return __load_samples_n_bits(filename, num_samples, num_skip, n_rx, 2, + value_lookup, channel_lookup) def load_samples(filename, num_samples=-1, num_skip=0, file_format='piksi'): """ @@ -277,6 +288,14 @@ def load_samples(filename, num_samples=-1, num_skip=0, file_format='piksi'): 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, 2) + elif file_format == '2bits_x4': + # Interleaved two bit samples from four receivers: -3, -1, +1, +3 + samples = __load_samples_two_bits(filename, num_samples, num_skip, 4, + [defaults.file_encoding_2bits_x4['GLO_L1'], + defaults.file_encoding_2bits_x4['GPS_L2'], + defaults.file_encoding_2bits_x4['GPS_L1'], + defaults.file_encoding_2bits_x4['GLO_L2']]) + else: raise ValueError("Unknown file type '%s'" % file_format) From d118ef929697336969fbda4bf1d8f3dd0bc1199d Mon Sep 17 00:00:00 2001 From: Adel Mamin Date: Fri, 26 Feb 2016 11:14:07 +0200 Subject: [PATCH 34/57] Take right GPS L1 and GPS L2 channel data from Piksi-v3 samples data --- peregrine/analysis/tracking_loop.py | 13 ++++---- peregrine/defaults.py | 48 +++++++++++++++++++++-------- peregrine/run.py | 12 +++++--- peregrine/samples.py | 22 +++++++------ peregrine/tracking.py | 4 ++- 5 files changed, 66 insertions(+), 33 deletions(-) diff --git a/peregrine/analysis/tracking_loop.py b/peregrine/analysis/tracking_loop.py index b0be4eb..386604c 100644 --- a/peregrine/analysis/tracking_loop.py +++ b/peregrine/analysis/tracking_loop.py @@ -192,12 +192,13 @@ def main(): sample_channel = channel, sample_index = 0) - track_results = track(samples = [ {'data': signals[0], 'IF': IF}, - {'data': signals[1], 'IF': IF} ], - channels = [acq_result], - ms_to_track = ms_to_track, - sampling_freq = sampling_freq, # [Hz] - l2c_handover = False) + track_results = track(samples = [ + {'data': signals[defaults.sample_channel_GPS_L1], 'IF': IF}, + {'data': signals[defaults.sample_channel_GPS_L2], 'IF': IF} ], + channels = [acq_result], + ms_to_track = ms_to_track, + sampling_freq = sampling_freq, # [Hz] + l2c_handover = False) dump_tracking_results_for_analysis(args.output_file, track_results) diff --git a/peregrine/defaults.py b/peregrine/defaults.py index aab1215..c56cece 100644 --- a/peregrine/defaults.py +++ b/peregrine/defaults.py @@ -17,21 +17,45 @@ code_period = code_length / chipping_rate -file_encoding_1bit_x2 = { - 'GPS_L1_IF': 0, - 'GPS_L2_IF': 1 } +sample_channel_GPS_L1 = 0 +sample_channel_GPS_L2 = 1 +sample_channel_GLO_L1 = 2 +sample_channel_GLO_L2 = 3 -file_encoding_2bits_x2 = { - 'GPS_L1_IF': 0, - 'GPS_L2_IF': 1 } +file_encoding_1bit_x2 = [ + sample_channel_GPS_L1, # GPS L1 + sample_channel_GPS_L2 ] # GPS L2 + +file_encoding_2bits_x2 = file_encoding_1bit_x2 # encoding is taken from here: -# https://swiftnav.hackpad.com/Initial-Testing-NT1065-XKgGd5aVF6W -file_encoding_2bits_x4 = { - 'GPS_L1': 0, - 'GPS_L2': 1, - 'GLO_L1': 2, - 'GLO_L2': 3 } +# https://swiftnav.hackpad.com/MicroZed-Sample-Grabber-IFgt5DbAunD +# 2 bits per frontend channel in every byte: +# RF4 RF3 RF2 RF1 +# 00 00 00 00 +# +# RF 1: +# GPS L1 @ 14.58MHz (1575.42MHz) +# Galileo E1 @ 14.58MHz (1575.42MHz) +# Beidou B1 @ 28.902 MHz (1561.098 MHz) +# +# RF 2: +# GLONASS L1 @ 12MHz (1602MHz) +# +# RF 3: +# GLONASS L2 @ 11MHz (1246MHz) +# Beidou B3 @ 33.52MHz (1268.52MHz) +# Galileo E6 @ 43.75 MHz(1278.75MHz) +# +# RF 4: +# GPS L2 @ 7.4MHz (1227.6MHz) +# Galileo E5b-I/Q @ 27.86MHz (1207.14MHz) +# Beidou B2 @ 27.86MHz (1207.14MHz) +file_encoding_2bits_x4 = [ + sample_channel_GPS_L2, # RF4 + sample_channel_GLO_L2, # RF3 + sample_channel_GLO_L1, # RF2 + sample_channel_GPS_L1] # RF1 file_encoding_profile = { '1bit_x2': file_encoding_1bit_x2, diff --git a/peregrine/run.py b/peregrine/run.py index 7327263..1278d9c 100755 --- a/peregrine/run.py +++ b/peregrine/run.py @@ -91,7 +91,8 @@ def main(): acq_samples = load_samples(args.file, 11 * samplesPerCode, settings.skipNumberOfBytes, file_format=args.file_format) - acq = Acquisition(acq_samples[0], + + acq = Acquisition(acq_samples[defaults.sample_channel_GPS_L1], freq_profile['sampling_freq'], freq_profile['GPS_L1_IF'], defaults.code_period * freq_profile['sampling_freq']) @@ -137,10 +138,13 @@ def main(): settings.msToProcess = ms_to_process - 22 if len(signal) > 1: - samples = [ {'data': signal[0], 'IF': freq_profile['GPS_L1_IF']}, - {'data': signal[1], 'IF': freq_profile['GPS_L2_IF']} ] + samples = [ {'data': signal[defaults.sample_channel_GPS_L1], + 'IF': freq_profile['GPS_L1_IF']}, + {'data': signal[defaults.sample_channel_GPS_L2], + 'IF': freq_profile['GPS_L2_IF']} ] else: - samples = [ {data: signal[0], 'IF': freq_profile['GPS_L1_IF']} ] + samples = [ {'data': signal[defaults.sample_channel_GPS_L1], + 'IF': freq_profile['GPS_L1_IF']} ] track_results = track( samples, acq_results, settings.msToProcess, freq_profile['sampling_freq']) diff --git a/peregrine/samples.py b/peregrine/samples.py index 8f5408a..f381c44 100644 --- a/peregrine/samples.py +++ b/peregrine/samples.py @@ -77,7 +77,8 @@ def __load_samples_n_bits(filename, num_samples, num_skip, n_rx, n_bits, samples[channel_lookup[rx]][:] = value_lookup[tmp] return samples -def __load_samples_one_bit(filename, num_samples, num_skip, n_rx): +def __load_samples_one_bit(filename, num_samples, num_skip, n_rx, + channel_lookup = None): ''' Helper method to load single-bit samples from a file. @@ -91,6 +92,8 @@ def __load_samples_one_bit(filename, num_samples, num_skip, n_rx): Number of samples to discard from the beginning of the file. n_rx : int Number of interleaved streams in the source file + channel_lookup : array-like + Array to map channels Returns ------- @@ -99,8 +102,9 @@ def __load_samples_one_bit(filename, num_samples, num_skip, n_rx): separates codes (bands). The second dimention contains samples with one of the values: -1, 1 ''' - lookup = np.asarray((1, -1), dtype=np.int8) - return __load_samples_n_bits(filename, num_samples, num_skip, n_rx, 1, lookup) + value_lookup = np.asarray((1, -1), dtype=np.int8) + return __load_samples_n_bits(filename, num_samples, num_skip, n_rx, 1, + value_lookup, channel_lookup) def __load_samples_two_bits(filename, num_samples, num_skip, n_rx, channel_lookup = None): @@ -281,21 +285,19 @@ def load_samples(filename, num_samples=-1, num_skip=0, file_format='piksi'): elif file_format == '1bit_x2': # Interleaved single bit samples from two receivers: -1, +1 - samples = __load_samples_one_bit(filename, num_samples, num_skip, 2) + samples = __load_samples_one_bit(filename, num_samples, num_skip, 2, + 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, 1) 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, 2) + samples = __load_samples_two_bits(filename, num_samples, num_skip, 2, + defaults.file_encoding_2bits_x2) elif file_format == '2bits_x4': # Interleaved two bit samples from four receivers: -3, -1, +1, +3 samples = __load_samples_two_bits(filename, num_samples, num_skip, 4, - [defaults.file_encoding_2bits_x4['GLO_L1'], - defaults.file_encoding_2bits_x4['GPS_L2'], - defaults.file_encoding_2bits_x4['GPS_L1'], - defaults.file_encoding_2bits_x4['GLO_L2']]) - + defaults.file_encoding_2bits_x4) else: raise ValueError("Unknown file type '%s'" % file_format) diff --git a/peregrine/tracking.py b/peregrine/tracking.py index 81e31d4..8db6bda 100644 --- a/peregrine/tracking.py +++ b/peregrine/tracking.py @@ -113,7 +113,9 @@ def track(samples, channels, n_channels = len(channels) - samples_length_ms = int(1e3 * len(samples[0]['data']) / sampling_freq) + samples_length_ms = int(1e3 * + len(samples[defaults.sample_channel_GPS_L1]['data']) / + sampling_freq) if ms_to_track is None: ms_to_track = samples_length_ms From 827f6b2f38ff250193e0b48e26c6c6a5daa83f95 Mon Sep 17 00:00:00 2001 From: Adel Mamin Date: Fri, 26 Feb 2016 13:26:24 +0200 Subject: [PATCH 35/57] Add 'normal_rate' and 'high_rate' to match iqgen --- peregrine/analysis/tracking_loop.py | 12 ++++++++---- peregrine/defaults.py | 17 ++++++++++++----- peregrine/run.py | 9 ++++++--- 3 files changed, 26 insertions(+), 12 deletions(-) diff --git a/peregrine/analysis/tracking_loop.py b/peregrine/analysis/tracking_loop.py index 386604c..abd5f31 100644 --- a/peregrine/analysis/tracking_loop.py +++ b/peregrine/analysis/tracking_loop.py @@ -25,7 +25,8 @@ def dump_tracking_results_for_analysis(output_file, track_results): if len(track_results) > 1: # mangle the result file name with the tracked signal name - filename = output_filename + (".%s" % track_results[j].signal) + \ + filename = output_filename + \ + (".%s.%d" % (track_results[j].signal, track_results[j].prn + 1)) +\ output_file_extension else: filename = output_file @@ -83,7 +84,8 @@ def main(): parser.add_argument("--profile", help="L1C/A & L2C IF + sampling frequency profile" - "('peregrine', 'low_rate', 'piksi_v3')", + "('peregrine', 'low_rate', 'normal_rate' (piksi_v3)" + "'high_rate')", default = 'peregrine') parser.add_argument("-P", "--prn", @@ -109,8 +111,10 @@ def main(): freq_profile = defaults.freq_profile_peregrine elif args.profile == 'low_rate': freq_profile = defaults.freq_profile_low_rate - elif args.profile == 'piksi_v3': - freq_profile = defaults.freq_profile_piksi_v3 + elif args.profile == 'normal_rate': + freq_profile = defaults.freq_profile_normal_rate + elif args.profile == 'high_rate': + freq_profile = defaults.freq_profile_high_rate else: raise NotImplementedError() diff --git a/peregrine/defaults.py b/peregrine/defaults.py index c56cece..4c2228d 100644 --- a/peregrine/defaults.py +++ b/peregrine/defaults.py @@ -17,6 +17,7 @@ code_period = code_length / chipping_rate +# original sample_channel_GPS_L1 = 0 sample_channel_GPS_L2 = 1 sample_channel_GLO_L1 = 2 @@ -70,16 +71,22 @@ # 'low_rate' frequencies profile freq_profile_low_rate = { - 'GPS_L1_IF': 1450000.0, - 'GPS_L2_IF': 750000.0, - 'sampling_freq': 2484375.0 } + 'GPS_L1_IF': 14.58e5, + 'GPS_L2_IF': 7.4e5, + 'sampling_freq': 24.84375e5 } -# 'piksi_v3' frequencies profile -freq_profile_piksi_v3 = { +# 'normal_rate' frequencies profile +freq_profile_normal_rate = { 'GPS_L1_IF': 14.58e6, 'GPS_L2_IF': 7.4e6, 'sampling_freq': 24.84375e6 } +# 'normal_rate' frequencies profile +freq_profile_high_rate = { + 'GPS_L1_IF': freq_profile_normal_rate['GPS_L1_IF'], + 'GPS_L2_IF': freq_profile_normal_rate['GPS_L2_IF'], + 'sampling_freq': 99.375e6 } + L1CA_CHANNEL_BANDWIDTH_HZ = 1000 L2C_CHANNEL_BANDWIDTH_HZ = 1000 diff --git a/peregrine/run.py b/peregrine/run.py index 1278d9c..cabad07 100755 --- a/peregrine/run.py +++ b/peregrine/run.py @@ -47,7 +47,8 @@ def main(): default = "-1") parser.add_argument("--profile", help="L1C/A & L2C IF + sampling frequency profile" - "('peregrine', 'low_rate', 'piksi_v3')", + "('peregrine', 'low_rate', 'normal_rate' (piksi_v3)" + "'high_rate')", default = 'peregrine') parser.add_argument("-f", "--file-format", default=defaults.file_format, help="the format of the sample data file " @@ -59,8 +60,10 @@ def main(): freq_profile = defaults.freq_profile_peregrine elif args.profile == 'low_rate': freq_profile = defaults.freq_profile_low_rate - elif args.profile == 'piksi_v3': - freq_profile = defaults.freq_profile_piksi_v3 + elif args.profile == 'normal_rate': + freq_profile = defaults.freq_profile_normal_rate + elif args.profile == 'high_rate': + freq_profile = defaults.freq_profile_high_rate else: raise NotImplementedError() From 3d857a8c82103c1bfd08c6f9a84b16550ac1caae Mon Sep 17 00:00:00 2001 From: Adel Mamin Date: Fri, 26 Feb 2016 17:09:40 +0200 Subject: [PATCH 36/57] Make 'custom_rate' and 'peregrine' frequency profiles synonims --- peregrine/analysis/tracking_loop.py | 6 +++--- peregrine/run.py | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/peregrine/analysis/tracking_loop.py b/peregrine/analysis/tracking_loop.py index abd5f31..c6d3d1b 100644 --- a/peregrine/analysis/tracking_loop.py +++ b/peregrine/analysis/tracking_loop.py @@ -84,8 +84,8 @@ def main(): parser.add_argument("--profile", help="L1C/A & L2C IF + sampling frequency profile" - "('peregrine', 'low_rate', 'normal_rate' (piksi_v3)" - "'high_rate')", + "('peregrine'/'custom_rate', 'low_rate', " + "'normal_rate' (piksi_v3), 'high_rate')", default = 'peregrine') parser.add_argument("-P", "--prn", @@ -107,7 +107,7 @@ def main(): args = parser.parse_args() - if args.profile == 'peregrine': + if args.profile == 'peregrine' or args.profile == 'custom_rate': freq_profile = defaults.freq_profile_peregrine elif args.profile == 'low_rate': freq_profile = defaults.freq_profile_low_rate diff --git a/peregrine/run.py b/peregrine/run.py index cabad07..3532aaa 100755 --- a/peregrine/run.py +++ b/peregrine/run.py @@ -47,8 +47,8 @@ def main(): default = "-1") parser.add_argument("--profile", help="L1C/A & L2C IF + sampling frequency profile" - "('peregrine', 'low_rate', 'normal_rate' (piksi_v3)" - "'high_rate')", + "('peregrine'/'custom_rate', 'low_rate', " + "'normal_rate' (piksi_v3), 'high_rate')", default = 'peregrine') parser.add_argument("-f", "--file-format", default=defaults.file_format, help="the format of the sample data file " @@ -56,7 +56,7 @@ def main(): "'1bit_x2', '2bits', '2bits_x2', '2bits_x4')") args = parser.parse_args() - if args.profile == 'peregrine': + if args.profile == 'peregrine' or args.profile == 'custom_rate': freq_profile = defaults.freq_profile_peregrine elif args.profile == 'low_rate': freq_profile = defaults.freq_profile_low_rate From 091fac445cdb7bed2c0341017df89d2144546203 Mon Sep 17 00:00:00 2001 From: Valeri Atamaniouk Date: Tue, 9 Feb 2016 18:27:46 +0200 Subject: [PATCH 37/57] iqgen: I/Q sample data generator Added GPS L1 C/A and L2 C sample generator with generic doppler control functions. --- peregrine/iqgen/__init__.py | 0 peregrine/iqgen/bits/__init__.py | 0 peregrine/iqgen/bits/amplitude_base.py | 57 ++ peregrine/iqgen/bits/amplitude_factory.py | 68 +++ peregrine/iqgen/bits/amplitude_poly.py | 95 +++ peregrine/iqgen/bits/amplitude_sine.py | 91 +++ peregrine/iqgen/bits/doppler_base.py | 334 +++++++++++ peregrine/iqgen/bits/doppler_factory.py | 83 +++ peregrine/iqgen/bits/doppler_poly.py | 246 ++++++++ peregrine/iqgen/bits/doppler_sine.py | 199 +++++++ peregrine/iqgen/bits/encoder_1bit.py | 82 +++ peregrine/iqgen/bits/encoder_2bits.py | 123 ++++ peregrine/iqgen/bits/encoder_base.py | 105 ++++ peregrine/iqgen/bits/encoder_factory.py | 109 ++++ peregrine/iqgen/bits/encoder_gps.py | 194 ++++++ peregrine/iqgen/bits/filter_bandpass.py | 70 +++ peregrine/iqgen/bits/filter_base.py | 71 +++ peregrine/iqgen/bits/filter_lowpass.py | 81 +++ peregrine/iqgen/bits/message_block.py | 85 +++ peregrine/iqgen/bits/message_cnav.py | 222 +++++++ peregrine/iqgen/bits/message_const.py | 65 ++ peregrine/iqgen/bits/message_factory.py | 124 ++++ peregrine/iqgen/bits/message_lnav.py | 273 +++++++++ peregrine/iqgen/bits/message_zeroone.py | 59 ++ peregrine/iqgen/bits/prn_gps_l1ca.py | 86 +++ peregrine/iqgen/bits/prn_gps_l2c.py | 206 +++++++ peregrine/iqgen/bits/satellite_base.py | 138 +++++ peregrine/iqgen/bits/satellite_factory.py | 81 +++ peregrine/iqgen/bits/satellite_gps.py | 227 +++++++ peregrine/iqgen/bits/signals.py | 171 ++++++ peregrine/iqgen/bits/tcxo_base.py | 46 ++ peregrine/iqgen/bits/tcxo_factory.py | 68 +++ peregrine/iqgen/bits/tcxo_poly.py | 95 +++ peregrine/iqgen/bits/tcxo_sine.py | 101 ++++ peregrine/iqgen/generate.py | 598 +++++++++++++++++++ peregrine/iqgen/if_iface.py | 222 +++++++ peregrine/iqgen/iqgen_main.py | 695 ++++++++++++++++++++++ 37 files changed, 5570 insertions(+) create mode 100644 peregrine/iqgen/__init__.py create mode 100644 peregrine/iqgen/bits/__init__.py create mode 100644 peregrine/iqgen/bits/amplitude_base.py create mode 100644 peregrine/iqgen/bits/amplitude_factory.py create mode 100644 peregrine/iqgen/bits/amplitude_poly.py create mode 100644 peregrine/iqgen/bits/amplitude_sine.py create mode 100644 peregrine/iqgen/bits/doppler_base.py create mode 100644 peregrine/iqgen/bits/doppler_factory.py create mode 100644 peregrine/iqgen/bits/doppler_poly.py create mode 100644 peregrine/iqgen/bits/doppler_sine.py create mode 100644 peregrine/iqgen/bits/encoder_1bit.py create mode 100644 peregrine/iqgen/bits/encoder_2bits.py create mode 100644 peregrine/iqgen/bits/encoder_base.py create mode 100644 peregrine/iqgen/bits/encoder_factory.py create mode 100644 peregrine/iqgen/bits/encoder_gps.py create mode 100644 peregrine/iqgen/bits/filter_bandpass.py create mode 100644 peregrine/iqgen/bits/filter_base.py create mode 100644 peregrine/iqgen/bits/filter_lowpass.py create mode 100644 peregrine/iqgen/bits/message_block.py create mode 100644 peregrine/iqgen/bits/message_cnav.py create mode 100644 peregrine/iqgen/bits/message_const.py create mode 100644 peregrine/iqgen/bits/message_factory.py create mode 100644 peregrine/iqgen/bits/message_lnav.py create mode 100644 peregrine/iqgen/bits/message_zeroone.py create mode 100644 peregrine/iqgen/bits/prn_gps_l1ca.py create mode 100644 peregrine/iqgen/bits/prn_gps_l2c.py create mode 100644 peregrine/iqgen/bits/satellite_base.py create mode 100644 peregrine/iqgen/bits/satellite_factory.py create mode 100644 peregrine/iqgen/bits/satellite_gps.py create mode 100644 peregrine/iqgen/bits/signals.py create mode 100644 peregrine/iqgen/bits/tcxo_base.py create mode 100644 peregrine/iqgen/bits/tcxo_factory.py create mode 100644 peregrine/iqgen/bits/tcxo_poly.py create mode 100644 peregrine/iqgen/bits/tcxo_sine.py create mode 100644 peregrine/iqgen/generate.py create mode 100644 peregrine/iqgen/if_iface.py create mode 100644 peregrine/iqgen/iqgen_main.py diff --git a/peregrine/iqgen/__init__.py b/peregrine/iqgen/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/peregrine/iqgen/bits/__init__.py b/peregrine/iqgen/bits/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/peregrine/iqgen/bits/amplitude_base.py b/peregrine/iqgen/bits/amplitude_base.py new file mode 100644 index 0000000..67a070d --- /dev/null +++ b/peregrine/iqgen/bits/amplitude_base.py @@ -0,0 +1,57 @@ +# Copyright (C) 2016 Swift Navigation Inc. +# Contact: Valeri Atamaniouk +# +# 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. + +""" +The :mod:`peregrine.iqgen.amplitude_base` module contains classes and functions +related to base implementation of amplitude class. + +""" + + +class AmplitudeBase(object): + ''' + Amplitude control for a signal source. + ''' + + def __init__(self): + ''' + Constructs base object for amplitude control. + ''' + super(AmplitudeBase, self).__init__() + + def applyAmplitude(self, signal, userTimeAll_s): + ''' + Applies amplitude modulation to signal. + + Parameters + ---------- + signal : numpy.ndarray + Signal sample vector. Each element defines signal amplitude in range + [-1; +1]. This vector is modified in place. + userTimeAll_s : numpy.ndarray + Sample time vector. Each element defines sample time in seconds. + + Returns + ------- + numpy.ndarray + Array with output samples + ''' + raise NotImplementedError() + + def computeMeanPower(self): + ''' + Computes mean signal power. + + Returns + ------- + float + Mean signal power for the configured amplitude + ''' + raise NotImplementedError() diff --git a/peregrine/iqgen/bits/amplitude_factory.py b/peregrine/iqgen/bits/amplitude_factory.py new file mode 100644 index 0000000..1c88ea3 --- /dev/null +++ b/peregrine/iqgen/bits/amplitude_factory.py @@ -0,0 +1,68 @@ +# Copyright (C) 2016 Swift Navigation Inc. +# Contact: Valeri Atamaniouk +# +# 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. + +""" +The :mod:`peregrine.iqgen.bits.amplitude_factory` module contains classes and +functions related to object factory for amplitude objects. + +""" + +from peregrine.iqgen.bits.amplitude_poly import AmplitudePoly as PolyAmplitude +from peregrine.iqgen.bits.amplitude_sine import AmplitudeSine as SineAmplitude + + +class ObjectFactory(object): + ''' + Object factory for amplitude objects. + ''' + + def __init__(self): + super(ObjectFactory, self).__init__() + + def toMapForm(self, obj): + t = type(obj) + if t is PolyAmplitude: + return self.__PolyAmplitude_ToMap(obj) + elif t is SineAmplitude: + return self.__SineAmplitude_ToMap(obj) + else: + raise ValueError("Invalid object type") + + def fromMapForm(self, data): + t = data['type'] + if t == 'PolyAmplitude': + return self.__MapTo_PolyAmplitude(data) + elif t == 'SineAmplitude': + return self.__MapTo_SineAmplitude(data) + else: + raise ValueError("Invalid object type") + + def __PolyAmplitude_ToMap(self, obj): + data = {'type': 'PolyAmplitude', 'coeffs': obj.coeffs} + return data + + def __SineAmplitude_ToMap(self, obj): + data = {'type': 'SineAmplitude', + 'initial': obj.initial, + 'amplitude': obj.amplitude, + 'period': obj.period_s} + return data + + def __MapTo_PolyAmplitude(self, data): + coeffs = data['coeffs'] + return PolyAmplitude(coeffs) + + def __MapTo_SineAmplitude(self, data): + initial = data['initial'] + amplitude = data['amplitude'] + period = data['period'] + return SineAmplitude(initial, amplitude, period) + +factoryObject = ObjectFactory() diff --git a/peregrine/iqgen/bits/amplitude_poly.py b/peregrine/iqgen/bits/amplitude_poly.py new file mode 100644 index 0000000..0f9275a --- /dev/null +++ b/peregrine/iqgen/bits/amplitude_poly.py @@ -0,0 +1,95 @@ +# Copyright (C) 2016 Swift Navigation Inc. +# Contact: Valeri Atamaniouk +# +# 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. + +""" +The :mod:`peregrine.iqgen.amplitude_poly` module contains classes and functions +related to implementation of polynomial-based amplitude class. + +""" + +from peregrine.iqgen.bits.amplitude_base import AmplitudeBase + +import numpy + + +class AmplitudePoly(AmplitudeBase): + ''' + Amplitude control with polynomial dependency over time. + ''' + + def __init__(self, coeffs): + ''' + Constructs polynomial amplitude control object. + + Parameters + coeffs : array-like + Polynomial coefficients + ''' + super(AmplitudePoly, self).__init__() + + self.coeffs = tuple([x for x in coeffs]) + if len(coeffs) > 0: + self.poly = numpy.poly1d(coeffs) + else: + self.poly = None + + def __str__(self): + ''' + Constructs literal presentation of object. + + Returns + ------- + string + Literal presentation of object + ''' + return "AmplitudePoly(c={})".format(self.coeffs) + + def applyAmplitude(self, signal, userTimeAll_s): + ''' + Applies amplitude modulation to signal. + + This method applies polynomial modulation. + + Parameters + ---------- + signal : numpy.ndarray + Signal sample vector. Each element defines signal amplitude in range + [-1; +1]. This vector is modified in place. + userTimeAll_s : numpy.ndarray + Sample time vector. Each element defines sample time in seconds. + + Returns + ------- + numpy.ndarray + Array with output samples + ''' + + poly = self.poly + if poly is not None: + amplitudeVector = poly(userTimeAll_s) + signal *= amplitudeVector + + return signal + + def computeMeanPower(self): + ''' + Computes mean signal power. + + Returns + ------- + float + Mean signal power for the configured amplitude + ''' + poly = self.poly + if poly is not None: + result = numpy.square(poly(0.)) + else: + result = 1. + return result diff --git a/peregrine/iqgen/bits/amplitude_sine.py b/peregrine/iqgen/bits/amplitude_sine.py new file mode 100644 index 0000000..ee18452 --- /dev/null +++ b/peregrine/iqgen/bits/amplitude_sine.py @@ -0,0 +1,91 @@ +# Copyright (C) 2016 Swift Navigation Inc. +# Contact: Valeri Atamaniouk +# +# 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. + +""" +The :mod:`peregrine.iqgen.amplitude_sine` module contains classes and functions +related to implementation of sine-based amplitude class. + +""" + +from peregrine.iqgen.bits.amplitude_base import AmplitudeBase + +import numpy +import scipy.constants + + +class AmplitudeSine(AmplitudeBase): + ''' + Amplitude control with sine modulation over time. + ''' + + def __init__(self, initial, amplitude, period_s): + ''' + Constructs sine amplitude control object. + + Parameters + initial : float + Initial amplitude value (median) + amplitude : float + Amplitude of change + period_s : float + Period of change in seconds + ''' + super(AmplitudeSine, self).__init__() + self.initial = initial + self.amplitude = amplitude + self.period_s = period_s + self.c = 2. * scipy.constants.pi / period_s + + def __str__(self): + ''' + Constructs literal presentation of object. + + Returns + ------- + string + Literal presentation of object + ''' + return "AmplitudeSine(base={}, amp={}, p={} s)".\ + format(self.initial, self.amplitude, self.period_s) + + def applyAmplitude(self, signal, userTimeAll_s): + ''' + Applies amplitude modulation to signal. + + Parameters + ---------- + signal : numpy.ndarray + Signal sample vector. Each element defines signal amplitude in range + [-1; +1]. This vector is modified in place. + userTimeAll_s : numpy.ndarray + Sample time vector. Each element defines sample time in seconds. + + Returns + ------- + numpy.ndarray + Array with output samples + ''' + + ampAll = numpy.sin(userTimeAll_s * self.c) * self.amplitude + self.initial + + return numpy.multiply(signal, ampAll, out=signal) + + def computeMeanPower(self): + ''' + Computes mean signal power. + + Returns + ------- + float + Mean signal power for the configured amplitude + ''' + amplitude = self.amplitude * 0.707 + self.initial + result = numpy.square(amplitude) + return result diff --git a/peregrine/iqgen/bits/doppler_base.py b/peregrine/iqgen/bits/doppler_base.py new file mode 100644 index 0000000..ebddc8e --- /dev/null +++ b/peregrine/iqgen/bits/doppler_base.py @@ -0,0 +1,334 @@ +# Copyright (C) 2016 Swift Navigation Inc. +# Contact: Valeri Atamaniouk +# +# 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. + +""" +The :mod:`peregrine.iqgen.doppler_base` module contains classes and functions +related to base implementation of doppler class. + +""" + +import scipy.constants +import numpy + + +class DopplerBase(object): + ''' + Doppler control for a signal source that moves with a constant speed. + ''' + + def __init__(self, distance0_m=0., tec_epm2=50., dtype=numpy.longdouble): + ''' + Constructs doppler base object for movement control. + + Parameters + ---------- + distance0_m : float + Distance to object in meters at time 0. + tec_epm2 : float + Total free electron content integrated along line of sight to the object + in electrons per m^2. + dtype : object, optional + Numpy type for sample computations. + ''' + super(DopplerBase, self).__init__() + self.distance0_m = distance0_m + self.tec_epm2 = tec_epm2 + self.dtype = dtype + self.codeDopplerIgnored = False + self.twoPi = scipy.constants.pi * 2. + + def isCodeDopplerIgnored(self): + ''' + Checks if the code is ignoring doppler control. + + Returns + ------- + bool + When True, the sample generator ignores doppler shift for data and code + processing. + ''' + return self.codeDopplerIgnored + + def setCodeDopplerIgnored(self, value): + ''' + Changes doppler control for data and code processing + + Parameters + ---------- + value : bool + True - ignore doppler for code and data processing, False - apply doppler. + ''' + self.codeDopplerIgnored = value + + def computeSignalDelayS(self, frequency_hz): + ''' + Computes delay in seconds for an epoch time (time 0) for a given carrier + frequency. + + The method computes signal delay, which is a sum of the following + parameters: + - Distance to object divided per speed of light + - Ionospheric delay according to TEC value for the given frequency + - Tropospheric delay + + Parameters + ---------- + frequency_hz : float + Signal frequency in hertz. + + Returns + ------- + float + Signal delay in seconds. + ''' + distanceDelay_s = self.distance0_m / scipy.constants.c + ionoDelay_s = 40.3 * self.tec_epm2 / numpy.square(frequency_hz) + delay_s = distanceDelay_s + ionoDelay_s + return delay_s + + def applySignalDelays(self, userTimeAll_s, carrierSignal): + ''' + Modifies time vector in accordance to signal delays due to distance and + atmospheric delays. + + Parameters + ---------- + userTimeAll_s : numpy.ndvector(shape=(n), dtype=numpy.float) + Vector of time stamps for which samples are generated + carrierSignal : object + Signal parameters object + + Returns + ------- + numpy.ndvector(shape=(n), dtype=numpy.float) + Vector of sample time stamps updated according to computed delays. + ''' + signalDelay_s = self.computeSignalDelayS(carrierSignal.CENTER_FREQUENCY_HZ) + return userTimeAll_s - signalDelay_s + + def computeDistanceM(self, svTime_s): + ''' + Computes doppler shift in meters. + + Parameters + ---------- + svTime_s : float + Time in seconds at which distance is computed. Please note that is not + a time of the observer. + + Returns + ------- + float + Distance to satellite in meters. + ''' + raise NotImplementedError() + + def computeSpeedMps(self, svTime_s): + ''' + Computes speed along the vecto2r to satellite in meters per second. + + Parameters + ---------- + svTime_s : float + Time in seconds at which speed is computed. Please note that is not + a time of the observer. + + Returns + ------- + float + Speed of satellite in meters per second. + ''' + raise NotImplementedError() + + def computeBatch(self, + userTimeAll_s, + amplitude, + carrierSignal, + ifFrequency_hz, + message, + code, + outputConfig, + debug): + ''' + Computes signal samples for the doppler object. + + Parameters + ---------- + userTimeAll_s : numpy.ndarray(dtype=numpy.float) + Sample timestamps in seconds + amplitude : float + Signal amplitude object. + carrierSignal : object + Carrier frequency object + ifFrequency_hz: float + Intermediate frequency in hertz + message : object + Message object for providing access to symbols + code : object + PRN code object for providing access to chips + debug : bool + Debug flag + + Returns + ------- + signal : numpy.ndarray(n_samples, dtype=float) + Generated samples + userTimeX_s : float + End of interval time in seconds + chipAll_idx : numpy.ndarray(n_samples, dtype=float) + Code chip phases for the samples + chips : numpy.ndarray(n_samples, dtype=int) + Code combined with data + ''' + + userTimeAll_s = self.applySignalDelays(userTimeAll_s, carrierSignal) + + # Computing doppler coefficients + twoPi = self.twoPi + + # Sine wave phase without doppler + phaseAll = userTimeAll_s * (twoPi * ifFrequency_hz) + # Get doppler shift in meters + doppler_m = self.computeDopplerShiftM(userTimeAll_s) + # Doppler for carrier center frequency + carrFreqRatio = -carrierSignal.CENTER_FREQUENCY_HZ / scipy.constants.c + phaseAll += doppler_m * (carrFreqRatio * twoPi) + + # Convert phase to signal value and multiply by amplitude + signal = scipy.cos(phaseAll) + + if amplitude: + amplitude.applyAmplitude(signal, userTimeAll_s) + + # PRN and data index computation + chipAll_idx = userTimeAll_s * carrierSignal.CODE_CHIP_RATE_HZ + if self.codeDopplerIgnored: + pass + else: + # Computing doppler coefficients + chipFreqRatio = -carrierSignal.CODE_CHIP_RATE_HZ / scipy.constants.c + chipAll_idx += doppler_m * chipFreqRatio + + chips = self.computeDataNChipVector(chipAll_idx, + carrierSignal, + message, + code) + + # Combine data and sine wave + signal *= chips + + # Generate debug data + doppler_hz = self.computeDopplerShiftHz(userTimeAll_s, + carrierSignal) if debug else None + return (signal, doppler_hz) + + @staticmethod + def computeDeltaUserTimeS(userTime0_s, n_samples, outputConfig): + ''' + Helper for computing generation interval duration in seconds. + + Parameters + ---------- + userTime0_s : float + Generation interval start + n_samples : int + Number of samples in the generation interval + outputConfig : object + Output configuration. + + Returns + ------- + float + Generation interval duration in seconds + ''' + deltaUserTime_s = float(n_samples) / outputConfig.SAMPLE_RATE_HZ + return deltaUserTime_s + + @staticmethod + def computeDopplerHz(frequency_hz, speed_mps): + ''' + Generic method for doppler shift computation. + + Parameters + ---------- + frequency_hz : float + Frequency in hertz for which doppler is computed. + speed_mps : float + Speed in meters per second for which doppler is computed. + + Returns + ------- + float + Doppler shift value in hertz. + ''' + doppler_hz = -frequency_hz / scipy.constants.c * speed_mps + return doppler_hz + + def computeDataNChipVector(self, chipAll_idx, carrierSignal, message, code): + ''' + Helper for computing vector that combines data and code chips. + + Parameters + ---------- + chipAll_idx : ndarray + vector of chip phases + carrierSignal : object + Signal description object + messge : object + Data bits source + code : objects + Code chips source + + Returns + ------- + ndarray + Array of code chips multiplied with data bits + ''' + + chipAll_long = chipAll_idx.astype(numpy.long) + dataBits = message.getDataBits( + chipAll_long / carrierSignal.CHIP_TO_SYMBOL_DIVIDER) + result = code.combineData(chipAll_long, dataBits) + + return result + + def computeDopplerShiftM(self, userTimeAll_s): + ''' + Method to compute metric doppler shift + + Parameters + ---------- + userTimeAll_s : numpy.ndarray(shape=(1, nSamples), dtype=numpy.float) + Time vector for sample timestamps in seconds + + Returns + ------- + numpy.ndarray(shape=(1, nSamples), dtype=numpy.float) + Computed doppler shift in meters + ''' + raise NotImplementedError("Metric doppler computation is not implemented") + + def computeDopplerShiftHz(self, userTimeAll_s, carrierSignal): + ''' + Method to compute doppler shift in Hz. + + Parameters + ---------- + userTimeAll_s : numpy.ndarray(shape=(1, nSamples), dtype=numpy.float) + Time vector for sample timestamps in seconds + carrierSignal : object + Carrier signal parameters + + Returns + ------- + numpy.ndarray(shape=(1, nSamples), dtype=numpy.float) + Computed doppler frquency shift in hertz + ''' + raise NotImplementedError("Metric doppler computation is not implemented") diff --git a/peregrine/iqgen/bits/doppler_factory.py b/peregrine/iqgen/bits/doppler_factory.py new file mode 100644 index 0000000..6de2a35 --- /dev/null +++ b/peregrine/iqgen/bits/doppler_factory.py @@ -0,0 +1,83 @@ +# Copyright (C) 2016 Swift Navigation Inc. +# Contact: Valeri Atamaniouk +# +# 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. + +""" +The :mod:`peregrine.iqgen.bits.doppler_factory` module contains classes and +functions related to object factory for doppler control objects. + +""" + +from peregrine.iqgen.bits.doppler_poly import Doppler as PolyDoppler +from peregrine.iqgen.bits.doppler_sine import Doppler as SineDoppler + + +class ObjectFactory(object): + ''' + Object factory for doppler control objects. + ''' + + def __init__(self): + super(ObjectFactory, self).__init__() + + def toMapForm(self, obj): + t = type(obj) + if t is PolyDoppler: + return self.__PolyDoppler_ToMap(obj) + elif t is SineDoppler: + return self.__SineDoppler_ToMap(obj) + else: + raise ValueError("Invalid object type") + + def fromMapForm(self, data): + t = data['type'] + if t == 'PolyDoppler': + return self.__MapTo_PolyDoppler(data) + elif t == 'SineDoppler': + return self.__MapTo_SineDoppler(data) + else: + raise ValueError("Invalid object type") + + def __PolyDoppler_ToMap(self, obj): + data = {'type': 'PolyDoppler', + 'distance0_m': obj.distance0_m, + 'tec_epm2': obj.tec_epm2, + 'coeffs': obj.coeffs} + return data + + def __SineDoppler_ToMap(self, obj): + data = {'type': 'SineDoppler', + 'distance0_m': obj.distance0_m, + 'tec_epm2': obj.tec_epm2, + 'speed0_mps': obj.speed0_mps, + 'amplutude_mps': obj.amplutude_mps, + 'period_s': obj.period_s} + return data + + def __MapTo_PolyDoppler(self, data): + distance0_m = data['distance0_m'] + tec_epm2 = data['tec_epm2'] + coeffs = data['coeffs'] + return PolyDoppler(distance0_m=distance0_m, + tec_epm2=tec_epm2, + coeffs=coeffs) + + def __MapTo_SineDoppler(self, data): + distance0_m = data['distance0_m'] + tec_epm2 = data['tec_epm2'] + speed0_mps = data['speed0_mps'] + amplutude_mps = data['amplutude_mps'] + period_s = data['period_s'] + return SineDoppler(distance0_m=distance0_m, + tec_epm2=tec_epm2, + speed0_mps=speed0_mps, + amplutude_mps=amplutude_mps, + period_s=period_s) + +factoryObject = ObjectFactory() diff --git a/peregrine/iqgen/bits/doppler_poly.py b/peregrine/iqgen/bits/doppler_poly.py new file mode 100644 index 0000000..d897716 --- /dev/null +++ b/peregrine/iqgen/bits/doppler_poly.py @@ -0,0 +1,246 @@ +# Copyright (C) 2016 Swift Navigation Inc. +# Contact: Valeri Atamaniouk +# +# 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. + +""" +The :mod:`peregrine.iqgen.bits.oppler_poly` module contains classes and +functions related to generation of signals with polynomial-based movement. + +""" + +import numpy +import scipy.constants +from peregrine.iqgen.bits.doppler_base import DopplerBase + + +class Doppler(DopplerBase): + ''' + Doppler control for an object that has constant acceleration. Such signal has + constant doppler value with a possible sign invert. + ''' + + TWO_PI = scipy.constants.pi * 2 + + def __init__(self, distance0_m, tec_epm2, coeffs): + ''' + Constructs doppler control object for linear acceleration. + + Parameters + ---------- + distance0_m : float + Distance to object in meters at time 0. + tec_epm2 : float + Total free electron content integrated along line of sight to the object + in electrons per m^2. + coeffs : array-like + Phase shift coefficients. Phase chift will be computed as: + C_n*t^n + C_(n-1)^(n-1) + ... + C_2*t^2 + C_1*t + C_0 + C_n..C_0 - values for speed of light + ''' + super(Doppler, self).__init__(distance0_m=distance0_m, + tec_epm2=tec_epm2) + self.coeffs = tuple([x for x in coeffs]) + self.n_coeffs = len(coeffs) + self.speedPoly = None + self.distancePoly = None + if self.n_coeffs > 0: + new_coeffs = [] + self.n_coeffs += 1 + for idx, c in enumerate(coeffs): + order = self.n_coeffs - idx - 1 + new_coeffs.append(c / order) + new_coeffs.append(0.) + self.distancePoly = numpy.poly1d(new_coeffs) + self.distanceCoeffs = new_coeffs + if self.n_coeffs > 1: + self.speedPoly = numpy.poly1d(coeffs) + else: + self.distanceCoeffs = None + + def __str__(self): + ''' + Constructs literal presentation of object. + + Returns + ------- + string + Literal presentation of object + ''' + return "DopplerPoly(coeffs={}, distance0_m={}," \ + " tec_epm2={} codeDopplerIgnored={})". \ + format(self.coeffs, self.distance0_m, + self.tec_epm2, self.codeDopplerIgnored) + + def computeDistanceM(self, svTime_s): + ''' + Computes doppler shift in meters. + + Parameters + ---------- + svTime_s : float + Time in seconds at which distance is computed. Please note that is not + a time of the observer. + + Returns + ------- + float + Distance to satellite in meters. + ''' + poly = self.distancePoly + if poly is not None: + return poly(svTime_s) # self.coeffs[cnt - 1] + else: + return 0. + + def computeSpeedMps(self, svTime_s): + ''' + Computes speed along the vector to satellite in meters per second. + + Parameters + ---------- + svTime_s : float + Time in seconds at which speed is computed. Please note that is not + a time of the observer. + + Returns + ------- + float + Speed of satellite in meters per second. + ''' + poly = self.speedPoly + if poly is not None: + return poly(svTime_s) + else: + return 0. + + def computeDopplerShiftM(self, userTimeAll_s): + ''' + Method to compute metric doppler shift + + Parameters + ---------- + userTimeAll_s : numpy.ndarray(shape=(1, nSamples), dtype=numpy.float) + Time vector for sample timestamps in seconds + + Returns + ------- + numpy.ndarray(shape=(1, nSamples), dtype=numpy.float) + Computed doppler shift in meters + ''' + distancePoly = self.distancePoly + if distancePoly is not None: + # Slower, but simple + doppler_m = distancePoly(userTimeAll_s) + else: + # No phase shift + doppler_m = numpy.zeros_like(userTimeAll_s) + return doppler_m + + def computeDopplerShiftHz(self, userTimeAll_s, carrierSignal): + ''' + Method to compute doppler shift in Hz. + + Parameters + ---------- + userTimeAll_s : numpy.ndarray(shape=(1, nSamples), dtype=numpy.float) + Time vector for sample timestamps in seconds + carrierSignal : object + Carrier signal parameters + + Returns + ------- + numpy.ndarray(shape=(1, nSamples), dtype=numpy.float) + Computed doppler frquency shift in hertz + ''' + speedPoly = self.speedPoly + if speedPoly is not None: + # Slower, but simple + c0 = -carrierSignal.CENTER_FREQUENCY_HZ / scipy.constants.c + doppler_hz = speedPoly(userTimeAll_s) * c0 + else: + # No phase shift + doppler_hz = numpy.zeros_like(userTimeAll_s) + return doppler_hz + + +def linearDoppler(distance0_m, + tec_epm2, + frequency_hz, + doppler0_hz, + dopplerChange_hzps): + ''' + Makes an object that corresponds to linear doppler change. + + Parameters + ---------- + distance0_m : float + Initial distance to object. + doppler0_hz : float + Initial doppler shift in hz. + frequency_hz + Carrier frequency in Hz. + dopplerChange_hzps : float + Doppler shift rate in Hz per second. + + Returns + ------- + Doppler + object that implments constant acceleration logic. + ''' + speed0_mps = -scipy.constants.c / frequency_hz * doppler0_hz + accel_mps2 = -scipy.constants.c / frequency_hz * dopplerChange_hzps + + return Doppler(distance0_m=distance0_m, + tec_epm2=tec_epm2, + coeffs=(accel_mps2, speed0_mps)) + + +def constDoppler(distance0_m, tec_epm2, frequency_hz, doppler_hz): + ''' + Makes an object that corresponds to a constant doppler value. + + Parameters + ---------- + distance0_m : float + Initial distance to object. + frequency_hz : float + Carrier frequency in Hz. + doppler_hz : float + Doppler shift in Hz. + + Returns + ------- + Doppler + Object that implements constant speed logic. + ''' + speed_mps = -scipy.constants.c / frequency_hz * doppler_hz + return Doppler(distance0_m=distance0_m, tec_epm2=tec_epm2, coeffs=(speed_mps,)) + + +def zeroDoppler(distance_m, tec_epm2, frequency_hz): + ''' + Makes an object that corresponds to zero doppler change. + + Parameters + ---------- + distance0_m : float + Initial distance to object. + doppler0_hz : float + Initial doppler shift in hz. + frequency_hz + Carrier frequency in Hz. + dopplerChange_hzps : float + Doppler shift rate in Hz per second. + + Returns + ------- + Doppler + object that implments constant acceleration logic. + ''' + return Doppler(distance0_m=distance_m, tec_epm2=tec_epm2, coeffs=()) diff --git a/peregrine/iqgen/bits/doppler_sine.py b/peregrine/iqgen/bits/doppler_sine.py new file mode 100644 index 0000000..95d85e3 --- /dev/null +++ b/peregrine/iqgen/bits/doppler_sine.py @@ -0,0 +1,199 @@ +# Copyright (C) 2016 Swift Navigation Inc. +# Contact: Valeri Atamaniouk +# +# 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. +""" +The :mod:`peregrine.iqgen.bits.doppler_sine` module contains classes and +functions related to generation of signals with circular changing doppler. + +""" + +from peregrine.iqgen.bits.doppler_base import DopplerBase + +import scipy.constants +import numpy + + +class Doppler(DopplerBase): + ''' + Doppler control for an object that has peridic acceleration. + ''' + + TWO_PI = scipy.constants.pi * 2. + + def __init__(self, distance0_m, tec_epm2, speed0_mps, amplutude_mps, period_s): + ''' + Constructs doppler control object for linear acceleration. + + Parameters + ---------- + distance0_m : float + Distance to object in meters at time 0. + tec_epm2 : float + Total free electron content integrated along line of sight to the object + in electrons per m^2. + speed0_mps : float + Speed of satellite at time 0 in meters per second. + amplutude_mps : float + Amplitude of change + period_s : float + Period of change + ''' + super(Doppler, self).__init__(distance0_m=distance0_m, + tec_epm2=tec_epm2) + self.speed0_mps = speed0_mps + self.amplutude_mps = amplutude_mps + self.period_s = period_s + + def __str__(self): + ''' + Constructs literal presentation of object. + + Returns + ------- + string + Literal presentation of object + ''' + return "SineDoppler(distance0_m={}, tec_epm2={}," \ + " speed0_mps={}, amplitude_mps={}, period_s={}," \ + " codeDopplerIgnored={})".\ + format(self.distance0_m, self.tec_epm2, self.speed0_mps, + self.amplutude_mps, self.period_s, self.codeDopplerIgnored) + + def __repr__(self): + ''' + Constructs python expression presentation of object. + + Returns + ------- + string + Python expression presentation of object + ''' + return "Doppler({}, {}, {}, {}, {})".format(self.distance0_m, + self.tec_epm2, + self.speed0_mps, + self.amplutude_mps, + self.period_s) + + def computeDistanceM(self, svTime_s): + ''' + Computes doppler shift in meters. + + Parameters + ---------- + svTime_s : float + Time in seconds at which distance is computed. Please note that is not + a time of the observer. + + Returns + ------- + float + Distance to satellite in meters. + ''' + return self.distance0_m + self.speed0_mps * svTime_s + \ + self.amplutude_mps * \ + (1 - numpy.cos(Doppler.TWO_PI * svTime_s / self.period_s)) + + def computeSpeedMps(self, svTime_s): + ''' + Computes speed along the vector to satellite in meters per second. + + Parameters + ---------- + svTime_s : float + Time in seconds at which speed is computed. Please note that is not + a time of the observer. + + Returns + ------- + float + Speed of satellite in meters per second. + ''' + return self.speed0_mps + self.amplutude_mps * \ + numpy.sin(Doppler.TWO_PI * svTime_s / self.period_s) + + def computeDopplerShiftM(self, userTimeAll_s): + ''' + Method to compute metric doppler shift + + Parameters + ---------- + userTimeAll_s : numpy.ndarray(shape=(1, nSamples), dtype=numpy.float) + Time vector for sample timestamps in seconds + + Returns + ------- + numpy.ndarray(shape=(1, nSamples), dtype=numpy.float) + Computed doppler shift in meters + ''' + D_0 = self.speed0_mps + D_1 = self.amplutude_mps * self.period_s / self.twoPi + D_2 = self.twoPi / self.period_s + + doppler_m = numpy.cos(D_2 * userTimeAll_s) + doppler_m -= 1. + doppler_m *= -D_1 + if D_0: + doppler_m += D_0 * userTimeAll_s + + return doppler_m + + def computeDopplerShiftHz(self, userTimeAll_s, carrierSignal): + ''' + Method to compute doppler shift in Hz. + + Parameters + ---------- + userTimeAll_s : numpy.ndarray(shape=(1, nSamples), dtype=numpy.float) + Time vector for sample timestamps in seconds + carrierSignal : object + Carrier signal parameters + + Returns + ------- + numpy.ndarray(shape=(1, nSamples), dtype=numpy.float) + Computed doppler frquency shift in hertz + ''' + D_0 = self.speed0_mps + D_1 = self.amplutude_mps + D_2 = self.twoPi / self.period_s + + doppler_hz = numpy.sin(D_2 * userTimeAll_s) * D_1 + if D_0: + doppler_hz += D_0 + doppler_hz *= -carrierSignal.CENTER_FREQUENCY_HZ / scipy.constants.c + return doppler_hz + + +def sineDoppler(distance0_m, tec_epm2, frequency_hz, doppler0_hz, dopplerAmplitude_hz, dopplerPeriod_s): + ''' + Makes an object that corresponds to linear doppler change. + + Parameters + ---------- + distance0_m : float + Initial distance to object. + frequency_hz + Carrier frequency in Hz. + doppler0_hz : float + Initial doppler shift in hz. + dopplerAmplitude_hz : float + Doppler change amplitude in Hz + dopplerPeriod_s : float + Doppler change period in seconds + + Returns + ------- + Doppler + object that implments constant acceleration logic. + ''' + dopplerCoeff = -scipy.constants.c / frequency_hz + speed0_mps = dopplerCoeff * doppler0_hz + amplitude_mps = dopplerCoeff * dopplerAmplitude_hz + + return Doppler(distance0_m, tec_epm2, speed0_mps, amplitude_mps, dopplerPeriod_s) diff --git a/peregrine/iqgen/bits/encoder_1bit.py b/peregrine/iqgen/bits/encoder_1bit.py new file mode 100644 index 0000000..2690bcc --- /dev/null +++ b/peregrine/iqgen/bits/encoder_1bit.py @@ -0,0 +1,82 @@ +# Copyright (C) 2016 Swift Navigation Inc. +# Contact: Valeri Atamaniouk +# +# 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. + +""" +The :mod:`peregrine.iqgen.bits.encoder_1bit` module contains classes and +functions related to generating single bit signal output. + +""" + +from peregrine.iqgen.bits.encoder_base import Encoder + + +class BandBitEncoder(Encoder): + ''' + Base class for single bit encoding. + ''' + + def __init__(self, bandIndex): + ''' + Initializes encoder object. + + Parameters + ---------- + bandIndex : int + Index of the band in the generated sample matrix. + ''' + super(BandBitEncoder, self).__init__() + self.bandIndex = bandIndex + + def addSamples(self, sample_array): + ''' + Extracts samples of the supported band and coverts them into bit stream. + + Parameters + ---------- + sample_array : numpy.ndarray((4, N)) + Sample vectors ordered by band index. + + Returns + ------- + numpy.ndarray((N), dtype=numpy.uint8) + Array of type uint8 containing the encoded data. + ''' + band_samples = sample_array[self.bandIndex] + n_samples = len(band_samples) + + self.ensureExtraCapacity(n_samples) + start = self.n_bits + end = start + n_samples + self.bits[start:end] = BandBitEncoder.convertBand(band_samples) + self.n_bits = end + + if (self.n_bits >= Encoder.BLOCK_SIZE): + return self.encodeValues() + + return Encoder.EMPTY_RESULT + + @staticmethod + def convertBand(band_samples): + ''' + Helper method for converting sampled signal band into output bits. + + The samples are compared to 0. Positive values yield value of False. + + Parameters + ---------- + band_samples : numpy.ndarray((N)) + Vector of signal samples + + Returns + ------- + signs : numpy.ndarray((N), dtype=numpy.bool) + Boolean vector of sample signs + ''' + return band_samples < 0 diff --git a/peregrine/iqgen/bits/encoder_2bits.py b/peregrine/iqgen/bits/encoder_2bits.py new file mode 100644 index 0000000..4fad87f --- /dev/null +++ b/peregrine/iqgen/bits/encoder_2bits.py @@ -0,0 +1,123 @@ +# Copyright (C) 2016 Swift Navigation Inc. +# Contact: Valeri Atamaniouk +# +# 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. + +""" +The :mod:`peregrine.iqgen.bits.encoder_2bits` module contains classes and +functions related to generating two bits signal output. + +""" + +import numpy + +from peregrine.iqgen.bits.encoder_base import Encoder + + +class BandTwoBitsEncoder(Encoder): + ''' + Base class for two bits encoding. + ''' + + def __init__(self, bandIndex): + ''' + Initializes encoder object. + + Parameters + ---------- + bandIndex : int + Index of the band in the generated sample matrix. + ''' + super(BandTwoBitsEncoder, self).__init__() + self.bandIndex = bandIndex + + @staticmethod + def convertBand(band_samples): + ''' + Helper method for converting sampled signal band into output bits. + + For the sign, the samples are compared to 0. Positive values yield sign of + True. + + The method builds a power histogram from signal samples. After a histogram + is built, the 67% power boundary is located. All samples, whose power is + lower, than the boundary, are reported as False. + + Parameters + ---------- + band_samples : numpy.ndarray + Vector of signal samples + + Returns + ------- + signs : numpy.ndarray(dtype=numpy.bool) + Boolean vector of sample signs: True for positive, False for negative + amps : numpy.ndarray(dtype=numpy.bool) + Boolean vector of sample power: True for high power, False for low power + ''' + + # Signal power is a square of the amplitude + power = numpy.square(band_samples) + totalPower = numpy.sum(power) + totalPowerLimit = totalPower * 0.67 + + # Build histrogram to find 67% power + hist, edges = numpy.histogram(power, + bins=10, + density=True) + lastPower = 0. + powerLimit = 0. + for i in range(10): + # Approximate power of samples in the bin + entryPower = hist[i] * (edges[i] + edges[i + 1]) / 2. + newPower = lastPower + entryPower + if newPower > totalPowerLimit: + powerLimit = edges[i] + break + else: + lastPower = newPower + + # Signal sign + signs = band_samples > 0 + amps = power >= powerLimit + + return signs, amps + + def addSamples(self, sample_array): + ''' + Extracts samples of the supported band and coverts them into bit stream. + + Parameters + ---------- + sample_array : numpy.ndarray((4, N)) + Sample vectors ordered by band index. + + Returns + ------- + numpy.ndarray(dtype=numpy.uint8) + Array of type uint8 containing the encoded data. + ''' + band_samples = sample_array[self.bandIndex] + n_samples = len(band_samples) + + # Signal signs and amplitude + signs, amps = self.convertBand(band_samples) + + self.ensureExtraCapacity(n_samples * 2) + + bits = self.bits + start = self.n_bits + end = start + n_samples * 2 + bits[start + 0:end:2] = signs + bits[start + 1:end:2] = amps + self.n_bits = end + + if (self.n_bits >= Encoder.BLOCK_SIZE): + return self.encodeValues() + else: + return Encoder.EMPTY_RESULT diff --git a/peregrine/iqgen/bits/encoder_base.py b/peregrine/iqgen/bits/encoder_base.py new file mode 100644 index 0000000..ee6b134 --- /dev/null +++ b/peregrine/iqgen/bits/encoder_base.py @@ -0,0 +1,105 @@ +# Copyright (C) 2016 Swift Navigation Inc. +# Contact: Valeri Atamaniouk +# +# 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. + +""" +The :mod:`peregrine.iqgen.bits.encoder_base` module contains classes and +functions related to generating signal output. + +""" + +import numpy + + +class Encoder(object): + ''' + Base encode class. + + Encoder accepts sequence of signal arrays as input and produces byte arrays + as output. + ''' + # Block size for encode. Must be a multiple of 8. + BLOCK_SIZE = 1024 * 8 + + EMPTY_RESULT = numpy.ndarray(0, dtype=numpy.uint8) # Internal empty array + + def __init__(self, bufferSize=1000): + ''' + Constructs encoder. + + Parameters + ---------- + bufferSize : int, optional + Size of the internal buffer to batch-process samples + ''' + self.bits = numpy.ndarray(bufferSize, dtype=numpy.int8) + self.n_bits = 0 + + def addSamples(self, sample_array): + ''' + Extracts samples of the supported band and coverts them into bit stream. + + Parameters + ---------- + sample_array : numpy.ndarray((4, N)) + Sample vectors ordered by band index. + + Returns + ------- + numpy.ndarray(dtype=numpy.uint8) + Array of type uint8 containing the encoded data. + ''' + return Encoder.EMPTY_RESULT + + def flush(self): + ''' + Flushes the data in the buffer. + + Returns + ------- + ndarray + Array of type uint8 containing the encoded data. + ''' + if self.n_bits > 0 and self.n_bits % 8 != 0: + self.bits += (8 - self.n_bits % 8) + res = numpy.packbits(self.bits[0:self.n_bits]) + self.n_bits = 0 + return res + + def encodeValues(self): + ''' + Converts buffered bit data into packed array. + + The method coverts multiple of 8 bits into single output byte. + + Returns + ------- + ndarray + Array of type uint8 containing the encoded data. + ''' + n_bytes = self.n_bits / 8 + n_offset = n_bytes * 8 + n_left = self.n_bits - n_offset + res = numpy.packbits(self.bits[0: n_offset]) + self.bits[0:n_left] = self.bits[n_offset:n_offset + n_left] + self.n_bits = n_left + return res + + def ensureExtraCapacity(self, extraBits): + ''' + Method verifies that current array has sufficient capacity to hold + additional bits. + + Parameters + ---------- + extraBits : int + Number of extra bits to reserve space for + ''' + if len(self.bits) < self.n_bits + extraBits: + self.bits.resize(self.n_bits + extraBits) diff --git a/peregrine/iqgen/bits/encoder_factory.py b/peregrine/iqgen/bits/encoder_factory.py new file mode 100644 index 0000000..ab73321 --- /dev/null +++ b/peregrine/iqgen/bits/encoder_factory.py @@ -0,0 +1,109 @@ +# Copyright (C) 2016 Swift Navigation Inc. +# Contact: Valeri Atamaniouk +# +# 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. + +""" +The :mod:`peregrine.iqgen.bits.encoder_factory` module contains classes and +functions related to object factory for output encoder objects. + +""" + +from peregrine.iqgen.bits.encoder_gps import GPSL1BitEncoder +from peregrine.iqgen.bits.encoder_gps import GPSL2BitEncoder +from peregrine.iqgen.bits.encoder_gps import GPSL1L2BitEncoder +from peregrine.iqgen.bits.encoder_gps import GPSL1TwoBitsEncoder +from peregrine.iqgen.bits.encoder_gps import GPSL2TwoBitsEncoder +from peregrine.iqgen.bits.encoder_gps import GPSL1L2TwoBitsEncoder + + +class ObjectFactory(object): + ''' + Object factory for encoder objects. + ''' + + def __init__(self): + super(ObjectFactory, self).__init__() + + def toMapForm(self, obj): + t = type(obj) + if t is GPSL1BitEncoder: + return self.__GPSL1BitEncoder_ToMap(obj) + elif t is GPSL2BitEncoder: + return self.__GPSL2BitEncoder_ToMap(obj) + elif t is GPSL1L2BitEncoder: + return self.__GPSL1L2BitEncoder_ToMap(obj) + elif t is GPSL1TwoBitsEncoder: + return self.__GPSL1TwoBitsEncoder_ToMap(obj) + elif t is GPSL2TwoBitsEncoder: + return self.__GPSL2TwoBitsEncoder_ToMap(obj) + elif t is GPSL1L2TwoBitsEncoder: + return self.__GPSL1L2TwoBitsEncoder_ToMap(obj) + else: + raise ValueError("Invalid object type") + + def fromMapForm(self, data): + t = data['type'] + if t == 'GPSL1BitEncoder': + return self.__MapTo_GPSL1BitEncoder(data) + elif t == 'GPSL2BitEncoder': + return self.__MapTo_GPSL2BitEncoder(data) + elif t == 'GPSL2BitEncoder': + return self.__MapTo_GPSL1L2BitEncoder(data) + elif t == 'GPSL1TwoBitsEncoder': + return self.__MapTo_GPSL1TwoBitsEncoder(data) + elif t == 'GPSL2TwoBitsEncoder': + return self.__MapTo_GPSL2TwoBitsEncoder(data) + elif t == 'GPSL1L2TwoBitsEncoder': + return self.__MapTo_GPSL1L2TwoBitsEncoder(data) + else: + raise ValueError("Invalid object type") + + def __GPSL1BitEncoder_ToMap(self, obj): + data = {'type': 'GPSL1BitEncoder'} + return data + + def __GPSL2BitEncoder_ToMap(self, obj): + data = {'type': 'GPSL2BitEncoder'} + return data + + def __GPSL1L2BitEncoder_ToMap(self, obj): + data = {'type': 'GPSL1L2BitEncoder'} + return data + + def __GPSL1TwoBitsEncoder_ToMap(self, obj): + data = {'type': 'GPSL1TwoBitsEncoder'} + return data + + def __GPSL2TwoBitsEncoder_ToMap(self, obj): + data = {'type': 'GPSL2TwoBitsEncoder'} + return data + + def __GPSL1L2TwoBitsEncoder_ToMap(self, obj): + data = {'type': 'GPSL1L2TwoBitsEncoder'} + return data + + def __MapTo_GPSL1BitEncoder(self, data): + return GPSL1BitEncoder() + + def __MapTo_GPSL2BitEncoder(self, data): + return GPSL2BitEncoder() + + def __MapTo_GPSL1L2BitEncoder(self, data): + return GPSL1L2BitEncoder() + + def __MapTo_GPSL1TwoBitsEncoder(self, data): + return GPSL1TwoBitsEncoder() + + def __MapTo_GPSL2TwoBitsEncoder(self, data): + return GPSL2TwoBitsEncoder() + + def __MapTo_GPSL1L2TwoBitsEncoder(self, data): + return GPSL1L2TwoBitsEncoder() + +factoryObject = ObjectFactory() diff --git a/peregrine/iqgen/bits/encoder_gps.py b/peregrine/iqgen/bits/encoder_gps.py new file mode 100644 index 0000000..99b46e1 --- /dev/null +++ b/peregrine/iqgen/bits/encoder_gps.py @@ -0,0 +1,194 @@ +# Copyright (C) 2016 Swift Navigation Inc. +# Contact: Valeri Atamaniouk +# +# 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. + +""" +The :mod:`peregrine.iqgen.bits.encoder_gps` module contains classes and +functions related to generating GPS signal output. + +""" + +from peregrine.iqgen.bits.encoder_base import Encoder +from peregrine.iqgen.bits.encoder_1bit import BandBitEncoder +from peregrine.iqgen.bits.encoder_2bits import BandTwoBitsEncoder + + +class GPSL1BitEncoder(BandBitEncoder): + ''' + Generic single bit encoder for GPS L1 C/A signal + ''' + + def __init__(self, outputConfig): + ''' + Constructs GPS L1 C/A band single bit encoder object. + + Parameters + ---------- + outputConfig : object + Output parameters object. + ''' + super(GPSL1BitEncoder, self).__init__(outputConfig.GPS.L1.INDEX) + + +class GPSL2BitEncoder(BandBitEncoder): + ''' + Generic single bit encoder for GPS L2 Civil signal + ''' + + def __init__(self, outputConfig): + ''' + Constructs GPS L2 C band single bit encoder object. + + Parameters + ---------- + outputConfig : object + Output parameters object. + ''' + super(GPSL2BitEncoder, self).__init__(outputConfig.GPS.L2.INDEX) + + +class GPSL1L2BitEncoder(Encoder): + ''' + Generic single bit encoder for GPS L1 C/A and L2 Civil signals + ''' + + def __init__(self, outputConfig): + ''' + Constructs GPS L1 C/A and L2 C dual band single bit encoder object. + + Parameters + ---------- + outputConfig : object + Output parameters object. + ''' + super(GPSL1L2BitEncoder, self).__init__() + self.l1Index = outputConfig.GPS.L1.INDEX + self.l2Index = outputConfig.GPS.L2.INDEX + + def addSamples(self, sample_array): + ''' + Extracts samples of the supported band and coverts them into bit stream. + + Parameters + ---------- + sample_array : numpy.ndarray((4, N)) + Sample vectors ordered by band index. + + Returns + ------- + numpy.ndarray(dtype=numpy.uint8) + Array of type uint8 containing the encoded data. + ''' + band1_bits = BandBitEncoder.convertBand(sample_array[self.l1Index]) + band2_bits = BandBitEncoder.convertBand(sample_array[self.l2Index]) + n_samples = len(band1_bits) + + self.ensureExtraCapacity(n_samples * 2) + start = self.n_bits + end = start + 2 * n_samples + + self.bits[start + 0:end:2] = band1_bits + self.bits[start + 1:end:2] = band2_bits + self.n_bits = end + + if (self.n_bits >= Encoder.BLOCK_SIZE): + return self.encodeValues() + else: + return Encoder.EMPTY_RESULT + + +class GPSL1TwoBitsEncoder(BandTwoBitsEncoder): + ''' + Generic single bit encoder for GPS L1 C/A signal + ''' + + def __init__(self, outputConfig): + ''' + Constructs GPS L1 C/A band single bit encoder object. + + Parameters + ---------- + outputConfig : object + Output parameters object. + ''' + super(GPSL1TwoBitsEncoder, self).__init__(outputConfig.GPS.L1.INDEX) + + +class GPSL2TwoBitsEncoder(BandTwoBitsEncoder): + ''' + Generic single bit encoder for GPS L2 Civil signal + ''' + + def __init__(self, outputConfig): + ''' + Constructs GPS L2 C band single bit encoder object. + + Parameters + ---------- + outputConfig : object + Output parameters object. + ''' + super(GPSL2TwoBitsEncoder, self).__init__(outputConfig.GPS.L2.INDEX) + + +class GPSL1L2TwoBitsEncoder(Encoder): + ''' + Generic single bit encoder for GPS L1 C/A and L2 Civil signals + ''' + + def __init__(self, outputConfig): + ''' + Constructs GPS L1 C/A and L2 C dual band single bit encoder object. + + Parameters + ---------- + outputConfig : object + Output parameters object. + ''' + super(GPSL1L2TwoBitsEncoder, self).__init__() + self.l1Index = outputConfig.GPS.L1.INDEX + self.l2Index = outputConfig.GPS.L2.INDEX + + def addSamples(self, sample_array): + ''' + Extracts samples of the supported band and coverts them into bit stream. + + Parameters + ---------- + sample_array : numpy.ndarray((4, N)) + Sample vectors ordered by band index. + + Returns + ------- + numpy.ndarray(dtype=numpy.uint8) + Array of type uint8 containing the encoded data. + ''' + band1_samples = sample_array[self.l1Index] + band2_samples = sample_array[self.l2Index] + n_samples = len(band1_samples) + + # Signal signs and amplitude + signs1, amps1 = BandTwoBitsEncoder.convertBand(band1_samples) + signs2, amps2 = BandTwoBitsEncoder.convertBand(band2_samples) + + self.ensureExtraCapacity(n_samples * 4) + + bits = self.bits + start = self.n_bits + end = start + 4 * n_samples + bits[start + 0:end:4] = signs1 + bits[start + 1:end:4] = amps1 + bits[start + 2:end:4] = signs2 + bits[start + 3:end:4] = amps2 + self.n_bits = end + + if (self.n_bits >= Encoder.BLOCK_SIZE): + return self.encodeValues() + else: + return Encoder.EMPTY_RESULT diff --git a/peregrine/iqgen/bits/filter_bandpass.py b/peregrine/iqgen/bits/filter_bandpass.py new file mode 100644 index 0000000..753f6b8 --- /dev/null +++ b/peregrine/iqgen/bits/filter_bandpass.py @@ -0,0 +1,70 @@ +# Copyright (C) 2016 Swift Navigation Inc. +# Contact: Valeri Atamaniouk +# +# 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. + +""" +The :mod:`peregrine.iqgen.bits.low_pass_filter` module contains classes and +functions related to generated signal attenuation. + +""" + +from scipy.signal.signaltools import lfiltic +from scipy.signal import cheby2, cheb2ord +from peregrine.iqgen.bits.filter_base import FilterBase + + +class BandPassFilter(FilterBase): + ''' + Chebyshev type 2 band-pass filter. + ''' + + def __init__(self, outputConfig, frequency_hz, bw_hz=1e6): + ''' + Initialize filter object. + + Parameters + ---------- + outputConfig : object + Output configuration parameters object + frequency_hz : float + Intermediate frequency in hertz + bw_hz : float, optional + Noise bandwidth in hertz + ''' + super(BandPassFilter, self).__init__(3., 40.) + + self.bw_hz = bw_hz + self.frequency_hz = frequency_hz + passBand_hz = bw_hz * 0.5 / outputConfig.SAMPLE_RATE_HZ + stopBand_hz = bw_hz * 0.6 / outputConfig.SAMPLE_RATE_HZ + mult = 2. / outputConfig.SAMPLE_RATE_HZ + order, wn = cheb2ord(wp=[(frequency_hz - passBand_hz) * mult, + (frequency_hz + passBand_hz) * mult], + ws=[(frequency_hz - stopBand_hz) * mult, + (frequency_hz + stopBand_hz) * mult], + gpass=self.passBandAtt_dbhz, + gstop=self.stopBandAtt_dbhz, + analog=False) + + b, a = cheby2(order, # Order of the filter + # Minimum attenuation required in the stop band in dB + self.stopBandAtt_dbhz, + wn, + btype="bandpass", + analog=False, + output='ba') + + self.a = a + self.b = b + self.zi = lfiltic(self.b, self.a, []) + + def __str__(self, *args, **kwargs): + return "BandPassFilter(center=%f, bw=%f, pb=%f, sp=%f)" % \ + (self.frequency_hz, self.bw_hz, + self.passBandAtt_dbhz, self.stopBandAtt_dbhz) diff --git a/peregrine/iqgen/bits/filter_base.py b/peregrine/iqgen/bits/filter_base.py new file mode 100644 index 0000000..179ddd4 --- /dev/null +++ b/peregrine/iqgen/bits/filter_base.py @@ -0,0 +1,71 @@ +# Copyright (C) 2016 Swift Navigation Inc. +# Contact: Valeri Atamaniouk +# +# 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. + +""" +The :mod:`peregrine.iqgen.bits.filter_base` module contains classes and +functions related to generated signal attenuation. + +""" + +from scipy.signal import lfilter + + +class FilterBase(object): + + def __init__(self, passBandAtt_dbhz, stopBandAtt_dbhz): + ''' + Parameters + ---------- + passBandAtt_dbhz : float + Pass band attenutation in dB*Hz + stopBandAtt_dbhz: float + Stop band attenutation in dB*Hz + ''' + self.passBandAtt_dbhz = passBandAtt_dbhz + self.stopBandAtt_dbhz = stopBandAtt_dbhz + self.a = None + self.b = None + self.zi = None + + def getPassBandAtt(self): + ''' + Returns + ------- + float + Pass band attenuation in dB*Hz. + ''' + return self.passBandAtt_dbhz + + def getStopBandAtt(self): + ''' + Returns + ------- + float + Pass band attenuation in dB*Hz. + ''' + return self.stopBandAtt_dbhz + + def filter(self, data): + ''' + Performs noise reduction using Chebyshev type 2 IIR filter. + + Parameters + ---------- + data : array-like + Data samples before LPF processing + + Returns + ------- + array-like + Data samples after LPF processing + ''' + data_out, zo = lfilter(self.b, self.a, data, zi=self.zi) + self.zi = zo + return data_out diff --git a/peregrine/iqgen/bits/filter_lowpass.py b/peregrine/iqgen/bits/filter_lowpass.py new file mode 100644 index 0000000..37c6f5d --- /dev/null +++ b/peregrine/iqgen/bits/filter_lowpass.py @@ -0,0 +1,81 @@ +# Copyright (C) 2016 Swift Navigation Inc. +# Contact: Valeri Atamaniouk +# +# 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. + +""" +The :mod:`peregrine.iqgen.bits.filter_lowpass` module contains classes and +functions related to generated signal attenuation. + +""" + +from scipy.signal.signaltools import lfiltic +from scipy.signal import cheby2, cheb2ord + +import logging + +from peregrine.iqgen.bits.filter_base import FilterBase + +logger = logging.getLogger(__name__) + + +class LowPassFilter(FilterBase): + ''' + Chebyshev type 2 low-pass filter. + + The filter simulates receiver lowpass effect: + - ~2MHz lowpass at 5*1.023MHz sampling pkg load signal + - b, a = cheby2(5, 40, 3e6/sample_freq) + + For 5.115MHz the coefficents are as follows: + - b = [0.082680, 0.245072, 0.397168, 0.397168 , 0.245072, 0.082680] + - a = [1.0000000, -0.3474596, 0.7770501, -0.0737540, 0.0922819, 0.0017200] + + ''' + + def __init__(self, outputConfig, frequency_hz=0.): + ''' + Initialize filter object. + + Parameters + ---------- + outputConfig : object + Output configuration parameters object + frequency_hz : float + Intermediate frequency + ''' + super(LowPassFilter, self).__init__(3., 40.) + + self.bw_hz = 1e3 + passBand_hz = self.bw_hz / outputConfig.SAMPLE_RATE_HZ + stopBand_hz = self.bw_hz * 1.1 / outputConfig.SAMPLE_RATE_HZ + mult = 2. / outputConfig.SAMPLE_RATE_HZ + order, wn = cheb2ord(wp=passBand_hz * mult, + ws=stopBand_hz * mult, + gpass=self.passBandAtt_dbhz, + gstop=self.stopBandAtt_dbhz, + analog=False) + self.order = order + self.wn = wn + + b, a = cheby2(order, # Order of the filter + # Minimum attenuation required in the stop band in dB + self.stopBandAtt_dbhz, + wn, + btype="lowpass", + analog=False, + output='ba') + + self.a = a + self.b = b + self.zi = lfiltic(self.b, self.a, []) + + def __str__(self, *args, **kwargs): + return "LowPassFilter(bw=%f, pb=%f, sp=%f, order=%d, wn=%s)" % \ + (self.bw_hz, self.passBandAtt_dbhz, + self.stopBandAtt_dbhz, self.order, str(self.wn)) diff --git a/peregrine/iqgen/bits/message_block.py b/peregrine/iqgen/bits/message_block.py new file mode 100644 index 0000000..425efff --- /dev/null +++ b/peregrine/iqgen/bits/message_block.py @@ -0,0 +1,85 @@ +# Copyright (C) 2016 Swift Navigation Inc. +# Contact: Valeri Atamaniouk +# +# 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. + +""" +The :mod:`peregrine.iqgen.bits.message_block` module contains classes and +functions related to providing predefined symbol contents. + +""" +import numpy + + +class Message(object): + ''' + Message that is a block of bits + ''' + + def __init__(self, messageData): + ''' + Constructs message object. + + Parameters + ---------- + messageData : array-like + Array with message bits. Bit 0 is encoded with 1, bit 1 is encoded with -1 + ''' + super(Message, self).__init__() + self.messageData = messageData[:] + self.messageLen = len(self.messageData) + tmp = numpy.asarray(self.messageData, dtype=numpy.uint8) + tmp *= -2 + tmp -= 1 + self.bits = tmp.astype(numpy.uint8) + + def __str__(self, *args, **kwargs): + ''' + Formats object as string literal + + Returns + ------- + string + String representation of the object + ''' + return "Block: length=%d" % len(self.bits) + + def getDataBits(self, dataAll_idx): + ''' + Generates vector of data bits corresponding to input index + + Parameters + ---------- + dataAll_idx : numpy.ndarray(dtype=numpy.int64) + Vector of bit indexes + + Returns + ------- + numpy.ndarray(dtype=numpy.uint8) + Vector of data bits + ''' + # numpy.take degrades performance a lot over time. + # return numpy.take(self.bits, dataAll_idx , mode='wrap') + return self.bits[dataAll_idx % self.messageLen] + + def getBit(self, bitIndex): + ''' + Provides bit at a given index + + Parameters + ---------- + bitIndex : long + Bit index + + Returns + ------- + int + Bit value: 1 for bit 0 and -1 for bit 1 + ''' + + return self.messageData[bitIndex % self.messageLen] diff --git a/peregrine/iqgen/bits/message_cnav.py b/peregrine/iqgen/bits/message_cnav.py new file mode 100644 index 0000000..c280500 --- /dev/null +++ b/peregrine/iqgen/bits/message_cnav.py @@ -0,0 +1,222 @@ +# Copyright (C) 2016 Swift Navigation Inc. +# Contact: Valeri Atamaniouk +# +# 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. + +''' +The :mod:`peregrine.iqgen.bits.message_cnav` module contains classes and +functions related to generating stub GPS CNAV messages. +''' + +import numpy +from swiftnav.cnav_msg import CNavRawMsg +import logging +G1 = 0171 # generator polinomial for p1 +G2 = 0133 # generator polinomial for p2 + +logger = logging.getLogger(__name__) + + +def generate27Vector(g1, g2): + ''' + Helper method for convolution encoder lookup table generation. + + Parameters + ---------- + g1 : int + First polynomial coefficient + g2 : int + Second polynomial coefficient + + Results + ------- + numpy.ndvector(shape=(128,2),dtype=numpy.uint8) + Lookup matrix for convolution encoder + ''' + + def parity6(value): + ''' + Helper for computing parity of 6-bit value. + + Parameters + ---------- + value : int + 6-bit integer value + + Results + ------- + int + Parity bit: 0 or 1. + ''' + return (0x6996 >> ((value ^ (value >> 4)) & 15)) & 1 + + vectorG = numpy.ndarray((128, 2), dtype=numpy.uint8) + for i in range(128): + vectorG[i][0] = parity6(i & g1) + vectorG[i][1] = parity6(i & g2) + + return vectorG + + +class ConvEncoder27(object): + ''' + Convolution encoder class. + + Standard 2-7 convolution encoder implementation. + ''' + + DEFAULT_VECTOR_G = generate27Vector(G1, G2) + + def __init__(self, g1=G1, g2=G2, state=0): + self.g1 = g1 + self.g2 = g2 + self.state = state + vectorG = ConvEncoder27.DEFAULT_VECTOR_G if g1 == G1 and g2 == G2 \ + else generate27Vector(g1, g2) + self.vectorG = vectorG + + def encode(self, bitArray): + ''' + Encodes source bit array. + + This method updates the encoder state during processing. + + Parameters + ---------- + bitArray : array-like + Array of bit values. Can be integers or booleans. + Returns + ------- + numpy.ndarray(shape(len(bitArray)), dtype=numpy.uint8) + Encoded output + ''' + result = numpy.ndarray((len(bitArray) * 2), dtype=numpy.uint8) + state = self.state + dstIndex = 0 + vectorG = self.vectorG + + for srcBit in bitArray: + state = (srcBit << 6) | (state >> 1) + result[dstIndex:dstIndex + 2] = vectorG[state] + dstIndex += 2 + + self.state = state + return result + + +class Message(object): + ''' + GPS LNAV message block. + + The object provides proper-formatted CNAV messages with random contents. + ''' + + def __init__(self, prn, tow0=2, n_msg=0, n_prefixBits=50): + ''' + Constructs message object. + + Parameters + ---------- + prn : int + Satellite PRN + tow0 : int + Time of week in 6-second units for the first message + n_msg : int, optional + Number of messages to generate for output + n_prefixBits : int, optional + Number of bits to issue before the first message + ''' + super(Message, self).__init__() + + if tow0 & 1: + logger.error("Initial ToW is not multiple of 2") + + self.prn = prn + self.tow0 = tow0 + self.n_msg0 = n_msg + self.n_prefixBits = n_prefixBits + + self.encoder = ConvEncoder27() + self.msgCount = 0 + self.messageLen = n_prefixBits * 2 + self.symbolData = numpy.zeros(self.messageLen, dtype=numpy.uint8) + + prefixBits = numpy.zeros(self.n_prefixBits, dtype=numpy.uint8) + prefixBits[0::2] = 1 + self.symbolData[:] = self.encoder.encode(prefixBits) + self.nextTow = tow0 + self.addMessages(n_msg) + + def __str__(self, *args, **kwargs): + ''' + Formats object as string literal + + Returns + ------- + string + String representation of the object + ''' + return "GPS CNAV: prn=%d pref=%d tow=%d" % \ + (self.prn, self.n_prefixBits, self.nextTow) + + def getDataBits(self, dataAll_idx): + ''' + Generates vector of data bits corresponding to input index + + Parameters + ---------- + dataAll_idx : numpy.ndarray(dtype=numpy.int64) + Vector of bit indexes + + Returns + ------- + numpy.ndarray(dtype=numpy.uint8) + Vector of data bits + ''' + + lastIdx = dataAll_idx[-1] + if lastIdx >= self.messageLen: + # Grow data bits + delta = lastIdx - self.messageLen + 1 + newMsgCount = delta / 600 + if delta % 600: + newMsgCount += 1 + self.addMessages(newMsgCount) + + # numpy.take degrades performance a lot over time. + # return numpy.take(self.symbolData, dataAll_idx , mode='wrap') + return self.symbolData[dataAll_idx] + + def addMessages(self, newMsgCount): + ''' + Generate additional CNAV messages + + This method generates and encodes additional CNAV messages. The message + contents is encoded using 2-7 convolution encoder and added to the internal + buffer. + + Parameters + ---------- + newMsgCount : int + Number of messages to generate + ''' + newMessageLen = newMsgCount * 600 + self.messageLen + newSymbolData = numpy.ndarray(newMessageLen, dtype=numpy.uint8) + newSymbolData[:self.messageLen] = self.symbolData + for i in range(self.messageLen, newMessageLen, 600): + logger.info("Generating CNAV message: prn=%d tow=%d msg_id=%d" % + (self.prn, self.nextTow, 0)) + cnav_msg = CNavRawMsg.generate(self.prn, 0, self.nextTow) + self.nextTow += 2 + if self.nextTow == 7 * 24 * 60 * 10: + self.nextTow = 0 + encoded = self.encoder.encode(cnav_msg) + newSymbolData[i:i + 600] = encoded + self.messageLen = newMessageLen + self.symbolData = newSymbolData + self.msgCount += newMsgCount diff --git a/peregrine/iqgen/bits/message_const.py b/peregrine/iqgen/bits/message_const.py new file mode 100644 index 0000000..dc3e135 --- /dev/null +++ b/peregrine/iqgen/bits/message_const.py @@ -0,0 +1,65 @@ +# Copyright (C) 2016 Swift Navigation Inc. +# Contact: Valeri Atamaniouk +# +# 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 + +""" +The :mod:`peregrine.iqgen.bits.message_const` module contains classes and +functions related to non-changing symbol contents. + +""" + + +class Message(object): + ''' + Message consisting of same bits + ''' + + def __init__(self, bitValue): + ''' + Initializes object. + + Parameters + ---------- + bitValue : int + Value for the bits. 1 for 0 bits, -1 for 1 bits. + ''' + super(Message, self).__init__() + self.bitValue = bitValue + self.binValue = 1 if bitValue < 0 else 0 + + def __str__(self, *args, **kwargs): + ''' + Formats object as string literal + + Returns + ------- + string + String representation of the object + ''' + return "Const: bit value=%d" % self.binValue + + def getDataBits(self, dataAll_idx): + ''' + Generates vector of data bits corresponding to input index + + Parameters + ---------- + dataAll_idx : numpy.ndarray(dtype=numpy.int64) + Vector of bit indexes + + Returns + ------- + numpy.ndarray(dtype=numpy.uint8) + Vector of data bits + ''' + result = numpy.ndarray(len(dataAll_idx), dtype=numpy.uint8) + result.fill(self.binValue) + return result diff --git a/peregrine/iqgen/bits/message_factory.py b/peregrine/iqgen/bits/message_factory.py new file mode 100644 index 0000000..67c5034 --- /dev/null +++ b/peregrine/iqgen/bits/message_factory.py @@ -0,0 +1,124 @@ +# Copyright (C) 2016 Swift Navigation Inc. +# Contact: Valeri Atamaniouk +# +# 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. + + +""" +The :mod:`peregrine.iqgen.bits.messager_factory` module contains classes and +functions related to object factory for message objects. + +""" + +from peregrine.iqgen.bits.message_block import Message as BlockMessage +from peregrine.iqgen.bits.message_cnav import Message as CNAVMessage +from peregrine.iqgen.bits.message_const import Message as ConstMessage +from peregrine.iqgen.bits.message_lnav import Message as LNAVMessage +from peregrine.iqgen.bits.message_zeroone import Message as ZeroOneMessage + + +class ObjectFactory(object): + ''' + Object factory for message objects. + ''' + + def __init__(self): + super(ObjectFactory, self).__init__() + + def toMapForm(self, obj): + t = type(obj) + if t is BlockMessage: + return self.__BlockMessage_ToMap(obj) + elif t is CNAVMessage: + return self.__CNAVMessage_ToMap(obj) + elif t is ConstMessage: + return self.__ConstMessage_ToMap(obj) + elif t is LNAVMessage: + return self.__LNAVMessage_ToMap(obj) + elif t is ZeroOneMessage: + return self.__ZeroOneMessage_ToMap(obj) + else: + raise ValueError("Invalid object type") + + def fromMapForm(self, data): + t = data['type'] + if t == 'BlockMessage': + return self.__MapTo_BlockMessage(data) + elif t == 'CNAVMessage': + return self.__MapTo_CNAVMessage(data) + elif t == 'ConstMessage': + return self.__MapTo_ConstMessage(data) + elif t == 'LNAVMessage': + return self.__MapTo_LNAVMessage(data) + elif t == 'ZeroOneMessage': + return self.__MapTo_ZeroOneMessage(data) + else: + raise ValueError("Invalid object type") + + def __BlockMessage_ToMap(self, obj): + data = {'type': 'BlockMessage', + 'data': obj.messageData} + return data + + def __CNAVMessage_ToMap(self, obj): + data = {'type': 'CNAVMessage', + 'prn': obj.prn, + 'n_prefixBits': obj.n_prefixBits, + 'n_msg0': obj.n_msg0, + 'tow0': obj.tow0} + return data + + def __ConstMessage_ToMap(self, obj): + data = {'type': 'ConstMessage', + 'bitValue': obj.bitValue} + return data + + def __LNAVMessage_ToMap(self, obj): + data = {'type': 'LNAVMessage', + 'prn': obj.prn, + 'n_prefixBits': obj.n_prefixBits, + 'n_msg0': obj.n_msg0, + 'tow0': obj.tow0} + return data + + def __ZeroOneMessage_ToMap(self, obj): + data = {'type': 'ZeroOneMessage'} + return data + + def __MapTo_BlockMessage(self, data): + messageData = data['data'] + return BlockMessage(messageData) + + def __MapTo_CNAVMessage(self, data): + prn = data['prn'] + n_prefixBits = data['n_prefixBits'] + n_msg0 = data['n_msg0'] + tow0 = data['tow0'] + return CNAVMessage(prn=prn, + tow0=tow0, + n_msg=n_msg0, + n_prefixBits=n_prefixBits) + + def __MapTo_ConstMessage(self, data): + bitValue = data['bitValue'] + return ConstMessage(bitValue) + + def __MapTo_LNAVMessage(self, data): + prn = data['prn'] + n_prefixBits = data['n_prefixBits'] + n_msg0 = data['n_msg0'] + tow0 = data['tow0'] + return LNAVMessage(prn=prn, + tow0=tow0, + n_msg=n_msg0, + n_prefixBits=n_prefixBits) + + def __MapTo_ZeroOneMessage(self, data): + return ZeroOneMessage() + +factoryObject = ObjectFactory() diff --git a/peregrine/iqgen/bits/message_lnav.py b/peregrine/iqgen/bits/message_lnav.py new file mode 100644 index 0000000..6a595ed --- /dev/null +++ b/peregrine/iqgen/bits/message_lnav.py @@ -0,0 +1,273 @@ +# Copyright (C) 2016 Swift Navigation Inc. +# Contact: Valeri Atamaniouk +# +# 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. + +''' +The :mod:`peregrine.iqgen.bits.message_lnav` module contains classes and +functions related to generating stub GPS LNAV messages. +''' + +import numpy +import logging +from swiftnav.bits import parity + +logger = logging.getLogger(__name__) + + +class Message(object): + ''' + GPS LNAV message generator + ''' + + def __init__(self, prn, tow0=1, n_msg=0, n_prefixBits=50): + ''' + Constructs message object. + + Parameters + ---------- + prn : int + Satellite PRN + tow0 : int + Time of week in 6-second units for the first message + n_msg : int, optional + Number of messages to generate for output + n_prefixBits : int, optional + Number of bits to issue before the first message + ''' + super(Message, self).__init__() + self.prn = prn + self.n_prefixBits = n_prefixBits + self.n_msg0 = n_msg + self.tow0 = tow0 + self.messageCount = 0 + self.messageLen = n_prefixBits + self.nextTow = tow0 + self.nextMsgId = 0 + self.messageBits = numpy.zeros(n_prefixBits, dtype=numpy.uint8) + words = (n_prefixBits + 29) / 30 + if words: + tmp = numpy.zeros(words * 30, dtype=numpy.uint8) + tmp[1::2] = 1 + if words > 1: + self.updateParity(tmp[0:30]) + for i in range(1, words - 1): + self.updateParity(tmp[i * 30 - 2: i * 30 + 30]) + self.updateParity(tmp[words * 30 - 32: words * 30], True) + else: + self.updateParity(tmp[0: 30], True) + self.messageBits[:] = tmp[-n_prefixBits:] + self.msgCount = 0 + self.a8 = numpy.ndarray(1, dtype=numpy.uint8) + self.a32 = numpy.ndarray(1, dtype=numpy.dtype('>u4')) + + def __str__(self, *args, **kwargs): + ''' + Formats object as string literal + + Returns + ------- + string + String representation of the object + ''' + return "GPS LNAV: prn=%d pref=%d tow=%d" % \ + (self.prn, self.n_prefixBits, self.nextTow) + + def getDataBits(self, dataAll_idx): + ''' + Generates vector of data bits corresponding to input index + + Parameters + ---------- + dataAll_idx : numpy.ndarray(dtype=numpy.int64) + Vector of bit indexes + + Returns + ------- + numpy.ndarray(dtype=numpy.uint8) + Vector of data bits + ''' + + lastIdx = dataAll_idx[-1] + if lastIdx >= self.messageLen: + # Grow data bits + delta = lastIdx - self.messageLen + 1 + newMsgCount = delta / 300 + if delta % 300: + newMsgCount += 1 + self.addMessages(newMsgCount) + + # numpy.take degrades performance a lot over time. + # return numpy.take(self.symbolData, dataAll_idx , mode='wrap') + return self.messageBits[dataAll_idx] + + def addMessages(self, newMsgCount): + ''' + Generate additional LNAV messages + + This method generates and encodes additional LNAV messages. The message + contents is added to the internal buffer. + + Parameters + ---------- + newMsgCount : int + Number of messages to generate + ''' + newMessageLen = newMsgCount * 300 + self.messageLen + newMessageData = numpy.ndarray(newMessageLen, dtype=numpy.uint8) + newMessageData[:self.messageLen] = self.messageBits + for i in range(self.messageLen, newMessageLen, 300): + logger.info("Generating LNAV message: prn=%d tow=%d msg_id=%d" % + (self.prn, self.nextTow, self.nextMsgId)) + lnav_msg = self.generateLNavMessage() + newMessageData[i:i + 300] = lnav_msg + self.messageLen = newMessageLen + self.messageBits = newMessageData + self.msgCount += newMsgCount + + def generateLNavMessage(self): + ''' + Produces additional GPS LNAV message. + + Returns + ------- + numpy.ndarray(shape=300, dtype=numpy.uint8) + Message bits. + ''' + msgData = numpy.zeros(300, dtype=numpy.uint8) + msgData[1::2] = 1 # Zero + one everywhere + + # TLM word + self.fillTlmWord(msgData[0:30], 0) + self.updateParity(msgData[0:30]) + # logger.debug("TLM: %s" % msgData[0:30]) + + # TOW word + self.fillTowWord(msgData[30:60], self.nextTow) + self.nextTow += 1 + if self.nextTow == 7 * 24 * 60 * 10: + self.nextTow = 0 + self.updateParity(msgData[28:60], True) + # logger.debug("TOW: %s" % msgData[30:60]) + + self.updateParity(msgData[58:90]) + self.updateParity(msgData[88:120]) + self.updateParity(msgData[118:150]) + self.updateParity(msgData[148:180]) + self.updateParity(msgData[178:210]) + self.updateParity(msgData[208:240]) + self.updateParity(msgData[238:270]) + self.updateParity(msgData[268:300], True) + + return msgData + + def getBits(self, value, nBits): + ''' + Converts integer into bit array + + Parameters + ---------- + value : int + Integer value + nBits : number of bits to produce + + Returns + ------- + numpy.ndarray(shape=(`nBits`), dtype=numpy.uint8) + Parameter `value` represented as a bit array + ''' + if nBits <= 8: + self.a8[0] = value + result = numpy.unpackbits(self.a8) + else: + self.a32[0] = value + result = numpy.unpackbits(self.a32.view(dtype=numpy.uint8)) + return result[-nBits:] + + def fillTlmWord(self, wordBits, msgId=0): + ''' + Fills in TLM word contents. + + Parameters + ---------- + wordBits : numpy.ndarray(shape=30, type=numpy.uint8) + Destination array + ''' + wordBits[0:8] = self.getBits(0b10001011, 8) # Preamble + wordBits[8:22] = self.getBits(msgId, 14) # TML message + wordBits[22] = 0 # Reserved + wordBits[23] = 0 # Integrity + return + + def fillTowWord(self, wordBits, tow): + ''' + Fills in TOW word contents. + + Parameters + ---------- + wordBits : numpy.ndarray(shape=30, type=numpy.uint8) + Destination array + ''' + wordBits[0:17] = self.getBits(tow, 17) # TOW count in 6 second units + wordBits[17] = 0 # Alert Flag + wordBits[18] = 0 # Anti-Spoof flag + wordBits[19:22] = self.getBits(0, 3) # Sub-frame ID + return + + def updateParity(self, dataBits, resolve=False): + ''' + Updates data bits and computes parity. + + When 32 bits are provided, they are used for parity computation and for + bit inversion. + + Parameters + ---------- + dataBits : numpy.ndarray(dtype=numpy.uint8) + 30 or 32 element array + resolve: bool, optional + When specified, bits d23 and d24 of the GPS word are updated to ensure + that parity bits d29 and d30 are zeros. + ''' + packed = numpy.packbits(dataBits) + acc = (packed[0] << 24) | (packed[1] << 16) | \ + (packed[2] << 8) | packed[3] + if len(dataBits) == 30: + acc >>= 2 + elif acc & 0x40000000: + acc ^= 0x3FFFFFC0 + dataBits[-30:-6] ^= 1 + + # D29 = D30*^d1^d3^d5^d6^d7^d9^d10^d14^d15^d16^d17^d18^d21^d22^d24 + d29 = parity(acc & 0b01101011101100011111001101000000) + # D30 = D29*^d3^d5^d6^d8^d9^d10^d11^d13^d15^d19^d22^d23^d24 + d30 = parity(acc & 0b10001011011110101000100111000000) + + if resolve: + if d29: + acc ^= 0x80 + d29 = False + d30 = not d30 + dataBits[-8] ^= 1 + if d30: + acc ^= 0x40 + d30 = False + dataBits[-7] ^= 1 + + # D25 = D29*^d1^d2^d3^d5^d6^d10^d11^d12^d13^d14^d17^d18^d20^d23 + dataBits[-6] = parity(acc & 0b10111011000111110011010010000000) + # D26 = D30*^d2^d3^d4^d6^d7^d11^d12^d13^d14^d15^d18^d19^d21^d24 + dataBits[-5] = parity(acc & 0b01011101100011111001101001000000) + # D27 = D29*^d1^d3^d4^d5^d7^d8^d12^d13^d14^d15^d16^d19^d20^d22 + dataBits[-4] = parity(acc & 0b10101110110001111100111000000000) + # D28 = D30*^d2^d4^d5^d6^d8^d9^d13^d14^d15^d16^d17^d20^d21^d23 + dataBits[-3] = parity(acc & 0b01010111011000111110011010000000) + # D29 = D30*^d1^d3^d5^d6^d7^d9^d10^d14^d15^d16^d17^d18^d21^d22^d24 + dataBits[-2] = d29 + # D30 = D29*^d3^d5^d6^d8^d9^d10^d11^d13^d15^d19^d22^d23^d24 + dataBits[-1] = d30 diff --git a/peregrine/iqgen/bits/message_zeroone.py b/peregrine/iqgen/bits/message_zeroone.py new file mode 100644 index 0000000..0882f53 --- /dev/null +++ b/peregrine/iqgen/bits/message_zeroone.py @@ -0,0 +1,59 @@ +# Copyright (C) 2016 Swift Navigation Inc. +# Contact: Valeri Atamaniouk +# +# 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. + +""" +The :mod:`peregrine.iqgen.bits.message_zeroone` module contains classes and +functions related to symbol contents that flips the value every other bit. + +""" + +import numpy + + +class Message(object): + ''' + Message that contains zeros and ones + ''' + + def __init__(self): + ''' + Constructs object. + ''' + super(Message, self).__init__() + self.bits = numpy.asarray([0, 1], dtype=numpy.uint8) + + def __str__(self, *args, **kwargs): + ''' + Formats object as string literal + + Returns + ------- + string + String representation of the object + ''' + return "ZeroOne" + + def getDataBits(self, dataAll_idx): + ''' + Generates vector of data bits corresponding to input index + + Parameters + ---------- + dataAll_idx : numpy.ndarray(dtype=numpy.int64) + Vector of bit indexes + + Returns + ------- + numpy.ndarray(dtype=numpy.uint8) + Vector of data bits + ''' + # numpy.take degrades performance a lot over time. + # return numpy.take(self.bits, dataAll_idx , mode='wrap') + return self.bits[dataAll_idx & 1] diff --git a/peregrine/iqgen/bits/prn_gps_l1ca.py b/peregrine/iqgen/bits/prn_gps_l1ca.py new file mode 100644 index 0000000..96893d6 --- /dev/null +++ b/peregrine/iqgen/bits/prn_gps_l1ca.py @@ -0,0 +1,86 @@ +# Copyright (C) 2016 Swift Navigation Inc. +# Contact: Valeri Atamaniouk +# +# 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 + +""" +The :mod:`peregrine.iqgen.bits.prn_gps_l1ca` module contains classes and +functions related to GPS L1 C/A PRN processing + +""" + +import peregrine.include.generateCAcode + +caCodes = peregrine.include.generateCAcode.caCodes + + +class PrnCode(object): + ''' + GPS L1 C/A code object + ''' + CODE_LENGTH = 1023 + CODE_FREQUENCY_HZ = 1023e3 + + def __init__(self, prnNo): + ''' + Initializes object. + + Parameters + ---------- + prnNo : int + SV identifier + ''' + super(PrnCode, self).__init__() + self.caCode = caCodes[prnNo - 1][:] + tmp = numpy.asarray(self.caCode, dtype=numpy.int8) + tmp -= 1 + tmp /= -2 + self.binCode = tmp + self.prnNo = prnNo + self.bitLookup = numpy.asarray([1, -1], dtype=numpy.int8) + + def getCodeBits(self, chipIndex_all): + ''' + Parameters + ---------- + chipIndex_all : numpy.ndarray(dtype=numpy.long) + Vector of chip indexes + + Returns + ------- + numpy.ndarray(dtype=numpy.uint8) + Vector of code chip bits + ''' + # numpy.take degrades performance a lot over time. + # return numpy.take(self.binCode, chipIndex_all, mode='wrap') + return self.binCode[chipIndex_all % len(self.binCode)] + + def combineData(self, chipIndex_all, dataBits): + ''' + Mixes in code chip and data + + Parameters + ---------- + chipIndex_all : numpy.ndarray(dtype=numpy.long) + Chip indexes + dataBits : numpy.ndarray(dtype=numpy.uint8) + Data bits + + Returns + ------- + numpy.ndarray(dtype=numpy.int8) + Vector of data bits modulated by chips + ''' + chipBits = self.getCodeBits(chipIndex_all) + combined = numpy.bitwise_xor(chipBits, dataBits) + # numpy.take degrades performance a lot over time. + # result = numpy.take(self.bitLookup, combined) + result = self.bitLookup[combined] + return result diff --git a/peregrine/iqgen/bits/prn_gps_l2c.py b/peregrine/iqgen/bits/prn_gps_l2c.py new file mode 100644 index 0000000..e424e58 --- /dev/null +++ b/peregrine/iqgen/bits/prn_gps_l2c.py @@ -0,0 +1,206 @@ +# Copyright (C) 2016 Swift Navigation Inc. +# Contact: Valeri Atamaniouk +# +# 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. + + +""" +The :mod:`peregrine.iqgen.bits.prn_gps_l2c` module contains classes and +functions related to GPS L2C PRN processing + +""" + +import numpy + +from peregrine.include.generateL2CMcode import L2CMCodes + + +class PrnCode(object): + ''' + Combined GPS L2 CM and CL code object + ''' + + class CM_Code(object): + ''' + GPS L2 Civil Medium code object + ''' + CODE_LENGTH = 10230 + CODE_FREQUENCY_HZ = 511.5e3 + + def __init__(self, prnNo): + ''' + Initializes object. + + Parameters + ---------- + prnNo : int + SV identifier + ''' + super(PrnCode.CM_Code, self).__init__() + self.caCode = L2CMCodes[prnNo - 1][:] + self.binCode = numpy.ndarray( + PrnCode.CM_Code.CODE_LENGTH, dtype=numpy.bool) + self.binCode[:] = numpy.asarray(self.caCode) < 0 + self.prnNo = prnNo + + def getCodeBits(self): + return self.binCode + + def getCodeBit(self, codeBitIndex): + ''' + Returns chip value by index. + + Parameters + ---------- + chipIndex : long + Chip index + + Returns + ------- + int + Chip value by index + ''' + return self.caCode[codeBitIndex % self.CODE_LENGTH] + + class CL_Code(object): + ''' + GPS L2 Civil Long code object + ''' + CODE_LENGTH = 767250 + CODE_FREQUENCY_HZ = 511.5e3 + + def __init__(self, prnNo, codeType): + ''' + Initializes object. + + Parameters + ---------- + prnNo : int + SV identifier + codeType : string + Type of the code: '01', '1', '0' + ''' + super(PrnCode.CL_Code, self).__init__() + self.prnNo = prnNo + self.binCode = numpy.ndarray( + PrnCode.CL_Code.CODE_LENGTH, dtype=numpy.bool) + if codeType == '01': + self.binCode.fill(False) + self.binCode[1::2].fill(True) + elif codeType == '1': + self.binCode.fill(True) + elif codeType == '0': + self.binCode.fill(False) + else: + raise ValueError('Unsupported GPS L2 CL generator type %s ' % + str(codeType)) + + def getCodeBits(self): + return self.binCode + + def getCodeBit(self, codeBitIndex): + ''' + Returns chip value by index. + + Currently GPS L2 CL code can be pseudo-random + + Parameters + ---------- + chipIndex : long + Chip index + + Returns + ------- + int + Chip value by index + ''' + if (codeBitIndex & 1 != 0): + return -1 + else: + return 1 + + CODE_LENGTH = CL_Code.CODE_LENGTH * 2 + CODE_FREQUENCY_HZ = 1023e3 + + def __init__(self, prnNo, clCodeType): + ''' + Initializes object. + + Parameters + ---------- + prnNo : int + SV identifier + clCodeType : string + Type of the code: '01', '1', '0' + ''' + super(PrnCode, self).__init__() + self.cl = PrnCode.CL_Code(prnNo, clCodeType) + self.cm = PrnCode.CM_Code(prnNo) + self.bitLookup = numpy.asarray([1, -1], dtype=numpy.int8) + tmp = numpy.ndarray(PrnCode.CL_Code.CODE_LENGTH * 2, dtype=numpy.uint8) + tmp[1::2] = self.cl.getCodeBits() + for i in range(0, PrnCode.CL_Code.CODE_LENGTH * 2, PrnCode.CM_Code.CODE_LENGTH * 2): + tmp[i:i + PrnCode.CM_Code.CODE_LENGTH * 2:2] = self.cm.getCodeBits() + self.binCode = tmp + self.prnNo = prnNo + + def getCodeBits(self, chipIndex_all): + ''' + Parameters + ---------- + chipIndex_all : numpy.ndarray(dtype=numpy.long) + Vector of chip indexes + + Returns + ------- + numpy.ndarray(dtype=numpy.uint8) + Vector of code chip bits + ''' + # numpy.take degrades performance a lot over time. + # return numpy.take(self.binCode, chipIndex_all, mode='wrap') + return self.binCode[chipIndex_all % PrnCode.CODE_LENGTH] + + def combineData(self, chipIndex_all, dataBits): + ''' + Mixes in code chip and data + + Parameters + ---------- + chipIndex_all : numpy.ndarray(dtype=numpy.long) + Chip indexes + dataBits : numpy.ndarray(dtype=numpy.uint8) + Data bits + + Returns + ------- + numpy.ndarray(dtype=numpy.int8) + Vector of data bits modulated by chips + ''' + chipBits = self.getCodeBits(chipIndex_all) + tmp = dataBits.copy() + oddChips = chipIndex_all & 1 == 0 + # print "idx=", chipIndex_all + # print "odd=", oddChips + # print "TMP1", tmp + tmp = tmp & oddChips + # print "TMP2", tmp + combined = numpy.bitwise_xor(chipBits, tmp) + # numpy.take degrades performance a lot over time. + # result = numpy.take(self.bitLookup, combined) + result = self.bitLookup[combined] + return result + + def __getCodeBit(self, codeBitIndex): + ''' + For GPS L2C code bits are taken from CM and CL codes in turn. + ''' + idx = long(codeBitIndex) + if idx & 1 != 0: + return self.cl.getCodeBit(idx / 2) + else: + return self.cm.getCodeBit(idx / 2) diff --git a/peregrine/iqgen/bits/satellite_base.py b/peregrine/iqgen/bits/satellite_base.py new file mode 100644 index 0000000..e5bfa0f --- /dev/null +++ b/peregrine/iqgen/bits/satellite_base.py @@ -0,0 +1,138 @@ +# Copyright (C) 2016 Swift Navigation Inc. +# Contact: Valeri Atamaniouk +# +# 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. + +""" +The :mod:`peregrine.iqgen.bits.satellite_base` module contains classes and +functions related to satellite base object. + +""" +from peregrine.iqgen.bits.doppler_poly import zeroDoppler +from peregrine.iqgen.bits.amplitude_poly import AmplitudePoly + + +class Satellite(object): + ''' + Satellite object. + + Satellite object combines speed/position computation and data generation for + all supported bands. + ''' + + def __init__(self, svName): + ''' + Constructor. + + Parameters + ---------- + svName : string + Satellite name + ''' + super(Satellite, self).__init__() + self.svName = svName + self.doppler = zeroDoppler(0., 0., 1.) + self.amplitude = AmplitudePoly(()) + + def getDoppler(self): + ''' + Returns doppler object. + + Returns + ------- + object + Doppler control object + ''' + return self.doppler + + def setDoppler(self, doppler): + ''' + Changes doppler object. + + Parameters + ------- + doppler : object + Doppler control object + ''' + self.doppler = doppler + + def getSvName(self): + ''' + Returns satellite name. + + Returns + ------- + string + Satellite name + ''' + return self.svName + + def setAmplitude(self, amplitude): + ''' + Changes amplitude + + Parameters + ---------- + amplitude : float + amplitude value for signal generation + ''' + self.amplitude = amplitude + + def getAmplitude(self): + ''' + Provides amplitude object + + Returns + ------- + object + Amplitude object + ''' + return self.amplitude + + def __str__(self): + return self.getSvName() + + def __repr__(self): + return self.getSvName() + + def getBatchSignals(self, userTimeAll_s, samples, outputConfig): + ''' + Generates signal samples. + + Parameters + ---------- + userTimeAll_s : numpy.ndarray(n_samples, dtype=numpy.float64) + Vector of observer's timestamps in seconds for the interval start. + samples : numpy.ndarray((4, n_samples)) + Array to which samples are added. + outputConfig : object + Output configuration object. + + Returns + ------- + list + Debug information + ''' + raise NotImplementedError() + + def isBandEnabled(self, bandIndex, outputConfig): + ''' + Checks if particular band is supported and enabled. + + Parameters + ---------- + bandIndex : int + Signal band index + outputConfig : object + Output configuration + + Returns: + bool + True, if the band is supported and enabled; False otherwise. + ''' + return False diff --git a/peregrine/iqgen/bits/satellite_factory.py b/peregrine/iqgen/bits/satellite_factory.py new file mode 100644 index 0000000..f62fc15 --- /dev/null +++ b/peregrine/iqgen/bits/satellite_factory.py @@ -0,0 +1,81 @@ +# Copyright (C) 2016 Swift Navigation Inc. +# Contact: Valeri Atamaniouk +# +# 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. + + +""" +The :mod:`peregrine.iqgen.bits.satellite_factory` module contains classes and +functions related to object factory for satellite objects. + +""" + +from peregrine.iqgen.bits.satellite_gps import GPSSatellite +from peregrine.iqgen.bits.amplitude_factory import factoryObject as amplitudeOF +from peregrine.iqgen.bits.doppler_factory import factoryObject as dopplerOF +from peregrine.iqgen.bits.message_factory import factoryObject as messageOF + + +class ObjectFactory(object): + ''' + Object factory for satellite types. + ''' + + def __init__(self): + super(ObjectFactory, self).__init__() + + def toMapForm(self, obj): + t = type(obj) + if t is GPSSatellite: + return self.__GPSSatellite_ToMap(obj) + else: + raise ValueError("Invalid object type") + + def fromMapForm(self, data): + t = data['type'] + if t == 'GPSSatellite': + return self.__MapTo_GPSSatellite(data) + else: + raise ValueError("Invalid object type") + + def __GPSSatellite_ToMap(self, obj): + data = {'type': 'GPSSatellite', + 'prn': obj.prn, + 'amplitude': amplitudeOF.toMapForm(obj.getAmplitude()), + 'l1caEnabled': obj.isL1CAEnabled(), + 'l2cEnabled': obj.isL2CEnabled(), + 'l1caMessage': messageOF.toMapForm(obj.getL1CAMessage()), + 'l2cMessage': messageOF.toMapForm(obj.getL2CMessage()), + 'doppler': dopplerOF.toMapForm(obj.getDoppler()), + 'l2clCodeType': obj.getL2CLCodeType(), + 'codeDopplerIgnored': obj.isCodeDopplerIgnored() + } + return data + + def __MapTo_GPSSatellite(self, data): + prn = data['prn'] + doppler = dopplerOF.fromMapForm(data['doppler']) + amplitude = amplitudeOF.fromMapForm(data['amplitude']) + l1caEnabled = data['l1caEnabled'] + l2cEnabled = data['l2cEnabled'] + l1caMessage = messageOF.fromMapForm(data['l1caMessage']) + l2cMessage = messageOF.fromMapForm(data['l2cMessage']) + clCodeType = data['l2clCodeType'] + codeDopplerIgnored = data['codeDopplerIgnored'] + satellite = GPSSatellite(prn) + satellite.setAmplitude(amplitude) + satellite.setDoppler(doppler) + satellite.setL1CAEnabled(l1caEnabled) + satellite.setL2CEnabled(l2cEnabled) + satellite.setL1CAMessage(l1caMessage) + satellite.setL2CMessage(l2cMessage) + satellite.setL2CLCodeType(clCodeType) + satellite.setCodeDopplerIgnored(codeDopplerIgnored) + return satellite + +factoryObject = ObjectFactory() diff --git a/peregrine/iqgen/bits/satellite_gps.py b/peregrine/iqgen/bits/satellite_gps.py new file mode 100644 index 0000000..410804d --- /dev/null +++ b/peregrine/iqgen/bits/satellite_gps.py @@ -0,0 +1,227 @@ +# Copyright (C) 2016 Swift Navigation Inc. +# Contact: Valeri Atamaniouk +# +# 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. + +""" +The :mod:`peregrine.iqgen.bits.satellite_gps` module contains classes and +functions related to GPS satellite configuration. + +""" +import peregrine.iqgen.bits.signals as signals +from peregrine.iqgen.bits.message_const import Message +from peregrine.iqgen.bits.prn_gps_l1ca import PrnCode as GPS_L1CA_Code +from peregrine.iqgen.bits.prn_gps_l2c import PrnCode as GPS_L2C_Code +from peregrine.iqgen.bits.satellite_base import Satellite + +import numpy + +DEFAULT_MESSAGE = Message(1) + + +class GPSSatellite(Satellite): + ''' + GPS satellite object. + ''' + + def __init__(self, prnNo): + ''' + Constructs satellite object + + Parameters + ---------- + prnNo : int + GPS satellite number for selecting PRN. + ''' + super(GPSSatellite, self).__init__("GPS{}".format(prnNo)) + self.prn = prnNo + self.l2clCodeType = '01' + self.l1caCode = GPS_L1CA_Code(prnNo) + self.l2cCode = GPS_L2C_Code(prnNo, self.l2clCodeType) + self.l1caEnabled = False + self.l2cEnabled = False + self.l1caMessage = DEFAULT_MESSAGE + self.l2cMessage = DEFAULT_MESSAGE + self.time0S = 0. + self.pr0M = 0. + self.phaseShift = 0. + + def setL1CAEnabled(self, enableFlag): + ''' + Enables or disable GPS L1 C/A sample generation + + Parameters + ---------- + enableFlag : boolean + Flag to enable (True) or disable (False) GPS L1 C/A samples + ''' + self.l1caEnabled = enableFlag + + def isL1CAEnabled(self): + ''' + Tests if L1 C/A signal generation is enabled + + Returns + ------- + bool + True, when L1 C/A signal generation is enabled, False otherwise + ''' + return self.l1caEnabled + + def setL2CEnabled(self, enableFlag): + ''' + Enables or disable GPS L2 C sample generation + + Parameters + ---------- + enableFlag : boolean + Flag to enable (True) or disable (False) GPS L2 C samples + ''' + self.l2cEnabled = enableFlag + + def isL2CEnabled(self): + ''' + Tests if L2 C signal generation is enabled + + Returns + ------- + bool + True, when L2 C signal generation is enabled, False otherwise + ''' + return self.l2cEnabled + + def setL2CLCodeType(self, clCodeType): + if self.l2clCodeType != clCodeType: + self.l2cCode = GPS_L2C_Code(self.prn, clCodeType) + self.l2clCodeType = clCodeType + + def getL2CLCodeType(self): + return self.l2clCodeType + + def setL1CAMessage(self, message): + ''' + Configures data source for L1 C/A signal. + + Parameters + ---------- + message : object + Message object that will provide symbols for L1 C/A signal. + ''' + self.l1caMessage = message + + def setL2CMessage(self, message): + ''' + Configures data source for L2 C signal. + + Parameters + ---------- + message : object + Message object that will provide symbols for L2 C signal. + ''' + self.l2cMessage = message + + def getL1CAMessage(self): + ''' + Returns configured message object for GPS L1 C/A band + + Returns + ------- + object + L1 C/A message object + ''' + return self.l1caMessage + + def getL2CMessage(self): + ''' + Returns configured message object for GPS L2 C band + + Returns + ------- + object + L2 C message object + ''' + return self.l2cMessage + + def getBatchSignals(self, userTimeAll_s, samples, outputConfig, debug): + ''' + Generates signal samples. + + Parameters + ---------- + userTimeAll_s : numpy.ndarray(n_samples, dtype=numpy.float64) + Vector of observer's timestamps in seconds for the interval start. + samples : numpy.ndarray((4, n_samples)) + Array to which samples are added. + outputConfig : object + Output configuration object. + debug : bool + Debug flag + + Returns + ------- + list + Debug information + ''' + result = [] + if (self.l1caEnabled): + intermediateFrequency_hz = outputConfig.GPS.L1.INTERMEDIATE_FREQUENCY_HZ + frequencyIndex = outputConfig.GPS.L1.INDEX + values = self.doppler.computeBatch(userTimeAll_s, + self.amplitude, + signals.GPS.L1CA, + intermediateFrequency_hz, + self.l1caMessage, + self.l1caCode, + outputConfig, + debug) + numpy.add(samples[frequencyIndex], + values[0], + out=samples[frequencyIndex]) + debugData = {'type': "GPSL1", 'doppler': values[1]} + result.append(debugData) + if (self.l2cEnabled): + intermediateFrequency_hz = outputConfig.GPS.L2.INTERMEDIATE_FREQUENCY_HZ + frequencyIndex = outputConfig.GPS.L2.INDEX + values = self.doppler.computeBatch(userTimeAll_s, + self.amplitude, + signals.GPS.L2C, + intermediateFrequency_hz, + self.l2cMessage, + self.l2cCode, + outputConfig, + debug) + numpy.add(samples[frequencyIndex], + values[0], + out=samples[frequencyIndex]) + debugData = {'type': "GPSL2", 'doppler': values[1]} + result.append(debugData) + return result + + def isBandEnabled(self, bandIndex, outputConfig): + ''' + Checks if particular band is supported and enabled. + + Parameters + ---------- + bandIndex : int + Signal band index + outputConfig : object + Output configuration + + Returns: + bool + True, if the band is supported and enabled; False otherwise. + ''' + result = None + if bandIndex == outputConfig.GPS.L1.INDEX: + result = self.isL1CAEnabled() + elif bandIndex == outputConfig.GPS.L2.INDEX: + result = self.isL2CEnabled() + else: + result = False + return result diff --git a/peregrine/iqgen/bits/signals.py b/peregrine/iqgen/bits/signals.py new file mode 100644 index 0000000..246fd76 --- /dev/null +++ b/peregrine/iqgen/bits/signals.py @@ -0,0 +1,171 @@ +# Copyright (C) 2016 Swift Navigation Inc. +# Contact: Valeri Atamaniouk +# +# 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. + +""" +The :mod:`peregrine.iqgen.bits.signals` module contains classes and functions +related to common satellite signal definitions and operations. + +""" + +import scipy.constants + + +class GPS: + ''' + GPS signal parameters and utilities. + ''' + + @staticmethod + def _calcDopplerShiftHz(frequency_hz, distance_m, velocity_mps): + ''' + Utility to compute doppler shift from ditance and velocity for a band + frequency. + + Parameters + ---------- + frequency_hz : float + Band frequency in hertz + distance_m : float + Distance to satellite in meters + velocity_m : float + Satellite velocity in meters per second. + + Return + ------ + float + Doppler shift in hertz + ''' + doppler_hz = -velocity_mps * frequency_hz / scipy.constants.c + return doppler_hz + + class L1CA: + ''' + GPS L1 C/A parameters and utilities. + ''' + SYMBOL_RATE_HZ = 50 + CENTER_FREQUENCY_HZ = 1575.42e6 + CODE_CHIP_RATE_HZ = 1023000 + CHIP_TO_SYMBOL_DIVIDER = 20460 + + @staticmethod + def calcDopplerShiftHz(distance_m, velocity_mps): + ''' + Converts relative speed into doppler value for GPS L1 C/A band. + + Parameters + ---------- + distance_m : float + Distance in meters + velocity_mps : float + Relative speed in meters per second. + + Returns + ------- + float + Doppler shift in Hz. + ''' + return GPS._calcDopplerShiftHz(GPS.L1CA.CENTER_FREQUENCY_HZ, distance_m, velocity_mps) + + @staticmethod + def getSymbolIndex(svTime_s): + ''' + Computes symbol index. + + Parameters + ---------- + svTime_s : float + SV time in seconds + + Returns + ------- + long + Symbol index + ''' + return long(svTime_s * GPS.L1CA.SYMBOL_RATE_HZ) + + @staticmethod + def getCodeChipIndex(svTime_s): + ''' + Computes code chip index. + + Parameters + ---------- + svTime_s : float + SV time in seconds + + Returns + ------- + long + Code chip index + ''' + return long(svTime_s * GPS.L1CA.CODE_CHIP_RATE_HZ) + + class L2C: + ''' + GPS L2 C parameters and utilities. + ''' + + SYMBOL_RATE_HZ = 50 + CENTER_FREQUENCY_HZ = 1227.60e6 + CODE_CHIP_RATE_HZ = 1023000 + CHIP_TO_SYMBOL_DIVIDER = 20460 + + @staticmethod + def calcDopplerShiftHz(distance_m, velocity_mps): + ''' + Converts relative speed into doppler value for GPS L2 C band. + + Parameters + ---------- + distance_m : float + Distance in meters + velocity_mps : float + Relative speed in meters per second. + + Returns + ------- + float + Doppler shift in Hz. + ''' + return GPS._calcDopplerShiftHz(GPS.L2C.CENTER_FREQUENCY_HZ, distance_m, velocity_mps) + + @staticmethod + def getSymbolIndex(svTime_s): + ''' + Computes symbol index. + + Parameters + ---------- + svTime_s : float + SV time in seconds + + Returns + ------- + long + Symbol index + ''' + return long(svTime_s * GPS.L2C.SYMBOL_RATE_HZ) + + @staticmethod + def getCodeChipIndex(svTime_s): + ''' + Computes code chip index. + + Parameters + ---------- + svTime_s : float + SV time in seconds + + Returns + ------- + long + Code chip index + ''' + return long(svTime_s * GPS.L2C.CODE_CHIP_RATE_HZ) diff --git a/peregrine/iqgen/bits/tcxo_base.py b/peregrine/iqgen/bits/tcxo_base.py new file mode 100644 index 0000000..3bd0585 --- /dev/null +++ b/peregrine/iqgen/bits/tcxo_base.py @@ -0,0 +1,46 @@ +# Copyright (C) 2016 Swift Navigation Inc. +# Contact: Valeri Atamaniouk +# +# 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. + +""" +The :mod:`peregrine.iqgen.bits.tcxo_base` module contains base class definitions +for TCXO control. + +""" + + +class TCXOBase(object): + ''' + Base class for TCXO control. The class computes time shifts of TCXO depending + on external conditions like temperature, vibration, etc. + ''' + + def __init__(self): + super(TCXOBase, self).__init__() + + def computeTcxoTime(self, fromSample, toSample, outputConfig): + ''' + Method generates time vector for the given sample index range depending on + TCXO behaviour. + + Parameters + ---------- + fromSample : int + Index of the first sample. + toSample: int + Index of the last sample plus 1. + outputConfig : object + Output configuration + + Returns + ------- + numpy.ndarray(shape=(toSample - fromSample), dtype=numpy.float) + Vector of the shifted time stamps for the given TCXO controller. + ''' + raise NotImplementedError() diff --git a/peregrine/iqgen/bits/tcxo_factory.py b/peregrine/iqgen/bits/tcxo_factory.py new file mode 100644 index 0000000..6501654 --- /dev/null +++ b/peregrine/iqgen/bits/tcxo_factory.py @@ -0,0 +1,68 @@ +# Copyright (C) 2016 Swift Navigation Inc. +# Contact: Valeri Atamaniouk +# +# 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. + +""" +The :mod:`peregrine.iqgen.bits.amplitude_factory` module contains classes and +functions related to object factory for amplitude objects. + +""" + +from peregrine.iqgen.bits.tcxo_poly import TCXOPoly as PolyTcxo +from peregrine.iqgen.bits.tcxo_sine import TCXOSine as SineTcxo + + +class ObjectFactory(object): + ''' + Object factory for amplitude objects. + ''' + + def __init__(self): + super(ObjectFactory, self).__init__() + + def toMapForm(self, obj): + t = type(obj) + if t is PolyTcxo: + return self.__PolyTcxo_ToMap(obj) + elif t is SineTcxo: + return self.__SineTcxo_ToMap(obj) + else: + raise ValueError("Invalid object type") + + def fromMapForm(self, data): + t = data['type'] + if t == 'PolyTcxo': + return self.__MapTo_PolyTcxo(data) + elif t == 'SineTcxo': + return self.__MapTo_SineTcxo(data) + else: + raise ValueError("Invalid object type") + + def __PolyTcxo_ToMap(self, obj): + data = {'type': 'PolyTcxo', 'coeffs': obj.coeffs} + return data + + def __SineTcxo_ToMap(self, obj): + data = {'type': 'SineTcxo', + 'initial_ppm': obj.initial_ppm, + 'amplitude_ppm': obj.amplitude_ppm, + 'period_s': obj.period_s} + return data + + def __MapTo_PolyTcxo(self, data): + coeffs = data['coeffs'] + return PolyTcxo(coeffs) + + def __MapTo_SineTcxo(self, data): + initial_ppm = data['initial_ppm'] + amplitude_ppm = data['amplitude_ppm'] + period_s = data['period_s'] + return SineTcxo(initial_ppm, amplitude_ppm, period_s) + +factoryObject = ObjectFactory() diff --git a/peregrine/iqgen/bits/tcxo_poly.py b/peregrine/iqgen/bits/tcxo_poly.py new file mode 100644 index 0000000..f7fcbe5 --- /dev/null +++ b/peregrine/iqgen/bits/tcxo_poly.py @@ -0,0 +1,95 @@ +# Copyright (C) 2016 Swift Navigation Inc. +# Contact: Valeri Atamaniouk +# +# 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. + +""" +The :mod:`peregrine.iqgen.bits.tcxo_poly` module contains class definitions +for TCXO control that can describe TCXO frequency drift as a polynome. + +""" + +from peregrine.iqgen.bits.tcxo_base import TCXOBase +import numpy + + +class TCXOPoly(TCXOBase): + ''' + Polynomial TCXO control class. + ''' + + def __init__(self, coeffs): + ''' + Constructs TCXO control object. + + Parameters + ---------- + coeffs : array-like + Coefficients for TCXO polynome. These coeffificens define a TCXO drift + over time in ppm. + ''' + super(TCXOPoly, self).__init__() + self.coeffs = coeffs[:] + if coeffs: + # Recompute drift coefficients from speed of drift into distance of drift + new_coeffs = [] + power_c = len(coeffs) + for idx, val in enumerate(coeffs): + power = power_c - idx + new_coeffs.append(val * 1e-6 / power) + new_coeffs.append(0) + self.poly = numpy.poly1d(new_coeffs) + else: + self.poly = None + + def __str__(self, *args, **kwargs): + ''' + Provides string representation of the object + ''' + return "TCXOPoly: coeffs=%s" % str(self.coeffs) + + def __repr__(self): + ''' + Provides string representation of the object + ''' + return "TCXOPoly(%s)" % repr(self.coeffs) + + def computeTcxoTime(self, fromSample, toSample, outputConfig): + ''' + Method generates time vector for the given sample index range depending on + TCXO behaviour. + + Parameters + ---------- + fromSample : int + Index of the first sample. + toSample: int + Index of the last sample plus 1. + outputConfig : object + Output configuration + + Returns + ------- + numpy.ndarray(shape=(toSample - fromSample), dtype=numpy.float) + Vector of the shifted time stamps for the given TCXO controller. + ''' + poly = self.poly + + if poly: + time0_s = fromSample / outputConfig.SAMPLE_RATE_HZ + timeX_s = toSample / outputConfig.SAMPLE_RATE_HZ + timeAll_s = numpy.linspace(time0_s, + timeX_s, + toSample - fromSample, + endpoint=False, + dtype=numpy.float) + result = poly(timeAll_s) + else: + result = None + + return result diff --git a/peregrine/iqgen/bits/tcxo_sine.py b/peregrine/iqgen/bits/tcxo_sine.py new file mode 100644 index 0000000..cc0b3b3 --- /dev/null +++ b/peregrine/iqgen/bits/tcxo_sine.py @@ -0,0 +1,101 @@ +# Copyright (C) 2016 Swift Navigation Inc. +# Contact: Valeri Atamaniouk +# +# 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. + +""" +The :mod:`peregrine.iqgen.bits.tcxo_sine` module contains class definitions +for TCXO control that can describe TCXO frequency drift as a periodic (sine) +function. + +""" + +from peregrine.iqgen.bits.tcxo_base import TCXOBase +import numpy +import scipy.constants + + +class TCXOSine(TCXOBase): + ''' + Sine TCXO control class. + ''' + + def __init__(self, initial_ppm, amplitude_ppm, period_s): + ''' + Constructs TCXO control object. + + Parameters + ---------- + initial_ppm : float + Initial drift in ppm + amplitude_ppm : float + Drift amplitude in ppm + period_s : float + Drift period in seconds + ''' + super(TCXOSine, self).__init__() + + self.initial_ppm = initial_ppm + self.amplitude_ppm = amplitude_ppm + self.period_s = period_s + self.c0 = -2. * scipy.constants.pi * amplitude_ppm * 1e-6 + self.c1 = 2. * scipy.constants.pi / period_s + self.c2 = initial_ppm * 1e-6 + + def __str__(self, *args, **kwargs): + ''' + Provides string representation of the object + ''' + return "TCXOSine: initial_ppm=%f amplitude_ppm=%f period_s=%f" % \ + (self.initial_ppm, self.amplitude_ppm, self.period_s) + + def __repr__(self): + ''' + Provides string representation of the object + ''' + return "TCXOSine(%f, %f, %f)" % \ + (self.initial_ppm, self.amplitude_ppm, self.period_s) + + def computeTcxoTime(self, fromSample, toSample, outputConfig): + ''' + Method generates time vector for the given sample index range depending on + TCXO behaviour. + + Parameters + ---------- + fromSample : int + Index of the first sample. + toSample: int + Index of the last sample plus 1. + outputConfig : object + Output configuration + + Returns + ------- + numpy.ndarray(shape=(toSample - fromSample), dtype=numpy.float) + Vector of the shifted time stamps for the given TCXO controller. + ''' + c0 = self.c0 + c1 = self.c1 + c2 = self.c2 + time0_s = fromSample / outputConfig.SAMPLE_RATE_HZ + timeX_s = toSample / outputConfig.SAMPLE_RATE_HZ + + timeAll_s = numpy.linspace(time0_s * c1, + timeX_s * c1, + toSample - fromSample, + endpoint=False, + dtype=numpy.float) + + result = numpy.cos(timeAll_s) + result += -1. + result *= c0 + if c2: + result += timeAll_s * c2 + + return result diff --git a/peregrine/iqgen/generate.py b/peregrine/iqgen/generate.py new file mode 100644 index 0000000..4d9a826 --- /dev/null +++ b/peregrine/iqgen/generate.py @@ -0,0 +1,598 @@ +# Copyright (C) 2016 Swift Navigation Inc. +# Contact: Valeri Atamaniouk +# +# 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 sys +import traceback + +""" +The :mod:`peregrine.iqgen.generate` module contains classes and functions +related to main loop of samples generation. + +""" +from peregrine.iqgen.bits.filter_lowpass import LowPassFilter +from peregrine.iqgen.bits.filter_bandpass import BandPassFilter + +from peregrine.iqgen.bits import signals + +import logging +import scipy +import numpy +import time + +import multiprocessing + +logger = logging.getLogger(__name__) + + +class Task(object): + ''' + Period computation task. This object performs a batch computation of signal + in the specified range. + ''' + + def __init__(self, + outputConfig, + signalSources, + noiseSigma, + tcxo, + signalFilters, + generateDebug): + ''' + Parameters + ---------- + outputConfig : object + Output profile + signalSources : array-like + List of satellites + noiseSigma : float + Noise sigma value + tcxo : object + TCXO control object + signalFilters : array-like + Output signal filter objects + generateDebug : bool + Flag if additional debug output is required + ''' + + self.outputConfig = outputConfig + self.signalSources = signalSources + self.signalFilters = signalFilters + self.generateDebug = generateDebug + self.noiseSigma = noiseSigma + self.tcxo = tcxo + self.signals = scipy.ndarray(shape=(4, outputConfig.SAMPLE_BATCH_SIZE), + dtype=numpy.float) + self.noise = self.createNoise(outputConfig.SAMPLE_BATCH_SIZE) + self.nSamples = outputConfig.SAMPLE_BATCH_SIZE + + def update(self, userTime0_s, nSamples, firstSampleIndex): + ''' + Configure object for the next batch generation + + The method stores parameters for the generation step and updates internal + arrays to match output shape. + + Parameters + ---------- + userTime0_s : float + Time of the interval start in seconds + nSamples : long + Number of samples in the interval + firstSampleIndex : long + Index of the first sample + ''' + self.userTime0_s = userTime0_s + self.firstSampleIndex = firstSampleIndex + + if (self.nSamples != nSamples): + newSignals = numpy.ndarray((4, nSamples), dtype=float) + newNoise = self.createNoise(nSamples) + self.nSamples = nSamples + self.signals = newSignals + self.noise = newNoise + + def createNoise(self, nSamples): + ''' + Generate noise array for a given noise sigma. + + Parameters + ---------- + nSamples : int + Length of the samples vectors + + Returns + ------- + numpy.ndarray(shape=(4, nSamples), dtype=numpy.float) + Noise values + ''' + noiseSigma = self.noiseSigma + if noiseSigma is not None: + # Initialize signal array with noise + noiseType = 1 + if noiseType == 1: + noise = noiseSigma * scipy.randn(4, nSamples) + else: + noise = numpy.random.normal(loc=0., + scale=noiseSigma, + size=(4, nSamples)) + # print self.noise + else: + noise = None + return noise + + def perform(self): + outputConfig = self.outputConfig + signalSources = self.signalSources + signalFilters = self.signalFilters + tcxo = self.tcxo + firstSampleIndex = self.firstSampleIndex + finalSampleIndex = firstSampleIndex + self.nSamples + + generateDebug = self.generateDebug + + userTime0_s = self.userTime0_s + userTimeX_s = userTime0_s + float(self.nSamples) / \ + float(outputConfig.SAMPLE_RATE_HZ) + userTimeAll_s = scipy.linspace(userTime0_s, + userTimeX_s, + self.nSamples, + endpoint=False) + + if tcxo: + tcxoTimeDrift_s = tcxo.computeTcxoTime(firstSampleIndex, + finalSampleIndex, + outputConfig) + if tcxoTimeDrift_s: + userTimeAll_s += tcxoTimeDrift_s + + noise = self.noise + sigs = self.signals + sigs.fill(0.) + if noise is not None: + # Initialize signal array with noise + sigs += noise + + if generateDebug: + signalData = [] + debugData = {'time': userTimeAll_s, 'signalData': signalData} + else: + debugData = None + + # Sum up signals for all SVs + for signalSource in signalSources: + # Add signal from source (satellite) to signal accumulator + t = signalSource.getBatchSignals(userTimeAll_s, + sigs, + outputConfig, + generateDebug) + # Debugging output + if generateDebug: + svDebug = {'name': signalSource.getSvName(), 'data': t} + signalData.append(svDebug) + t = None + + if signalFilters is list: + # Filter signal values through LPF, BPF or another + for i in range(len(self.filters)): + filterObject = signalFilters[i] + if filterObject is not None: + sigs[i][:] = filterObject.filter(sigs[i]) + + inputParams = (self.userTime0_s, self.nSamples, self.firstSampleIndex) + return (inputParams, sigs, debugData) + + +class Worker(multiprocessing.Process): + + def __init__(self, + outputConfig, + signalSources, + noiseSigma, + tcxo, + signalFilters, + generateDebug): + super(Worker, self).__init__() + self.queueIn = multiprocessing.Queue() + self.queueOut = multiprocessing.Queue() + self.totalWaitTime_s = 0. + self.totalExecTime_s = 0. + self.outputConfig = outputConfig + self.signalSources = signalSources + self.noiseSigma = noiseSigma + self.tcxo = tcxo + self.signalFilters = signalFilters + self.generateDebug = generateDebug + + def run(self): + task = Task(self.outputConfig, + self.signalSources, + noiseSigma=self.noiseSigma, + tcxo=self.tcxo, + signalFilters=self.signalFilters, + generateDebug=self.generateDebug) + + while True: + opStartTime_s = time.clock() + inputRequest = self.queueIn.get() + if inputRequest is None: + # EOF reached + break + (userTime0_s, nSamples, firstSampleIndex) = inputRequest + # print "Received params", userTime0_s, nSamples, firstSampleIndex + opDuration_s = time.clock() - opStartTime_s + self.totalWaitTime_s += opDuration_s + startTime_s = time.clock() + try: + task.update(userTime0_s, nSamples, firstSampleIndex) + result = task.perform() + import copy + result = copy.deepcopy(result) + self.queueOut.put(result) + except: + exType, exValue, exTraceback = sys.exc_info() + traceback.print_exception( + exType, exValue, exTraceback, file=sys.stderr) + self.queueOut.put(None) + self.queueIn.close() + self.queueOut.close() + sys.exit(1) + duration_s = time.clock() - startTime_s + self.totalExecTime_s += duration_s + statistics = (self.totalWaitTime_s, self.totalExecTime_s) + self.queueOut.put(statistics) + self.queueIn.close() + self.queueOut.close() + sys.exit(0) + + +def computeTimeIntervalS(outputConfig): + ''' + Helper for computing generation interval duration in seconds. + + Parameters + ---------- + outputConfig : object + Output configuration. + + Returns + ------- + float + Generation interval duration in seconds + ''' + deltaTime_s = float(outputConfig.SAMPLE_BATCH_SIZE) / \ + outputConfig.SAMPLE_RATE_HZ + return deltaTime_s + + +def generateSamples(outputFile, + sv_list, + encoder, + time0S, + nSamples, + outputConfig, + SNR=None, + tcxo=None, + filterType="none", + logFile=None, + threadCount=0, + pbar=None): + ''' + Generates samples. + + Parameters + ---------- + fileName : string + Output file name. + sv_list : list + List of configured satellite objects. + encoder : Encoder + Output encoder object. + time0S : float + Time epoch for the first sample. + nSamples : long + Total number of samples to generate. + outputConfig : object + Output parameters + SNR : float, optional + When specified, adds random noise to the output. + tcxo : object, optional + When specified, controls TCXO drift + filterType : string, optional + Controls IIR/FIR signal post-processing. Disabled by default. + debugLog : bool, optional + Control generation of additional debug output. Disabled by default. + ''' + + # + # Print out parameters + # + print "Generating samples, sample rate={} Hz, interval={} seconds, SNR={}".format( + outputConfig.SAMPLE_RATE_HZ, nSamples / outputConfig.SAMPLE_RATE_HZ, SNR) + print "Jobs: ", threadCount + + _t0 = time.clock() + _count = 0l + + # Check which bands are enabled, configure band-specific parameters + bands = [outputConfig.GPS.L1, outputConfig.GPS.L2] # Supported bands + lpf = [None] * len(bands) + bandsEnabled = [False] * len(bands) + + bandPass = False + lowPass = False + if filterType == 'lowpass': + lowPass = True + elif filterType == 'bandpass': + bandPass = True + elif filterType == 'none': + pass + else: + raise ValueError("Invalid filter type %s" % repr(filter)) + + for band in bands: + for sv in sv_list: + bandsEnabled[band.INDEX] |= sv.isBandEnabled(band.INDEX, outputConfig) + + filterObject = None + if lowPass: + filterObject = LowPassFilter(outputConfig, + band.INTERMEDIATE_FREQUENCY_HZ) + elif bandPass: + filterObject = BandPassFilter(outputConfig, + band.INTERMEDIATE_FREQUENCY_HZ) + if filterObject: + lpf[band.INDEX] = filterObject + logger.debug("Band %d filter NBW is %s" % + (band.INDEX, str(filterObject))) + + if SNR is not None: + sourcePower = 0. + for sv in sv_list: + svMeanPower = sv.getAmplitude().computeMeanPower() + sourcePower += svMeanPower + logger.debug("[%s] Estimated mean power is %f" % + (sv.getSvName(), svMeanPower)) + meanPower = sourcePower / len(sv_list) + meanAmplitude = scipy.sqrt(meanPower) + logger.debug("Estimated total signal power is %f, mean %f, mean amplitude %f" % + (sourcePower, meanPower, meanAmplitude)) + + # Nsigma and while noise amplitude computation: check if the Nsigma is + # actually a correct value for white noise with normal distribution. + + # Number of samples for 1023 MHz + freqTimesTau = outputConfig.SAMPLE_RATE_HZ / 1.023e6 + noiseVariance = freqTimesTau * meanPower / (4. * 10. ** (float(SNR) / 10.)) + noiseSigma = numpy.sqrt(noiseVariance) + logger.info("Selected noise sigma %f (variance %f) for SNR %f" % + (noiseSigma, noiseVariance, float(SNR))) + + else: + noiseVariance = None + noiseSigma = None + logger.info("SNR is not provided, noise is not generated.") + + # + # Print out SV parameters + # + for _sv in sv_list: + _svNo = _sv.getSvName() + _amp = _sv.amplitude + _svTime0_s = 0 + _dist0_m = _sv.doppler.computeDistanceM(_svTime0_s) + _speed_mps = _sv.doppler.computeSpeedMps(_svTime0_s) + _bit = signals.GPS.L1CA.getSymbolIndex(_svTime0_s) + _c1 = signals.GPS.L1CA.getCodeChipIndex(_svTime0_s) + _c2 = signals.GPS.L2C.getCodeChipIndex(_svTime0_s) + _d1 = signals.GPS.L1CA.calcDopplerShiftHz(_dist0_m, _speed_mps) + _d2 = signals.GPS.L2C.calcDopplerShiftHz(_dist0_m, _speed_mps) + svMeanPower = _sv.getAmplitude().computeMeanPower() + # SNR for a satellite. Depends on sampling rate. + if noiseVariance: + svSNR = svMeanPower / (4. * noiseVariance) * freqTimesTau + else: + svSNR = 1e6 + svSNR_db = 10. * numpy.log10(svSNR) + # Filters lower the power according to their attenuation levels + l1FA_db = lpf[0].getPassBandAtt() if lpf[0] else 0. + l2FA_db = lpf[1].getPassBandAtt() if lpf[1] else 0. + # CNo for L1 + svCNoL1 = svSNR_db + 10. * numpy.log10(1.023e6) - l1FA_db + # CNo for L2, half power used (-3dB) + svCNoL2 = svSNR_db + 10. * numpy.log10(1.023e6) - 3. - l2FA_db + + print "{} = {{".format(_svNo) + print " .amplitude: {}".format(_amp) + print " .doppler: {}".format(_sv.doppler) + print " .l1_message: {}".format(_sv.getL1CAMessage()) + print " .l2_message: {}".format(_sv.getL2CMessage()) + print " .l2_cl_type: {}".format(_sv.getL2CLCodeType()) + print " .SNR (dBHz): {}".format(svSNR_db) + print " .L1 CNo: {}".format(svCNoL1) + print " .L2 CNo: {}".format(svCNoL2) + print " .epoc:" + print " .distance: {} m".format(_dist0_m) + print " .speed: {} m/s".format(_speed_mps) + print " .symbol: {}".format(_bit) + print " .l1_doppler: {} hz".format(_d1) + print " .l2_doppler: {} hz".format(_d2) + print " .l1_chip: {}".format(_c1) + print " .l2_chip: {}".format(_c2) + print "}" + + userTime_s = float(time0S) + + deltaUserTime_s = computeTimeIntervalS(outputConfig) + debugFlag = logFile is not None + + if debugFlag: + logFile.write("Index,Time") + for sv in sv_list: + svName = sv.getSvName() + if sv.isL1CAEnabled(): + logFile.write(",%s/L1/doppler" % svName) + if sv.isL2CEnabled(): + logFile.write(",%s/L2/doppler" % svName) + # End of line + logFile.write("\n") + + if threadCount > 0: + workerPool = [Worker(outputConfig, + sv_list, + noiseSigma, + tcxo, + lpf, + debugFlag) for _ in range(threadCount)] + + for worker in workerPool: + worker.start() + maxTaskListSize = threadCount * 2 + else: + workerPool = None + task = Task(outputConfig, + sv_list, + noiseSigma=noiseSigma, + tcxo=tcxo, + signalFilters=lpf, + generateDebug=debugFlag) + maxTaskListSize = 1 + + workerPutIndex = 0 + workerGetIndex = 0 + activeTasks = 0 + + totalSampleCounter = 0 + taskQueuedCounter = 0 + taskReceivedCounter = 0 + + totalEncodeTime_s = 0. + totalWaitTime_s = 0. + + while True: + while activeTasks < maxTaskListSize and totalSampleCounter < nSamples: + # We have space in the task backlog and not all batchIntervals are issued + userTime0_s = userTime_s + userTimeX_s = userTime_s + deltaUserTime_s + sampleCount = outputConfig.SAMPLE_BATCH_SIZE + + if totalSampleCounter + sampleCount > nSamples: + # Last interval may contain less than full batch size of samples + sampleCount = nSamples - totalSampleCounter + userTimeX_s = userTime0_s + float(sampleCount) / \ + outputConfig.SAMPLE_RATE_HZ + + params = (userTime0_s, sampleCount, totalSampleCounter) + # print ">>> ", userTime0_s, sampleCount, totalSampleCounter, + # workerPutIndex + if workerPool is not None: + workerPool[workerPutIndex].queueIn.put(params) + workerPutIndex = (workerPutIndex + 1) % threadCount + else: + task.update(userTime0_s, sampleCount, totalSampleCounter) + activeTasks += 1 + + # Update parameters for the next batch interval + userTime_s = userTimeX_s + totalSampleCounter += sampleCount + taskQueuedCounter += 1 + + # What for the data only if we have something to wait + if taskReceivedCounter == taskQueuedCounter and \ + totalSampleCounter == nSamples: + # No more tasks to issue to generator + # No more tasks to wait + break + + try: + if workerPool is not None: + # Wait for the first task + worker = workerPool[workerGetIndex] + waitStartTime_s = time.time() + # print "waiting data from worker", workerGetIndex + result = worker.queueOut.get() + # print "Data received from worker", workerGetIndex + workerGetIndex = (workerGetIndex + 1) % threadCount + waitDuration_s = time.time() - waitStartTime_s + totalWaitTime_s += waitDuration_s + else: + result = task.perform() + except: + exType, exValue, exTraceback = sys.exc_info() + traceback.print_exception(exType, exValue, exTraceback, file=sys.stderr) + result = None + taskReceivedCounter += 1 + activeTasks -= 1 + + if result is None: + print "Error in processor; aborting." + break + + (inputParams, signalSamples, debugData) = result + (_userTime0_s, _sampleCount, _firstSampleIndex) = inputParams + # print "<<< ", _userTime0_s, _sampleCount, _firstSampleIndex + + if logFile is not None: + # Data from all satellites is collected. Now we can dump the debug matrix + + userTimeAll_s = debugData['time'] + signalData = debugData['signalData'] + for smpl_idx in range(_sampleCount): + logFile.write("{},{}".format(_firstSampleIndex + smpl_idx, + userTimeAll_s[smpl_idx])) + for svIdx in range(len(signalData)): + # signalSourceName = signalData[svIdx]['name'] + signalSourceData = signalData[svIdx]['data'] + for band in signalSourceData: + # bandType = band['type'] + doppler = band['doppler'] + logFile.write(",{}".format(doppler[smpl_idx])) + # End of line + logFile.write("\n") + + encodeStartTime_s = time.time() + # Feed data into encoder + encodedSamples = encoder.addSamples(signalSamples) + signalSamples = None + + if len(encodedSamples) > 0: + _count += len(encodedSamples) + encodedSamples.tofile(outputFile) + encodedSamples = None + encodeDuration_s = time.time() - encodeStartTime_s + totalEncodeTime_s += encodeDuration_s + + if pbar: + pbar.update(_firstSampleIndex + _sampleCount) + + logger.debug("MAIN: Encode duration: %f" % totalEncodeTime_s) + logger.debug("MAIN: wait duration: %f" % totalWaitTime_s) + + encodedSamples = encoder.flush() + if len(encodedSamples) > 0: + encodedSamples.tofile(outputFile) + + if debugFlag: + logFile.close() + + if workerPool is not None: + for worker in workerPool: + worker.queueIn.put(None) + for worker in workerPool: + try: + statistics = worker.queueOut.get(timeout=2) + print "Statistics:", statistics + except: + exType, exValue, exTraceback = sys.exc_info() + traceback.print_exception( + exType, exValue, exTraceback, file=sys.stderr) + worker.queueIn.close() + worker.queueOut.close() + worker.terminate() + worker.join() diff --git a/peregrine/iqgen/if_iface.py b/peregrine/iqgen/if_iface.py new file mode 100644 index 0000000..b432b35 --- /dev/null +++ b/peregrine/iqgen/if_iface.py @@ -0,0 +1,222 @@ +# Copyright (C) 2016 Swift Navigation Inc. +# Contact: Valeri Atamaniouk +# +# 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. + +""" +The :mod:`peregrine.iqgen.if_iface` module contains classes and functions +related to radio interface parameters + +""" + +from peregrine.defaults import freq_profile_peregrine + + +class LowRateConfig(object): + ''' + Output control configuration for quick tests. + + Attributes + ---------- + NAME : string + Configuration name + SAMPLE_RATE_HZ : float + Sample rate in hertz for data generation. + SAMPLE_BATCH_SIZE : int + Size of the sample batch in samples. + GPS : object + GPS band information + Galileo : object + Galileo band information + Beidou : object + Beidou band information + Glonass : object + Glonass band information + ''' + NAME = "Low rate configuration for fast tests" + SAMPLE_RATE_HZ = 24.84375e5 + SAMPLE_BATCH_SIZE = 100000 + + class GPS(object): + + class L1(object): + INTERMEDIATE_FREQUENCY_HZ = 14.58e5 + INDEX = 0 + + class L2(object): + INTERMEDIATE_FREQUENCY_HZ = 7.4e+5 + INDEX = 1 + + class Glonass(object): + + class L1(object): + INTERMEDIATE_FREQUENCY_HZ = 12e5 + INDEX = 1 + + class L2(object): + INTERMEDIATE_FREQUENCY_HZ = 11e5 + INDEX = 2 + + class Galileo(object): + + class E1(object): + INTERMEDIATE_FREQUENCY_HZ = 14.58e5 + INDEX = 0 + + class E6(object): + INTERMEDIATE_FREQUENCY_HZ = 43.75e5 + INDEX = 2 + + class E5b(object): + INTERMEDIATE_FREQUENCY_HZ = 27.86e5 + INDEX = 3 + + class Beidou(object): + + class B1(object): + INTERMEDIATE_FREQUENCY_HZ = 28.902e5 + INDEX = 0 + + class B2: + INTERMEDIATE_FREQUENCY_HZ = 27.86e5 + INDEX = 3 + + class B3(object): + INTERMEDIATE_FREQUENCY_HZ = 33.52e5 + INDEX = 2 + + +class NormalRateConfig(object): + ''' + Output control configuration for normal tests. + + Attributes + ---------- + NAME : string + Configuration name + SAMPLE_RATE_HZ : float + Sample rate in hertz for data generation. + SAMPLE_BATCH_SIZE : int + Size of the sample batch in samples. + GPS : object + GPS band information + Galileo : object + Galileo band information + Beidou : object + Beidou band information + Glonass : object + Glonass band information + ''' + NAME = "Normal rate configuration equivalent to decimated data output" + SAMPLE_RATE_HZ = 24.84375e6 + SAMPLE_BATCH_SIZE = 100000 + + class GPS(object): + ''' + Parameters for GPS bands data generation. + ''' + class L1(object): + INTERMEDIATE_FREQUENCY_HZ = 14.58e+6 + INDEX = 0 + + class L2(object): + INTERMEDIATE_FREQUENCY_HZ = 7.4e+6 + INDEX = 1 + + class Glonass(object): + + class L1(object): + INTERMEDIATE_FREQUENCY_HZ = 12e6 + INDEX = 1 + + class L2(object): + INTERMEDIATE_FREQUENCY_HZ = 11e6 + INDEX = 2 + + class Galileo(object): + + class E1(object): + INTERMEDIATE_FREQUENCY_HZ = 14.58e6 + INDEX = 0 + + class E6(object): + INTERMEDIATE_FREQUENCY_HZ = 43.75e6 + INDEX = 2 + + class E5b(object): + INTERMEDIATE_FREQUENCY_HZ = 27.86e6 + INDEX = 3 + + class Beidou(object): + + class B1(object): + INTERMEDIATE_FREQUENCY_HZ = 28.902e6 + INDEX = 0 + + class B2: + INTERMEDIATE_FREQUENCY_HZ = 27.86e6 + INDEX = 3 + + class B3(object): + INTERMEDIATE_FREQUENCY_HZ = 33.52e6 + INDEX = 2 + + +class HighRateConfig(object): + ''' + Output control configuration for high data rate tests. + + Attributes + ---------- + NAME : string + Configuration name + SAMPLE_RATE_HZ : float + Sample rate in hertz for data generation. + SAMPLE_BATCH_SIZE : int + Size of the sample batch in samples. + GPS : object + GPS band information + ''' + NAME = "High rate configuration equivalent to full rate data output" + SAMPLE_RATE_HZ = 99.375e6 + SAMPLE_BATCH_SIZE = 100000 + + GPS = NormalRateConfig.GPS + Glonass = NormalRateConfig.Glonass + Galileo = NormalRateConfig.Galileo + Beidou = NormalRateConfig.Beidou + + +class CustomRateConfig(object): + ''' + Output control configuration for comparison tests. + + Attributes + ---------- + NAME : string + Configuration name + SAMPLE_RATE_HZ : float + Sample rate in hertz for data generation. + SAMPLE_BATCH_SIZE : int + Size of the sample batch in samples. + GPS : object + GPS band information + ''' + NAME = "Custom configuration for fast tests" + SAMPLE_RATE_HZ = freq_profile_peregrine['sampling_freq'] + SAMPLE_BATCH_SIZE = 100000 + + class GPS(object): + + class L1(object): + INTERMEDIATE_FREQUENCY_HZ = freq_profile_peregrine['GPS_L1_IF'] + INDEX = 0 + + class L2(object): + INTERMEDIATE_FREQUENCY_HZ = freq_profile_peregrine['GPS_L2_IF'] + INDEX = 1 diff --git a/peregrine/iqgen/iqgen_main.py b/peregrine/iqgen/iqgen_main.py new file mode 100644 index 0000000..009a57b --- /dev/null +++ b/peregrine/iqgen/iqgen_main.py @@ -0,0 +1,695 @@ +#!/bin/python +# Copyright (C) 2016 Swift Navigation Inc. +# Contact: Valeri Atamaniouk +# +# 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. + +""" +The :mod:`peregrine.iqgen.iqgen_main` module contains classes and functions +related to parameter processing. + +""" +import time +import argparse +import scipy.constants +import numpy +import json +import logging +try: + import progressbar + hasProgressBar = True +except: + hasProgressBar = False + +from peregrine.iqgen.bits.satellite_gps import GPSSatellite + +# Doppler objects +from peregrine.iqgen.bits.doppler_poly import zeroDoppler +from peregrine.iqgen.bits.doppler_poly import constDoppler +from peregrine.iqgen.bits.doppler_poly import linearDoppler +from peregrine.iqgen.bits.doppler_sine import sineDoppler + +# Amplitude objects +from peregrine.iqgen.bits.amplitude_poly import AmplitudePoly +from peregrine.iqgen.bits.amplitude_sine import AmplitudeSine + +# TCXO objects +from peregrine.iqgen.bits.tcxo_poly import TCXOPoly +from peregrine.iqgen.bits.tcxo_sine import TCXOSine + +# from signals import GPS, GPS_L2C_Signal, GPS_L1CA_Signal +import peregrine.iqgen.bits.signals as signals + +from peregrine.iqgen.if_iface import LowRateConfig +from peregrine.iqgen.if_iface import NormalRateConfig +from peregrine.iqgen.if_iface import HighRateConfig +from peregrine.iqgen.if_iface import CustomRateConfig + +# Message data +from peregrine.iqgen.bits.message_const import Message as ConstMessage +from peregrine.iqgen.bits.message_zeroone import Message as ZeroOneMessage +from peregrine.iqgen.bits.message_block import Message as BlockMessage +from peregrine.iqgen.bits.message_cnav import Message as CNavMessage +from peregrine.iqgen.bits.message_lnav import Message as LNavMessage + +# PRN code generators +from peregrine.iqgen.bits.prn_gps_l1ca import PrnCode as GPS_L1CA_Code +from peregrine.iqgen.bits.prn_gps_l2c import PrnCode as GPS_L2C_Code + +# Bit stream encoders +from peregrine.iqgen.bits.encoder_gps import GPSL1BitEncoder +from peregrine.iqgen.bits.encoder_gps import GPSL2BitEncoder +from peregrine.iqgen.bits.encoder_gps import GPSL1L2BitEncoder +from peregrine.iqgen.bits.encoder_gps import GPSL1TwoBitsEncoder +from peregrine.iqgen.bits.encoder_gps import GPSL2TwoBitsEncoder +from peregrine.iqgen.bits.encoder_gps import GPSL1L2TwoBitsEncoder + +from peregrine.iqgen.generate import generateSamples + +from peregrine.iqgen.bits.satellite_factory import factoryObject as satelliteFO +from peregrine.iqgen.bits.tcxo_factory import factoryObject as tcxoFO + +logger = logging.getLogger(__name__) + + +def computeTimeDelay(doppler, symbol_index, chip_index, signal, code): + ''' + Helper function to compute signal delay to match given symbol and chip + indexes. + + Parameters + ---------- + doppler : object + Doppler object + symbol_index : long + Index of the symbol or pseudosymbol + chip_index : long + Chip index + signal : object + Signal object + code : object + Code object + + Returns + ------- + float + User's time in seconds when the user starts receiving the given symbol + and code. + ''' + if symbol_index == 0 and chip_index == 0: + return 0. + + symbolDelay_s = (1. / signal.SYMBOL_RATE_HZ) * symbol_index + chipDelay_s = (1. / signal.CODE_CHIP_RATE_HZ) * chip_index + distance_m = doppler.computeDistanceM(symbolDelay_s + chipDelay_s) + return distance_m / scipy.constants.c + + +def prepareArgsParser(): + ''' + Constructs command line argument parser. + + Returns + ------- + object + Command line argument parser object. + ''' + class AddSv(argparse.Action): + + def __init__(self, option_strings, dest, nargs=None, **kwargs): + super(AddSv, self).__init__(option_strings, dest, **kwargs) + + def __call__(self, parser, namespace, values, option_string=None): + # Initialize SV list if not yet done + if namespace.gps_sv is None: + namespace.gps_sv = [] + + # Add SV to the tail of the list. + sv = GPSSatellite(int(values)) + namespace.gps_sv.append(sv) + + # Reset all configuration parameters + namespace.l2cl_code_type = '01' + namespace.ignore_code_doppler = False + + # Doppler + namespace.doppler_type = "zero" + namespace.doppler_value = 0. + namespace.doppler_speed = 0. + namespace.distance = 0. + namespace.tec = 50. + namespace.doppler_amplitude = 0. + namespace.doppler_period = 1. + + # Source message data + namespace.message_type = "zero" + namespace.message_file = None + + # Amplitude parameters + namespace.amplitude_type = "poly" + namespace.amplitude_a0 = None + namespace.amplitude_a1 = None + namespace.amplitude_a2 = None + namespace.amplitude_a3 = None + namespace.amplitude_period = None + + class UpdateSv(argparse.Action): + + def __init__(self, option_strings, dest, nargs=None, **kwargs): + super(UpdateSv, self).__init__(option_strings, dest, **kwargs) + + def __call__(self, parser, namespace, values, option_string=None): + sv_list = getattr(namespace, "gps_sv") + if sv_list is None: + raise ValueError("No SV specified") + setattr(namespace, self.dest, values) + # super(UpdateSv, self).__call__(parser, namespace, values, option_string) + self.doUpdate(sv_list[len(sv_list) - 1], parser, namespace, values, + option_string) + + def doUpdate(self, sv, parser, namespace, values, option_string): + pass + + class UpdateBands(UpdateSv): + + def __init__(self, option_strings, dest, nargs=None, **kwargs): + super(UpdateBands, self).__init__(option_strings, dest, **kwargs) + + def doUpdate(self, sv, parser, namespace, values, option_string): + l1caEnabled = False + l2cEnabled = False + if namespace.bands == "l1ca": + l1caEnabled = True + elif namespace.bands == "l2c": + l2cEnabled = True + elif namespace.bands == "l1ca+l2c": + l1caEnabled = True + l2cEnabled = True + else: + raise ValueError() + sv.setL2CLCodeType(namespace.l2cl_code_type) + sv.setL1CAEnabled(l1caEnabled) + sv.setL2CEnabled(l2cEnabled) + + class UpdateDopplerType(UpdateSv): + + def __init__(self, option_strings, dest, nargs=None, **kwargs): + super(UpdateDopplerType, self).__init__(option_strings, dest, **kwargs) + + def doUpdate(self, sv, parser, namespace, values, option_string): + if sv.l1caEnabled: + frequency_hz = signals.GPS.L1CA.CENTER_FREQUENCY_HZ + elif sv.l2cEnabled: + frequency_hz = signals.GPS.L2C.CENTER_FREQUENCY_HZ + else: + raise ValueError("Signal band must be specified before doppler") + + if namespace.doppler_type == "zero": + doppler = zeroDoppler(namespace.distance, namespace.tec, frequency_hz) + elif namespace.doppler_type == "const": + doppler = constDoppler(namespace.distance, + namespace.tec, + frequency_hz, + namespace.doppler_value) + elif namespace.doppler_type == "linear": + doppler = linearDoppler(namespace.distance, + namespace.tec, + frequency_hz, + namespace.doppler_value, + namespace.doppler_speed) + elif namespace.doppler_type == "sine": + doppler = sineDoppler(namespace.distance, + namespace.tec, + frequency_hz, + namespace.doppler_value, + namespace.doppler_amplitude, + namespace.doppler_period) + else: + raise ValueError("Unsupported doppler type") + sv.doppler = doppler + + class DisableCodeDoppler(UpdateSv): + + def __init__(self, option_strings, dest, nargs=None, **kwargs): + super(DisableCodeDoppler, self).__init__(option_strings, dest, **kwargs) + + def doUpdate(self, sv, parser, namespace, values, option_string): + sv.getDoppler().setCodeDopplerDisabled(True) + + class UpdateAmplitudeType(UpdateSv): + + def __init__(self, option_strings, dest, nargs=None, **kwargs): + super(UpdateAmplitudeType, self).__init__(option_strings, dest, **kwargs) + + def doUpdate(self, sv, parser, namespace, values, option_string): + if namespace.amplitude_type == "poly": + coeffs = [] + hasHighOrder = False + + srcA = [namespace.amplitude_a3, namespace.amplitude_a2, + namespace.amplitude_a1, namespace.amplitude_a0] + for a in srcA: + if a is not None: + coeffs.append(a) + hasHighOrder = True + elif hasHighOrder: + coeffs.append(0.) + amplitude = AmplitudePoly(tuple(coeffs)) + elif namespace.amplitude_type == "sine": + initial = 1. + ampl = 0.5 + period_s = 1. + if namespace.amplitude_a0 is not None: + initial = namespace.amplitude_a0 + if namespace.amplitude_a1 is not None: + ampl = namespace.amplitude_a1 + if namespace.amplitude_period is not None: + period_s = namespace.amplitude_period + + amplitude = AmplitudeSine(initial, ampl, period_s) + else: + raise ValueError("Unsupported amplitude type") + sv.setAmplitude(amplitude) + + class UpdateTcxoType(UpdateSv): + + def __init__(self, option_strings, dest, nargs=None, **kwargs): + super(UpdateTcxoType, self).__init__(option_strings, dest, **kwargs) + + def doUpdate(self, sv, parser, namespace, values, option_string): + if namespace.tcxo_type == "poly": + coeffs = [] + hasHighOrder = False + + srcA = [namespace.tcxo_a3, namespace.tcxo_a2, + namespace.tcxo_a1, namespace.tcxo_a0] + for a in srcA: + if a is not None: + coeffs.append(a) + hasHighOrder = True + elif hasHighOrder: + coeffs.append(0.) + tcxo = TCXOPoly(coeffs) + elif namespace.tcxo_type == "sine": + initial = 0. + ampl = 0.5 + period_s = 1. + if namespace.tcxo_a0 is not None: + ampl = namespace.tcxo_a0 + if namespace.amplitude_a1 is not None: + ampl = namespace._a1 + if namespace.tcxo_period is not None: + period_s = namespace.tcxo_period + + tcxo = TCXOSine(initial, ampl, period_s) + else: + raise ValueError("Unsupported amplitude type") + namespace.tcxo = tcxo + + class UpdateMessageType(UpdateSv): + + def __init__(self, option_strings, dest, nargs=None, **kwargs): + super(UpdateMessageType, self).__init__(option_strings, dest, **kwargs) + + def doUpdate(self, sv, parser, namespace, values, option_string): + if namespace.message_type == "zero": + messageL1 = ConstMessage(1) + messageL2 = messageL1 + elif namespace.message_type == "one": + messageL1 = ConstMessage(-1) + messageL2 = messageL1 + elif namespace.message_type == "zero+one": + messageL1 = ZeroOneMessage() + messageL2 = messageL1 + elif namespace.message_type == "crc": + messageL1 = LNavMessage(sv.prn) + messageL2 = CNavMessage(sv.prn) + else: + raise ValueError("Unsupported message type") + sv.setL1CAMessage(messageL1) + sv.setL2CMessage(messageL2) + + class UpdateMessageFile(UpdateSv): + + def __init__(self, option_strings, dest, nargs=None, **kwargs): + super(UpdateMessageFile, self).__init__(option_strings, dest, **kwargs) + + def doUpdate(self, sv, parser, namespace, values, option_string): + data = numpy.fromfile(namespace.message_file, dtype=numpy.uint8) + namespace.message_file.close() + data = numpy.unpackbits(data) + data = numpy.asarray(data, dtype=numpy.int8) + data <<= 1 + data -= 1 + numpy.negative(data, out=data) + message = BlockMessage(data) + + sv.setL1CAMessage(message) + sv.setL2CMessage(message) + + class SaveConfigAction(argparse.Action): + + def __init__(self, option_strings, dest, nargs=None, **kwargs): + super(SaveConfigAction, self).__init__(option_strings, dest, **kwargs) + + def __call__(self, parser, namespace, values, option_string=None): + + gps_sv = namespace.gps_sv + + encoded_gps_sv = [satelliteFO.toMapForm(sv) for sv in gps_sv] + + data = {'type': 'Namespace', + 'gps_sv': encoded_gps_sv, + 'profile': namespace.profile, + 'encoder': namespace.encoder, + 'chip_delay': namespace.chip_delay, + 'symbol_delay': namespace.symbol_delay, + 'generate': namespace.generate, + 'snr': namespace.snr, + 'filter_type': namespace.filter_type, + 'tcxo': tcxoFO.toMapForm(namespace.tcxo) + } + json.dump(data, values, indent=2) + values.close() + namespace.no_run = True + + class LoadConfigAction(argparse.Action): + + def __init__(self, option_strings, dest, nargs=None, **kwargs): + super(LoadConfigAction, self).__init__(option_strings, dest, **kwargs) + + def __call__(self, parser, namespace, values, option_string=None): + loaded = json.load(values) + namespace.profile = loaded['profile'] + namespace.encoder = loaded['encoder'] + namespace.chip_delay = loaded['chip_delay'] + namespace.symbol_delay = loaded['symbol_delay'] + namespace.generate = loaded['generate'] + namespace.snr = loaded['snr'] + namespace.filter_type = loaded['filter_type'] + namespace.tcxo = tcxoFO.fromMapForm(loaded['tcxo']) + namespace.gps_sv = [ + satelliteFO.fromMapForm(sv) for sv in loaded['gps_sv']] + values.close() + + parser = argparse.ArgumentParser( + description="Signal generator", usage='%(prog)s [options]') + parser.add_argument('--gps-sv', + default=[], + help='Enable GPS satellite', + action=AddSv) + parser.add_argument('--bands', + default="l1ca", + choices=["l1ca", "l2c", "l1ca+l2c"], + help="Signal bands to enable", + action=UpdateBands) + parser.add_argument('--l2cl-code-type', + default='01', + choices=['01', '1', '0'], + help="GPS L2 CL code type", + action=UpdateBands) + parser.add_argument('--doppler-type', + default="zero", + choices=["zero", "const", "linear", "sine"], + help="Configure doppler type", + action=UpdateDopplerType) + parser.add_argument('--doppler-value', + type=float, + help="Doppler shift in hertz (initial)", + action=UpdateDopplerType) + parser.add_argument('--doppler-speed', + type=float, + help="Doppler shift change in hertz/second", + action=UpdateDopplerType) + parser.add_argument('--distance', + type=float, + help="Distance in meters for signal delay (initial)", + action=UpdateDopplerType) + parser.add_argument('--tec', + type=float, + help="Ionosphere TEC for signal delay" + " (electrons per meter^2)", + action=UpdateDopplerType) + parser.add_argument('--doppler-amplitude', + type=float, + help="Doppler change amplitude (hertz)", + action=UpdateDopplerType) + parser.add_argument('--doppler-period', + type=float, + help="Doppler change period (seconds)", + action=UpdateDopplerType) + parser.add_argument('--ignore-code-doppler', + help="Disable doppler for code and data processing", + action=DisableCodeDoppler) + parser.add_argument('--amplitude-type', + default="poly", + choices=["poly", "sine"], + help="Configure amplitude type: polynomial or sine.", + action=UpdateAmplitudeType) + parser.add_argument('--amplitude-a0', + type=float, + help="Amplitude coefficient (a0 for polynomial;" + " offset for sine)", + action=UpdateAmplitudeType) + parser.add_argument('--amplitude-a1', + type=float, + help="Amplitude coefficient (a1 for polynomial," + " amplitude for size)", + action=UpdateAmplitudeType) + parser.add_argument('--amplitude-a2', + type=float, + help="Amplitude coefficient (a2 for polynomial)", + action=UpdateAmplitudeType) + parser.add_argument('--amplitude-a3', + type=float, + help="Amplitude coefficient (a3 for polynomial)", + action=UpdateAmplitudeType) + parser.add_argument('--amplitude-period', + type=float, + help="Amplitude period in seconds for sine", + action=UpdateAmplitudeType) + parser.add_argument('--message-type', default="zero", + choices=["zero", "one", "zero+one", "crc"], + help="Message type", + action=UpdateMessageType) + parser.add_argument('--message-file', + type=argparse.FileType('rb'), + help="Source file for message contents.", + action=UpdateMessageFile) + parser.add_argument('--symbol_delay', + type=int, + help="Initial symbol index") + parser.add_argument('--chip_delay', + type=int, + help="Initial chip index") + parser.add_argument('--filter-type', + default='none', + choices=['none', 'lowpass', 'bandpass'], + help="Enable filter") + parser.add_argument('--snr', + type=float, + help="SNR for noise generation") + parser.add_argument('--tcxo-type', + choices=["poly", "sine"], + help="TCXO drift type", + action=UpdateTcxoType) + parser.add_argument('--tcxo-a0', + type=float, + help="TCXO a0 coefficient for polynomial TCXO drift" + " or initial shift for sine TCXO drift", + action=UpdateTcxoType) + parser.add_argument('--tcxo-a1', + type=float, + help="TCXO a1 coefficient for polynomial TCXO drift" + " or amplitude for sine TCXO drift", + action=UpdateTcxoType) + parser.add_argument('--tcxo-a2', + type=float, + help="TCXO a2 coefficient for polynomial TCXO drift", + action=UpdateTcxoType) + parser.add_argument('--tcxo-a3', + type=float, + help="TCXO a3 coefficient for polynomial TCXO drift", + action=UpdateTcxoType) + parser.add_argument('--tcxo-period', + type=float, + help="TCXO period in seconds for sine TCXO drift", + action=UpdateTcxoType) + parser.add_argument('--debug', + type=argparse.FileType('wb'), + help="Debug output file") + parser.add_argument('--generate', + type=float, + default=3., + help="Amount of data to generate, in seconds") + parser.add_argument('--encoder', + default="2bits", + choices=["1bit", "2bits"], + help="Output data format") + parser.add_argument('--output', + type=argparse.FileType('wb'), + help="Output file name") + parser.add_argument('--profile', + default="normal_rate", + choices=["low_rate", "normal_rate", "high_rate", + "custom_rate"], + help="Output profile configuration") + parser.add_argument('-j', '--jobs', + type=int, + default=0, + help="Use parallel threads") + + parser.add_argument('--save-config', + type=argparse.FileType('wt'), + help="Store configuration into file (implies --no-run)", + action=SaveConfigAction) + + parser.add_argument('--load-config', + type=argparse.FileType('rt'), + help="Restore configuration from file", + action=LoadConfigAction) + + parser.add_argument('--no-run', + action="store_true", + default=False, + help="Do not generate output.") + + parser.set_defaults(tcxo=TCXOPoly(())) + + return parser + + +def main(): + from peregrine.log import default_logging_config + default_logging_config() + + parser = prepareArgsParser() + args = parser.parse_args() + + if args.no_run: + return 0 + + if args.output is None: + parser.print_help() + return 0 + + if args.profile == "low_rate": + outputConfig = LowRateConfig + elif args.profile == "normal_rate": + outputConfig = NormalRateConfig + elif args.profile == "high_rate": + outputConfig = HighRateConfig + elif args.profile == "custom_rate": + outputConfig = CustomRateConfig + else: + raise ValueError() + + print "Output configuration:" + print " Description: ", outputConfig.NAME + print " Sampling rate: ", outputConfig.SAMPLE_RATE_HZ + print " Batch size: ", outputConfig.SAMPLE_BATCH_SIZE + print " GPS L1 IF: ", outputConfig.GPS.L1.INTERMEDIATE_FREQUENCY_HZ + print " GPS L2 IF: ", outputConfig.GPS.L2.INTERMEDIATE_FREQUENCY_HZ + print "Other parameters:" + print " TCXO: ", args.tcxo + print " SNR: ", args.snr + print " tSatellites: ", args.gps_sv + + # Check which signals are enabled on each of satellite to select proper + # output encoder + enabledGPSL1 = False + enabledGPSL2 = False + + for sv in args.gps_sv: + enabledGPSL1 |= sv.isBandEnabled(outputConfig.GPS.L1.INDEX, outputConfig) + enabledGPSL2 |= sv.isBandEnabled(outputConfig.GPS.L2.INDEX, outputConfig) + + # Configure data encoder + if args.encoder == "1bit": + if enabledGPSL1 and enabledGPSL2: + encoder = GPSL1L2BitEncoder(outputConfig) + elif enabledGPSL2: + encoder = GPSL2BitEncoder(outputConfig) + else: + encoder = GPSL1BitEncoder(outputConfig) + elif args.encoder == "2bits": + if enabledGPSL1 and enabledGPSL2: + encoder = GPSL1L2TwoBitsEncoder(outputConfig) + elif enabledGPSL2: + encoder = GPSL2TwoBitsEncoder(outputConfig) + else: + encoder = GPSL1TwoBitsEncoder(outputConfig) + else: + raise ValueError("Encoder type is not supported") + + if enabledGPSL1: + signal = signals.GPS.L1CA + code = GPS_L1CA_Code + elif enabledGPSL2: + signal = signals.GPS.L2C + code = GPS_L2C_Code + else: + signal = signals.GPS.L1CA + code = GPS_L1CA_Code + + # Compute time delay for the needed bit/chip number + # This delay is computed for the first satellite + initial_symbol_idx = 0 # Initial symbol index + initial_chip_idx = 0 # Initial chip index + if args.chip_delay is not None: + initial_chip_idx = args.chip_delay + if args.symbol_delay is not None: + initial_chip_idx = args.symbol_delay + + time0_s = computeTimeDelay(args.gps_sv[0].doppler, + initial_symbol_idx, + initial_chip_idx, + signal, + code) + logger.debug("Computed symbol/chip delay={} seconds".format(time0_s)) + + startTime_s = time.time() + n_samples = long(outputConfig.SAMPLE_RATE_HZ * args.generate) + + logger.debug("Generating {} samples for {} seconds". + format(n_samples, args.generate)) + + if hasProgressBar: + widgets = ['Generating ', + progressbar.Counter(), ' ', + progressbar.Percentage(), ' ', + progressbar.ETA(), ' ', + progressbar.Bar()] + pbar = progressbar.ProgressBar(widgets=widgets, + maxval=n_samples).start() + else: + pbar = None + + generateSamples(args.output, + args.gps_sv, + encoder, + time0_s, + n_samples, + outputConfig, + tcxo=args.tcxo, + SNR=args.snr, + filterType=args.filter_type, + logFile=args.debug, + threadCount=args.jobs, + pbar=pbar) + args.output.close() + # if pbar: + # pbar.finish() + + duration_s = time.time() - startTime_s + ratio = n_samples / duration_s + logger.debug("Total time = {} sec. Ratio={} samples per second". + format(duration_s, ratio)) + +if __name__ == '__main__': + main() From dd027889c8de87a2199b78dee8c9c38f484e385a Mon Sep 17 00:00:00 2001 From: Adel Mamin Date: Mon, 29 Feb 2016 13:42:58 +0200 Subject: [PATCH 38/57] Fix 2bit one signal case --- peregrine/analysis/tracking_loop.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/peregrine/analysis/tracking_loop.py b/peregrine/analysis/tracking_loop.py index c6d3d1b..a3adb7d 100644 --- a/peregrine/analysis/tracking_loop.py +++ b/peregrine/analysis/tracking_loop.py @@ -180,11 +180,15 @@ def main(): channel = 0 if len(signals) > 1: + samples = [ {'data': signals[defaults.sample_channel_GPS_L1], 'IF': IF}, + {'data': signals[defaults.sample_channel_GPS_L2], 'IF': IF} ] if isL1CA: channel = 0 else: channel = 1 pass + else: + samples = [ {'data': signals[defaults.sample_channel_GPS_L1], 'IF': IF} ] acq_result = AcquisitionResult(prn = prn, snr = 25, # dB @@ -196,13 +200,11 @@ def main(): sample_channel = channel, sample_index = 0) - track_results = track(samples = [ - {'data': signals[defaults.sample_channel_GPS_L1], 'IF': IF}, - {'data': signals[defaults.sample_channel_GPS_L2], 'IF': IF} ], - channels = [acq_result], - ms_to_track = ms_to_track, - sampling_freq = sampling_freq, # [Hz] - l2c_handover = False) + track_results = track(samples = samples, + channels = [acq_result], + ms_to_track = ms_to_track, + sampling_freq = sampling_freq, # [Hz] + l2c_handover = False) dump_tracking_results_for_analysis(args.output_file, track_results) From 869d76bd1ffa45ac4981734172c4bc1fb6ac86de Mon Sep 17 00:00:00 2001 From: Adel Mamin Date: Wed, 2 Mar 2016 14:52:33 +0200 Subject: [PATCH 39/57] Remove --IF and add --l2c-hadover to the tracking loop utility --- peregrine/analysis/tracking_loop.py | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/peregrine/analysis/tracking_loop.py b/peregrine/analysis/tracking_loop.py index a3adb7d..a9d734a 100644 --- a/peregrine/analysis/tracking_loop.py +++ b/peregrine/analysis/tracking_loop.py @@ -76,9 +76,6 @@ def main(): "(-1: use all available data", default = "-1") - parser.add_argument("-I", "--IF", - help = "intermediate frequency [Hz]. ") - parser.add_argument("-s", "--sampling-freq", help = "sampling frequency [Hz]. "); @@ -105,6 +102,10 @@ def main(): choices = [L1CA, L2C], help = "Signal type (l1ca / l2c)") + parser.add_argument("--l2c-handover", + action = 'store_true', + help = "Perform L2C handover") + args = parser.parse_args() if args.profile == 'peregrine' or args.profile == 'custom_rate': @@ -130,8 +131,10 @@ def main(): else: raise NotImplementedError() - if args.IF is not None: - IF = float(args.IF) + if args.l2c_handover is not None and not isL2C: + l2c_handover = True + else: + l2c_handover = False if args.sampling_freq is not None: sampling_freq = float(args.sampling_freq) # [Hz] @@ -170,7 +173,8 @@ def main(): print "File format: %s" % args.file_format print "PRN to track [1-32]: %s" % args.prn print "Time to process [ms]: %s" % ms_to_track - print "IF [Hz]: %f" % IF + print "L1 IF [Hz]: %f" % freq_profile['GPS_L1_IF'] + print "L2 IF [Hz]: %f" % freq_profile['GPS_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 @@ -180,15 +184,18 @@ def main(): channel = 0 if len(signals) > 1: - samples = [ {'data': signals[defaults.sample_channel_GPS_L1], 'IF': IF}, - {'data': signals[defaults.sample_channel_GPS_L2], 'IF': IF} ] + samples = [ {'data': signals[defaults.sample_channel_GPS_L1], + 'IF': freq_profile['GPS_L1_IF']}, + {'data': signals[defaults.sample_channel_GPS_L2], + 'IF': freq_profile['GPS_L2_IF']} ] if isL1CA: channel = 0 else: channel = 1 pass else: - samples = [ {'data': signals[defaults.sample_channel_GPS_L1], 'IF': IF} ] + samples = [ {'data': signals[defaults.sample_channel_GPS_L1], + 'IF': freq_profile['GPS_L1_IF']} ] acq_result = AcquisitionResult(prn = prn, snr = 25, # dB @@ -204,7 +211,7 @@ def main(): channels = [acq_result], ms_to_track = ms_to_track, sampling_freq = sampling_freq, # [Hz] - l2c_handover = False) + l2c_handover = l2c_handover) dump_tracking_results_for_analysis(args.output_file, track_results) From d04018f638536f32ffe349bef804608445eb32cd Mon Sep 17 00:00:00 2001 From: Adel Mamin Date: Thu, 3 Mar 2016 15:14:15 +0200 Subject: [PATCH 40/57] Fix bug in L2C handover sample offset computation --- peregrine/acquisition.py | 2 +- peregrine/analysis/tracking_loop.py | 13 ++++++++++--- peregrine/defaults.py | 2 +- peregrine/tracking.py | 24 +++++++----------------- 4 files changed, 19 insertions(+), 22 deletions(-) diff --git a/peregrine/acquisition.py b/peregrine/acquisition.py index 028da5d..1e12a05 100644 --- a/peregrine/acquisition.py +++ b/peregrine/acquisition.py @@ -543,7 +543,7 @@ class AcquisitionResult: def __init__(self, prn, carr_freq, doppler, code_phase, snr, status, signal, sample_channel = 0, - sample_index = None): + sample_index = 0): self.prn = prn self.snr = snr self.carr_freq = carr_freq diff --git a/peregrine/analysis/tracking_loop.py b/peregrine/analysis/tracking_loop.py index a9d734a..252809d 100644 --- a/peregrine/analysis/tracking_loop.py +++ b/peregrine/analysis/tracking_loop.py @@ -36,7 +36,8 @@ def dump_tracking_results_for_analysis(output_file, track_results): "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\n") + "lock_detect_lpfi,lock_detect_lpfq,alias_detect_err_hz," + "code_phase_acc\n") for i in range(len(track_results[j].carr_phase)): f1.write("%s," % track_results[j].IF) f1.write("%s," % track_results[j].carr_phase[i]) @@ -57,7 +58,8 @@ def dump_tracking_results_for_analysis(output_file, track_results): f1.write("%s," % track_results[j].lock_detect_pcount2[i]) f1.write("%s," % track_results[j].lock_detect_lpfi[i]) f1.write("%s," % track_results[j].lock_detect_lpfq[i]) - f1.write("%s\n" % track_results[j].alias_detect_err_hz[i]) + f1.write("%s," % track_results[j].alias_detect_err_hz[i]) + f1.write("%s\n" % track_results[j].code_phase_acc[i]) def main(): default_logging_config() @@ -106,8 +108,13 @@ def main(): action = 'store_true', help = "Perform L2C handover") + parser.add_argument("--skip-samples", default = 0, + help = "How many samples to skip") + args = parser.parse_args() + skip_samples = int(args.skip_samples) + if args.profile == 'peregrine' or args.profile == 'custom_rate': freq_profile = defaults.freq_profile_peregrine elif args.profile == 'low_rate': @@ -161,7 +168,7 @@ def main(): samples_num = -1 # all available samples signals = load_samples(args.file, int(samples_num), - 0, # skip samples + skip_samples, file_format = args.file_format) if ms_to_track < 0: diff --git a/peregrine/defaults.py b/peregrine/defaults.py index 4c2228d..3794f4c 100644 --- a/peregrine/defaults.py +++ b/peregrine/defaults.py @@ -103,7 +103,7 @@ l2c_loop_filter_params = { "loop_freq": 50, # loop frequency [Hz] - "code_bw": 1, # Code loop NBW + "code_bw": 1.4, # Code loop NBW "code_zeta": 0.707, # Code loop zeta "code_k": 1, # Code loop k "carr_to_code": 1200, # Carrier-to-code freq ratio (carrier aiding) diff --git a/peregrine/tracking.py b/peregrine/tracking.py index 8db6bda..94d1998 100644 --- a/peregrine/tracking.py +++ b/peregrine/tracking.py @@ -113,9 +113,8 @@ def track(samples, channels, n_channels = len(channels) - samples_length_ms = int(1e3 * - len(samples[defaults.sample_channel_GPS_L1]['data']) / - sampling_freq) + total_samples_num = len(samples[defaults.sample_channel_GPS_L1]['data']) + samples_length_ms = int(1e3 * total_samples_num / sampling_freq) if ms_to_track is None: ms_to_track = samples_length_ms @@ -179,7 +178,7 @@ def do_channel(chan, n=None, q_progress=None): (chan.prn + 1, chan.signal, IF, chan.doppler, chan.code_phase, chan.sample_channel, - chan.sample_index if chan.sample_index else 0)) + chan.sample_index)) if isL1CA: loop_filter_params = defaults.l1ca_stage1_loop_filter_params @@ -256,7 +255,8 @@ def do_channel(chan, n=None, q_progress=None): samples_per_chip = int(round(sampling_freq / chipping_rate)) # Set sample_index to start on a code rollover - sample_index = chan.code_phase * samples_per_chip + sample_index = chan.sample_index + sample_index += chan.code_phase * samples_per_chip # Start in 1ms integration until we know the nav bit phase stage1 = True @@ -267,19 +267,9 @@ def do_channel(chan, n=None, q_progress=None): progress = 0 ms_tracked = 0 i = 0 - # For L2C, proceed in steps of full milliseconds up to the ms when - # handover succeeded. Do not set sample_index := chan.sample_index - # since the sub ms part of sample_index presents code phase. - # Therefore, skip just full ms steps to preserve code phase. - if isL2C: - samples_per_ms = sampling_freq * defaults.code_period - skip_ms = int((chan.sample_index - sample_index) / samples_per_ms) - skip_samples = skip_ms * samples_per_ms - sample_index += skip_samples - ms_tracked += skip_ms # Process the specified number of ms - while ms_tracked < ms_to_track: + while ms_tracked < ms_to_track and sample_index < total_samples_num: if pbar: pbar.update(ms_tracked + n * num_points, attr={'chan': n + 1}) @@ -706,7 +696,7 @@ def update_bit_sync(self, corr, ms): class NBSMatchBit(NavBitSync): - def __init__(self, thres=22): + def __init__(self, thres=25): NavBitSync.__init__(self) self.hist = np.zeros(20) self.acc = 0 From 5dd6ef045fec6240b4d0b8d1f260c381d75e9d0a Mon Sep 17 00:00:00 2001 From: Valeri Atamaniouk Date: Fri, 4 Mar 2016 13:04:16 +0200 Subject: [PATCH 41/57] iqgen: fixed bug with 2 bit encoding Resolved issue with incorrect sample magnitude computation for two bit encoding. --- peregrine/iqgen/bits/encoder_2bits.py | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/peregrine/iqgen/bits/encoder_2bits.py b/peregrine/iqgen/bits/encoder_2bits.py index 4fad87f..286a884 100644 --- a/peregrine/iqgen/bits/encoder_2bits.py +++ b/peregrine/iqgen/bits/encoder_2bits.py @@ -66,21 +66,15 @@ def convertBand(band_samples): totalPower = numpy.sum(power) totalPowerLimit = totalPower * 0.67 - # Build histrogram to find 67% power + # Build histogram to find 67% power + totalBins = 30 hist, edges = numpy.histogram(power, - bins=10, - density=True) - lastPower = 0. - powerLimit = 0. - for i in range(10): - # Approximate power of samples in the bin - entryPower = hist[i] * (edges[i] + edges[i + 1]) / 2. - newPower = lastPower + entryPower - if newPower > totalPowerLimit: - powerLimit = edges[i] - break - else: - lastPower = newPower + bins=totalBins, + density=False) + avg = (edges[:-1] + edges[1:]) * 0.5 + powers = numpy.cumsum(hist * avg) + idx = numpy.searchsorted(powers, totalPowerLimit, side="right") + powerLimit = avg[idx] # Signal sign signs = band_samples > 0 From d648ba72c5567c60bb8a48e732bb34c890fa948f Mon Sep 17 00:00:00 2001 From: Dmitry Tatarinov Date: Fri, 26 Feb 2016 11:53:09 +0200 Subject: [PATCH 42/57] Correct BW calculation for CN0 estimator --- peregrine/tracking.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/peregrine/tracking.py b/peregrine/tracking.py index 94d1998..ef2e565 100644 --- a/peregrine/tracking.py +++ b/peregrine/tracking.py @@ -221,7 +221,7 @@ def do_channel(chan, n=None, q_progress=None): time_diff = 1) cn0_est = swiftnav.track.CN0Estimator( - bw=1e3, + bw=1e3/coherent_ms, cn0_0=cn0_0, cutoff_freq=10, loop_freq=loop_filter_params["loop_freq"] From 03dda79d818a1956afa3d8b07f3b61c8af5685a1 Mon Sep 17 00:00:00 2001 From: Valeri Atamaniouk Date: Tue, 15 Mar 2016 15:04:27 +0200 Subject: [PATCH 43/57] peregrine: adding stage profiles for over 1ms coherent integration Added L1 C/A two-stage profiles according to firmware implementation. Added new parameters to select L1 C/A stage profile. --- peregrine/analysis/tracking_loop.py | 115 ++++++++++++++++------------ peregrine/defaults.py | 105 +++++++++++++++++++++++-- peregrine/run.py | 55 ++++++++----- peregrine/tracking.py | 113 ++++++++++++++------------- 4 files changed, 262 insertions(+), 126 deletions(-) diff --git a/peregrine/analysis/tracking_loop.py b/peregrine/analysis/tracking_loop.py index 252809d..b058367 100644 --- a/peregrine/analysis/tracking_loop.py +++ b/peregrine/analysis/tracking_loop.py @@ -18,6 +18,7 @@ from peregrine.gps_constants import L1CA, L2C from peregrine.initSettings import initSettings + def dump_tracking_results_for_analysis(output_file, track_results): output_filename, output_file_extension = os.path.splitext(output_file) @@ -26,8 +27,8 @@ def dump_tracking_results_for_analysis(output_file, track_results): if len(track_results) > 1: # mangle the result file name with the tracked signal name filename = output_filename + \ - (".%s.%d" % (track_results[j].signal, track_results[j].prn + 1)) +\ - output_file_extension + (".%s.%d" % (track_results[j].signal, track_results[j].prn + 1)) +\ + output_file_extension else: filename = output_file @@ -61,58 +62,67 @@ def dump_tracking_results_for_analysis(output_file, track_results): f1.write("%s," % track_results[j].alias_detect_err_hz[i]) f1.write("%s\n" % track_results[j].code_phase_acc[i]) + def main(): default_logging_config() parser = argparse.ArgumentParser() parser.add_argument("file", - help = "the sample data file to process") + help="the sample data file to process") parser.add_argument("-f", "--file-format", - help = "the format of the sample data file " + help="the format of the sample data file " "('piksi', 'int8', '1bit', '1bitrev', " "'1bit_x2', '2bits', '2bits_x2', '2bits_x4')") parser.add_argument("-t", "--ms-to-track", - help = "the number of milliseconds to track." + help="the number of milliseconds to track." "(-1: use all available data", - default = "-1") + default="-1") parser.add_argument("-s", "--sampling-freq", - help = "sampling frequency [Hz]. "); + help="sampling frequency [Hz]. ") parser.add_argument("--profile", help="L1C/A & L2C IF + sampling frequency profile" "('peregrine'/'custom_rate', 'low_rate', " "'normal_rate' (piksi_v3), 'high_rate')", - default = 'peregrine') + default='peregrine') parser.add_argument("-P", "--prn", - help = "PRN to track. ") + help="PRN to track. ") parser.add_argument("-p", "--code-phase", - help = "code phase [chips]. ") + help="code phase [chips]. ") parser.add_argument("-d", "--carr-doppler", - help = "carrier Doppler frequency [Hz]. ") + help="carrier Doppler frequency [Hz]. ") - parser.add_argument("-o", "--output-file", default = "track.csv", - help = "Track results file name. " + parser.add_argument("-o", "--output-file", default="track.csv", + help="Track results file name. " "Default: %s" % "track.csv") parser.add_argument("-S", "--signal", - choices = [L1CA, L2C], - help = "Signal type (l1ca / l2c)") + choices=[L1CA, L2C], + help="Signal type (l1ca / l2c)") parser.add_argument("--l2c-handover", - action = 'store_true', - help = "Perform L2C handover") + action='store_true', + help="Perform L2C handover") + + parser.add_argument('--l1ca-profile', + help='L1 C/A stage profile', + choices=defaults.l1ca_stage_profiles.keys()) - parser.add_argument("--skip-samples", default = 0, - help = "How many samples to skip") + parser.add_argument("--skip-samples", default=0, + help="How many samples to skip") args = parser.parse_args() + if args.file is None: + parser.print_help() + return + skip_samples = int(args.skip_samples) if args.profile == 'peregrine' or args.profile == 'custom_rate': @@ -138,7 +148,7 @@ def main(): else: raise NotImplementedError() - if args.l2c_handover is not None and not isL2C: + if args.l2c_handover and not isL2C: l2c_handover = True else: l2c_handover = False @@ -146,16 +156,13 @@ def main(): if args.sampling_freq is not None: sampling_freq = float(args.sampling_freq) # [Hz] else: - sampling_freq = freq_profile['sampling_freq'] # [Hz] + sampling_freq = freq_profile['sampling_freq'] # [Hz] # Initialize constants, settings settings = initSettings(freq_profile) settings.fileName = args.file - samplesPerCode = int(round(sampling_freq / - (settings.codeFreqBasis / settings.codeLength))) - carr_doppler = float(args.carr_doppler) code_phase = float(args.code_phase) prn = int(args.prn) - 1 @@ -165,11 +172,11 @@ def main(): if ms_to_track > 0: samples_num = sampling_freq * 1e-3 * ms_to_track else: - samples_num = -1 # all available samples + samples_num = -1 # all available samples signals = load_samples(args.file, int(samples_num), skip_samples, - file_format = args.file_format) + file_format=args.file_format) if ms_to_track < 0: # use all available data @@ -187,38 +194,50 @@ def main(): print "Initial code phase [chips]: %s" % code_phase print "Track results file name: %s" % args.output_file print "Signal: %s" % args.signal + print "L1 stage profile: %s" % args.l1ca_profile print "======================================================================" channel = 0 if len(signals) > 1: - samples = [ {'data': signals[defaults.sample_channel_GPS_L1], - 'IF': freq_profile['GPS_L1_IF']}, - {'data': signals[defaults.sample_channel_GPS_L2], - 'IF': freq_profile['GPS_L2_IF']} ] + samples = [{'data': signals[defaults.sample_channel_GPS_L1], + 'IF': freq_profile['GPS_L1_IF']}, + {'data': signals[defaults.sample_channel_GPS_L2], + 'IF': freq_profile['GPS_L2_IF']}] if isL1CA: channel = 0 else: channel = 1 pass else: - samples = [ {'data': signals[defaults.sample_channel_GPS_L1], - 'IF': freq_profile['GPS_L1_IF']} ] - - acq_result = AcquisitionResult(prn = prn, - snr = 25, # dB - carr_freq = IF + carr_doppler, - doppler = carr_doppler, - code_phase = code_phase, - status = 'A', - signal = signal, - sample_channel = channel, - sample_index = 0) - - track_results = track(samples = samples, - channels = [acq_result], - ms_to_track = ms_to_track, - sampling_freq = sampling_freq, # [Hz] - l2c_handover = l2c_handover) + samples = [{'data': signals[defaults.sample_channel_GPS_L1], + 'IF': freq_profile['GPS_L1_IF']}] + + acq_result = AcquisitionResult(prn=prn, + snr=25, # dB + carr_freq=IF + carr_doppler, + doppler=carr_doppler, + code_phase=code_phase, + status='A', + signal=signal, + sample_channel=channel, + sample_index=0) + + if args.l1ca_profile: + profile = defaults.l1ca_stage_profiles[args.l1ca_profile] + stage2_coherent_ms = profile[1]['coherent_ms'] + stage2_params = profile[1]['loop_filter_params'] + print "S2=", stage2_params + else: + stage2_coherent_ms = None + stage2_params = None + + track_results = track(samples=samples, + channels=[acq_result], + ms_to_track=ms_to_track, + sampling_freq=sampling_freq, # [Hz] + l2c_handover=l2c_handover, + stage2_coherent_ms=stage2_coherent_ms, + stage2_loop_filter_params=stage2_params) dump_tracking_results_for_analysis(args.output_file, track_results) diff --git a/peregrine/defaults.py b/peregrine/defaults.py index 3794f4c..a9802d7 100644 --- a/peregrine/defaults.py +++ b/peregrine/defaults.py @@ -25,7 +25,7 @@ file_encoding_1bit_x2 = [ sample_channel_GPS_L1, # GPS L1 - sample_channel_GPS_L2 ] # GPS L2 + sample_channel_GPS_L2] # GPS L2 file_encoding_2bits_x2 = file_encoding_1bit_x2 @@ -61,31 +61,31 @@ file_encoding_profile = { '1bit_x2': file_encoding_1bit_x2, '2bits_x2': file_encoding_2bits_x2, - '2bits_x4': file_encoding_2bits_x4 } + '2bits_x4': file_encoding_2bits_x4} # 'peregrine' frequencies profile freq_profile_peregrine = { 'GPS_L1_IF': 4.092e6, 'GPS_L2_IF': 4.092e6, - 'sampling_freq': 16.368e6 } + 'sampling_freq': 16.368e6} # 'low_rate' frequencies profile freq_profile_low_rate = { 'GPS_L1_IF': 14.58e5, 'GPS_L2_IF': 7.4e5, - 'sampling_freq': 24.84375e5 } + 'sampling_freq': 24.84375e5} # 'normal_rate' frequencies profile freq_profile_normal_rate = { 'GPS_L1_IF': 14.58e6, 'GPS_L2_IF': 7.4e6, - 'sampling_freq': 24.84375e6 } + 'sampling_freq': 24.84375e6} # 'normal_rate' frequencies profile freq_profile_high_rate = { 'GPS_L1_IF': freq_profile_normal_rate['GPS_L1_IF'], 'GPS_L2_IF': freq_profile_normal_rate['GPS_L2_IF'], - 'sampling_freq': 99.375e6 } + 'sampling_freq': 99.375e6} L1CA_CHANNEL_BANDWIDTH_HZ = 1000 L2C_CHANNEL_BANDWIDTH_HZ = 1000 @@ -95,7 +95,7 @@ "code_bw": 1, # Code loop NBW "code_zeta": 0.7, # Code loop zeta "code_k": 1, # Code loop k - "carr_to_code": 1540, # Carrier-to-code freq ratio (carrier aiding) + "carr_to_code": 1540, # Carrier-to-code freq ratio (carrier aiding) "carr_bw": 10, # Carrier loop NBW "carr_zeta": 0.7, # Carrier loop zeta "carr_k": 1, # Carrier loop k @@ -106,12 +106,101 @@ "code_bw": 1.4, # Code loop NBW "code_zeta": 0.707, # Code loop zeta "code_k": 1, # Code loop k - "carr_to_code": 1200, # Carrier-to-code freq ratio (carrier aiding) + "carr_to_code": 1200, # Carrier-to-code freq ratio (carrier aiding) "carr_bw": 13, # Carrier loop NBW "carr_zeta": 0.707, # Carrier loop zeta "carr_k": 1, # Carrier loop k "carr_freq_b1": 5} # Carrier loop aiding_igain + +# Tracking stages. See track.c for more details. +# 1;20 ms stages +l1ca_stage_params_slow = \ + ({'coherent_ms': 1, + 'loop_filter_params': {'code_params': (1., 0.7, 1.), # NBW, zeta, k + 'carr_params': (10., 0.7, 1.), # NBW, zeta, k + 'loop_freq': 1000., # 1000/coherent_ms + 'carr_freq_igain': 5., # fll_aid + 'carr_to_code': 1540. # carr_to_code + } + }, + {'coherent_ms': 20, + 'loop_filter_params': {'code_params': (1., 0.7, 1.), # NBW, zeta, k + 'carr_params': (12., 0.7, 1.), # NBW, zeta, k + 'loop_freq': 1000. / 20, # 1000/coherent_ms + 'carr_freq_igain': 0., # fll_aid + 'carr_to_code': 1540. # carr_to_code + } + } + ) + +# 1;5 ms stages +l1ca_stage_params_med = \ + ({'coherent_ms': 1, + 'loop_filter_params': {'code_params': (1., 0.7, 1.), # NBW, zeta, k + 'carr_params': (10., 0.7, 1.), # NBW, zeta, k + 'loop_freq': 1000., # 1000/coherent_ms + 'carr_freq_igain': 5., # fll_aid + 'carr_to_code': 1540. # carr_to_code + } + }, + + {'coherent_ms': 5, + 'loop_filter_params': {'code_params': (1., 0.7, 1.), # NBW, zeta, k + 'carr_params': (50., 0.7, 1.), # NBW, zeta, k + 'loop_freq': 1000. / 5, # 1000/coherent_ms + 'carr_freq_igain': 0., # fll_aid + 'carr_to_code': 1540. # carr_to_code + } + } + ) + +# 1;4 ms stages +l1ca_stage_params_fast = \ + ({'coherent_ms': 1, + 'loop_filter_params': {'code_params': (1., 0.7, 1.), # NBW, zeta, k + 'carr_params': (10., 0.7, 1.), # NBW, zeta, k + 'loop_freq': 1000., # 1000/coherent_ms + 'carr_freq_igain': 5., # fll_aid + 'carr_to_code': 1540. # carr_to_code + } + }, + {'coherent_ms': 4, + 'loop_filter_params': {'code_params': (1., 0.7, 1.), # NBW, zeta, k + 'carr_params': (62., 0.7, 1.), # NBW, zeta, k + 'loop_freq': 1000. / 4, # 1000/coherent_ms + 'carr_freq_igain': 0., # fll_aid + 'carr_to_code': 1540. # carr_to_code + } + } + ) + +# 1;2 ms stages +l1ca_stage_params_extrafast = \ + ({'coherent_ms': 1, + 'loop_filter_params': {'code_params': (1., 0.7, 1.), # NBW, zeta, k + 'carr_params': (10., 0.7, 1.), # NBW, zeta, k + 'loop_freq': 1000., # 1000/coherent_ms + 'carr_freq_igain': 5., # fll_aid + 'carr_to_code': 1540. # carr_to_code + } + }, + {'coherent_ms': 2, + 'loop_filter_params': {'code_params': (1., 0.7, 1.), # NBW, zeta, k + 'carr_params': (100., 0.7, 1.), # NBW, zeta, k + 'loop_freq': 1000. / 2, # 1000/coherent_ms + 'carr_freq_igain': 0., # fll_aid + 'carr_to_code': 1540. # carr_to_code + } + } + ) + +# L1 C/A stage profiles +l1ca_stage_profiles = {'slow': l1ca_stage_params_slow, + 'med': l1ca_stage_params_med, + 'fast': l1ca_stage_params_fast, + 'extrafast': l1ca_stage_params_extrafast} + # pessimistic set l1ca_lock_detect_params_pess = {"k1": 0.10, "k2": 1.4, "lp": 200, "lo": 50} diff --git a/peregrine/run.py b/peregrine/run.py index 3532aaa..40c7040 100755 --- a/peregrine/run.py +++ b/peregrine/run.py @@ -22,9 +22,9 @@ from peregrine.tracking import track from peregrine.log import default_logging_config from peregrine.analysis.tracking_loop import dump_tracking_results_for_analysis -import defaults +from peregrine import defaults +from peregrine.initSettings import initSettings -from initSettings import initSettings def main(): default_logging_config() @@ -42,20 +42,27 @@ def main(): help="use previously saved navigation results", action="store_true") parser.add_argument("--ms-to-process", - help = "the number of milliseconds to process." + help="the number of milliseconds to process." "(-1: use all available data", - default = "-1") + default="-1") parser.add_argument("--profile", help="L1C/A & L2C IF + sampling frequency profile" "('peregrine'/'custom_rate', 'low_rate', " "'normal_rate' (piksi_v3), 'high_rate')", - default = 'peregrine') + default='peregrine') parser.add_argument("-f", "--file-format", default=defaults.file_format, help="the format of the sample data file " "('piksi', 'int8', '1bit', '1bitrev', " "'1bit_x2', '2bits', '2bits_x2', '2bits_x4')") + parser.add_argument('--l1ca-profile', + help='L1 C/A stage profile', + choices=defaults.l1ca_stage_profiles.keys()) args = parser.parse_args() + if args.file is None: + parser.print_help() + return + if args.profile == 'peregrine' or args.profile == 'custom_rate': freq_profile = defaults.freq_profile_peregrine elif args.profile == 'low_rate': @@ -67,6 +74,14 @@ def main(): else: raise NotImplementedError() + if args.l1ca_profile: + profile = defaults.l1ca_stage_profiles[args.l1ca_profile] + stage2_coherent_ms = profile[1]['coherent_ms'] + stage2_params = profile[1]['loop_filter_params'] + else: + stage2_coherent_ms = None + stage2_params = None + settings = initSettings(freq_profile) settings.fileName = args.file @@ -74,7 +89,7 @@ def main(): if ms_to_process > 0: samples_num = freq_profile['sampling_freq'] * 1e-3 * ms_to_process else: - samples_num = -1 # all available samples + samples_num = -1 # all available samples samplesPerCode = int(round(settings.samplingFreq / (settings.codeFreqBasis / settings.codeLength))) @@ -141,16 +156,19 @@ def main(): settings.msToProcess = ms_to_process - 22 if len(signal) > 1: - samples = [ {'data': signal[defaults.sample_channel_GPS_L1], - 'IF': freq_profile['GPS_L1_IF']}, - {'data': signal[defaults.sample_channel_GPS_L2], - 'IF': freq_profile['GPS_L2_IF']} ] + samples = [{'data': signal[defaults.sample_channel_GPS_L1], + 'IF': freq_profile['GPS_L1_IF']}, + {'data': signal[defaults.sample_channel_GPS_L2], + 'IF': freq_profile['GPS_L2_IF']}] else: - samples = [ {'data': signal[defaults.sample_channel_GPS_L1], - 'IF': freq_profile['GPS_L1_IF']} ] - - track_results = track( samples, acq_results, - settings.msToProcess, freq_profile['sampling_freq']) + samples = [{'data': signal[defaults.sample_channel_GPS_L1], + 'IF': freq_profile['GPS_L1_IF']}] + + track_results = track(samples, acq_results, + settings.msToProcess, + freq_profile['sampling_freq'], + stage2_coherent_ms=stage2_coherent_ms, + stage2_loop_filter_params=stage2_params) try: with open(track_results_file, 'wb') as f: cPickle.dump(track_results, f, protocol=cPickle.HIGHEST_PROTOCOL) @@ -170,9 +188,10 @@ def main(): nav_results += [(t, s.pos_llh, s.vel_ned)] if len(nav_results): print "First nav solution: t=%s lat=%.5f lon=%.5f h=%.1f vel_ned=(%.2f, %.2f, %.2f)" % ( - nav_results[0][0], - np.degrees(nav_results[0][1][0]), np.degrees(nav_results[0][1][1]), nav_results[0][1][2], - nav_results[0][2][0], nav_results[0][2][1], nav_results[0][2][2]) + nav_results[0][0], + np.degrees(nav_results[0][1][0]), np.degrees( + nav_results[0][1][1]), nav_results[0][1][2], + nav_results[0][2][0], nav_results[0][2][1], nav_results[0][2][2]) with open(nav_results_file, 'wb') as f: cPickle.dump(nav_results, f, protocol=cPickle.HIGHEST_PROTOCOL) print "and %d more are cPickled in '%s'." % (len(nav_results) - 1, nav_results_file) diff --git a/peregrine/tracking.py b/peregrine/tracking.py index ef2e565..b679fd6 100644 --- a/peregrine/tracking.py +++ b/peregrine/tracking.py @@ -11,8 +11,7 @@ import numpy as np from include.generateCAcode import caCodes from include.generateL2CMcode import L2CMCodes -import gps_constants -import progressbar +from peregrine import gps_constants import math import parallel_processing as pp @@ -21,7 +20,7 @@ import swiftnav.nav_msg import swiftnav.cnav_msg import swiftnav.ephemeris -import defaults +from peregrine import defaults from peregrine.acquisition import AcquisitionResult import logging @@ -35,15 +34,15 @@ _progressbar_available = False default_stage1_loop_filter_params = { - 'code_loop_bw': 1, - 'code_loop_zeta': 0.7, - 'code_loop_k': 1, - 'carr_loop_bw': 25, - 'carr_loop_zeta': 0.7, - 'carr_loop_k': 1, - 'loop_freq': 1e3, - 'carr_freq_b1': 5, - 'carr_to_code': 1540, + 'code_loop_bw': 1, + 'code_loop_zeta': 0.7, + 'code_loop_k': 1, + 'carr_loop_bw': 25, + 'carr_loop_zeta': 0.7, + 'carr_loop_k': 1, + 'loop_freq': 1e3, + 'carr_freq_b1': 5, + 'carr_to_code': 1540, } @@ -103,7 +102,7 @@ def track(samples, channels, ms_to_track, sampling_freq, chipping_rate=defaults.chipping_rate, - l2c_handover = True, + l2c_handover=True, show_progress=True, loop_filter_class=swiftnav.track.AidedTrackingLoop, correlator=swiftnav.correlate.track_correlate, @@ -184,10 +183,10 @@ def do_channel(chan, n=None, q_progress=None): loop_filter_params = defaults.l1ca_stage1_loop_filter_params lock_detect_params = defaults.l1ca_lock_detect_params_opt lock_detect = swiftnav.track.LockDetector( - k1 = lock_detect_params["k1"], - k2 = lock_detect_params["k2"], - lp = lock_detect_params["lp"], - lo = lock_detect_params["lo"]) + k1=lock_detect_params["k1"], + k2=lock_detect_params["k2"], + lp=lock_detect_params["lp"], + lo=lock_detect_params["lo"]) prn_code = caCodes[chan.prn] coherent_ms = 1 nav_msg = swiftnav.nav_msg.NavMsg() @@ -200,10 +199,10 @@ def do_channel(chan, n=None, q_progress=None): loop_filter_params = defaults.l2c_loop_filter_params lock_detect_params = defaults.l2c_lock_detect_params_20ms lock_detect = swiftnav.track.LockDetector( - k1 = lock_detect_params["k1"], - k2 = lock_detect_params["k2"], - lp = lock_detect_params["lp"], - lo = lock_detect_params["lo"]) + k1=lock_detect_params["k1"], + k2=lock_detect_params["k2"], + lp=lock_detect_params["lp"], + lo=lock_detect_params["lo"]) prn_code = L2CMCodes[chan.prn] coherent_ms = 20 cnav_msg = swiftnav.cnav_msg.CNavMsg() @@ -214,14 +213,14 @@ def do_channel(chan, n=None, q_progress=None): else: raise NotImplementedError() - alias_detect_init = 1 # require alias_detect_first() call - # or alias_detect.reinit() call or both + alias_detect_init = 1 # require alias_detect_first() call + # or alias_detect.reinit() call or both alias_detect = swiftnav.track.AliasDetector( - acc_len = defaults.alias_detect_interval_ms / coherent_ms, - time_diff = 1) + acc_len=defaults.alias_detect_interval_ms / coherent_ms, + time_diff=1) cn0_est = swiftnav.track.CN0Estimator( - bw=1e3/coherent_ms, + bw=1e3 / coherent_ms, cn0_0=cn0_0, cutoff_freq=10, loop_freq=loop_filter_params["loop_freq"] @@ -229,9 +228,11 @@ def do_channel(chan, n=None, q_progress=None): # Estimate initial code freq via aiding from acq carrier freq if isL1CA: - code_freq_init = chan.doppler * gps_constants.chip_rate / gps_constants.l1 + code_freq_init = chan.doppler * \ + gps_constants.chip_rate / gps_constants.l1 elif isL2C: - code_freq_init = chan.doppler * gps_constants.chip_rate / gps_constants.l2 + code_freq_init = chan.doppler * \ + gps_constants.chip_rate / gps_constants.l2 else: raise NotImplementedError() @@ -241,8 +242,8 @@ def do_channel(chan, n=None, q_progress=None): code_bw=loop_filter_params['code_bw'], code_zeta=loop_filter_params['code_zeta'], code_k=loop_filter_params['code_k'], - carr_to_code=0, # the provided code frequency accounts for Doppler - carr_freq = chan.doppler, + carr_to_code=0, # the provided code frequency accounts for Doppler + carr_freq=chan.doppler, carr_bw=loop_filter_params['carr_bw'], carr_zeta=loop_filter_params['carr_zeta'], carr_k=loop_filter_params['carr_k'], @@ -279,14 +280,18 @@ def do_channel(chan, n=None, q_progress=None): stage2_coherent_ms and \ nav_bit_sync.bit_phase == nav_bit_sync.bit_phase_ref: + logger.info("[PRN: %d (%s)] switching to stage2, coherent_ms=%d" % + (chan.prn + 1, chan.signal, stage2_coherent_ms)) + stage1 = False coherent_ms = stage2_coherent_ms - loop_filter.retune(*stage2_loop_filter_params) + + loop_filter.retune(**stage2_loop_filter_params) lock_detect.reinit( - k1 = lock_detect_params["k1"] * coherent_ms, - k2 = lock_detect_params["k2"], - lp = lock_detect_params["lp"], - lo = lock_detect_params["lo"]); + k1=lock_detect_params["k1"] * coherent_ms, + k2=lock_detect_params["k2"], + lp=lock_detect_params["lp"], + lo=lock_detect_params["lo"]) cn0_est = swiftnav.track.CN0Estimator(bw=1e3 / stage2_coherent_ms, cn0_0=track_result.cn0[i - 1], cutoff_freq=10, @@ -331,18 +336,18 @@ def do_channel(chan, n=None, q_progress=None): # Update PLL lock detector lock_detect_outo, lock_detect_outp, \ - lock_detect_pcount1, lock_detect_pcount2, \ - lock_detect_lpfi, lock_detect_lpfq = lock_detect.update(P.real, P.imag, - coherent_ms) + lock_detect_pcount1, lock_detect_pcount2, \ + lock_detect_lpfi, lock_detect_lpfq = lock_detect.update(P.real, P.imag, + coherent_ms) if lock_detect_outo: if alias_detect_init: alias_detect_init = 0 alias_detect.reinit(defaults.alias_detect_interval_ms / coherent_ms, - time_diff = 1) + time_diff=1) alias_detect.first(P.real, P.imag) alias_detect_err_hz = alias_detect.second(P.real, P.imag) * np.pi * \ - (1e3 / defaults.alias_detect_interval_ms) + (1e3 / defaults.alias_detect_interval_ms) alias_detect.first(P.real, P.imag) else: alias_detect_init = 1 @@ -366,7 +371,7 @@ def do_channel(chan, n=None, q_progress=None): (chan.prn + 1, chan.signal, res)) elif res > 0: logger.info("[PRN: %d (%s)] Subframe decoded" % - (chan.prn + 1, chan.signal) ) + (chan.prn + 1, chan.signal)) else: # Subframe decoding is in progress pass @@ -389,7 +394,8 @@ def do_channel(chan, n=None, q_progress=None): cnav_msg.getAlert(), delay)) tow = cnav_msg.getTow() * 6000 + delay * 20 - logger.debug("[PRN: %d (%s)] ToW %d" % (chan.prn + 1, chan.signal, tow)) + logger.debug("[PRN: %d (%s)] ToW %d" % + (chan.prn + 1, chan.signal, tow)) track_result.tow[i] = tow else: track_result.tow[i] = track_result.tow[i - 1] + coherent_ms @@ -403,8 +409,8 @@ def do_channel(chan, n=None, q_progress=None): track_result.code_phase[i] = code_phase track_result.code_phase_acc[i] = code_phase_acc - track_result.code_freq[ - i] = loop_filter.to_dict()['code_freq'] + chipping_rate + track_result.code_freq[i] = loop_filter.to_dict()['code_freq'] + \ + chipping_rate # Record stuff for postprocessing track_result.absolute_sample[i] = sample_index @@ -429,15 +435,17 @@ def do_channel(chan, n=None, q_progress=None): chan_snr = track_result.cn0[i] chan_snr -= 10 * np.log10(defaults.L1CA_CHANNEL_BANDWIDTH_HZ) chan_snr = np.power(10, chan_snr / 10) - l2c_doppler = loop_filter.to_dict()['carr_freq'] * gps_constants.l2 / gps_constants.l1 + l2c_doppler = loop_filter.to_dict( + )['carr_freq'] * gps_constants.l2 / gps_constants.l1 l2c_handover_chan = AcquisitionResult(track_result.prn, - samples[chan.sample_channel]['IF'] + l2c_doppler, - l2c_doppler, # carrier doppler + samples[chan.sample_channel][ + 'IF'] + l2c_doppler, + l2c_doppler, # carrier doppler track_result.code_phase[i], chan_snr, 'A', 'l2c', - 1, # samples' channel index + 1, # samples' channel index track_result.absolute_sample[i]) i += 1 if isL1CA or isL2C: @@ -551,11 +559,11 @@ def _equal(self, other): """ if self.__dict__.keys() != other.__dict__.keys(): return False - + for k in self.__dict__.keys(): if isinstance(self.__dict__[k], np.ndarray): # If np.ndarray, elements might be floats, so compare accordingly. - if any(np.greater((self.__dict__[k]-other.__dict__[k]), np.ones(len(self.__dict__[k]))*10e-6)): + if any(np.greater((self.__dict__[k] - other.__dict__[k]), np.ones(len(self.__dict__[k])) * 10e-6)): return False elif self.__dict__[k] != other.__dict__[k]: return False @@ -617,17 +625,18 @@ def _equal(self, other): """ if self.__dict__.keys() != other.__dict__.keys(): return False - + for k in self.__dict__.keys(): if isinstance(self.__dict__[k], np.ndarray): # If np.ndarray, elements might be floats, so compare accordingly. - if any((self.__dict__[k]-other.__dict__[k]) > 10e-6): + if any((self.__dict__[k] - other.__dict__[k]) > 10e-6): return False elif self.__dict__[k] != other.__dict__[k]: return False return True + class NavBitSyncSBAS: def __init__(self): From 6e85c4065af6a0499a33fc465cd9f82de093c0db Mon Sep 17 00:00:00 2001 From: Valeri Atamaniouk Date: Wed, 16 Mar 2016 09:01:30 +0200 Subject: [PATCH 44/57] peregrine: added pipelining with doppler predicition Added initial support for FPGA pipelining simulation with generic doppler prediction. --- peregrine/analysis/tracking_loop.py | 20 ++++- peregrine/defaults.py | 23 ++++++ peregrine/run.py | 14 +++- peregrine/tracking.py | 110 +++++++++++++++++++--------- 4 files changed, 129 insertions(+), 38 deletions(-) diff --git a/peregrine/analysis/tracking_loop.py b/peregrine/analysis/tracking_loop.py index b058367..a2e2fa0 100644 --- a/peregrine/analysis/tracking_loop.py +++ b/peregrine/analysis/tracking_loop.py @@ -108,12 +108,20 @@ def main(): parser.add_argument("--l2c-handover", action='store_true', - help="Perform L2C handover") + help="Perform L2C handover", + default=False) parser.add_argument('--l1ca-profile', help='L1 C/A stage profile', choices=defaults.l1ca_stage_profiles.keys()) + parser.add_argument("--pipelining", + type=float, + nargs='?', + help="FPGA pipelining coefficient", + const=defaults.pipelining_k, + default=None) + parser.add_argument("--skip-samples", default=0, help="How many samples to skip") @@ -182,6 +190,11 @@ def main(): # use all available data ms_to_track = int(1e3 * len(signals[0]) / sampling_freq) + if args.pipelining is not None: + tracker_options = {'mode': 'pipelining', 'k': args.pipelining} + else: + tracker_options = None + print "==================== Tracking parameters =============================" print "File: %s" % args.file print "File format: %s" % args.file_format @@ -195,6 +208,7 @@ def main(): print "Track results file name: %s" % args.output_file print "Signal: %s" % args.signal print "L1 stage profile: %s" % args.l1ca_profile + print "Tracker options: %s" % str(tracker_options) print "======================================================================" channel = 0 @@ -226,7 +240,6 @@ def main(): profile = defaults.l1ca_stage_profiles[args.l1ca_profile] stage2_coherent_ms = profile[1]['coherent_ms'] stage2_params = profile[1]['loop_filter_params'] - print "S2=", stage2_params else: stage2_coherent_ms = None stage2_params = None @@ -237,7 +250,8 @@ def main(): sampling_freq=sampling_freq, # [Hz] l2c_handover=l2c_handover, stage2_coherent_ms=stage2_coherent_ms, - stage2_loop_filter_params=stage2_params) + stage2_loop_filter_params=stage2_params, + tracker_options=tracker_options) dump_tracking_results_for_analysis(args.output_file, track_results) diff --git a/peregrine/defaults.py b/peregrine/defaults.py index a9802d7..f1ed7b1 100644 --- a/peregrine/defaults.py +++ b/peregrine/defaults.py @@ -134,6 +134,25 @@ } ) +l1ca_stage_params_slow2 = \ + ({'coherent_ms': 1, + 'loop_filter_params': {'code_params': (1., 0.7, 1.), # NBW, zeta, k + 'carr_params': (10., 0.7, 1.), # NBW, zeta, k + 'loop_freq': 1000., # 1000/coherent_ms + 'carr_freq_igain': 5., # fll_aid + 'carr_to_code': 1540. # carr_to_code + } + }, + {'coherent_ms': 10, + 'loop_filter_params': {'code_params': (1., 0.7, 1.), # NBW, zeta, k + 'carr_params': (12., 0.7, 1.), # NBW, zeta, k + 'loop_freq': 1000. / 10, # 1000/coherent_ms + 'carr_freq_igain': 0., # fll_aid + 'carr_to_code': 1540. # carr_to_code + } + } + ) + # 1;5 ms stages l1ca_stage_params_med = \ ({'coherent_ms': 1, @@ -197,6 +216,7 @@ # L1 C/A stage profiles l1ca_stage_profiles = {'slow': l1ca_stage_params_slow, + 'slow2': l1ca_stage_params_slow2, 'med': l1ca_stage_params_med, 'fast': l1ca_stage_params_fast, 'extrafast': l1ca_stage_params_extrafast} @@ -229,3 +249,6 @@ 'lo': 240} # 4800ms worth of I/Q samples to lower optimistic lock alias_detect_interval_ms = 500 + +# Default pipelining prediction coefficient +pipelining_k = .9549 diff --git a/peregrine/run.py b/peregrine/run.py index 40c7040..5734d5f 100755 --- a/peregrine/run.py +++ b/peregrine/run.py @@ -57,6 +57,12 @@ def main(): parser.add_argument('--l1ca-profile', help='L1 C/A stage profile', choices=defaults.l1ca_stage_profiles.keys()) + parser.add_argument("--pipelining", + type=float, + nargs='?', + help="FPGA pipelining coefficient", + const=defaults.pipelining_k, + default=None) args = parser.parse_args() if args.file is None: @@ -82,6 +88,11 @@ def main(): stage2_coherent_ms = None stage2_params = None + if args.pipelining is not None: + tracker_options = {'mode': 'pipelining', 'k': args.pipelining} + else: + tracker_options = None + settings = initSettings(freq_profile) settings.fileName = args.file @@ -168,7 +179,8 @@ def main(): settings.msToProcess, freq_profile['sampling_freq'], stage2_coherent_ms=stage2_coherent_ms, - stage2_loop_filter_params=stage2_params) + stage2_loop_filter_params=stage2_params, + tracker_options=tracker_options) try: with open(track_results_file, 'wb') as f: cPickle.dump(track_results, f, protocol=cPickle.HIGHEST_PROTOCOL) diff --git a/peregrine/tracking.py b/peregrine/tracking.py index b679fd6..a38788f 100644 --- a/peregrine/tracking.py +++ b/peregrine/tracking.py @@ -9,27 +9,33 @@ # WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. import numpy as np -from include.generateCAcode import caCodes -from include.generateL2CMcode import L2CMCodes -from peregrine import gps_constants import math import parallel_processing as pp -import swiftnav.track -import swiftnav.correlate -import swiftnav.nav_msg -import swiftnav.cnav_msg -import swiftnav.ephemeris +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 +from swiftnav.cnav_msg import CNavMsg +from swiftnav.cnav_msg import CNavMsgDecoder +from swiftnav.ephemeris import Ephemeris from peregrine import defaults +from peregrine import gps_constants from peregrine.acquisition import AcquisitionResult +from peregrine.include.generateCAcode import caCodes +from peregrine.include.generateL2CMcode import L2CMCodes import logging + logger = logging.getLogger(__name__) # Import progressbar if it is available. _progressbar_available = True try: import progressbar + import progressbar.Attribute except ImportError: _progressbar_available = False @@ -104,11 +110,12 @@ def track(samples, channels, chipping_rate=defaults.chipping_rate, l2c_handover=True, show_progress=True, - loop_filter_class=swiftnav.track.AidedTrackingLoop, - correlator=swiftnav.correlate.track_correlate, + loop_filter_class=AidedTrackingLoop, + correlator=track_correlate, stage2_coherent_ms=None, stage2_loop_filter_params=None, - multi=True): + multi=True, + tracker_options=None): n_channels = len(channels) @@ -182,14 +189,14 @@ def do_channel(chan, n=None, q_progress=None): if isL1CA: loop_filter_params = defaults.l1ca_stage1_loop_filter_params lock_detect_params = defaults.l1ca_lock_detect_params_opt - lock_detect = swiftnav.track.LockDetector( + lock_detect = LockDetector( k1=lock_detect_params["k1"], k2=lock_detect_params["k2"], lp=lock_detect_params["lp"], lo=lock_detect_params["lo"]) prn_code = caCodes[chan.prn] coherent_ms = 1 - nav_msg = swiftnav.nav_msg.NavMsg() + nav_msg = NavMsg() nav_msg_bit_phase_ref = np.zeros(num_points) nav_bit_sync = NBSMatchBit() if chan.prn < 32 else NBSSBAS() # Convert acquisition SNR to C/N0 @@ -198,15 +205,15 @@ def do_channel(chan, n=None, q_progress=None): elif isL2C: loop_filter_params = defaults.l2c_loop_filter_params lock_detect_params = defaults.l2c_lock_detect_params_20ms - lock_detect = swiftnav.track.LockDetector( + lock_detect = LockDetector( k1=lock_detect_params["k1"], k2=lock_detect_params["k2"], lp=lock_detect_params["lp"], lo=lock_detect_params["lo"]) prn_code = L2CMCodes[chan.prn] coherent_ms = 20 - cnav_msg = swiftnav.cnav_msg.CNavMsg() - cnav_msg_decoder = swiftnav.cnav_msg.CNavMsgDecoder() + cnav_msg = CNavMsg() + cnav_msg_decoder = CNavMsgDecoder() # Convert acquisition SNR to C/N0 cn0_0 = 10 * np.log10(chan.snr) cn0_0 += 10 * np.log10(defaults.L2C_CHANNEL_BANDWIDTH_HZ) @@ -215,11 +222,11 @@ def do_channel(chan, n=None, q_progress=None): alias_detect_init = 1 # require alias_detect_first() call # or alias_detect.reinit() call or both - alias_detect = swiftnav.track.AliasDetector( + alias_detect = AliasDetector( acc_len=defaults.alias_detect_interval_ms / coherent_ms, time_diff=1) - cn0_est = swiftnav.track.CN0Estimator( + cn0_est = CN0Estimator( bw=1e3 / coherent_ms, cn0_0=cn0_0, cutoff_freq=10, @@ -251,6 +258,8 @@ def do_channel(chan, n=None, q_progress=None): ) code_phase = 0.0 carr_phase = 0.0 + next_code_freq = loop_filter.to_dict()['code_freq'] + next_carr_freq = loop_filter.to_dict()['carr_freq'] # Number of samples to seek ahead in file samples_per_chip = int(round(sampling_freq / chipping_rate)) @@ -269,6 +278,16 @@ def do_channel(chan, n=None, q_progress=None): ms_tracked = 0 i = 0 + pipelining = False + pipelining_k = 0. + if tracker_options: + mode = tracker_options['mode'] + if mode == 'pipelining': + pipelining = True + pipelining_k = tracker_options['k'] + else: + raise ValueError("Invalid tracker mode %s" % str(mode)) + # Process the specified number of ms while ms_tracked < ms_to_track and sample_index < total_samples_num: if pbar: @@ -292,10 +311,10 @@ def do_channel(chan, n=None, q_progress=None): k2=lock_detect_params["k2"], lp=lock_detect_params["lp"], lo=lock_detect_params["lo"]) - cn0_est = swiftnav.track.CN0Estimator(bw=1e3 / stage2_coherent_ms, - cn0_0=track_result.cn0[i - 1], - cutoff_freq=10, - loop_freq=1e3 / stage2_coherent_ms) + cn0_est = CN0Estimator(bw=1e3 / stage2_coherent_ms, + cn0_0=track_result.cn0[i - 1], + cutoff_freq=10, + loop_freq=1e3 / stage2_coherent_ms) coherent_iter = coherent_ms elif isL2C: @@ -305,9 +324,29 @@ def do_channel(chan, n=None, q_progress=None): else: raise NotImplementedError() - E = 0 + 0.j - P = 0 + 0.j - L = 0 + 0.j + if pipelining: + # Pipelining and prediction + corr_code_freq, corr_carr_freq = next_code_freq, next_carr_freq + + next_code_freq = loop_filter.to_dict()['code_freq'] + next_carr_freq = loop_filter.to_dict()['carr_freq'] + + # There is an error between target frequency and actual one. Affect + # the target frequency according to the computed error + carr_freq_error = next_carr_freq - corr_carr_freq + next_carr_freq += carr_freq_error * pipelining_k + + code_freq_error = next_code_freq - corr_code_freq + next_code_freq += code_freq_error * pipelining_k + + else: + # Immediate correction simulation + next_code_freq = loop_filter.to_dict()['code_freq'] + next_carr_freq = loop_filter.to_dict()['carr_freq'] + + corr_code_freq, corr_carr_freq = next_code_freq, next_carr_freq + + E = P = L = 0.j for _ in range(coherent_iter): @@ -318,17 +357,15 @@ def do_channel(chan, n=None, q_progress=None): E_, P_, L_, blksize, code_phase, carr_phase = correlator( samples_, - loop_filter.to_dict()['code_freq'] + chipping_rate, code_phase, - loop_filter.to_dict()['carr_freq'] + IF, carr_phase, + corr_code_freq + chipping_rate, code_phase, + corr_carr_freq + IF, carr_phase, prn_code, sampling_freq, chan.signal ) sample_index += blksize - carr_phase_acc += loop_filter.to_dict()['carr_freq'] * \ - blksize / sampling_freq - code_phase_acc += loop_filter.to_dict()['code_freq'] * \ - blksize / sampling_freq + carr_phase_acc += corr_carr_freq * blksize / sampling_freq + code_phase_acc += corr_code_freq * blksize / sampling_freq E += E_ P += P_ @@ -364,7 +401,7 @@ def do_channel(chan, n=None, q_progress=None): logger.info("[PRN: %d (%s)] ToW %d" % (chan.prn + 1, chan.signal, tow)) if nav_msg.subframe_ready(): - eph = swiftnav.ephemeris.Ephemeris() + eph = Ephemeris() res = nav_msg.process_subframe(eph) if res < 0: logger.error("[PRN: %d (%s)] Subframe decoding error %d" % @@ -465,6 +502,11 @@ def do_channel(chan, n=None, q_progress=None): if q_progress: q_progress.put(1.0 - progress) + logger.info("[PRN: %d (%s)] Results: CN0_mean: %f, pipelining: %d, k: %f" % + (chan.prn + 1, chan.signal, + np.mean(track_result.cn0), + pipelining, pipelining_k)) + return track_result, l2c_handover_chan # Run L1CA @@ -523,7 +565,7 @@ def __init__(self, n_points, prn, signal): self.lock_detect_lpfi = np.zeros(n_points) self.lock_detect_lpfq = np.zeros(n_points) self.alias_detect_err_hz = np.zeros(n_points) - self.nav_msg = swiftnav.nav_msg.NavMsg() + self.nav_msg = NavMsg() self.nav_msg_bit_phase_ref = np.zeros(n_points) self.nav_bit_sync = NBSMatchBit() if prn < 32 else NBSSBAS() self.tow = np.empty(n_points) @@ -695,7 +737,7 @@ class NBSLibSwiftNav(NavBitSync): def __init__(self): NavBitSync.__init__(self) - self.nav_msg = swiftnav.nav_msg.NavMsg() + self.nav_msg = NavMsg() def update_bit_sync(self, corr, ms): self.nav_msg.update(corr, ms) From 66428c97f6a8f499364c341267aba95dd651baec Mon Sep 17 00:00:00 2001 From: Adel Mamin Date: Wed, 16 Mar 2016 18:07:10 +0200 Subject: [PATCH 45/57] Fix broken progress bar in tracking.py --- peregrine/tracking.py | 1 - 1 file changed, 1 deletion(-) diff --git a/peregrine/tracking.py b/peregrine/tracking.py index a38788f..28dd73e 100644 --- a/peregrine/tracking.py +++ b/peregrine/tracking.py @@ -35,7 +35,6 @@ _progressbar_available = True try: import progressbar - import progressbar.Attribute except ImportError: _progressbar_available = False From 8d54f55d7dfb15b75cb1d6d4b7a1566ef180c2a0 Mon Sep 17 00:00:00 2001 From: Adel Mamin Date: Sun, 20 Mar 2016 08:14:17 +0200 Subject: [PATCH 46/57] Add big files support --- libswiftnav | 2 +- peregrine/acquisition.py | 2 - peregrine/analysis/tracking_loop.py | 154 +++---- peregrine/defaults.py | 1 + peregrine/run.py | 87 ++-- peregrine/samples.py | 93 ++++- peregrine/tracking.py | 605 +++++++++++++++++++++++++++- 7 files changed, 768 insertions(+), 176 deletions(-) diff --git a/libswiftnav b/libswiftnav index 8a10026..bb45031 160000 --- a/libswiftnav +++ b/libswiftnav @@ -1 +1 @@ -Subproject commit 8a10026a694cd373fed1ecd6ea12d0d295f85641 +Subproject commit bb45031436864520c4005cddcec792cb2c2b7e11 diff --git a/peregrine/acquisition.py b/peregrine/acquisition.py index 1e12a05..0c4a26a 100644 --- a/peregrine/acquisition.py +++ b/peregrine/acquisition.py @@ -542,7 +542,6 @@ class AcquisitionResult: 'code_phase', 'snr', 'status', 'signal') def __init__(self, prn, carr_freq, doppler, code_phase, snr, status, signal, - sample_channel = 0, sample_index = 0): self.prn = prn self.snr = snr @@ -551,7 +550,6 @@ def __init__(self, prn, carr_freq, doppler, code_phase, snr, status, signal, self.code_phase = code_phase self.status = status self.signal = signal - self.sample_channel = sample_channel self.sample_index = sample_index def __str__(self): diff --git a/peregrine/analysis/tracking_loop.py b/peregrine/analysis/tracking_loop.py index a2e2fa0..2d5d4cc 100644 --- a/peregrine/analysis/tracking_loop.py +++ b/peregrine/analysis/tracking_loop.py @@ -14,55 +14,10 @@ from peregrine.acquisition import AcquisitionResult from peregrine import defaults from peregrine.log import default_logging_config -from peregrine.tracking import track +import peregrine.tracking as tracking from peregrine.gps_constants import L1CA, L2C from peregrine.initSettings import initSettings - -def dump_tracking_results_for_analysis(output_file, track_results): - output_filename, output_file_extension = os.path.splitext(output_file) - - for j in range(len(track_results)): - - if len(track_results) > 1: - # mangle the result file name with the tracked signal name - filename = output_filename + \ - (".%s.%d" % (track_results[j].signal, track_results[j].prn + 1)) +\ - output_file_extension - else: - filename = output_file - - with open(filename, 'w') as f1: - f1.write("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(len(track_results[j].carr_phase)): - f1.write("%s," % track_results[j].IF) - f1.write("%s," % track_results[j].carr_phase[i]) - f1.write("%s," % (track_results[j].carr_freq[i] - - track_results[j].IF)) - f1.write("%s," % track_results[j].code_phase[i]) - f1.write("%s," % track_results[j].code_freq[i]) - f1.write("%s," % track_results[j].cn0[i]) - f1.write("%s," % track_results[j].E[i].real) - f1.write("%s," % track_results[j].E[i].imag) - f1.write("%s," % track_results[j].P[i].real) - f1.write("%s," % track_results[j].P[i].imag) - f1.write("%s," % track_results[j].L[i].real) - f1.write("%s," % track_results[j].L[i].imag) - f1.write("%s," % track_results[j].lock_detect_outp[i]) - f1.write("%s," % track_results[j].lock_detect_outo[i]) - f1.write("%s," % track_results[j].lock_detect_pcount1[i]) - f1.write("%s," % track_results[j].lock_detect_pcount2[i]) - f1.write("%s," % track_results[j].lock_detect_lpfi[i]) - f1.write("%s," % track_results[j].lock_detect_lpfq[i]) - f1.write("%s," % track_results[j].alias_detect_err_hz[i]) - f1.write("%s\n" % track_results[j].code_phase_acc[i]) - - def main(): default_logging_config() @@ -177,24 +132,42 @@ def main(): ms_to_track = int(args.ms_to_track) - if ms_to_track > 0: - samples_num = sampling_freq * 1e-3 * ms_to_track - else: - samples_num = -1 # all available samples - signals = load_samples(args.file, - int(samples_num), - skip_samples, - file_format=args.file_format) - - if ms_to_track < 0: - # use all available data - ms_to_track = int(1e3 * len(signals[0]) / sampling_freq) - if args.pipelining is not None: tracker_options = {'mode': 'pipelining', 'k': args.pipelining} else: tracker_options = None + acq_result = AcquisitionResult(prn = prn, + snr = 25, # dB + carr_freq = IF + carr_doppler, + doppler = carr_doppler, + code_phase = code_phase, + status = 'A', + signal = signal, + sample_index = 0) + + if args.l1ca_profile: + profile = defaults.l1ca_stage_profiles[args.l1ca_profile] + stage2_coherent_ms = profile[1]['coherent_ms'] + stage2_params = profile[1]['loop_filter_params'] + else: + stage2_coherent_ms = None + stage2_params = None + + samples = {L1CA: {'IF': freq_profile['GPS_L1_IF']}, + L2C: {'IF': freq_profile['GPS_L2_IF']}, + 'samples_total': -1, + 'sample_index': 0} + + samples = load_samples(samples = samples, + sample_index = 0, + filename = args.file, + file_format = args.file_format) + + if ms_to_track < 0: + # use all available data + ms_to_track = int(1e3 * samples['samples_total'] / sampling_freq) + print "==================== Tracking parameters =============================" print "File: %s" % args.file print "File format: %s" % args.file_format @@ -205,55 +178,32 @@ def main(): print "Sampling frequency [Hz]: %f" % sampling_freq print "Initial carrier Doppler frequency [Hz]: %s" % carr_doppler print "Initial code phase [chips]: %s" % code_phase - print "Track results file name: %s" % args.output_file print "Signal: %s" % args.signal print "L1 stage profile: %s" % args.l1ca_profile print "Tracker options: %s" % str(tracker_options) print "======================================================================" - channel = 0 - if len(signals) > 1: - samples = [{'data': signals[defaults.sample_channel_GPS_L1], - 'IF': freq_profile['GPS_L1_IF']}, - {'data': signals[defaults.sample_channel_GPS_L2], - 'IF': freq_profile['GPS_L2_IF']}] - if isL1CA: - channel = 0 + tracker = tracking.Tracker(samples = samples, + channels = [acq_result], + ms_to_track = ms_to_track, + sampling_freq = sampling_freq, # [Hz] + l2c_handover = l2c_handover, + stage2_coherent_ms = stage2_coherent_ms, + stage2_loop_filter_params = stage2_params, + tracker_options = tracker_options, + output_file = args.output_file) + tracker.start() + condition = True + while condition: + sample_index = tracker.run_channels(samples) + if sample_index == samples['sample_index']: + condition = False else: - channel = 1 - pass - else: - samples = [{'data': signals[defaults.sample_channel_GPS_L1], - 'IF': freq_profile['GPS_L1_IF']}] - - acq_result = AcquisitionResult(prn=prn, - snr=25, # dB - carr_freq=IF + carr_doppler, - doppler=carr_doppler, - code_phase=code_phase, - status='A', - signal=signal, - sample_channel=channel, - sample_index=0) - - if args.l1ca_profile: - profile = defaults.l1ca_stage_profiles[args.l1ca_profile] - stage2_coherent_ms = profile[1]['coherent_ms'] - stage2_params = profile[1]['loop_filter_params'] - else: - stage2_coherent_ms = None - stage2_params = None - - track_results = track(samples=samples, - channels=[acq_result], - ms_to_track=ms_to_track, - sampling_freq=sampling_freq, # [Hz] - l2c_handover=l2c_handover, - stage2_coherent_ms=stage2_coherent_ms, - stage2_loop_filter_params=stage2_params, - tracker_options=tracker_options) - - dump_tracking_results_for_analysis(args.output_file, track_results) + samples = load_samples(samples = samples, + sample_index = sample_index, + filename = args.file, + file_format = args.file_format) + tracker.stop() if __name__ == '__main__': main() diff --git a/peregrine/defaults.py b/peregrine/defaults.py index f1ed7b1..230de11 100644 --- a/peregrine/defaults.py +++ b/peregrine/defaults.py @@ -11,6 +11,7 @@ ms_to_track = 37 * 1e3 skip_samples = 1000 file_format = 'piksi' +processing_block_size = 4e6 # [samples] chipping_rate = 1.023e6 # Hz code_length = 1023 # chips diff --git a/peregrine/run.py b/peregrine/run.py index 5734d5f..6c3999d 100755 --- a/peregrine/run.py +++ b/peregrine/run.py @@ -19,12 +19,11 @@ from peregrine.samples import load_samples from peregrine.acquisition import Acquisition, load_acq_results, save_acq_results from peregrine.navigation import navigation -from peregrine.tracking import track +import peregrine.tracking as tracking from peregrine.log import default_logging_config -from peregrine.analysis.tracking_loop import dump_tracking_results_for_analysis from peregrine import defaults from peregrine.initSettings import initSettings - +from peregrine.gps_constants import L1CA, L2C def main(): default_logging_config() @@ -105,6 +104,11 @@ def main(): samplesPerCode = int(round(settings.samplingFreq / (settings.codeFreqBasis / settings.codeLength))) + samples = {L1CA: {'IF': freq_profile['GPS_L1_IF']}, + L2C: {'IF': freq_profile['GPS_L2_IF']}, + 'samples_total': -1, + 'sample_index': 0} + # Do acquisition acq_results_file = args.file + ".acq_results" if args.skip_acquisition: @@ -117,11 +121,13 @@ def main(): sys.exit(1) else: # Get 11ms of acquisition samples for fine frequency estimation - acq_samples = load_samples(args.file, 11 * samplesPerCode, - settings.skipNumberOfBytes, - file_format=args.file_format) + acq_samples = load_samples(samples = samples, + sample_index = settings.skipNumberOfBytes, + num_samples = 11 * samplesPerCode, + filename = args.file, + file_format = args.file_format) - acq = Acquisition(acq_samples[defaults.sample_channel_GPS_L1], + acq = Acquisition(acq_samples[L1CA]['samples'], freq_profile['sampling_freq'], freq_profile['GPS_L1_IF'], defaults.code_period * freq_profile['sampling_freq']) @@ -157,39 +163,42 @@ def main(): track_results_file) sys.exit(1) else: - signal = load_samples(args.file, - int(samples_num), - settings.skipNumberOfBytes, - file_format=args.file_format) - if ms_to_process < 0: - ms_to_process = int(1e3 * len(signal[0]) / freq_profile['sampling_freq']) - - settings.msToProcess = ms_to_process - 22 + samples = load_samples(samples = samples, + sample_index = 0, + filename = args.file, + file_format = args.file_format) - if len(signal) > 1: - samples = [{'data': signal[defaults.sample_channel_GPS_L1], - 'IF': freq_profile['GPS_L1_IF']}, - {'data': signal[defaults.sample_channel_GPS_L2], - 'IF': freq_profile['GPS_L2_IF']}] - else: - samples = [{'data': signal[defaults.sample_channel_GPS_L1], - 'IF': freq_profile['GPS_L1_IF']}] - - track_results = track(samples, acq_results, - settings.msToProcess, - freq_profile['sampling_freq'], - stage2_coherent_ms=stage2_coherent_ms, - stage2_loop_filter_params=stage2_params, - tracker_options=tracker_options) - try: - with open(track_results_file, 'wb') as f: - cPickle.dump(track_results, f, protocol=cPickle.HIGHEST_PROTOCOL) - logging.debug("Saving tracking results as '%s'" % track_results_file) - logging.debug("Saving tracking results for analysis") - dump_tracking_results_for_analysis(track_results_file, track_results) - except IOError: - logging.error("Couldn't save tracking results file '%s'.", - track_results_file) + if ms_to_process < 0: + ms_to_process = int(1e3 * samples['samples_total'] / freq_profile['sampling_freq']) + + tracker = tracking.Tracker(samples = samples, + channels = acq_results, + ms_to_track = ms_to_process, + sampling_freq = freq_profile['sampling_freq'], # [Hz] + stage2_coherent_ms = stage2_coherent_ms, + stage2_loop_filter_params = stage2_params, + tracker_options = tracker_options, + output_file = args.file) + tracker.start() + condition = True + while condition: + sample_index = tracker.run_channels(samples) + if sample_index == samples['sample_index']: + condition = False + else: + samples = load_samples(samples = samples, + sample_index = sample_index, + filename = args.file, + file_format = args.file_format) + tracker.stop() + + # try: + # with open(track_results_file, 'wb') as f: + # cPickle.dump(track_results, f, protocol=cPickle.HIGHEST_PROTOCOL) + # logging.debug("Saving tracking results as '%s'" % track_results_file) + # except IOError: + # logging.error("Couldn't save tracking results file '%s'.", + # track_results_file) # Do navigation nav_results_file = args.file + ".nav_results" diff --git a/peregrine/samples.py b/peregrine/samples.py index f381c44..2ac0558 100644 --- a/peregrine/samples.py +++ b/peregrine/samples.py @@ -9,8 +9,11 @@ """Functions for handling sample data and sample data files.""" +import os import numpy as np +import math import defaults +from peregrine.gps_constants import L1CA, L2C __all__ = ['load_samples', 'save_samples'] @@ -47,25 +50,22 @@ def __load_samples_n_bits(filename, num_samples, num_skip, n_rx, n_bits, channel_lookup = range(n_rx) sample_block_size = n_bits * n_rx - byte_offset = num_skip / (8 / sample_block_size) + byte_offset = int(math.floor(num_skip / (8 / sample_block_size))) sample_offset = num_skip % (8 / sample_block_size) s_file = np.memmap(filename, offset=byte_offset, dtype=np.uint8, mode='r') if num_samples > 0: - # Number of samples is defined: trim the source from start and end - s_file = s_file[(sample_offset * sample_block_size + 7) / 8: - (num_samples * sample_block_size + 7) / 8] - else: - # Number of samples is not defined: trim the source from start only - # compute actual number of samples - s_file = s_file[(sample_offset * sample_block_size + 7) / 8:] - num_samples = len(s_file) * 8 / sample_block_size + # Number of samples is defined: trim the source from end + s_file = s_file[:(num_samples * sample_block_size + 7) / 8] + + num_samples = len(s_file) * 8 / sample_block_size # Compute total data block size to ignore bits in the tail. rounded_len = num_samples * sample_block_size bits = np.unpackbits(s_file) - samples = np.empty((n_rx, num_samples), dtype=value_lookup.dtype) + samples = np.empty((n_rx, num_samples - sample_offset), + dtype=value_lookup.dtype) for rx in range(n_rx): # Construct multi-bit sample values @@ -74,7 +74,9 @@ def __load_samples_n_bits(filename, num_samples, num_skip, n_rx, n_bits, tmp <<= 1 tmp += bits[rx * n_bits + bit:rounded_len:sample_block_size] # Generate sample values using value_lookup table - samples[channel_lookup[rx]][:] = value_lookup[tmp] + chan = value_lookup[tmp] + chan = chan[sample_offset:] + samples[channel_lookup[rx]][:] = chan return samples def __load_samples_one_bit(filename, num_samples, num_skip, n_rx, @@ -137,7 +139,10 @@ def __load_samples_two_bits(filename, num_samples, num_skip, n_rx, return __load_samples_n_bits(filename, num_samples, num_skip, n_rx, 2, value_lookup, channel_lookup) -def load_samples(filename, num_samples=-1, num_skip=0, file_format='piksi'): +def _load_samples(filename, + num_samples = defaults.processing_block_size, + num_skip = 0, + file_format = 'piksi'): """ Load sample data from a file. @@ -301,9 +306,67 @@ def load_samples(filename, num_samples=-1, num_skip=0, file_format='piksi'): else: raise ValueError("Unknown file type '%s'" % file_format) - if len(samples[0]) < num_samples: - raise EOFError("Failed to read %d samples from file '%s'" % - (num_samples, filename)) + return samples + +def __get_samples_total(filename, file_format, sample_index): + if file_format == 'int8': + samples_block_size = 8 + elif file_format == 'piksi': + """ + Piksi format is packed 3-bit sign-magnitude samples, 2 samples per byte. + + Bits: + [1..0] Flags (reserved for future use) + [3..2] Sample 2 magnitude + [4] Sample 2 sign (1 is -ve) + [6..5] Sample 1 magnitude + [7] Sample 1 sign (1 is -ve) + + """ + samples_block_size = 4 + elif file_format == '1bit' or file_format == '1bitrev': + samples_block_size = 1 + elif file_format == '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': + # Interleaved two bit samples from four receivers: -3, -1, +1, +3 + samples_block_size = 8 + else: + raise ValueError("Unknown file type '%s'" % file_format) + + file_size = os.path.getsize(filename) + samples_total = 8 * file_size / samples_block_size + + if sample_index < samples_total: + samples_total -= sample_index + + return samples_total + +def load_samples(samples, + filename, + num_samples = defaults.processing_block_size, + sample_index = 0, + file_format = 'piksi'): + + if samples['samples_total'] == -1: + samples['samples_total'] = __get_samples_total(filename, + file_format, + sample_index) + signal = _load_samples(filename, + num_samples, + sample_index, + file_format) + samples['sample_index'] = sample_index + samples[L1CA]['samples'] = signal[defaults.sample_channel_GPS_L1] + if len(signal) > 1: + samples[L2C]['samples'] = signal[defaults.sample_channel_GPS_L2] return samples diff --git a/peregrine/tracking.py b/peregrine/tracking.py index 28dd73e..e246afd 100644 --- a/peregrine/tracking.py +++ b/peregrine/tracking.py @@ -9,8 +9,10 @@ # WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. import numpy as np +import os import math import parallel_processing as pp +import multiprocessing as mp from swiftnav.track import LockDetector from swiftnav.track import CN0Estimator @@ -38,19 +40,6 @@ except ImportError: _progressbar_available = False -default_stage1_loop_filter_params = { - 'code_loop_bw': 1, - 'code_loop_zeta': 0.7, - 'code_loop_k': 1, - 'carr_loop_bw': 25, - 'carr_loop_zeta': 0.7, - 'carr_loop_k': 1, - 'loop_freq': 1e3, - 'carr_freq_b1': 5, - 'carr_to_code': 1540, -} - - class TrackingLoop(object): """ Abstract base class for a tracking loop. @@ -102,6 +91,533 @@ def update(self, e, p, l): """ raise NotImplementedError() +def _tracking_channel_factory(parameters): + if parameters['acq'].signal == gps_constants.L1CA: + return TrackingChannelL1CA(parameters) + if parameters['acq'].signal == gps_constants.L2C: + return TrackingChannelL2C(parameters) + +class TrackingChannel(object): + + def __init__(self, params): + for (key, value) in params.iteritems(): + setattr(self, key, value) + + self.prn = params['acq'].prn + self.signal = params['acq'].signal + + self.results_num = 1000 + self.stage1 = True + + self.lock_detect = LockDetector( + k1 = self.lock_detect_params["k1"], + k2 = self.lock_detect_params["k2"], + lp = self.lock_detect_params["lp"], + lo = self.lock_detect_params["lo"]) + + self.alias_detect = AliasDetector( + acc_len = defaults.alias_detect_interval_ms / self.coherent_ms, + time_diff = 1) + + self.cn0_est = CN0Estimator( + bw=1e3 / self.coherent_ms, + cn0_0 = self.cn0_0, + cutoff_freq = 10, + loop_freq = self.loop_filter_params["loop_freq"] + ) + + self.loop_filter = self.loop_filter_class( + loop_freq = self.loop_filter_params['loop_freq'], + code_freq = self.code_freq_init, + code_bw = self.loop_filter_params['code_bw'], + code_zeta = self.loop_filter_params['code_zeta'], + code_k = self.loop_filter_params['code_k'], + carr_to_code = 0, # the provided code frequency accounts for Doppler + carr_freq = self.acq.doppler, + carr_bw = self.loop_filter_params['carr_bw'], + carr_zeta = self.loop_filter_params['carr_zeta'], + carr_k = self.loop_filter_params['carr_k'], + carr_freq_b1 = self.loop_filter_params['carr_freq_b1'], + ) + + self.next_code_freq = self.loop_filter.to_dict()['code_freq'] + self.next_carr_freq = self.loop_filter.to_dict()['carr_freq'] + + 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)) + self.sample_index = self.acq.sample_index + self.sample_index += self.acq.code_phase * self.samples_per_chip + self.sample_index = int(math.floor(self.sample_index)) + self.carr_phase_acc = 0.0 + self.code_phase_acc = 0.0 + self.samples_tracked = 0 + self.i = 0 + #self.samples_offset = self.samples['samples_offset'] + + self.pipelining = False + self.pipelining_k = 0. + if self.tracker_options: + self.mode = self.tracker_options['mode'] + if self.mode == 'pipelining': + self.pipelining = True + self.pipelining_k = self.tracker_options['k'] + else: + raise ValueError("Invalid tracker mode %s" % str(self.mode)) + + def dump(self): + filename = self.track_result.dump(self.output_file, self.i) + self.i = 0 + return filename + + def start(self): + logger.info("[PRN: %d (%s)] Tracking is started. " + "IF: %.1f, Doppler: %.1f, code phase: %.1f, " + "sample channel: %d sample index: %d" % + (self.prn + 1, + self.signal, + self.IF, + self.acq.doppler, + self.acq.code_phase, + self.acq.sample_channel, + self.acq.sample_index)) + + def get_index(self): + return self.sample_index + + def _run_preprocess(self): # optionally redefined in subclasses + pass + + def _run_postprocess(self): # optionally redefine in subclasses + pass + + def _get_result(self): # optionally redefine in subclasses + return None + + def run_parallel(self, samples): + handover = self.run(samples) + return (handover, self) + + def run(self, samples): + + self.samples = samples + + if self.sample_index < samples['sample_index']: + raise ValueError("Incorrent samples offset") + + sample_index = self.sample_index - samples['sample_index'] + samples_processed = 0 + samples_total = len(samples[self.signal]['samples']) + + estimated_blksize = self.coherent_ms * self.sampling_freq / 1e3 + + while self.samples_tracked < self.samples_to_track and \ + (sample_index + 2 * estimated_blksize) < samples_total: + + self._run_preprocess() + + if self.pipelining: + # Pipelining and prediction + corr_code_freq, corr_carr_freq = self.next_code_freq, self.next_carr_freq + + self.next_code_freq = self.loop_filter.to_dict()['code_freq'] + self.next_carr_freq = self.loop_filter.to_dict()['carr_freq'] + + # There is an error between target frequency and actual one. Affect + # the target frequency according to the computed error + carr_freq_error = self.next_carr_freq - corr_carr_freq + self.next_carr_freq += carr_freq_error * pipelining_k + + code_freq_error = self.next_code_freq - corr_code_freq + self.next_code_freq += code_freq_error * pipelining_k + + else: + # Immediate correction simulation + self.next_code_freq = self.loop_filter.to_dict()['code_freq'] + self.next_carr_freq = self.loop_filter.to_dict()['carr_freq'] + + corr_code_freq, corr_carr_freq = self.next_code_freq, self.next_carr_freq + + self.E = self.P = self.L = 0.j + + for _ in range(self.coherent_iter): + + if (sample_index + 2 * estimated_blksize) >= samples_total: + break + + samples_ = samples[self.signal]['samples'][sample_index:] + + E_, P_, L_, blksize, self.code_phase, self.carr_phase = self.correlator( + samples_, + corr_code_freq + self.chipping_rate, self.code_phase, + corr_carr_freq + self.IF, self.carr_phase, + self.prn_code, + self.sampling_freq, + self.signal + ) + + if blksize > estimated_blksize: + estimated_blksize = blksize + + sample_index += blksize + samples_processed += blksize + self.carr_phase_acc += corr_carr_freq * blksize / self.sampling_freq + self.code_phase_acc += corr_code_freq * blksize / self.sampling_freq + + self.E += E_ + self.P += P_ + self.L += L_ + + # 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, + self.coherent_ms) + + 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_ms, + 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.loop_filter.update(self.E, self.P, self.L) + self.track_result.coherent_ms[self.i] = self.coherent_ms + + self.track_result.IF = self.IF + self.track_result.carr_phase[self.i] = self.carr_phase + self.track_result.carr_phase_acc[self.i] = self.carr_phase_acc + self.track_result.carr_freq[self.i] = \ + self.loop_filter.to_dict()['carr_freq'] + self.IF + + self.track_result.code_phase[self.i] = self.code_phase + self.track_result.code_phase_acc[self.i] = self.code_phase_acc + self.track_result.code_freq[self.i] = \ + self.loop_filter.to_dict()['code_freq'] + self.chipping_rate + + # Record stuff for postprocessing + self.track_result.absolute_sample[self.i] = self.sample_index + \ + samples_processed + + self.track_result.E[self.i] = self.E + self.track_result.P[self.i] = self.P + self.track_result.L[self.i] = self.L + + self.track_result.cn0[self.i] = self.cn0_est.update(self.P.real, self.P.imag) + + self.track_result.lock_detect_outo[self.i] = lock_detect_outo + self.track_result.lock_detect_outp[self.i] = lock_detect_outp + self.track_result.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._run_postprocess() + + self.samples_tracked = self.sample_index + samples_processed + self.track_result.ms_tracked[self.i] = self.samples_tracked * 1e3 / \ + self.sampling_freq + + self.i += 1 + if self.i >= self.results_num: + self.dump() + + self.sample_index += samples_processed + self.track_result.status = 'T' + + return self._get_result() + +class TrackingChannelL1CA(TrackingChannel): + def __init__(self, params): + # Convert acquisition SNR to C/N0 + cn0_0 = 10 * np.log10(params['acq'].snr) + cn0_0 += 10 * np.log10(defaults.L1CA_CHANNEL_BANDWIDTH_HZ) + + params['cn0_0'] = cn0_0 + params['coherent_ms'] = 1 + params['IF'] = params['samples'][gps_constants.L1CA]['IF'] + params['prn_code'] = caCodes[params['acq'].prn] + params['code_freq_init'] = params['acq'].doppler * \ + gps_constants.chip_rate / gps_constants.l1 + params['loop_filter_params'] = defaults.l1ca_stage1_loop_filter_params + params['lock_detect_params'] = defaults.l1ca_lock_detect_params_opt + + TrackingChannel.__init__(self, params) + + self.nav_msg = NavMsg() + self.nav_bit_sync = NBSMatchBit() if self.prn < 32 else NBSSBAS() + self.l2c_handover_chan = None + self.l2c_handover_done = False + + def _run_preprocess(self): + # For L1 C/A there are coherent and non-coherent tracking options. + if self.stage1 and \ + self.stage2_coherent_ms and \ + self.nav_bit_sync.bit_phase == self.nav_bit_sync.bit_phase_ref: + + logger.info("[PRN: %d (%s)] switching to stage2, coherent_ms=%d" % + (self.prn + 1, self.signal, self.stage2_coherent_ms)) + + self.stage1 = False + self.coherent_ms = self.stage2_coherent_ms + + self.loop_filter.retune(**self.stage2_loop_filter_params) + self.lock_detect.reinit( + k1=self.lock_detect_params["k1"] * self.coherent_ms, + k2=self.lock_detect_params["k2"], + lp=self.lock_detect_params["lp"], + lo=self.lock_detect_params["lo"]) + cn0_est = CN0Estimator(bw = 1e3 / self.stage2_coherent_ms, + cn0_0 = self.track_result.cn0[self.i - 1], + cutoff_freq = 10, + loop_freq = 1e3 / self.stage2_coherent_ms) + + self.coherent_iter = self.coherent_ms + + def _get_result(self): + if self.l2c_handover_chan and not self.l2c_handover_done: + self.l2c_handover_done = True + return self.l2c_handover_chan + return None + + def _run_postprocess(self): + sync, bit = self.nav_bit_sync.update(np.real(self.P), self.coherent_ms) + if sync: + tow = self.nav_msg.update(bit) + if tow >= 0: + logger.info("[PRN: %d (%s)] ToW %d" % + (self.prn + 1, self.signal, tow)) + if self.nav_msg.subframe_ready(): + eph = Ephemeris() + res = self.nav_msg.process_subframe(eph) + if res < 0: + logger.error("[PRN: %d (%s)] Subframe decoding error %d" % + (self.prn + 1, self.signal, res)) + elif res > 0: + logger.info("[PRN: %d (%s)] Subframe decoded" % + (self.prn + 1, self.signal)) + else: + # Subframe decoding is in progress + pass + else: + tow = -1 + self.track_result.tow[self.i] = tow if tow >= 0 else ( + self.track_result.tow[self.i - 1] + self.coherent_ms) + + # Handover to L2C if possible + if not self.l2c_handover_chan and \ + 'samples' in self.samples[gps_constants.L2C] and sync: + chan_snr = self.track_result.cn0[self.i] + chan_snr -= 10 * np.log10(defaults.L1CA_CHANNEL_BANDWIDTH_HZ) + chan_snr = np.power(10, chan_snr / 10) + l2c_doppler = self.loop_filter.to_dict( + )['carr_freq'] * gps_constants.l2 / gps_constants.l1 + self.l2c_handover_chan = AcquisitionResult(self.prn, + self.samples[gps_constants.L2C]['IF'] + l2c_doppler, + l2c_doppler, # carrier doppler + self.track_result.code_phase[self.i], + chan_snr, + 'A', + gps_constants.L2C, + self.track_result.absolute_sample[self.i]) + +class TrackingChannelL2C(TrackingChannel): + def __init__(self, params): + # Convert acquisition SNR to C/N0 + cn0_0 = 10 * np.log10(params['acq'].snr) + cn0_0 += 10 * np.log10(defaults.L2C_CHANNEL_BANDWIDTH_HZ) + params['cn0_0'] = cn0_0 + params['coherent_ms'] = 20 + params['coherent_iter'] = 1 + params['loop_filter_params'] = defaults.l2c_loop_filter_params + params['lock_detect_params'] = defaults.l2c_lock_detect_params_20ms + params['IF'] = params['samples'][gps_constants.L2C]['IF'] + params['prn_code'] = L2CMCodes[params['acq'].prn] + params['code_freq_init'] = params['acq'].doppler * \ + gps_constants.chip_rate / gps_constants.l2 + + TrackingChannel.__init__(self, params) + + self.cnav_msg = CNavMsg() + self.cnav_msg_decoder = CNavMsgDecoder() + + def _run_postprocess(self): + symbol = 0xFF if np.real(self.P) >= 0 else 0x00 + res, delay = self.cnav_msg_decoder.decode(symbol, self.cnav_msg) + if res: + logger.debug("[PRN: %d (%s)] CNAV message decoded: " + "prn=%d msg_id=%d tow=%d alert=%d delay=%d" % + (self.prn + 1, + self.signal, + self.cnav_msg.getPrn(), + self.cnav_msg.getMsgId(), + self.cnav_msg.getTow(), + self.cnav_msg.getAlert(), + delay)) + tow = self.cnav_msg.getTow() * 6000 + delay * 20 + logger.debug("[PRN: %d (%s)] ToW %d" % + (self.prn + 1, self.signal, tow)) + self.track_result.tow[self.i] = tow + else: + self.track_result.tow[self.i] = self.track_result.tow[self.i - 1] + \ + self.coherent_ms + +class Tracker(object): + + def __init__(self, + samples, + channels, + ms_to_track, + sampling_freq, + chipping_rate = defaults.chipping_rate, + l2c_handover = True, + show_progress = True, + loop_filter_class = AidedTrackingLoop, + correlator = track_correlate, + stage2_coherent_ms = None, + stage2_loop_filter_params = None, + multi = True, + tracker_options = None, + output_file = None): + + self.samples = samples + self.sampling_freq = sampling_freq + self.ms_to_track = ms_to_track + self.tracker_options = tracker_options + self.output_file = output_file + self.chipping_rate = chipping_rate + self.l2c_handover = l2c_handover + self.show_progress = show_progress + self.correlator = correlator + self.stage2_coherent_ms = stage2_coherent_ms + self.stage2_loop_filter_params = stage2_loop_filter_params + self.multi = multi + self.loop_filter_class = loop_filter_class + + if self.ms_to_track: + 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)) + self.samples_to_track = samples['samples_total'] + else: + self.samples_to_track = samples['samples_total'] + + # If progressbar is not available, disable show_progress. + if show_progress and not _progressbar_available: + show_progress = False + logger.warning("show_progress = True but progressbar module not found.") + + # Setup our progress bar if we need it + if show_progress: + widgets = [' Tracking ', + progressbar.Attribute(['sample', 'samples'], + '(sample: %d/%d)', + '(sample: -/-)'), ' ', + progressbar.Percentage(), ' ', + progressbar.ETA(), ' ', + progressbar.Bar()] + self.pbar = progressbar.ProgressBar(widgets=widgets, + maxval=samples['samples_total'], + attr={'samples': self.samples['samples_total']}) + else: + self.pbar = None + + channels = (c for c in channels if c.snr > 100) + self.tracking_channels = map(self._create_channel, channels) + + def start(self): + logger.info("Number of CPUs: %d" % (mp.cpu_count())) + + logger.info("Tracking %.4fs of data (%d samples)" % + (self.samples_to_track / self.sampling_freq, + self.samples_to_track)) + + logger.info("Tracking starting") + logger.debug("Tracking PRNs %s" % + ([chan.prn + 1 for chan in self.tracking_channels])) + + self.pbar.start() + + def _print_name(self, name): + print name + + def stop(self): + if self.pbar: + self.pbar.finish() + + filenames = map(lambda chan: chan.dump(), self.tracking_channels) + + print "The tracking results were stored into:" + map(self._print_name, filenames) + + logger.info("Tracking finished") + + def _create_channel(self, acq): + if not acq: + return + parameters = {'acq': acq, + 'samples': self.samples, + 'loop_filter_class': self.loop_filter_class, + 'tracker_options': self.tracker_options, + 'output_file': self.output_file, + 'samples_to_track': self.samples_to_track, + 'sampling_freq': self.sampling_freq, + 'chipping_rate': self.chipping_rate, + 'l2c_handover': self.l2c_handover, + 'show_progress': self.show_progress, + 'correlator': self.correlator, + 'stage2_coherent_ms': self.stage2_coherent_ms, + 'stage2_loop_filter_params': self.stage2_loop_filter_params, + 'multi': self.multi} + return _tracking_channel_factory(parameters) + + def run_channels(self, samples): + self.samples = samples + tracking_channels = self.tracking_channels + + while tracking_channels and not all(v is None for v in tracking_channels): + if self.multi and mp.cpu_count() > 1: + res = pp.parmap(lambda x: self.run(samples), + tracking_channels, + show_progress=False, + func_progress=False) + + handover = map(lambda x: x[0], res) + tracking_channels = map(lambda x: x[1], res) + else: + handover = map(lambda x: x.run(samples), tracking_channels) + + handover = [h for h in handover if h is not None] + if handover: + tracking_channels = map(self._create_channel, handover); + self.tracking_channels += tracking_channels + else: + tracking_channels = None + + indexes = map(lambda x: x.get_index(), self.tracking_channels) + min_index = min(indexes) + + if self.pbar: + self.pbar.update(min_index, attr={'samples': min_index}) + + return min_index def track(samples, channels, ms_to_track, @@ -113,12 +629,12 @@ def track(samples, channels, correlator=track_correlate, stage2_coherent_ms=None, stage2_loop_filter_params=None, - multi=True, + multi=False, tracker_options=None): n_channels = len(channels) - total_samples_num = len(samples[defaults.sample_channel_GPS_L1]['data']) + total_samples_num = len(samples[defaults.sample_channel_GPS_L1]['samples']) samples_length_ms = int(1e3 * total_samples_num / sampling_freq) if ms_to_track is None: @@ -349,10 +865,10 @@ def do_channel(chan, n=None, q_progress=None): for _ in range(coherent_iter): - if sample_index >= len(samples[chan.sample_channel]['data']): + if sample_index >= len(samples[chan.sample_channel]['samples']): break - samples_ = samples[chan.sample_channel]['data'][sample_index:] + samples_ = samples[chan.sample_channel]['samples'][sample_index:] E_, P_, L_, blksize, code_phase, carr_phase = correlator( samples_, @@ -543,6 +1059,7 @@ def do_channel(chan, n=None, q_progress=None): class TrackResults: def __init__(self, n_points, prn, signal): + self.print_start = 1 self.status = '-' self.prn = prn self.IF = 0 @@ -573,6 +1090,60 @@ def __init__(self, n_points, prn, signal): # self.cnav_msg = swiftnav.cnav_msg.CNavMsg() # self.cnav_msg_decoder = swiftnav.cnav_msg.CNavMsgDecoder() self.signal = signal + self.ms_tracked = np.zeros(n_points) + + def dump(self, output_file, size): + output_filename, output_file_extension = os.path.splitext(output_file) + + # mangle the result file name with the tracked signal name + filename = output_filename + \ + (".PRN-%d.%s" % (self.prn + 1, self.signal)) +\ + output_file_extension + + if self.print_start: + mode = 'w' + else: + mode = 'a' + + #print "Storing tracking results into file: ", filename + + with open(filename, mode) as f1: + if self.print_start: + f1.write("sample_index,ms_tracked,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]) + f1.write("%s," % self.IF) + f1.write("%s," % self.carr_phase[i]) + f1.write("%s," % (self.carr_freq[i] - + self.IF)) + f1.write("%s," % self.code_phase[i]) + f1.write("%s," % self.code_freq[i]) + f1.write("%s," % self.cn0[i]) + f1.write("%s," % self.E[i].real) + f1.write("%s," % self.E[i].imag) + f1.write("%s," % self.P[i].real) + f1.write("%s," % self.P[i].imag) + f1.write("%s," % self.L[i].real) + f1.write("%s," % self.L[i].imag) + f1.write("%s," % int(self.lock_detect_outp[i])) + f1.write("%s," % int(self.lock_detect_outo[i])) + f1.write("%s," % int(self.lock_detect_pcount1[i])) + f1.write("%s," % int(self.lock_detect_pcount2[i])) + f1.write("%s," % self.lock_detect_lpfi[i]) + f1.write("%s," % self.lock_detect_lpfq[i]) + f1.write("%s," % self.alias_detect_err_hz[i]) + f1.write("%s\n" % self.code_phase_acc[i]) + + self.print_start = 0 + + return filename def resize(self, n_points): for k in dir(self): From 7d7e2ab0e5812d18a19056f1731a0cfb03b0e4bb Mon Sep 17 00:00:00 2001 From: Adel Mamin Date: Tue, 22 Mar 2016 10:14:49 +0200 Subject: [PATCH 47/57] Fix skip_samples parameter handling + cleanup --- peregrine/analysis/tracking_loop.py | 5 +- peregrine/run.py | 10 +- peregrine/samples.py | 6 +- peregrine/tracking.py | 439 +--------------------------- 4 files changed, 7 insertions(+), 453 deletions(-) diff --git a/peregrine/analysis/tracking_loop.py b/peregrine/analysis/tracking_loop.py index 2d5d4cc..db762d2 100644 --- a/peregrine/analysis/tracking_loop.py +++ b/peregrine/analysis/tracking_loop.py @@ -144,7 +144,7 @@ def main(): code_phase = code_phase, status = 'A', signal = signal, - sample_index = 0) + sample_index = skip_samples) if args.l1ca_profile: profile = defaults.l1ca_stage_profiles[args.l1ca_profile] @@ -157,10 +157,9 @@ def main(): samples = {L1CA: {'IF': freq_profile['GPS_L1_IF']}, L2C: {'IF': freq_profile['GPS_L2_IF']}, 'samples_total': -1, - 'sample_index': 0} + 'sample_index': skip_samples} samples = load_samples(samples = samples, - sample_index = 0, filename = args.file, file_format = args.file_format) diff --git a/peregrine/run.py b/peregrine/run.py index 6c3999d..2d9796e 100755 --- a/peregrine/run.py +++ b/peregrine/run.py @@ -96,10 +96,6 @@ def main(): settings.fileName = args.file ms_to_process = int(args.ms_to_process) - if ms_to_process > 0: - samples_num = freq_profile['sampling_freq'] * 1e-3 * ms_to_process - else: - samples_num = -1 # all available samples samplesPerCode = int(round(settings.samplingFreq / (settings.codeFreqBasis / settings.codeLength))) @@ -107,7 +103,7 @@ def main(): samples = {L1CA: {'IF': freq_profile['GPS_L1_IF']}, L2C: {'IF': freq_profile['GPS_L2_IF']}, 'samples_total': -1, - 'sample_index': 0} + 'sample_index': settings.skipNumberOfBytes} # Do acquisition acq_results_file = args.file + ".acq_results" @@ -122,7 +118,6 @@ def main(): else: # Get 11ms of acquisition samples for fine frequency estimation acq_samples = load_samples(samples = samples, - sample_index = settings.skipNumberOfBytes, num_samples = 11 * samplesPerCode, filename = args.file, file_format = args.file_format) @@ -164,7 +159,6 @@ def main(): sys.exit(1) else: samples = load_samples(samples = samples, - sample_index = 0, filename = args.file, file_format = args.file_format) @@ -186,8 +180,8 @@ def main(): if sample_index == samples['sample_index']: condition = False else: + samples['sample_index'] = sample_index samples = load_samples(samples = samples, - sample_index = sample_index, filename = args.file, file_format = args.file_format) tracker.stop() diff --git a/peregrine/samples.py b/peregrine/samples.py index 2ac0558..973b5df 100644 --- a/peregrine/samples.py +++ b/peregrine/samples.py @@ -352,18 +352,16 @@ def __get_samples_total(filename, file_format, sample_index): def load_samples(samples, filename, num_samples = defaults.processing_block_size, - sample_index = 0, file_format = 'piksi'): if samples['samples_total'] == -1: samples['samples_total'] = __get_samples_total(filename, file_format, - sample_index) + samples['sample_index']) signal = _load_samples(filename, num_samples, - sample_index, + samples['sample_index'], file_format) - samples['sample_index'] = sample_index samples[L1CA]['samples'] = signal[defaults.sample_channel_GPS_L1] if len(signal) > 1: samples[L2C]['samples'] = signal[defaults.sample_channel_GPS_L2] diff --git a/peregrine/tracking.py b/peregrine/tracking.py index e246afd..cbe2e56 100644 --- a/peregrine/tracking.py +++ b/peregrine/tracking.py @@ -383,7 +383,7 @@ def _run_preprocess(self): k2=self.lock_detect_params["k2"], lp=self.lock_detect_params["lp"], lo=self.lock_detect_params["lo"]) - cn0_est = CN0Estimator(bw = 1e3 / self.stage2_coherent_ms, + self.cn0_est = CN0Estimator(bw = 1e3 / self.stage2_coherent_ms, cn0_0 = self.track_result.cn0[self.i - 1], cutoff_freq = 10, loop_freq = 1e3 / self.stage2_coherent_ms) @@ -619,443 +619,6 @@ def run_channels(self, samples): return min_index -def track(samples, channels, - ms_to_track, - sampling_freq, - chipping_rate=defaults.chipping_rate, - l2c_handover=True, - show_progress=True, - loop_filter_class=AidedTrackingLoop, - correlator=track_correlate, - stage2_coherent_ms=None, - stage2_loop_filter_params=None, - multi=False, - tracker_options=None): - - n_channels = len(channels) - - total_samples_num = len(samples[defaults.sample_channel_GPS_L1]['samples']) - samples_length_ms = int(1e3 * total_samples_num / sampling_freq) - - if ms_to_track is None: - ms_to_track = samples_length_ms - - if samples_length_ms < ms_to_track: - logger.warning("Samples set too short for requested tracking length (%.4fs)" - % (ms_to_track * 1e-3)) - ms_to_track = samples_length_ms - - logger.info("Tracking %.4fs of data (%d samples)" % - (ms_to_track * 1e-3, ms_to_track * 1e-3 * sampling_freq)) - - # Make sure we have an integer number of points - num_points = int(math.floor(ms_to_track)) - - logger.info("Tracking starting") - logger.debug("Tracking %d channels, PRNs %s" % - (n_channels, [chan.prn + 1 for chan in channels])) - - # If progressbar is not available, disable show_progress. - if show_progress and not _progressbar_available: - show_progress = False - logger.warning("show_progress = True but progressbar module not found.") - - # Setup our progress bar if we need it - if show_progress and not multi: - widgets = [' Tracking ', - progressbar.Attribute(['chan', 'nchan'], - '(CH: %d/%d)', - '(CH: -/-)'), ' ', - progressbar.Percentage(), ' ', - progressbar.ETA(), ' ', - progressbar.Bar()] - pbar = progressbar.ProgressBar(widgets=widgets, - maxval=n_channels * num_points, - attr={'nchan': n_channels}) - pbar.start() - else: - pbar = None - - # Run tracking for each channel - def do_channel(chan, n=None, q_progress=None): - isL1CA = (chan.signal == gps_constants.L1CA) - isL2C = (chan.signal == gps_constants.L2C) - - if not isL1CA and not isL2C: - NotImplementedError("Signal type '%s' is not supported" % chan.signal) - - track_result = TrackResults(num_points, chan.prn, chan.signal) - l2c_handover_chan = AcquisitionResult(chan.prn, 0, 0, 0, 0, '-', 'l2c') - - # Do not track if acquisition or handover failed - if chan.status == '-': - return track_result, l2c_handover_chan - - IF = samples[chan.sample_channel]['IF'] - - logger.info("[PRN: %d (%s)] Tracking is started. " - "IF: %.1f, Doppler: %.1f, code phase: %.1f, " - "sample channel: %d sample index: %d" % - (chan.prn + 1, chan.signal, - IF, chan.doppler, chan.code_phase, - chan.sample_channel, - chan.sample_index)) - - if isL1CA: - loop_filter_params = defaults.l1ca_stage1_loop_filter_params - lock_detect_params = defaults.l1ca_lock_detect_params_opt - lock_detect = LockDetector( - k1=lock_detect_params["k1"], - k2=lock_detect_params["k2"], - lp=lock_detect_params["lp"], - lo=lock_detect_params["lo"]) - prn_code = caCodes[chan.prn] - coherent_ms = 1 - nav_msg = NavMsg() - nav_msg_bit_phase_ref = np.zeros(num_points) - nav_bit_sync = NBSMatchBit() if chan.prn < 32 else NBSSBAS() - # Convert acquisition SNR to C/N0 - cn0_0 = 10 * np.log10(chan.snr) - cn0_0 += 10 * np.log10(defaults.L1CA_CHANNEL_BANDWIDTH_HZ) - elif isL2C: - loop_filter_params = defaults.l2c_loop_filter_params - lock_detect_params = defaults.l2c_lock_detect_params_20ms - lock_detect = LockDetector( - k1=lock_detect_params["k1"], - k2=lock_detect_params["k2"], - lp=lock_detect_params["lp"], - lo=lock_detect_params["lo"]) - prn_code = L2CMCodes[chan.prn] - coherent_ms = 20 - cnav_msg = CNavMsg() - cnav_msg_decoder = CNavMsgDecoder() - # Convert acquisition SNR to C/N0 - cn0_0 = 10 * np.log10(chan.snr) - cn0_0 += 10 * np.log10(defaults.L2C_CHANNEL_BANDWIDTH_HZ) - else: - raise NotImplementedError() - - alias_detect_init = 1 # require alias_detect_first() call - # or alias_detect.reinit() call or both - alias_detect = AliasDetector( - acc_len=defaults.alias_detect_interval_ms / coherent_ms, - time_diff=1) - - cn0_est = CN0Estimator( - bw=1e3 / coherent_ms, - cn0_0=cn0_0, - cutoff_freq=10, - loop_freq=loop_filter_params["loop_freq"] - ) - - # Estimate initial code freq via aiding from acq carrier freq - if isL1CA: - code_freq_init = chan.doppler * \ - gps_constants.chip_rate / gps_constants.l1 - elif isL2C: - code_freq_init = chan.doppler * \ - gps_constants.chip_rate / gps_constants.l2 - else: - raise NotImplementedError() - - loop_filter = loop_filter_class( - loop_freq=loop_filter_params['loop_freq'], - code_freq=code_freq_init, - code_bw=loop_filter_params['code_bw'], - code_zeta=loop_filter_params['code_zeta'], - code_k=loop_filter_params['code_k'], - carr_to_code=0, # the provided code frequency accounts for Doppler - carr_freq=chan.doppler, - carr_bw=loop_filter_params['carr_bw'], - carr_zeta=loop_filter_params['carr_zeta'], - carr_k=loop_filter_params['carr_k'], - carr_freq_b1=loop_filter_params['carr_freq_b1'], - ) - code_phase = 0.0 - carr_phase = 0.0 - next_code_freq = loop_filter.to_dict()['code_freq'] - next_carr_freq = loop_filter.to_dict()['carr_freq'] - - # Number of samples to seek ahead in file - samples_per_chip = int(round(sampling_freq / chipping_rate)) - - # Set sample_index to start on a code rollover - sample_index = chan.sample_index - sample_index += chan.code_phase * samples_per_chip - - # Start in 1ms integration until we know the nav bit phase - stage1 = True - - carr_phase_acc = 0.0 - code_phase_acc = 0.0 - - progress = 0 - ms_tracked = 0 - i = 0 - - pipelining = False - pipelining_k = 0. - if tracker_options: - mode = tracker_options['mode'] - if mode == 'pipelining': - pipelining = True - pipelining_k = tracker_options['k'] - else: - raise ValueError("Invalid tracker mode %s" % str(mode)) - - # Process the specified number of ms - while ms_tracked < ms_to_track and sample_index < total_samples_num: - if pbar: - pbar.update(ms_tracked + n * num_points, attr={'chan': n + 1}) - - if isL1CA: - # For L1 C/A there are coherent and non-coherent tracking options. - if stage1 and \ - stage2_coherent_ms and \ - nav_bit_sync.bit_phase == nav_bit_sync.bit_phase_ref: - - logger.info("[PRN: %d (%s)] switching to stage2, coherent_ms=%d" % - (chan.prn + 1, chan.signal, stage2_coherent_ms)) - - stage1 = False - coherent_ms = stage2_coherent_ms - - loop_filter.retune(**stage2_loop_filter_params) - lock_detect.reinit( - k1=lock_detect_params["k1"] * coherent_ms, - k2=lock_detect_params["k2"], - lp=lock_detect_params["lp"], - lo=lock_detect_params["lo"]) - cn0_est = CN0Estimator(bw=1e3 / stage2_coherent_ms, - cn0_0=track_result.cn0[i - 1], - cutoff_freq=10, - loop_freq=1e3 / stage2_coherent_ms) - - coherent_iter = coherent_ms - elif isL2C: - # L2 C is always tracked coherently - coherent_ms = 20 - coherent_iter = 1 - else: - raise NotImplementedError() - - if pipelining: - # Pipelining and prediction - corr_code_freq, corr_carr_freq = next_code_freq, next_carr_freq - - next_code_freq = loop_filter.to_dict()['code_freq'] - next_carr_freq = loop_filter.to_dict()['carr_freq'] - - # There is an error between target frequency and actual one. Affect - # the target frequency according to the computed error - carr_freq_error = next_carr_freq - corr_carr_freq - next_carr_freq += carr_freq_error * pipelining_k - - code_freq_error = next_code_freq - corr_code_freq - next_code_freq += code_freq_error * pipelining_k - - else: - # Immediate correction simulation - next_code_freq = loop_filter.to_dict()['code_freq'] - next_carr_freq = loop_filter.to_dict()['carr_freq'] - - corr_code_freq, corr_carr_freq = next_code_freq, next_carr_freq - - E = P = L = 0.j - - for _ in range(coherent_iter): - - if sample_index >= len(samples[chan.sample_channel]['samples']): - break - - samples_ = samples[chan.sample_channel]['samples'][sample_index:] - - E_, P_, L_, blksize, code_phase, carr_phase = correlator( - samples_, - corr_code_freq + chipping_rate, code_phase, - corr_carr_freq + IF, carr_phase, - prn_code, - sampling_freq, - chan.signal - ) - sample_index += blksize - carr_phase_acc += corr_carr_freq * blksize / sampling_freq - code_phase_acc += corr_code_freq * blksize / sampling_freq - - E += E_ - P += P_ - L += L_ - - # Update PLL lock detector - lock_detect_outo, lock_detect_outp, \ - lock_detect_pcount1, lock_detect_pcount2, \ - lock_detect_lpfi, lock_detect_lpfq = lock_detect.update(P.real, P.imag, - coherent_ms) - - if lock_detect_outo: - if alias_detect_init: - alias_detect_init = 0 - alias_detect.reinit(defaults.alias_detect_interval_ms / coherent_ms, - time_diff=1) - alias_detect.first(P.real, P.imag) - alias_detect_err_hz = alias_detect.second(P.real, P.imag) * np.pi * \ - (1e3 / defaults.alias_detect_interval_ms) - alias_detect.first(P.real, P.imag) - else: - alias_detect_init = 1 - alias_detect_err_hz = 0 - - loop_filter.update(E, P, L) - track_result.coherent_ms[i] = coherent_ms - - if isL1CA: - sync, bit = nav_bit_sync.update(np.real(P), coherent_ms) - if sync: - tow = nav_msg.update(bit) - if tow >= 0: - logger.info("[PRN: %d (%s)] ToW %d" % - (chan.prn + 1, chan.signal, tow)) - if nav_msg.subframe_ready(): - eph = Ephemeris() - res = nav_msg.process_subframe(eph) - if res < 0: - logger.error("[PRN: %d (%s)] Subframe decoding error %d" % - (chan.prn + 1, chan.signal, res)) - elif res > 0: - logger.info("[PRN: %d (%s)] Subframe decoded" % - (chan.prn + 1, chan.signal)) - else: - # Subframe decoding is in progress - pass - else: - tow = -1 - nav_msg_bit_phase_ref[i] = nav_msg.bit_phase_ref - track_result.tow[i] = tow if tow >= 0 else ( - track_result.tow[i - 1] + coherent_ms) - elif isL2C: - symbol = 0xFF if np.real(P) >= 0 else 0x00 - res, delay = cnav_msg_decoder.decode(symbol, cnav_msg) - if res: - logger.debug("[PRN: %d (%s)] CNAV message decoded: " - "prn=%d msg_id=%d tow=%d alert=%d delay=%d" % - (chan.prn + 1, - chan.signal, - cnav_msg.getPrn(), - cnav_msg.getMsgId(), - cnav_msg.getTow(), - cnav_msg.getAlert(), - delay)) - tow = cnav_msg.getTow() * 6000 + delay * 20 - logger.debug("[PRN: %d (%s)] ToW %d" % - (chan.prn + 1, chan.signal, tow)) - track_result.tow[i] = tow - else: - track_result.tow[i] = track_result.tow[i - 1] + coherent_ms - else: - raise NotImplementedError() - - track_result.IF = IF - track_result.carr_phase[i] = carr_phase - track_result.carr_phase_acc[i] = carr_phase_acc - track_result.carr_freq[i] = loop_filter.to_dict()['carr_freq'] + IF - - track_result.code_phase[i] = code_phase - track_result.code_phase_acc[i] = code_phase_acc - track_result.code_freq[i] = loop_filter.to_dict()['code_freq'] + \ - chipping_rate - - # Record stuff for postprocessing - track_result.absolute_sample[i] = sample_index - - track_result.E[i] = E - track_result.P[i] = P - track_result.L[i] = L - - track_result.cn0[i] = cn0_est.update(P.real, P.imag) - - track_result.lock_detect_outo[i] = lock_detect_outo - track_result.lock_detect_outp[i] = lock_detect_outp - track_result.lock_detect_pcount1[i] = lock_detect_pcount1 - track_result.lock_detect_pcount2[i] = lock_detect_pcount2 - track_result.lock_detect_lpfi[i] = lock_detect_lpfi - track_result.lock_detect_lpfq[i] = lock_detect_lpfq - - track_result.alias_detect_err_hz[i] = alias_detect_err_hz - - # Handover to L2C if possible - if isL1CA and l2c_handover_chan.status == '-' and sync: - chan_snr = track_result.cn0[i] - chan_snr -= 10 * np.log10(defaults.L1CA_CHANNEL_BANDWIDTH_HZ) - chan_snr = np.power(10, chan_snr / 10) - l2c_doppler = loop_filter.to_dict( - )['carr_freq'] * gps_constants.l2 / gps_constants.l1 - l2c_handover_chan = AcquisitionResult(track_result.prn, - samples[chan.sample_channel][ - 'IF'] + l2c_doppler, - l2c_doppler, # carrier doppler - track_result.code_phase[i], - chan_snr, - 'A', - 'l2c', - 1, # samples' channel index - track_result.absolute_sample[i]) - i += 1 - if isL1CA or isL2C: - ms_tracked += coherent_ms - else: - raise NotImplementedError() - - if q_progress and (i % 200 == 0): - p = 1.0 * ms_tracked / ms_to_track - q_progress.put(p - progress) - progress = p - - # Possibility for lock-detection later - track_result.status = 'T' - - track_result.resize(i) - if q_progress: - q_progress.put(1.0 - progress) - - logger.info("[PRN: %d (%s)] Results: CN0_mean: %f, pipelining: %d, k: %f" % - (chan.prn + 1, chan.signal, - np.mean(track_result.cn0), - pipelining, pipelining_k)) - - return track_result, l2c_handover_chan - - # Run L1CA - if multi: - track_handover_results = pp.parmap(do_channel, channels, - show_progress=show_progress, func_progress=show_progress) - else: - track_handover_results = map( - lambda (n, chan): do_channel(chan, n=n), enumerate(channels)) - # Extract track and handover results - l1ca_track_results = map(lambda x: x[0], track_handover_results) - l2c_handover_channels = map(lambda x: x[1], track_handover_results) - - # Run L2C - if l2c_handover: - if multi: - track_handover_results = pp.parmap(do_channel, l2c_handover_channels, - show_progress=show_progress, func_progress=show_progress) - else: - track_handover_results = map(lambda (n, chan): do_channel( - chan, n=n), enumerate(l2c_handover_channels)) - # Extract track results, handover results are unused - l2c_track_results = map(lambda x: x[0], track_handover_results) - else: - l2c_track_results = [] - - if pbar: - pbar.finish() - - logger.info("Tracking finished") - - return l1ca_track_results + l2c_track_results - - class TrackResults: def __init__(self, n_points, prn, signal): From 59109c60cd9fecf1a9d9178267ecc3043a765c5b Mon Sep 17 00:00:00 2001 From: Adel Mamin Date: Tue, 22 Mar 2016 10:32:28 +0200 Subject: [PATCH 48/57] Fix l2c_handover parameter handling in tracking.py --- libswiftnav | 2 +- peregrine/tracking.py | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/libswiftnav b/libswiftnav index bb45031..383972d 160000 --- a/libswiftnav +++ b/libswiftnav @@ -1 +1 @@ -Subproject commit bb45031436864520c4005cddcec792cb2c2b7e11 +Subproject commit 383972dde3c7f492e4180180b293453ca8e9f863 diff --git a/peregrine/tracking.py b/peregrine/tracking.py index cbe2e56..02e5e2a 100644 --- a/peregrine/tracking.py +++ b/peregrine/tracking.py @@ -362,7 +362,7 @@ def __init__(self, params): self.nav_msg = NavMsg() self.nav_bit_sync = NBSMatchBit() if self.prn < 32 else NBSSBAS() - self.l2c_handover_chan = None + self.l2c_handover_acq = None self.l2c_handover_done = False def _run_preprocess(self): @@ -391,9 +391,9 @@ def _run_preprocess(self): self.coherent_iter = self.coherent_ms def _get_result(self): - if self.l2c_handover_chan and not self.l2c_handover_done: + if self.l2c_handover_acq and not self.l2c_handover_done: self.l2c_handover_done = True - return self.l2c_handover_chan + return self.l2c_handover_acq return None def _run_postprocess(self): @@ -421,14 +421,14 @@ def _run_postprocess(self): self.track_result.tow[self.i - 1] + self.coherent_ms) # Handover to L2C if possible - if not self.l2c_handover_chan and \ + if self.l2c_handover and not self.l2c_handover_acq and \ 'samples' in self.samples[gps_constants.L2C] and sync: chan_snr = self.track_result.cn0[self.i] chan_snr -= 10 * np.log10(defaults.L1CA_CHANNEL_BANDWIDTH_HZ) chan_snr = np.power(10, chan_snr / 10) l2c_doppler = self.loop_filter.to_dict( )['carr_freq'] * gps_constants.l2 / gps_constants.l1 - self.l2c_handover_chan = AcquisitionResult(self.prn, + self.l2c_handover_acq = AcquisitionResult(self.prn, self.samples[gps_constants.L2C]['IF'] + l2c_doppler, l2c_doppler, # carrier doppler self.track_result.code_phase[self.i], From 99ad68435323d7fb7952e56f169043323d36c30b Mon Sep 17 00:00:00 2001 From: Adel Mamin Date: Tue, 22 Mar 2016 13:50:29 +0200 Subject: [PATCH 49/57] Removed SNR filtering --- peregrine/tracking.py | 1 - 1 file changed, 1 deletion(-) diff --git a/peregrine/tracking.py b/peregrine/tracking.py index 02e5e2a..41b8794 100644 --- a/peregrine/tracking.py +++ b/peregrine/tracking.py @@ -539,7 +539,6 @@ def __init__(self, else: self.pbar = None - channels = (c for c in channels if c.snr > 100) self.tracking_channels = map(self._create_channel, channels) def start(self): From f57ef1b11344819423b2282de055698d372379f2 Mon Sep 17 00:00:00 2001 From: Valeri Atamaniouk Date: Tue, 22 Mar 2016 13:59:30 +0200 Subject: [PATCH 50/57] peregrine: progressbar and traking fixes Fixed merge issues related to progressbar and sample loading. --- peregrine/analysis/tracking_loop.py | 2 +- peregrine/tracking.py | 174 +++++++++++++++------------- 2 files changed, 95 insertions(+), 81 deletions(-) diff --git a/peregrine/analysis/tracking_loop.py b/peregrine/analysis/tracking_loop.py index db762d2..ceaa2ca 100644 --- a/peregrine/analysis/tracking_loop.py +++ b/peregrine/analysis/tracking_loop.py @@ -198,8 +198,8 @@ def main(): if sample_index == samples['sample_index']: condition = False else: + samples['sample_index'] = sample_index samples = load_samples(samples = samples, - sample_index = sample_index, filename = args.file, file_format = args.file_format) tracker.stop() diff --git a/peregrine/tracking.py b/peregrine/tracking.py index 41b8794..72f6a51 100644 --- a/peregrine/tracking.py +++ b/peregrine/tracking.py @@ -40,6 +40,7 @@ except ImportError: _progressbar_available = False + class TrackingLoop(object): """ Abstract base class for a tracking loop. @@ -91,12 +92,14 @@ def update(self, e, p, l): """ raise NotImplementedError() + def _tracking_channel_factory(parameters): if parameters['acq'].signal == gps_constants.L1CA: return TrackingChannelL1CA(parameters) if parameters['acq'].signal == gps_constants.L2C: return TrackingChannelL2C(parameters) + class TrackingChannel(object): def __init__(self, params): @@ -110,40 +113,41 @@ def __init__(self, params): self.stage1 = True self.lock_detect = LockDetector( - k1 = self.lock_detect_params["k1"], - k2 = self.lock_detect_params["k2"], - lp = self.lock_detect_params["lp"], - lo = self.lock_detect_params["lo"]) + k1=self.lock_detect_params["k1"], + k2=self.lock_detect_params["k2"], + lp=self.lock_detect_params["lp"], + lo=self.lock_detect_params["lo"]) self.alias_detect = AliasDetector( - acc_len = defaults.alias_detect_interval_ms / self.coherent_ms, - time_diff = 1) + 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, - cutoff_freq = 10, - loop_freq = self.loop_filter_params["loop_freq"] + cn0_0=self.cn0_0, + cutoff_freq=10, + loop_freq=self.loop_filter_params["loop_freq"] ) self.loop_filter = self.loop_filter_class( - loop_freq = self.loop_filter_params['loop_freq'], - code_freq = self.code_freq_init, - code_bw = self.loop_filter_params['code_bw'], - code_zeta = self.loop_filter_params['code_zeta'], - code_k = self.loop_filter_params['code_k'], - carr_to_code = 0, # the provided code frequency accounts for Doppler - carr_freq = self.acq.doppler, - carr_bw = self.loop_filter_params['carr_bw'], - carr_zeta = self.loop_filter_params['carr_zeta'], - carr_k = self.loop_filter_params['carr_k'], - carr_freq_b1 = self.loop_filter_params['carr_freq_b1'], + loop_freq=self.loop_filter_params['loop_freq'], + code_freq=self.code_freq_init, + code_bw=self.loop_filter_params['code_bw'], + code_zeta=self.loop_filter_params['code_zeta'], + code_k=self.loop_filter_params['code_k'], + carr_to_code=0, # the provided code frequency accounts for Doppler + carr_freq=self.acq.doppler, + carr_bw=self.loop_filter_params['carr_bw'], + carr_zeta=self.loop_filter_params['carr_zeta'], + carr_k=self.loop_filter_params['carr_k'], + carr_freq_b1=self.loop_filter_params['carr_freq_b1'], ) self.next_code_freq = self.loop_filter.to_dict()['code_freq'] self.next_carr_freq = self.loop_filter.to_dict()['carr_freq'] - self.track_result = TrackResults(self.results_num, self.acq.prn, self.acq.signal) + 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 @@ -174,26 +178,26 @@ def dump(self): def start(self): logger.info("[PRN: %d (%s)] Tracking is started. " - "IF: %.1f, Doppler: %.1f, code phase: %.1f, " - "sample channel: %d sample index: %d" % - (self.prn + 1, - self.signal, - self.IF, - self.acq.doppler, - self.acq.code_phase, - self.acq.sample_channel, - self.acq.sample_index)) + "IF: %.1f, Doppler: %.1f, code phase: %.1f, " + "sample channel: %d sample index: %d" % + (self.prn + 1, + self.signal, + self.IF, + self.acq.doppler, + self.acq.code_phase, + self.acq.sample_channel, + self.acq.sample_index)) def get_index(self): return self.sample_index - def _run_preprocess(self): # optionally redefined in subclasses + def _run_preprocess(self): # optionally redefined in subclasses pass - def _run_postprocess(self): # optionally redefine in subclasses + def _run_postprocess(self): # optionally redefine in subclasses pass - def _get_result(self): # optionally redefine in subclasses + def _get_result(self): # optionally redefine in subclasses return None def run_parallel(self, samples): @@ -214,7 +218,7 @@ def run(self, samples): estimated_blksize = self.coherent_ms * self.sampling_freq / 1e3 while self.samples_tracked < self.samples_to_track and \ - (sample_index + 2 * estimated_blksize) < samples_total: + (sample_index + 2 * estimated_blksize) < samples_total: self._run_preprocess() @@ -228,10 +232,10 @@ def run(self, samples): # There is an error between target frequency and actual one. Affect # the target frequency according to the computed error carr_freq_error = self.next_carr_freq - corr_carr_freq - self.next_carr_freq += carr_freq_error * pipelining_k + self.next_carr_freq += carr_freq_error * self.pipelining_k code_freq_error = self.next_code_freq - corr_code_freq - self.next_code_freq += code_freq_error * pipelining_k + self.next_code_freq += code_freq_error * self.pipelining_k else: # Immediate correction simulation @@ -272,24 +276,24 @@ def run(self, samples): # 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, - self.coherent_ms) + 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, + self.coherent_ms) if lock_detect_outo: if self.alias_detect_init: self.alias_detect_init = 0 - self.alias_detect.reinit(defaults.alias_detect_interval_ms / \ + self.alias_detect.reinit(defaults.alias_detect_interval_ms / self.coherent_ms, 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.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 @@ -302,22 +306,23 @@ def run(self, samples): self.track_result.carr_phase[self.i] = self.carr_phase self.track_result.carr_phase_acc[self.i] = self.carr_phase_acc self.track_result.carr_freq[self.i] = \ - self.loop_filter.to_dict()['carr_freq'] + self.IF + self.loop_filter.to_dict()['carr_freq'] + self.IF self.track_result.code_phase[self.i] = self.code_phase self.track_result.code_phase_acc[self.i] = self.code_phase_acc self.track_result.code_freq[self.i] = \ - self.loop_filter.to_dict()['code_freq'] + self.chipping_rate + self.loop_filter.to_dict()['code_freq'] + self.chipping_rate # Record stuff for postprocessing self.track_result.absolute_sample[self.i] = self.sample_index + \ - samples_processed + samples_processed self.track_result.E[self.i] = self.E self.track_result.P[self.i] = self.P self.track_result.L[self.i] = self.L - self.track_result.cn0[self.i] = self.cn0_est.update(self.P.real, self.P.imag) + self.track_result.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 @@ -332,7 +337,7 @@ def run(self, samples): self.samples_tracked = self.sample_index + samples_processed self.track_result.ms_tracked[self.i] = self.samples_tracked * 1e3 / \ - self.sampling_freq + self.sampling_freq self.i += 1 if self.i >= self.results_num: @@ -343,7 +348,9 @@ def run(self, samples): return self._get_result() + class TrackingChannelL1CA(TrackingChannel): + def __init__(self, params): # Convert acquisition SNR to C/N0 cn0_0 = 10 * np.log10(params['acq'].snr) @@ -354,7 +361,7 @@ def __init__(self, params): params['IF'] = params['samples'][gps_constants.L1CA]['IF'] params['prn_code'] = caCodes[params['acq'].prn] params['code_freq_init'] = params['acq'].doppler * \ - gps_constants.chip_rate / gps_constants.l1 + gps_constants.chip_rate / gps_constants.l1 params['loop_filter_params'] = defaults.l1ca_stage1_loop_filter_params params['lock_detect_params'] = defaults.l1ca_lock_detect_params_opt @@ -383,10 +390,10 @@ def _run_preprocess(self): k2=self.lock_detect_params["k2"], lp=self.lock_detect_params["lp"], lo=self.lock_detect_params["lo"]) - self.cn0_est = CN0Estimator(bw = 1e3 / self.stage2_coherent_ms, - cn0_0 = self.track_result.cn0[self.i - 1], - cutoff_freq = 10, - loop_freq = 1e3 / self.stage2_coherent_ms) + self.cn0_est = CN0Estimator(bw=1e3 / self.stage2_coherent_ms, + cn0_0=self.track_result.cn0[self.i - 1], + cutoff_freq=10, + loop_freq=1e3 / self.stage2_coherent_ms) self.coherent_iter = self.coherent_ms @@ -429,15 +436,19 @@ def _run_postprocess(self): l2c_doppler = self.loop_filter.to_dict( )['carr_freq'] * gps_constants.l2 / gps_constants.l1 self.l2c_handover_acq = AcquisitionResult(self.prn, - self.samples[gps_constants.L2C]['IF'] + l2c_doppler, - l2c_doppler, # carrier doppler - self.track_result.code_phase[self.i], - chan_snr, - 'A', - gps_constants.L2C, - self.track_result.absolute_sample[self.i]) + self.samples[gps_constants.L2C][ + 'IF'] + l2c_doppler, + l2c_doppler, # carrier doppler + self.track_result.code_phase[ + self.i], + chan_snr, + 'A', + gps_constants.L2C, + self.track_result.absolute_sample[self.i]) + class TrackingChannelL2C(TrackingChannel): + def __init__(self, params): # Convert acquisition SNR to C/N0 cn0_0 = 10 * np.log10(params['acq'].snr) @@ -450,7 +461,7 @@ def __init__(self, params): params['IF'] = params['samples'][gps_constants.L2C]['IF'] params['prn_code'] = L2CMCodes[params['acq'].prn] params['code_freq_init'] = params['acq'].doppler * \ - gps_constants.chip_rate / gps_constants.l2 + gps_constants.chip_rate / gps_constants.l2 TrackingChannel.__init__(self, params) @@ -476,7 +487,8 @@ def _run_postprocess(self): self.track_result.tow[self.i] = tow else: self.track_result.tow[self.i] = self.track_result.tow[self.i - 1] + \ - self.coherent_ms + self.coherent_ms + class Tracker(object): @@ -485,16 +497,16 @@ def __init__(self, channels, ms_to_track, sampling_freq, - chipping_rate = defaults.chipping_rate, - l2c_handover = True, - show_progress = True, - loop_filter_class = AidedTrackingLoop, - correlator = track_correlate, - stage2_coherent_ms = None, - stage2_loop_filter_params = None, - multi = True, - tracker_options = None, - output_file = None): + chipping_rate=defaults.chipping_rate, + l2c_handover=True, + show_progress=True, + loop_filter_class=AidedTrackingLoop, + correlator=track_correlate, + stage2_coherent_ms=None, + stage2_loop_filter_params=None, + multi=True, + tracker_options=None, + output_file=None): self.samples = samples self.sampling_freq = sampling_freq @@ -534,8 +546,9 @@ def __init__(self, progressbar.ETA(), ' ', progressbar.Bar()] self.pbar = progressbar.ProgressBar(widgets=widgets, - maxval=samples['samples_total'], - attr={'samples': self.samples['samples_total']}) + maxval=samples['samples_total'], + attr={'samples': self.samples['samples_total'], + 'sample': 0l}) else: self.pbar = None @@ -605,7 +618,7 @@ def run_channels(self, samples): handover = [h for h in handover if h is not None] if handover: - tracking_channels = map(self._create_channel, handover); + tracking_channels = map(self._create_channel, handover) self.tracking_channels += tracking_channels else: tracking_channels = None @@ -614,10 +627,11 @@ def run_channels(self, samples): min_index = min(indexes) if self.pbar: - self.pbar.update(min_index, attr={'samples': min_index}) + self.pbar.update(min_index, attr={'sample': min_index}) return min_index + class TrackResults: def __init__(self, n_points, prn, signal): @@ -667,7 +681,7 @@ def dump(self, output_file, size): else: mode = 'a' - #print "Storing tracking results into file: ", filename + # print "Storing tracking results into file: ", filename with open(filename, mode) as f1: if self.print_start: From c2dbccd47a81492aa679cfa34fddf36baa0c8cc6 Mon Sep 17 00:00:00 2001 From: Valeri Atamaniouk Date: Mon, 21 Mar 2016 09:45:59 +0200 Subject: [PATCH 51/57] peregrine: added support for short/long cycles Added support for simulating short/long cycles firmware behaviour. --- peregrine/acquisition.py | 111 ++++++++------ peregrine/analysis/print_res.py | 15 +- peregrine/analysis/tracking_loop.py | 228 ++++++++++++++++++---------- peregrine/run.py | 89 +++++++---- peregrine/tracking.py | 103 +++++++++---- 5 files changed, 356 insertions(+), 190 deletions(-) mode change 100644 => 100755 peregrine/analysis/print_res.py mode change 100644 => 100755 peregrine/analysis/tracking_loop.py diff --git a/peregrine/acquisition.py b/peregrine/acquisition.py index 0c4a26a..f8052d7 100644 --- a/peregrine/acquisition.py +++ b/peregrine/acquisition.py @@ -13,6 +13,7 @@ """ +import sys import numpy as np import pyfftw import cPickle @@ -81,7 +82,7 @@ def __init__(self, samples_per_code, code_length=defaults.code_length, n_codes_integrate=4, - offsets = None, + offsets=None, wisdom_file=DEFAULT_WISDOM_FILE): self.sampling_freq = sampling_freq @@ -95,11 +96,11 @@ def __init__(self, if n_codes_integrate <= 10: offsets = [0, self.n_integrate] elif n_codes_integrate <= 13: - offsets = [0, 2*(n_codes_integrate - 10)*self.samples_per_code, + offsets = [0, 2 * (n_codes_integrate - 10) * self.samples_per_code, self.n_integrate] elif n_codes_integrate <= 15: offsets = [0, (n_codes_integrate - 10) * self.samples_per_code, - 2*(n_codes_integrate - 10) * self.samples_per_code, + 2 * (n_codes_integrate - 10) * self.samples_per_code, self.n_integrate] else: raise ValueError("Integration interval too long to guess nav-declobber " @@ -129,9 +130,9 @@ def __init__(self, # Allocate aligned arrays for the inverse FFT. self.corr_ft = pyfftw.n_byte_align_empty((self.n_integrate), 16, - dtype=np.complex128) + dtype=np.complex128) self.corr = pyfftw.n_byte_align_empty((self.n_integrate), 16, - dtype=np.complex128) + dtype=np.complex128) # Setup FFTW transforms for inverse FFT. self.corr_ifft = pyfftw.FFTW(self.corr_ft, self.corr, @@ -226,13 +227,13 @@ def interpolate(self, S_0, S_1, S_2, interpolation='gaussian'): """ if interpolation == 'parabolic': # Parabolic interpolation. - return 0.5 * (S_2 - S_0) / (2*S_1 - S_0 - S_2) + return 0.5 * (S_2 - S_0) / (2 * S_1 - S_0 - S_2) elif interpolation == 'gaussian': # Gaussian interpolation. ln_S_0 = np.log(S_0) ln_S_1 = np.log(S_1) ln_S_2 = np.log(S_2) - return 0.5 * (ln_S_2 - ln_S_0) / (2*ln_S_1 - ln_S_0 - ln_S_2) + return 0.5 * (ln_S_2 - ln_S_0) / (2 * ln_S_1 - ln_S_0 - ln_S_2) elif interpolation == 'none': return 0 else: @@ -270,8 +271,9 @@ def acquire(self, code, freqs, progress_callback=None): # Upsample the code to our sampling frequency. code_indices = np.arange(1.0, self.n_integrate + 1.0) / \ - self.samples_per_chip - code_indices = np.remainder(np.asarray(code_indices, np.int), self.code_length) + self.samples_per_chip + code_indices = np.remainder( + np.asarray(code_indices, np.int), self.code_length) self.code[:] = code[code_indices] # Find the conjugate Fourier transform of the code which will be used to @@ -288,7 +290,7 @@ def acquire(self, code, freqs, progress_callback=None): # Shift the signal in the frequency domain to remove the carrier # i.e. mix down to baseband. shift = int((float(freq) * len(self.short_samples_ft[0]) / - self.sampling_freq) + 0.5) + self.sampling_freq) + 0.5) # Search over the possible nav bit offset intervals for offset_i in range(len(self.offsets)): @@ -307,7 +309,6 @@ def acquire(self, code, freqs, progress_callback=None): max_indices = np.unravel_index(results.argmax(), results.shape) return results[max_indices[0]] - def find_peak(self, freqs, results, interpolation='gaussian'): """ Find the peak within an set of acquisition results. @@ -338,17 +339,19 @@ def find_peak(self, freqs, results, interpolation='gaussian'): freq_index, cp_samples = np.unravel_index(results.argmax(), results.shape) - if freq_index > 1 and freq_index < len(freqs)-1: + if freq_index > 1 and freq_index < len(freqs) - 1: delta = self.interpolate( - results[freq_index-1][cp_samples], - results[freq_index][cp_samples], - results[freq_index+1][cp_samples], - interpolation + results[freq_index - 1][cp_samples], + results[freq_index][cp_samples], + results[freq_index + 1][cp_samples], + interpolation ) if delta > 0: - freq = freqs[freq_index] + (freqs[freq_index+1] - freqs[freq_index]) * delta + freq = freqs[freq_index] + \ + (freqs[freq_index + 1] - freqs[freq_index]) * delta else: - freq = freqs[freq_index] - (freqs[freq_index-1] - freqs[freq_index]) * delta + freq = freqs[freq_index] - \ + (freqs[freq_index - 1] - freqs[freq_index]) * delta else: freq = freqs[freq_index] @@ -361,13 +364,13 @@ def find_peak(self, freqs, results, interpolation='gaussian'): def acquisition(self, prns=range(32), - doppler_priors = None, - doppler_search = 7000, - doppler_step = None, + doppler_priors=None, + doppler_search=7000, + doppler_step=None, threshold=DEFAULT_THRESHOLD, - show_progress=True, + progress_bar_output='none', multi=True - ): + ): """ Perform an acquisition for a given list of PRNs. @@ -421,6 +424,15 @@ def acquisition(self, if doppler_priors is None: doppler_priors = np.zeros_like(prns) + if progress_bar_output == 'stdout': + show_progress = True + progress_fd = sys.stdout + elif progress_bar_output == 'stderr': + show_progress = True + progress_fd = sys.stderr + else: + show_progress = False + progress_fd = -1 # If progressbar is not available, disable show_progress. if show_progress and not _progressbar_available: @@ -436,7 +448,8 @@ def acquisition(self, progressbar.Bar()] pbar = progressbar.ProgressBar(widgets=widgets, maxval=int(len(prns) * - (2 * doppler_search / doppler_step + 1))) + (2 * doppler_search / doppler_step + 1)), + fd=progress_fd) pbar.start() else: pbar = None @@ -448,16 +461,15 @@ def do_acq(n): doppler_prior + doppler_search, doppler_step) + self.IF 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={'prn': prn + 1}) else: progress_callback = None coarse_results = self.acquire(caCodes[prn], freqs, progress_callback=progress_callback) - code_phase, carr_freq, snr = self.find_peak(freqs, coarse_results, - interpolation = 'gaussian') + interpolation='gaussian') # If the result is above the threshold, then we have acquired the # satellite. @@ -481,7 +493,8 @@ def progress_callback(freq_num, num_freqs): return acq_result if multi: - acq_results = parmap(do_acq, range(len(prns)), show_progress=show_progress) + acq_results = parmap( + do_acq, range(len(prns)), show_progress=show_progress) else: acq_results = map(do_acq, range(len(prns))) @@ -507,7 +520,8 @@ def load_wisdom(self, wisdom_file=DEFAULT_WISDOM_FILE): def save_wisdom(self, wisdom_file=DEFAULT_WISDOM_FILE): """Save FFTW wisdom to file.""" with open(wisdom_file, 'wb') as f: - cPickle.dump(pyfftw.export_wisdom(), f, protocol=cPickle.HIGHEST_PROTOCOL) + cPickle.dump( + pyfftw.export_wisdom(), f, protocol=cPickle.HIGHEST_PROTOCOL) class AcquisitionResult: @@ -538,11 +552,11 @@ class AcquisitionResult: sample_index : Index of sample when acquisition succeeded """ - __slots__ = ('prn', 'carr_freq', 'doppler', \ + __slots__ = ('prn', 'carr_freq', 'doppler', 'code_phase', 'snr', 'status', 'signal') def __init__(self, prn, carr_freq, doppler, code_phase, snr, status, signal, - sample_index = 0): + sample_index=0): self.prn = prn self.snr = snr self.carr_freq = carr_freq @@ -554,7 +568,7 @@ def __init__(self, prn, carr_freq, doppler, code_phase, snr, status, signal, def __str__(self): return "PRN %2d (%s) SNR %6.2f @ CP %6.1f, %+8.2f Hz %s" % \ - (self.prn + 1, self.signal, self.snr, self.code_phase, \ + (self.prn + 1, self.signal, self.snr, self.code_phase, self.doppler, self.status) def __repr__(self): @@ -579,7 +593,7 @@ def _equal(self, other): ------ out : bool True if the passed :class:`AcquisitionResult` object is identical. - + """ if set(self.__dict__.keys()) != set(other.__dict__.keys()): return False @@ -593,6 +607,7 @@ def _equal(self, other): return True + def save_acq_results(filename, acq_results): """ Save a set of acquisition results to a file. @@ -608,6 +623,7 @@ def save_acq_results(filename, acq_results): with open(filename, 'wb') as f: cPickle.dump(acq_results, f, protocol=cPickle.HIGHEST_PROTOCOL) + def load_acq_results(filename): """ Load a set of acquisition results from a file. @@ -626,7 +642,8 @@ def load_acq_results(filename): with open(filename, 'rb') as f: return cPickle.load(f) -def print_scores(acq_results, pred, pred_dopp = None): + +def print_scores(acq_results, pred, pred_dopp=None): if pred_dopp is None: pred_dopp = np.zeros_like(pred) @@ -637,19 +654,19 @@ def print_scores(acq_results, pred, pred_dopp = None): sum_abs_dopp_err = 0 for i, prn in enumerate(pred): - print "%2d\t%+6.0f" % (prn + 1, pred_dopp[i]), - if acq_results[i].status == 'A': - n_match += 1 - dopp_err = acq_results[i].doppler - pred_dopp[i] - sum_dopp_err += dopp_err - sum_abs_dopp_err += abs(dopp_err) - if abs(dopp_err) > abs(worst_dopp_err): - worst_dopp_err = dopp_err - print "\t%+6.0f\t%+5.0f\t%5.1f" % ( - acq_results[i].doppler, dopp_err, acq_results[i].snr) - else: - print + print "%2d\t%+6.0f" % (prn + 1, pred_dopp[i]), + if acq_results[i].status == 'A': + n_match += 1 + dopp_err = acq_results[i].doppler - pred_dopp[i] + sum_dopp_err += dopp_err + sum_abs_dopp_err += abs(dopp_err) + if abs(dopp_err) > abs(worst_dopp_err): + worst_dopp_err = dopp_err + print "\t%+6.0f\t%+5.0f\t%5.1f" % ( + acq_results[i].doppler, dopp_err, acq_results[i].snr) + else: + print 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/analysis/print_res.py b/peregrine/analysis/print_res.py old mode 100644 new mode 100755 index f7c415a..48c74b9 --- a/peregrine/analysis/print_res.py +++ b/peregrine/analysis/print_res.py @@ -1,9 +1,22 @@ +#!/usr/bin/env python + +# 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. + from scipy import signal import numpy as np import matplotlib.pyplot as plt from StringIO import StringIO import argparse + def main(): parser = argparse.ArgumentParser() @@ -37,4 +50,4 @@ def main(): plt.show() if __name__ == '__main__': - main() + main() diff --git a/peregrine/analysis/tracking_loop.py b/peregrine/analysis/tracking_loop.py old mode 100644 new mode 100755 index ceaa2ca..51fd650 --- a/peregrine/analysis/tracking_loop.py +++ b/peregrine/analysis/tracking_loop.py @@ -1,3 +1,5 @@ +#!/usr/bin/env python + # Copyright (C) 2016 Swift Navigation Inc. # Contact: Adel Mamin # @@ -8,77 +10,132 @@ # EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED # WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. -import os import argparse +import sys from peregrine.samples import load_samples from peregrine.acquisition import AcquisitionResult from peregrine import defaults from peregrine.log import default_logging_config -import peregrine.tracking as tracking +from peregrine.tracking import Tracker from peregrine.gps_constants import L1CA, L2C from peregrine.initSettings import initSettings + def main(): default_logging_config() parser = argparse.ArgumentParser() - parser.add_argument("file", - help="the sample data file to process") - - parser.add_argument("-f", "--file-format", - help="the format of the sample data file " - "('piksi', 'int8', '1bit', '1bitrev', " - "'1bit_x2', '2bits', '2bits_x2', '2bits_x4')") - - parser.add_argument("-t", "--ms-to-track", - help="the number of milliseconds to track." - "(-1: use all available data", - default="-1") - - parser.add_argument("-s", "--sampling-freq", - help="sampling frequency [Hz]. ") - - parser.add_argument("--profile", - help="L1C/A & L2C IF + sampling frequency profile" - "('peregrine'/'custom_rate', 'low_rate', " - "'normal_rate' (piksi_v3), 'high_rate')", - default='peregrine') - - parser.add_argument("-P", "--prn", - help="PRN to track. ") - - parser.add_argument("-p", "--code-phase", - help="code phase [chips]. ") - parser.add_argument("-d", "--carr-doppler", - help="carrier Doppler frequency [Hz]. ") - - parser.add_argument("-o", "--output-file", default="track.csv", - help="Track results file name. " - "Default: %s" % "track.csv") - - parser.add_argument("-S", "--signal", - choices=[L1CA, L2C], - help="Signal type (l1ca / l2c)") - - parser.add_argument("--l2c-handover", - action='store_true', - help="Perform L2C handover", - default=False) - - parser.add_argument('--l1ca-profile', - help='L1 C/A stage profile', - choices=defaults.l1ca_stage_profiles.keys()) - - parser.add_argument("--pipelining", - type=float, - nargs='?', - help="FPGA pipelining coefficient", - const=defaults.pipelining_k, - default=None) - - parser.add_argument("--skip-samples", default=0, - help="How many samples to skip") + if sys.stdout.isatty(): + progress_bar_default = 'stdout' + elif sys.stderr.isatty(): + progress_bar_default = 'stderr' + else: + progress_bar_default = 'none' + + parser.add_argument("--progress-bar", + metavar='FLAG', + choices=['stdout', 'stderr', 'none'], + default=progress_bar_default, + help="Show progress bar. Default is '%s'" % + progress_bar_default) + + inputCtrl = parser.add_argument_group('Data Source', + 'Data source configuration') + inputCtrl.add_argument("file", + help="The sample data file to process") + + inputCtrl.add_argument("-f", "--file-format", + choices=['piksi', 'int8', '1bit', '1bitrev', + '1bit_x2', '2bits', '2bits_x2', '2bits_x4'], + metavar='FORMAT', + help="The format of the sample data file " + "('piksi', 'int8', '1bit', '1bitrev', " + "'1bit_x2', '2bits', '2bits_x2', '2bits_x4')") + + inputCtrl.add_argument("-t", "--ms-to-track", + metavar='MS', + help="the number of milliseconds to track." + "(-1: use all available data", + default="-1") + + inputCtrl.add_argument("--skip-samples", + default=0, + metavar='N_SAMPLES', + help="How many samples to skip") + + inputCtrl.add_argument("-s", + "--sampling-freq", + metavar='FREQ', + help="Sampling frequency [Hz]. ") + + inputCtrl.add_argument("--profile", + choices=['peregrine', 'custom_rate', 'low_rate', + 'normal_rate', 'piksi_v3', 'high_rate'], + metavar='PROFILE', + help="L1C/A & L2C IF + sampling frequency profile" + "('peregrine'/'custom_rate', 'low_rate', " + "'normal_rate', 'piksi_v3', 'high_rate')", + default='peregrine') + + signalParam = parser.add_argument_group('Signal tracking', + 'Parameters for satellite vehicle' + ' signal') + + signalParam.add_argument("-P", "--prn", + help="PRN to track. ") + + signalParam.add_argument("-p", "--code-phase", + metavar='CHIPS', + help="Code phase [chips]. ") + + signalParam.add_argument("-d", "--carr-doppler", + metavar='DOPPLER', + help="carrier Doppler frequency [Hz]. ") + + signalParam.add_argument("-S", "--signal", + choices=[L1CA, L2C], + metavar='BAND', + help="Signal type (l1ca / l2c)") + signalParam.add_argument("--l2c-handover", + action='store_true', + help="Perform L2C handover", + default=False) + signalParam.add_argument('--l1ca-profile', + metavar='PROFILE', + help='L1 C/A stage profile. Controls coherent' + ' integration time and tuning parameters: %s.' % + str(defaults.l1ca_stage_profiles.keys()), + choices=defaults.l1ca_stage_profiles.keys()) + + fpgaSim = parser.add_argument_group('FPGA simulation', + 'FPGA delay control simulation') + fpgaExcl = fpgaSim.add_mutually_exclusive_group(required=False) + fpgaExcl.add_argument("--pipelining", + type=float, + nargs='?', + metavar='PIPELINING_K', + help="Use FPGA pipelining simulation. Supply optional " + " coefficient (%f)" % defaults.pipelining_k, + const=defaults.pipelining_k, + default=None) + + fpgaExcl.add_argument("--short-long-cycles", + type=float, + nargs='?', + metavar='PIPELINING_K', + help="Use FPGA short-long cycle simulation. Supply" + " optional pipelining coefficient (0.)", + const=0., + default=None) + + outputCtrl = parser.add_argument_group('Output parameters', + 'Parameters that control output' + ' data stream.') + outputCtrl.add_argument("-o", "--output-file", + default="track.csv", + help="Track results file name. Default: %s" % + "track.csv") args = parser.parse_args() @@ -133,18 +190,22 @@ def main(): ms_to_track = int(args.ms_to_track) if args.pipelining is not None: - tracker_options = {'mode': 'pipelining', 'k': args.pipelining} + tracker_options = {'mode': 'pipelining', + 'k': args.pipelining} + elif args.short_long_cycles is not None: + tracker_options = {'mode': 'short-long-cycles', + 'k': args.short_long_cycles} else: tracker_options = None - acq_result = AcquisitionResult(prn = prn, - snr = 25, # dB - carr_freq = IF + carr_doppler, - doppler = carr_doppler, - code_phase = code_phase, - status = 'A', - signal = signal, - sample_index = skip_samples) + acq_result = AcquisitionResult(prn=prn, + snr=25, # dB + carr_freq=IF + carr_doppler, + doppler=carr_doppler, + code_phase=code_phase, + status='A', + signal=signal, + sample_index=skip_samples) if args.l1ca_profile: profile = defaults.l1ca_stage_profiles[args.l1ca_profile] @@ -155,13 +216,13 @@ def main(): stage2_params = None samples = {L1CA: {'IF': freq_profile['GPS_L1_IF']}, - L2C: {'IF': freq_profile['GPS_L2_IF']}, + L2C: {'IF': freq_profile['GPS_L2_IF']}, 'samples_total': -1, 'sample_index': skip_samples} - samples = load_samples(samples = samples, - filename = args.file, - file_format = args.file_format) + load_samples(samples=samples, + filename=args.file, + file_format=args.file_format) if ms_to_track < 0: # use all available data @@ -182,15 +243,16 @@ def main(): print "Tracker options: %s" % str(tracker_options) print "======================================================================" - tracker = tracking.Tracker(samples = samples, - channels = [acq_result], - ms_to_track = ms_to_track, - sampling_freq = sampling_freq, # [Hz] - l2c_handover = l2c_handover, - stage2_coherent_ms = stage2_coherent_ms, - stage2_loop_filter_params = stage2_params, - tracker_options = tracker_options, - output_file = args.output_file) + tracker = Tracker(samples=samples, + channels=[acq_result], + ms_to_track=ms_to_track, + sampling_freq=sampling_freq, # [Hz] + l2c_handover=l2c_handover, + stage2_coherent_ms=stage2_coherent_ms, + stage2_loop_filter_params=stage2_params, + tracker_options=tracker_options, + output_file=args.output_file, + progress_bar_output=args.progress_bar) tracker.start() condition = True while condition: @@ -199,9 +261,9 @@ def main(): condition = False else: samples['sample_index'] = sample_index - samples = load_samples(samples = samples, - filename = args.file, - file_format = args.file_format) + load_samples(samples=samples, + filename=args.file, + file_format=args.file_format) tracker.stop() if __name__ == '__main__': diff --git a/peregrine/run.py b/peregrine/run.py index 2d9796e..6f48349 100755 --- a/peregrine/run.py +++ b/peregrine/run.py @@ -13,8 +13,8 @@ import argparse import cPickle import logging -from operator import attrgetter import numpy as np +from operator import attrgetter from peregrine.samples import load_samples from peregrine.acquisition import Acquisition, load_acq_results, save_acq_results @@ -25,6 +25,7 @@ from peregrine.initSettings import initSettings from peregrine.gps_constants import L1CA, L2C + def main(): default_logging_config() @@ -56,12 +57,37 @@ def main(): parser.add_argument('--l1ca-profile', help='L1 C/A stage profile', choices=defaults.l1ca_stage_profiles.keys()) - parser.add_argument("--pipelining", - type=float, - nargs='?', - help="FPGA pipelining coefficient", - const=defaults.pipelining_k, - default=None) + if sys.stdout.isatty(): + progress_bar_default = 'stdout' + elif sys.stderr.isatty(): + progress_bar_default = 'stderr' + else: + progress_bar_default = 'none' + parser.add_argument("--progress-bar", + metavar='FLAG', + choices=['stdout', 'stderr', 'none'], + default=progress_bar_default, + help="Show progress bar. Default is '%s'" % + progress_bar_default) + + fpgaSim = parser.add_argument_group('FPGA simulation', + 'FPGA delay control simulation') + + fpgaSim.add_argument("--pipelining", + type=float, + nargs='?', + help="Use FPGA pipelining simulation. Supply optional " + " coefficient (%f)" % defaults.pipelining_k, + const=defaults.pipelining_k, + default=None) + + fpgaSim.add_argument("--short-long-cycles", + type=float, + nargs='?', + help="Use FPGA short-long cycle simulation. Supply" + " optional pipelining coefficient (0.)", + const=0., + default=None) args = parser.parse_args() if args.file is None: @@ -101,7 +127,7 @@ def main(): (settings.codeFreqBasis / settings.codeLength))) samples = {L1CA: {'IF': freq_profile['GPS_L1_IF']}, - L2C: {'IF': freq_profile['GPS_L2_IF']}, + L2C: {'IF': freq_profile['GPS_L2_IF']}, 'samples_total': -1, 'sample_index': settings.skipNumberOfBytes} @@ -117,16 +143,16 @@ def main(): sys.exit(1) else: # Get 11ms of acquisition samples for fine frequency estimation - acq_samples = load_samples(samples = samples, - num_samples = 11 * samplesPerCode, - filename = args.file, - file_format = args.file_format) + load_samples(samples=samples, + num_samples=11 * samplesPerCode, + filename=args.file, + file_format=args.file_format) - acq = Acquisition(acq_samples[L1CA]['samples'], + acq = Acquisition(samples[L1CA]['samples'], freq_profile['sampling_freq'], freq_profile['GPS_L1_IF'], defaults.code_period * freq_profile['sampling_freq']) - acq_results = acq.acquisition() + acq_results = acq.acquisition(progress_bar_output=args.progress_bar) print "Acquisition is over!" @@ -158,21 +184,24 @@ def main(): track_results_file) sys.exit(1) else: - samples = load_samples(samples = samples, - filename = args.file, - file_format = args.file_format) + load_samples(samples=samples, + filename=args.file, + file_format=args.file_format) if ms_to_process < 0: - ms_to_process = int(1e3 * samples['samples_total'] / freq_profile['sampling_freq']) - - tracker = tracking.Tracker(samples = samples, - channels = acq_results, - ms_to_track = ms_to_process, - sampling_freq = freq_profile['sampling_freq'], # [Hz] - stage2_coherent_ms = stage2_coherent_ms, - stage2_loop_filter_params = stage2_params, - tracker_options = tracker_options, - output_file = args.file) + ms_to_process = int( + 1e3 * samples['samples_total'] / freq_profile['sampling_freq']) + + tracker = tracking.Tracker(samples=samples, + channels=acq_results, + ms_to_track=ms_to_process, + sampling_freq=freq_profile[ + 'sampling_freq'], # [Hz] + stage2_coherent_ms=stage2_coherent_ms, + stage2_loop_filter_params=stage2_params, + tracker_options=tracker_options, + output_file=args.file, + progress_bar_output=args.progress_bar) tracker.start() condition = True while condition: @@ -181,9 +210,9 @@ def main(): condition = False else: samples['sample_index'] = sample_index - samples = load_samples(samples = samples, - filename = args.file, - file_format = args.file_format) + load_samples(samples=samples, + filename=args.file, + file_format=args.file_format) tracker.stop() # try: diff --git a/peregrine/tracking.py b/peregrine/tracking.py index 72f6a51..a99b1e2 100644 --- a/peregrine/tracking.py +++ b/peregrine/tracking.py @@ -30,6 +30,7 @@ from peregrine.include.generateL2CMcode import L2CMCodes import logging +import sys logger = logging.getLogger(__name__) @@ -146,8 +147,9 @@ def __init__(self, params): self.next_code_freq = self.loop_filter.to_dict()['code_freq'] self.next_carr_freq = self.loop_filter.to_dict()['carr_freq'] - self.track_result = TrackResults( - self.results_num, self.acq.prn, self.acq.signal) + 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 @@ -161,15 +163,21 @@ def __init__(self, params): self.i = 0 #self.samples_offset = self.samples['samples_offset'] - self.pipelining = False - self.pipelining_k = 0. + self.pipelining = False # Flag if pipelining is used + self.pipelining_k = 0. # Error prediction coefficient for pipelining + self.short_n_long = False # Short/Long cycle simulation + self.short_step = False # Short cycle if self.tracker_options: - self.mode = self.tracker_options['mode'] - if self.mode == 'pipelining': + mode = self.tracker_options['mode'] + if mode == 'pipelining': + self.pipelining = True + self.pipelining_k = self.tracker_options['k'] + elif mode == 'short-long-cycles': + self.short_n_long = True self.pipelining = True self.pipelining_k = self.tracker_options['k'] else: - raise ValueError("Invalid tracker mode %s" % str(self.mode)) + raise ValueError("Invalid tracker mode %s" % str(mode)) def dump(self): filename = self.track_result.dump(self.output_file, self.i) @@ -229,13 +237,20 @@ def run(self, samples): self.next_code_freq = self.loop_filter.to_dict()['code_freq'] self.next_carr_freq = self.loop_filter.to_dict()['carr_freq'] + if self.short_n_long and not self.stage1 and not self.short_step: + # In case of short/long cycles, the correction applicable for the + # long cycle is smaller proportionally to the actual cycle size + pipelining_k = self.pipelining_k / (self.coherent_ms - 1) + else: + pipelining_k = self.pipelining_k + # There is an error between target frequency and actual one. Affect # the target frequency according to the computed error carr_freq_error = self.next_carr_freq - corr_carr_freq - self.next_carr_freq += carr_freq_error * self.pipelining_k + self.next_carr_freq += carr_freq_error * pipelining_k code_freq_error = self.next_code_freq - corr_code_freq - self.next_code_freq += code_freq_error * self.pipelining_k + self.next_code_freq += code_freq_error * pipelining_k else: # Immediate correction simulation @@ -244,7 +259,16 @@ def run(self, samples): corr_code_freq, corr_carr_freq = self.next_code_freq, self.next_carr_freq - self.E = self.P = self.L = 0.j + if self.short_n_long and not self.stage1: + # When simulating short and long cycles, short step resets EPL + # registers, and long one adds up to them + if self.short_step: + self.E = self.P = self.L = 0.j + self.coherent_iter = 1 + else: + self.coherent_iter = self.coherent_ms - 1 + else: + self.E = self.P = self.L = 0.j for _ in range(self.coherent_iter): @@ -274,6 +298,16 @@ def run(self, samples): self.P += P_ self.L += L_ + if not self.stage1 and self.short_n_long: + if self.short_step: + # In case of short step - go to next integration period + self.short_step = False + self.alias_detect.first(self.P.real, self.P.imag) + continue + else: + # Next step is short cycle + self.short_step = True + # Update PLL lock detector lock_detect_outo, \ lock_detect_outp, \ @@ -282,13 +316,13 @@ def run(self, samples): lock_detect_lpfi, \ lock_detect_lpfq = self.lock_detect.update(self.P.real, self.P.imag, - self.coherent_ms) + self.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_ms, + self.coherent_iter, time_diff=1) self.alias_detect.first(self.P.real, self.P.imag) alias_detect_err_hz = \ @@ -435,16 +469,17 @@ def _run_postprocess(self): chan_snr = np.power(10, chan_snr / 10) l2c_doppler = self.loop_filter.to_dict( )['carr_freq'] * gps_constants.l2 / gps_constants.l1 - self.l2c_handover_acq = AcquisitionResult(self.prn, - self.samples[gps_constants.L2C][ - 'IF'] + l2c_doppler, - l2c_doppler, # carrier doppler - self.track_result.code_phase[ - self.i], - chan_snr, - 'A', - gps_constants.L2C, - self.track_result.absolute_sample[self.i]) + self.l2c_handover_acq = \ + AcquisitionResult(self.prn, + self.samples[gps_constants.L2C][ + 'IF'] + l2c_doppler, + l2c_doppler, # carrier doppler + self.track_result.code_phase[ + self.i], + chan_snr, + 'A', + gps_constants.L2C, + self.track_result.absolute_sample[self.i]) class TrackingChannelL2C(TrackingChannel): @@ -499,12 +534,12 @@ def __init__(self, sampling_freq, chipping_rate=defaults.chipping_rate, l2c_handover=True, - show_progress=True, + progress_bar_output='none', loop_filter_class=AidedTrackingLoop, correlator=track_correlate, stage2_coherent_ms=None, stage2_loop_filter_params=None, - multi=True, + multi=False, tracker_options=None, output_file=None): @@ -515,7 +550,6 @@ def __init__(self, self.output_file = output_file self.chipping_rate = chipping_rate self.l2c_handover = l2c_handover - self.show_progress = show_progress self.correlator = correlator self.stage2_coherent_ms = stage2_coherent_ms self.stage2_loop_filter_params = stage2_loop_filter_params @@ -531,13 +565,23 @@ def __init__(self, else: self.samples_to_track = samples['samples_total'] + if progress_bar_output == 'stdout': + self.show_progress = True + progress_fd = sys.stdout + elif progress_bar_output == 'stderr': + self.show_progress = True + progress_fd = sys.stderr + else: + self.show_progress = False + progress_fd = -1 + # If progressbar is not available, disable show_progress. - if show_progress and not _progressbar_available: - show_progress = False + if self.show_progress and not _progressbar_available: + self.show_progress = False logger.warning("show_progress = True but progressbar module not found.") # Setup our progress bar if we need it - if show_progress: + if self.show_progress: widgets = [' Tracking ', progressbar.Attribute(['sample', 'samples'], '(sample: %d/%d)', @@ -548,7 +592,8 @@ def __init__(self, self.pbar = progressbar.ProgressBar(widgets=widgets, maxval=samples['samples_total'], attr={'samples': self.samples['samples_total'], - 'sample': 0l}) + 'sample': 0l}, + fd=progress_fd) else: self.pbar = None From 733e27303c11a442ad162e71c63036a266517cee Mon Sep 17 00:00:00 2001 From: Adel Mamin Date: Wed, 23 Mar 2016 18:32:14 +0200 Subject: [PATCH 52/57] Fix the issue with the tracking stage not utilizing more than one CPU core --- libswiftnav | 2 +- peregrine/defaults.py | 2 +- peregrine/parallel_processing.py | 3 ++ peregrine/run.py | 54 ++++++++++--------- peregrine/tracking.py | 89 +++++++++++++++++++++++--------- 5 files changed, 101 insertions(+), 49 deletions(-) diff --git a/libswiftnav b/libswiftnav index 383972d..19a3718 160000 --- a/libswiftnav +++ b/libswiftnav @@ -1 +1 @@ -Subproject commit 383972dde3c7f492e4180180b293453ca8e9f863 +Subproject commit 19a3718005fc4c8a8e2fe4a60c381bb5829e34c5 diff --git a/peregrine/defaults.py b/peregrine/defaults.py index 230de11..7e521af 100644 --- a/peregrine/defaults.py +++ b/peregrine/defaults.py @@ -11,7 +11,7 @@ ms_to_track = 37 * 1e3 skip_samples = 1000 file_format = 'piksi' -processing_block_size = 4e6 # [samples] +processing_block_size = 20e6 # [samples] chipping_rate = 1.023e6 # Hz code_length = 1023 # chips diff --git a/peregrine/parallel_processing.py b/peregrine/parallel_processing.py index 6826856..be8b79b 100644 --- a/peregrine/parallel_processing.py +++ b/peregrine/parallel_processing.py @@ -38,6 +38,9 @@ def parmap(f, X, nprocs=mp.cpu_count(), show_progress=True, func_progress=False) else: q_progress = None + if nprocs > mp.cpu_count(): + nprocs = mp.cpu_count() + proc = [mp.Process(target=spawn(f), args=(q_in, q_out, q_progress)) for _ in range(nprocs)] diff --git a/peregrine/run.py b/peregrine/run.py index 6f48349..0112137 100755 --- a/peregrine/run.py +++ b/peregrine/run.py @@ -25,6 +25,19 @@ from peregrine.initSettings import initSettings from peregrine.gps_constants import L1CA, L2C +def unpickle_iter(filenames): + try: + f = [open(filename, "r") for filename in filenames] + + while True: + yield [cPickle.load(fh) for fh in f] + + except EOFError: + raise StopIteration + + finally: + for fh in f: + fh.close() def main(): default_logging_config() @@ -213,34 +226,29 @@ def main(): load_samples(samples=samples, filename=args.file, file_format=args.file_format) - tracker.stop() + fn_results = tracker.stop() - # try: - # with open(track_results_file, 'wb') as f: - # cPickle.dump(track_results, f, protocol=cPickle.HIGHEST_PROTOCOL) - # logging.debug("Saving tracking results as '%s'" % track_results_file) - # except IOError: - # logging.error("Couldn't save tracking results file '%s'.", - # track_results_file) + logging.debug("Saving tracking results as '%s'" % fn_results) # Do navigation nav_results_file = args.file + ".nav_results" if not args.skip_navigation: - nav_solns = navigation(track_results, settings) - nav_results = [] - for s, t in nav_solns: - nav_results += [(t, s.pos_llh, s.vel_ned)] - if len(nav_results): - print "First nav solution: t=%s lat=%.5f lon=%.5f h=%.1f vel_ned=(%.2f, %.2f, %.2f)" % ( - nav_results[0][0], - np.degrees(nav_results[0][1][0]), np.degrees( - nav_results[0][1][1]), nav_results[0][1][2], - nav_results[0][2][0], nav_results[0][2][1], nav_results[0][2][2]) - with open(nav_results_file, 'wb') as f: - cPickle.dump(nav_results, f, protocol=cPickle.HIGHEST_PROTOCOL) - print "and %d more are cPickled in '%s'." % (len(nav_results) - 1, nav_results_file) - else: - print "No navigation results." + for track_results in unpickle_iter(fn_results): + nav_solns = navigation(track_results, settings) + nav_results = [] + for s, t in nav_solns: + nav_results += [(t, s.pos_llh, s.vel_ned)] + if len(nav_results): + print "First nav solution: t=%s lat=%.5f lon=%.5f h=%.1f vel_ned=(%.2f, %.2f, %.2f)" % ( + nav_results[0][0], + np.degrees(nav_results[0][1][0]), np.degrees( + nav_results[0][1][1]), nav_results[0][1][2], + nav_results[0][2][0], nav_results[0][2][1], nav_results[0][2][2]) + with open(nav_results_file, 'wb') as f: + cPickle.dump(nav_results, f, protocol=cPickle.HIGHEST_PROTOCOL) + print "and %d more are cPickled in '%s'." % (len(nav_results) - 1, nav_results_file) + else: + print "No navigation results." if __name__ == '__main__': main() diff --git a/peregrine/tracking.py b/peregrine/tracking.py index a99b1e2..0361533 100644 --- a/peregrine/tracking.py +++ b/peregrine/tracking.py @@ -13,6 +13,7 @@ import math import parallel_processing as pp import multiprocessing as mp +import cPickle from swiftnav.track import LockDetector from swiftnav.track import CN0Estimator @@ -110,7 +111,7 @@ def __init__(self, params): self.prn = params['acq'].prn self.signal = params['acq'].signal - self.results_num = 1000 + self.results_num = 500 self.stage1 = True self.lock_detect = LockDetector( @@ -180,9 +181,9 @@ def __init__(self, params): raise ValueError("Invalid tracker mode %s" % str(mode)) def dump(self): - filename = self.track_result.dump(self.output_file, self.i) + fn_analysis, fn_results = self.track_result.dump(self.output_file, self.i) self.i = 0 - return filename + return fn_analysis, fn_results def start(self): logger.info("[PRN: %d (%s)] Tracking is started. " @@ -208,6 +209,9 @@ def _run_postprocess(self): # optionally redefine in subclasses def _get_result(self): # optionally redefine in subclasses return None + def is_pickleable(self): + return True + def run_parallel(self, samples): handover = self.run(samples) return (handover, self) @@ -503,6 +507,9 @@ def __init__(self, params): self.cnav_msg = CNavMsg() self.cnav_msg_decoder = CNavMsgDecoder() + def is_pickleable(self): + return False # could not pickle cnav_msg_decoder Cython object + def _run_postprocess(self): symbol = 0xFF if np.real(self.P) >= 0 else 0x00 res, delay = self.cnav_msg_decoder.decode(symbol, self.cnav_msg) @@ -553,7 +560,12 @@ def __init__(self, self.correlator = correlator self.stage2_coherent_ms = stage2_coherent_ms self.stage2_loop_filter_params = stage2_loop_filter_params - self.multi = multi + + if mp.cpu_count() > 1: + self.multi = multi + else: + self.multi = False + self.loop_filter_class = loop_filter_class if self.ms_to_track: @@ -619,13 +631,19 @@ def stop(self): if self.pbar: self.pbar.finish() - filenames = map(lambda chan: chan.dump(), self.tracking_channels) + # (fn_analysis, fn_results) = map(lambda chan: chan.dump(), self.tracking_channels) + res = map(lambda chan: chan.dump(), self.tracking_channels) + + fn_analysis = map(lambda x: x[0], res) + fn_results = map(lambda x: x[1], res) print "The tracking results were stored into:" - map(self._print_name, filenames) + map(self._print_name, fn_analysis) logger.info("Tracking finished") + return fn_results + def _create_channel(self, acq): if not acq: return @@ -645,28 +663,44 @@ def _create_channel(self, acq): 'multi': self.multi} return _tracking_channel_factory(parameters) + def _run_parallel(self, i, samples): + handover = self.parallel_channels[i].run(samples) + return self.parallel_channels[i], handover + def run_channels(self, samples): - self.samples = samples - tracking_channels = self.tracking_channels + channels = self.tracking_channels + self.tracking_channels = [] + + while channels and not all(v is None for v in channels): + + if self.multi: + self.parallel_channels = filter(lambda x: x.is_pickleable(), channels) + else: + self.parallel_channels = [] + + serial_channels = list(set(channels) - set(self.parallel_channels)) + channels = [] + handover = [] - while tracking_channels and not all(v is None for v in tracking_channels): - if self.multi and mp.cpu_count() > 1: - res = pp.parmap(lambda x: self.run(samples), - tracking_channels, + if self.parallel_channels: + res = pp.parmap(lambda i: self._run_parallel(i, samples), + range(len(self.parallel_channels)), + nprocs = len(self.parallel_channels), show_progress=False, func_progress=False) - handover = map(lambda x: x[0], res) - tracking_channels = map(lambda x: x[1], res) - else: - handover = map(lambda x: x.run(samples), tracking_channels) + channels = map(lambda x: x[0], res) + handover += map(lambda x: x[1], res) + + if serial_channels: + handover += map(lambda x: x.run(samples), serial_channels) + self.tracking_channels += channels + serial_channels handover = [h for h in handover if h is not None] if handover: - tracking_channels = map(self._create_channel, handover) - self.tracking_channels += tracking_channels + channels = map(self._create_channel, handover) else: - tracking_channels = None + channels = None indexes = map(lambda x: x.get_index(), self.tracking_channels) min_index = min(indexes) @@ -716,19 +750,26 @@ def __init__(self, n_points, prn, signal): def dump(self, output_file, size): output_filename, output_file_extension = os.path.splitext(output_file) - # mangle the result file name with the tracked signal name - filename = output_filename + \ + # mangle the analyses file name with the tracked signal name + fn_analysis = output_filename + \ (".PRN-%d.%s" % (self.prn + 1, self.signal)) +\ output_file_extension + # mangle the results file name with the tracked signal name + fn_results = output_filename + \ + (".PRN-%d.%s" % (self.prn + 1, self.signal)) +\ + output_file_extension + '.track_results' + if self.print_start: mode = 'w' else: mode = 'a' - # print "Storing tracking results into file: ", filename + # saving tracking results for navigation stage + with open(fn_results, mode) as f1: + cPickle.dump(self, f1, protocol = cPickle.HIGHEST_PROTOCOL) - with open(filename, mode) as f1: + with open(fn_analysis, mode) as f1: if self.print_start: f1.write("sample_index,ms_tracked,IF,doppler_phase,carr_doppler," "code_phase, code_freq," @@ -764,7 +805,7 @@ def dump(self, output_file, size): self.print_start = 0 - return filename + return fn_analysis, fn_results def resize(self, n_points): for k in dir(self): From 7ed91ed1c89cad7f9d57ad03f5a0eb67e0509c4f Mon Sep 17 00:00:00 2001 From: Adel Mamin Date: Wed, 23 Mar 2016 23:33:08 +0200 Subject: [PATCH 53/57] Enable multi core processing in tracking.py --- peregrine/tracking.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/peregrine/tracking.py b/peregrine/tracking.py index 0361533..59c424b 100644 --- a/peregrine/tracking.py +++ b/peregrine/tracking.py @@ -546,7 +546,7 @@ def __init__(self, correlator=track_correlate, stage2_coherent_ms=None, stage2_loop_filter_params=None, - multi=False, + multi=True, tracker_options=None, output_file=None): From 911202545141402e4b9b669a720bbb780943a7a7 Mon Sep 17 00:00:00 2001 From: Adel Mamin Date: Sat, 26 Mar 2016 11:52:27 +0200 Subject: [PATCH 54/57] Fix travis CI checks --- peregrine/run.py | 5 +++-- peregrine/tracking.py | 4 ++-- .../gpsl1ca_ci_samples.piksi_format.acq_results | Bin 0 -> 3527 bytes tests/test_run.py | 9 +++++++-- 4 files changed, 12 insertions(+), 6 deletions(-) create mode 100644 tests/gpsl1ca_ci_samples.piksi_format.acq_results diff --git a/peregrine/run.py b/peregrine/run.py index 0112137..f998495 100755 --- a/peregrine/run.py +++ b/peregrine/run.py @@ -233,8 +233,9 @@ def main(): # Do navigation nav_results_file = args.file + ".nav_results" if not args.skip_navigation: - for track_results in unpickle_iter(fn_results): - nav_solns = navigation(track_results, settings) + track_results_generator = lambda: unpickle_iter(fn_results) + for track_results in track_results_generator(): + nav_solns = navigation(track_results_generator, settings) nav_results = [] for s, t in nav_solns: nav_results += [(t, s.pos_llh, s.vel_ned)] diff --git a/peregrine/tracking.py b/peregrine/tracking.py index 59c424b..4d5935a 100644 --- a/peregrine/tracking.py +++ b/peregrine/tracking.py @@ -702,8 +702,8 @@ def run_channels(self, samples): else: channels = None - indexes = map(lambda x: x.get_index(), self.tracking_channels) - min_index = min(indexes) + indicies = map(lambda x: x.get_index(), self.tracking_channels) + min_index = min(indicies) if self.pbar: self.pbar.update(min_index, attr={'sample': min_index}) diff --git a/tests/gpsl1ca_ci_samples.piksi_format.acq_results b/tests/gpsl1ca_ci_samples.piksi_format.acq_results new file mode 100644 index 0000000000000000000000000000000000000000..920e154a5e256b4347d67b95470098b13fd667d5 GIT binary patch literal 3527 zcmZA3dsLHU90%|L6UIcrJCML4FC$=zHz4lb5T{cni;D@4ZMf;!_H0Z=d2}L(7$#ov zmKmlcYEjz7#1na~lc}A&RCbikDbzBLre$d!+wXaK^5?rhIGnxDp3n3BJ-656JB#=z zl?I2&X`17-*i0(Dfw(O$ONGU5lV?5sUu$x?trc?OYbSn`NR>K&SB1X9?IPWDK3Tc| zqut@Knw-SnU~`u{Dpdx%)1)f*{=%YnI`x%umqBmUJLM!mcrG_qR60y@BGdVnC8;H9 zpE@lGRI2@SKB*dwX7Us7hy77YDoS-SiK63CPTi9B6gSL>J`JmwU%hVzC)b3rP>}H-0Xglk_wPm;-e(ZR-)k<=~1;Zy&+BK!bNwC1p+% zAtA!!CyLgSDT&jvypMb71U~LkrP-Ul4<&t-W^d|vv%m0L z$&Q!?pQaC?>wf00VVQ%}K4$M2%w2rt*y7UnuaAH>{motGxxS}7kH#`{fZ6LfTCo#K zNf>jo{&zgGKhEUrhCafPH0(rB5-D=pCWeJH z?u9<0kmHA)qMpCm5(+iYM!}u;a{Z+n(wXx-IMsKs6HUnrqPofAq-Cv}+r51ZLXH$W z7h?M*-%1$<&S35=U6gSzY&3I*fMbl7p(>=KfoEYSE!_I@ppA`JCcsMvO za_57Fy!R&xPAoVzA=rtdBwpm)j_EZj^)U3ILXJOnGNylP-f&O_ZH95@!3J6Q+3_sT za8@^o2cWL?mO#k}k(2q|?Re=@$TJc-0oYkp)$&cd^9-q@i@;I zN)koR&KnbM=I?^BCLu?LojtYrNmrM!JjvWqF3Nq_O;~R)fm8fU-BKt?6*(QfR1+?- z`AI`gAa-^fDN>|=%Jxq>cTS`gC5{&6Cj*=nIXF)yC0WdQx_|P5qNCH^gu0DIjvPC4 ze~u~+&7K9B$8qPkss$f+jAZjO9-P(Ju`_{^iK0A`{ z%_Q!8k#Ib+Z!&Xoz?rMW&SXl|BIkx@P>8Du`j~>8AnYh=?S+S3%$drah(>8?WD0XM z;H+AYooSRz7uD^2G|e8+-0pR9k<$Y^+8p;XPkaW9Ma!KjA1hQXLfvM7Q-8|?P}jQ7 zqeLglbN*3Q=Rptj@iKCHV&`$vwWtY~Se|_D9E~!jrwDZ`@H!dySRC#YQc@&x;Cb<< zkY^@xg0a&%wnxCjRJMO!;ST)FCzjQ17UZ!u!#CV1rldsV99p;g>^!!AW+NvAJ7GLgqX@cQ$Vs`Hn9;-zt&Q z4?8;#ukC*MPbbuE5qG|B_@`x>aK0@Dry&VDRg_SX)7odYWgL4ySc07X*vX6g<$i4L z6mXVu=a#hK2amAcJm6@|xNgfRsTS2uvF%vFubW_gmLq2XcCNjaHmYbLySHn&vwi>d zLs}tEEjZ4-*jYhIoyZ9ZY$>#|`*bC8Lb21^_~OPPHO#5!&PwUUxK80dT?NjHY0 z*?S&tg4(Y`PB?agv}XoRP8|bn)^n#V{7hc3a9(c!hn@n4I~yt4B+ApSpeN5Rfj*j$ z^Bi{ir3S1s)Ump4=8pf(*!e?*b94(hB_8Z-rDU7P>8QJNJdC}TZ%58R>} Date: Sat, 26 Mar 2016 12:09:34 +0200 Subject: [PATCH 55/57] Fix bug in pbar handling at tracking stage --- peregrine/tracking.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/peregrine/tracking.py b/peregrine/tracking.py index 4d5935a..5f888a8 100644 --- a/peregrine/tracking.py +++ b/peregrine/tracking.py @@ -622,7 +622,8 @@ def start(self): logger.debug("Tracking PRNs %s" % ([chan.prn + 1 for chan in self.tracking_channels])) - self.pbar.start() + if self.pbar: + self.pbar.start() def _print_name(self, name): print name From 0bbfb035d6d3acf96b1db6a0c96beaf84276f47f Mon Sep 17 00:00:00 2001 From: Adel Mamin Date: Sat, 26 Mar 2016 12:23:14 +0200 Subject: [PATCH 56/57] Disable multi-core CPU support to pass travis checks. Re-enable once PR#313 is merged --- peregrine/tracking.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/peregrine/tracking.py b/peregrine/tracking.py index 5f888a8..15b88a8 100644 --- a/peregrine/tracking.py +++ b/peregrine/tracking.py @@ -546,7 +546,7 @@ def __init__(self, correlator=track_correlate, stage2_coherent_ms=None, stage2_loop_filter_params=None, - multi=True, + multi=False, tracker_options=None, output_file=None): From 2d90a1f625d1902c4932cae31e8c831d5151a29f Mon Sep 17 00:00:00 2001 From: Pasi Miettinen Date: Thu, 7 Apr 2016 12:59:57 +0300 Subject: [PATCH 57/57] Add acquisition support for Glonass Depends on https://github.com/swift-nav/peregrine/pull/26 --- peregrine/acquisition.py | 126 +++++++++++++++++++++++++++---------- peregrine/defaults.py | 11 +++- peregrine/gps_constants.py | 24 ++++--- peregrine/initSettings.py | 3 + peregrine/run.py | 30 +++++++-- peregrine/samples.py | 6 +- 6 files changed, 153 insertions(+), 47 deletions(-) diff --git a/peregrine/acquisition.py b/peregrine/acquisition.py index f8052d7..da0e17e 100644 --- a/peregrine/acquisition.py +++ b/peregrine/acquisition.py @@ -20,6 +20,8 @@ import defaults from include.generateCAcode import caCodes +from include.generateGLOcode import GLOCode +from peregrine.gps_constants import L1CA, GLO_L1, glo_l1_step import logging logger = logging.getLogger(__name__) @@ -188,13 +190,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 @@ -363,7 +367,9 @@ def find_peak(self, freqs, results, interpolation='gaussian'): return (code_phase, freq, snr) def acquisition(self, - prns=range(32), + bandcode=L1CA, + prns=xrange(32), + channels=[x - 7 for x in xrange(14)], doppler_priors=None, doppler_search=7000, doppler_step=None, @@ -372,10 +378,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 @@ -387,8 +393,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. @@ -406,10 +417,15 @@ 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") + if bandcode != L1CA and bandcode != GLO_L1: + logger.critical("Unkown/Unsupported code " + bandcode) + return + + logger.info("Acquisition starting for " + bandcode) from peregrine.parallel_processing import parmap # If the Doppler step is not specified, compute it from the coarse @@ -421,9 +437,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 @@ -439,33 +452,55 @@ def acquisition(self, show_progress = False logger.warning("show_progress = True but progressbar module not found.") + if bandcode == 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)), + (2 * doppler_search / doppler_step + 1)), fd=progress_fd) pbar.start() else: pbar = None def do_acq(n): - prn = prns[n] + if bandcode == L1CA: + obj = prns[n] + code = caCodes[obj] + int_f = self.IF + attr = {'prn': obj + 1} + else: + obj = channels[n] + code = GLOCode + int_f = self.IF + obj * glo_l1_step + attr = {'ch': obj} 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, @@ -478,13 +513,23 @@ 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, - 'l1ca') + if bandcode == L1CA: + acq_result = AcquisitionResult(obj, + carr_freq, + carr_freq - self.IF, + code_phase, + snr, + status, + L1CA) + else: + acq_result = GloAcquisitionResult(obj, + carr_freq, + carr_freq - + self.IF - obj * glo_l1_step, + code_phase, + snr, + status, + GLO_L1) # If the acquisition was successful, log it if (snr > threshold): @@ -494,9 +539,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 @@ -505,9 +550,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 bandcode == L1CA: + logger.info("Acquired %d satellites, PRNs: %s.", len(acq), acq) + else: + logger.info("Acquired %d channels: %s.", len(acq), acq) return acq_results @@ -524,7 +571,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. @@ -553,7 +600,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): @@ -608,6 +655,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.1f, %+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. @@ -669,4 +730,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 7e521af..8e9e35c 100644 --- a/peregrine/defaults.py +++ b/peregrine/defaults.py @@ -11,13 +11,18 @@ ms_to_track = 37 * 1e3 skip_samples = 1000 file_format = 'piksi' -processing_block_size = 20e6 # [samples] +processing_block_size = 20e6 # [samples] chipping_rate = 1.023e6 # Hz code_length = 1023 # chips code_period = code_length / chipping_rate +glo_chipping_rate = 0.511e6 # Hz +glo_code_length = 511 # chips + +glo_code_period = glo_code_length / glo_chipping_rate + # original sample_channel_GPS_L1 = 0 sample_channel_GPS_L2 = 1 @@ -80,12 +85,14 @@ freq_profile_normal_rate = { 'GPS_L1_IF': 14.58e6, 'GPS_L2_IF': 7.4e6, + 'GLO_L1_IF': 12e6, 'sampling_freq': 24.84375e6} -# 'normal_rate' frequencies profile +# 'high_rate' frequencies profile freq_profile_high_rate = { '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} L1CA_CHANNEL_BANDWIDTH_HZ = 1000 diff --git a/peregrine/gps_constants.py b/peregrine/gps_constants.py index dcf2601..620cc27 100644 --- a/peregrine/gps_constants.py +++ b/peregrine/gps_constants.py @@ -2,23 +2,33 @@ # Some fundamental constants have specific numeric definitions to ensure # consistent results in curve fits: -c = 2.99792458e8 # m/s +c = 2.99792458e8 # m/s pi = 3.1415926535898 # Physical parameters of the Earth -earth_gm = 3.986005e14 # m^3/s^2 (WGS84 earth's gravitational constant) -omegae_dot = 7.2921151467e-005 # rad/s (WGS84 earth rotation rate) +earth_gm = 3.986005e14 # m^3/s^2 (WGS84 earth's gravitational constant) +omegae_dot = 7.2921151467e-005 # rad/s (WGS84 earth rotation rate) # GPS system parameters: -l1 = 1.57542e9 # Hz -l2 = 1.22760e9 # Hz +l1 = 1.57542e9 # Hz +l2 = 1.22760e9 # Hz chips_per_code = 1023 -chip_rate = 1.023e6 # Hz -nominal_range = 26000e3 # m +chip_rate = 1.023e6 # Hz +nominal_range = 26000e3 # m + +# GLO system parameters +glo_l1 = 1.602e9 # Hz +glo_l2 = 1.246e9 # Hz +glo_chips_per_code = 511 +glo_chip_rate = 0.511e6 # Hz +glo_l1_step = 0.5625e6 # Hz # Useful derived quantities: code_period = chips_per_code / chip_rate code_wavelength = code_period * c +glo_code_period = glo_chips_per_code / glo_chip_rate +glo_code_wavelength = glo_code_period * c L1CA = 'l1ca' L2C = 'l2c' +GLO_L1 = 'glo_l1' diff --git a/peregrine/initSettings.py b/peregrine/initSettings.py index 4d7d209..1333633 100644 --- a/peregrine/initSettings.py +++ b/peregrine/initSettings.py @@ -15,9 +15,12 @@ def __init__(self, freq_profile): self.skipNumberOfBytes = 0 # Skip bytes in sample file before loading samples for acquisition (bytes) self.L1_IF = freq_profile['GPS_L1_IF'] # L1 intermediate frequency of signal in sample file (Hz) self.L2_IF = freq_profile['GPS_L2_IF'] # L2 intermediate frequency of signal in sample file (Hz) + self.GLO_L1_IF = freq_profile['GLO_L1_IF'] # GLO L1 intermediate frequency of signal in sample file (Hz) self.samplingFreq = freq_profile['sampling_freq'] # Sampling frequency of sample file (Hz) self.codeFreqBasis = defaults.chipping_rate # Frequency of chipping code (Hz) self.codeLength = defaults.code_length # Length of chipping code (chips) + self.gloCodeFreqBasis = defaults.glo_chipping_rate # GLO frequency of chipping code (Hz) + self.gloCodeLength = defaults.glo_code_length # GLO length of chipping code (chips) self.acqThreshold = 21.0 # SNR (unitless) self.acqSanityCheck = True # Check for sats known to be below the horizon self.navSanityMaxResid = 25.0 # meters per SV, normalized nav residuals diff --git a/peregrine/run.py b/peregrine/run.py index f998495..a0c6500 100755 --- a/peregrine/run.py +++ b/peregrine/run.py @@ -17,13 +17,15 @@ 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 from peregrine import defaults from peregrine.initSettings import initSettings -from peregrine.gps_constants import L1CA, L2C +from peregrine.gps_constants import L1CA, L2C, GLO_L1 + def unpickle_iter(filenames): try: @@ -39,12 +41,16 @@ def unpickle_iter(filenames): for fh in f: fh.close() + def main(): default_logging_config() parser = argparse.ArgumentParser() parser.add_argument("file", help="the sample data file to process") + parser.add_argument("--skip-glonass", + help="skip glonass", + action="store_true") parser.add_argument("-a", "--skip-acquisition", help="use previously saved acquisition results", action="store_true") @@ -139,8 +145,13 @@ def main(): samplesPerCode = int(round(settings.samplingFreq / (settings.codeFreqBasis / settings.codeLength))) + gloSamplesPerCode = int(round(settings.samplingFreq / + (settings.gloCodeFreqBasis / + settings.gloCodeLength))) + samples = {L1CA: {'IF': freq_profile['GPS_L1_IF']}, L2C: {'IF': freq_profile['GPS_L2_IF']}, + GLO_L1: {'IF': freq_profile['GLO_L1_IF']}, 'samples_total': -1, 'sample_index': settings.skipNumberOfBytes} @@ -157,7 +168,7 @@ def main(): else: # Get 11ms of acquisition samples for fine frequency estimation load_samples(samples=samples, - num_samples=11 * samplesPerCode, + num_samples=11 * max(samplesPerCode, gloSamplesPerCode), filename=args.file, file_format=args.file_format) @@ -167,6 +178,16 @@ def main(): defaults.code_period * freq_profile['sampling_freq']) acq_results = acq.acquisition(progress_bar_output=args.progress_bar) + if not args.skip_glonass: + acq = Acquisition(samples[GLO_L1]['samples'], + freq_profile['sampling_freq'], + freq_profile['GLO_L1_IF'], + defaults.glo_code_period * + freq_profile['sampling_freq'], + code_length=settings.gloCodeLength) + acq_results = acq.acquisition(bandcode=GLO_L1, + progress_bar_output=args.progress_bar) + print "Acquisition is over!" try: @@ -247,7 +268,8 @@ def main(): nav_results[0][2][0], nav_results[0][2][1], nav_results[0][2][2]) with open(nav_results_file, 'wb') as f: cPickle.dump(nav_results, f, protocol=cPickle.HIGHEST_PROTOCOL) - print "and %d more are cPickled in '%s'." % (len(nav_results) - 1, nav_results_file) + print "and %d more are cPickled in '%s'." % (len(nav_results) - 1, + nav_results_file) else: print "No navigation results." diff --git a/peregrine/samples.py b/peregrine/samples.py index 973b5df..558edb3 100644 --- a/peregrine/samples.py +++ b/peregrine/samples.py @@ -13,7 +13,7 @@ import numpy as np import math import defaults -from peregrine.gps_constants import L1CA, L2C +from peregrine.gps_constants import L1CA, L2C, GLO_L1 __all__ = ['load_samples', 'save_samples'] @@ -280,7 +280,7 @@ def _load_samples(filename, samples *= 2 samples -= 1 if file_format == '1bitrev': - samples = np.reshape(samples, (-1, 8))[:, ::-1].flatten(); + samples = np.reshape(samples, (-1, 8))[:, ::-1].flatten() samples = samples[num_skip_samples:] if num_samples > 0: samples = samples[:num_samples] @@ -365,6 +365,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