From 8a681a92043715f4dbaf19228c587edf20ae04b3 Mon Sep 17 00:00:00 2001 From: Colin Beighley Date: Fri, 19 Feb 2016 12:55:02 -0800 Subject: [PATCH 01/67] 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/67] 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/67] 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/67] 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/67] 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/67] 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/67] 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/67] 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/67] 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/67] 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/67] 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/67] 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/67] 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/67] 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/67] 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/67] 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/67] 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/67] 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/67] 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/67] 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/67] 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/67] 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/67] 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/67] 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/67] 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/67] 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/67] 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/67] 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/67] 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/67] 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/67] 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/67] 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/67] 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/67] 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/67] 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/67] 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/67] 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/67] 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/67] 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/67] 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/67] 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/67] 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/67] 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/67] 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/67] 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/67] 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/67] 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/67] 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/67] 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/67] 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/67] 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/67] 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/67] 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/67] 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/67] 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/67] 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 59dcaef16aaf0e92f30eef4afec54f13aa1fcc9a Mon Sep 17 00:00:00 2001 From: Valeri Atamaniouk Date: Tue, 5 Apr 2016 13:06:20 +0300 Subject: [PATCH 57/67] iqgen: added GLONASS signal generation support to IQgen Added GLONASS support to IQgen: - Generation of GLONASS sub-bands - GLONASS encoders and GPS+GLONASS encoders - Updated SNR computation for GLONASS - Updated configuration parameters and save/load --- peregrine/iqgen/bits/encoder_1bit.py | 99 ++++++ peregrine/iqgen/bits/encoder_2bits.py | 109 +++++++ peregrine/iqgen/bits/encoder_base.py | 2 +- peregrine/iqgen/bits/encoder_factory.py | 90 ++++++ peregrine/iqgen/bits/encoder_glo.py | 126 ++++++++ peregrine/iqgen/bits/encoder_gps.py | 85 +----- peregrine/iqgen/bits/encoder_other.py | 58 ++++ peregrine/iqgen/bits/prn_glo_l1l2.py | 84 ++++++ peregrine/iqgen/bits/prn_gps_l2c.py | 77 +---- peregrine/iqgen/bits/satellite_base.py | 24 ++ peregrine/iqgen/bits/satellite_factory.py | 37 +++ peregrine/iqgen/bits/satellite_glo.py | 218 ++++++++++++++ peregrine/iqgen/bits/signals.py | 223 ++++++++++++-- peregrine/iqgen/generate.py | 90 ++++-- peregrine/iqgen/if_iface.py | 26 +- peregrine/iqgen/iqgen_main.py | 350 ++++++++++++++-------- 16 files changed, 1363 insertions(+), 335 deletions(-) create mode 100644 peregrine/iqgen/bits/encoder_glo.py create mode 100644 peregrine/iqgen/bits/encoder_other.py create mode 100644 peregrine/iqgen/bits/prn_glo_l1l2.py create mode 100644 peregrine/iqgen/bits/satellite_glo.py mode change 100644 => 100755 peregrine/iqgen/iqgen_main.py diff --git a/peregrine/iqgen/bits/encoder_1bit.py b/peregrine/iqgen/bits/encoder_1bit.py index 2690bcc..4cfea18 100644 --- a/peregrine/iqgen/bits/encoder_1bit.py +++ b/peregrine/iqgen/bits/encoder_1bit.py @@ -80,3 +80,102 @@ def convertBand(band_samples): Boolean vector of sample signs ''' return band_samples < 0 + + +class TwoBandsBitEncoder(Encoder): + ''' + Generic single bit encoder for two band signals + ''' + + def __init__(self, band1, band2): + ''' + Constructs GPS L1 C/A and L2 C dual band single bit encoder object. + + Parameters + ---------- + outputConfig : object + Output parameters object. + ''' + super(TwoBandsBitEncoder, self).__init__() + self.l1Index = band1 + self.l2Index = band2 + + def addSamples(self, sample_array): + ''' + Extracts samples of the supported band and converts 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 FourBandsBitEncoder(Encoder): + ''' + Generic single bit encoder for two band signals + ''' + + def __init__(self, band1, band2, band3, band4): + ''' + Constructs GPS L1 C/A and L2 C dual band single bit encoder object. + + Parameters + ---------- + outputConfig : object + Output parameters object. + ''' + super(FourBandsBitEncoder, self).__init__() + self.bandIndexes = [band1, band2, band3, band4] + + def addSamples(self, sample_array): + ''' + Extracts samples of the supported band and converts them into bit stream. + + Parameters + ---------- + sample_array : numpy.ndarray((4, N)) + Sample vectors ordered by band index. + + Returns + ------- + numpy.ndarray(shape=(4, N/2), dtype=numpy.uint8) + Array of type uint8 containing the encoded data. + ''' + n_samples = len(sample_array[0]) + self.ensureExtraCapacity(n_samples * 4) + start = self.n_bits + end = start + 4 * n_samples + + for band in range(4): + bandIndex = self.bandIndexes[band] + band_bits = BandBitEncoder.convertBand(sample_array[bandIndex]) + self.bits[start + band:end:2] = band_bits + + 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_2bits.py b/peregrine/iqgen/bits/encoder_2bits.py index 286a884..0f543de 100644 --- a/peregrine/iqgen/bits/encoder_2bits.py +++ b/peregrine/iqgen/bits/encoder_2bits.py @@ -115,3 +115,112 @@ def addSamples(self, sample_array): return self.encodeValues() else: return Encoder.EMPTY_RESULT + + +class TwoBandsTwoBitsEncoder(Encoder): + ''' + Generic single bit encoder for GPS L1 C/A and L2 Civil signals + ''' + + def __init__(self, bandIndex1, bandIndex2): + ''' + Constructs GPS L1 C/A and L2 C dual band single bit encoder object. + + Parameters + ---------- + outputConfig : object + Output parameters object. + ''' + super(TwoBandsTwoBitsEncoder, self).__init__() + self.l1Index = bandIndex1 + self.l2Index = bandIndex2 + + def addSamples(self, sample_array): + ''' + Extracts samples of the supported band and converts 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 + + +class FourBandsTwoBitsEncoder(Encoder): + ''' + Generic single bit encoder for two band signals + ''' + + def __init__(self, band1, band2, band3, band4): + ''' + Constructs GPS L1 C/A and L2 C dual band single bit encoder object. + + Parameters + ---------- + outputConfig : object + Output parameters object. + ''' + super(FourBandsTwoBitsEncoder, self).__init__() + self.bandIndexes = [band1, band2, band3, band4] + + def addSamples(self, sample_array): + ''' + Extracts samples of the supported bands and converts 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. + ''' + n_samples = len(sample_array[0]) + self.ensureExtraCapacity(n_samples * 8) + start = self.n_bits + end = start + 8 * n_samples + bits = self.bits + + for band in range(4): + bandIndex = self.bandIndexes[band] + # Signal signs and amplitude + signs, amps = BandTwoBitsEncoder.convertBand(sample_array[bandIndex]) + bits[start + band * 2 + 0:end:8] = signs + bits[start + band * 2 + 1:end:8] = 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 index ee6b134..1bade6b 100644 --- a/peregrine/iqgen/bits/encoder_base.py +++ b/peregrine/iqgen/bits/encoder_base.py @@ -76,7 +76,7 @@ def encodeValues(self): ''' Converts buffered bit data into packed array. - The method coverts multiple of 8 bits into single output byte. + The method converts multiple of 8 bits into single output byte. Returns ------- diff --git a/peregrine/iqgen/bits/encoder_factory.py b/peregrine/iqgen/bits/encoder_factory.py index ab73321..f90bd22 100644 --- a/peregrine/iqgen/bits/encoder_factory.py +++ b/peregrine/iqgen/bits/encoder_factory.py @@ -20,6 +20,14 @@ 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.bits.encoder_glo import GLONASSL1BitEncoder +from peregrine.iqgen.bits.encoder_glo import GLONASSL2BitEncoder +from peregrine.iqgen.bits.encoder_glo import GLONASSL1L2BitEncoder +from peregrine.iqgen.bits.encoder_glo import GLONASSL1TwoBitsEncoder +from peregrine.iqgen.bits.encoder_glo import GLONASSL2TwoBitsEncoder +from peregrine.iqgen.bits.encoder_glo import GLONASSL1L2TwoBitsEncoder +from peregrine.iqgen.bits.encoder_other import GPSGLONASSBitEncoder +from peregrine.iqgen.bits.encoder_other import GPSGLONASSTwoBitsEncoder class ObjectFactory(object): @@ -44,6 +52,22 @@ def toMapForm(self, obj): return self.__GPSL2TwoBitsEncoder_ToMap(obj) elif t is GPSL1L2TwoBitsEncoder: return self.__GPSL1L2TwoBitsEncoder_ToMap(obj) + elif t is GLONASSL1BitEncoder: + return self.__GLONASSL1BitEncoder_ToMap(obj) + elif t is GLONASSL2BitEncoder: + return self.__GLONASSL2BitEncoder_ToMap(obj) + elif t is GLONASSL1L2BitEncoder: + return self.__GLONASSL1L2BitEncoder_ToMap(obj) + elif t is GLONASSL1TwoBitsEncoder: + return self.__GLONASSL1TwoBitsEncoder_ToMap(obj) + elif t is GLONASSL2TwoBitsEncoder: + return self.__GLONASSL2TwoBitsEncoder_ToMap(obj) + elif t is GLONASSL1L2TwoBitsEncoder: + return self.__GLONASSL1L2TwoBitsEncoder_ToMap(obj) + elif t is GPSGLONASSBitEncoder: + return self.__GPSGLONASSBitEncoder_ToMap(obj) + elif t is GPSGLONASSTwoBitsEncoder: + return self.__GPSGLONASSTwoBitsEncoder_ToMap(obj) else: raise ValueError("Invalid object type") @@ -61,6 +85,22 @@ def fromMapForm(self, data): return self.__MapTo_GPSL2TwoBitsEncoder(data) elif t == 'GPSL1L2TwoBitsEncoder': return self.__MapTo_GPSL1L2TwoBitsEncoder(data) + elif t == 'GLONASSL1BitEncoder': + return self.__MapTo_GLONASSL1BitEncoder(data) + elif t == 'GLONASSL2BitEncoder': + return self.__MapTo_GLONASSL2BitEncoder(data) + elif t == 'GLONASSL2BitEncoder': + return self.__MapTo_GLONASSL1L2BitEncoder(data) + elif t == 'GLONASSL1TwoBitsEncoder': + return self.__MapTo_GLONASSL1TwoBitsEncoder(data) + elif t == 'GLONASSL2TwoBitsEncoder': + return self.__MapTo_GLONASSL2TwoBitsEncoder(data) + elif t == 'GLONASSL1L2TwoBitsEncoder': + return self.__MapTo_GLONASSL1L2TwoBitsEncoder(data) + elif t == 'GPSGLONASSBitEncoder': + return self.__MapTo_GPSGLONASSBitEncoder(data) + elif t == 'GPSGLONASSTwoBitsEncoder': + return self.__MapTo_GPSGLONASSTwoBitsEncoder(data) else: raise ValueError("Invalid object type") @@ -88,6 +128,38 @@ def __GPSL1L2TwoBitsEncoder_ToMap(self, obj): data = {'type': 'GPSL1L2TwoBitsEncoder'} return data + def __GLONASSL1BitEncoder_ToMap(self, obj): + data = {'type': 'GLONASSL1BitEncoder'} + return data + + def __GLONASSL2BitEncoder_ToMap(self, obj): + data = {'type': 'GLONASSL2BitEncoder'} + return data + + def __GLONASSL1L2BitEncoder_ToMap(self, obj): + data = {'type': 'GLONASSL1L2BitEncoder'} + return data + + def __GLONASSL1TwoBitsEncoder_ToMap(self, obj): + data = {'type': 'GLONASSL1TwoBitsEncoder'} + return data + + def __GLONASSL2TwoBitsEncoder_ToMap(self, obj): + data = {'type': 'GLONASSL2TwoBitsEncoder'} + return data + + def __GLONASSL1L2TwoBitsEncoder_ToMap(self, obj): + data = {'type': 'GLONASSL1L2TwoBitsEncoder'} + return data + + def __GPSGLONASSBitEncoder_ToMap(self, obj): + data = {'type': 'GPSGLONASSBitEncoder'} + return data + + def __GPSGLONASSTwoBitsEncoder_ToMap(self, obj): + data = {'type': 'GPSGLONASSTwoBitsEncoder'} + return data + def __MapTo_GPSL1BitEncoder(self, data): return GPSL1BitEncoder() @@ -106,4 +178,22 @@ def __MapTo_GPSL2TwoBitsEncoder(self, data): def __MapTo_GPSL1L2TwoBitsEncoder(self, data): return GPSL1L2TwoBitsEncoder() + def __MapTo_GLONASSL1BitEncoder(self, data): + return GLONASSL1BitEncoder() + + def __MapTo_GLONASSL2BitEncoder(self, data): + return GLONASSL2BitEncoder() + + def __MapTo_GLONASSL1L2BitEncoder(self, data): + return GLONASSL1L2BitEncoder() + + def __MapTo_GLONASSL1TwoBitsEncoder(self, data): + return GLONASSL1TwoBitsEncoder() + + def __MapTo_GLONASSL2TwoBitsEncoder(self, data): + return GLONASSL2TwoBitsEncoder() + + def __MapTo_GLONASSL1L2TwoBitsEncoder(self, data): + return GLONASSL1L2TwoBitsEncoder() + factoryObject = ObjectFactory() diff --git a/peregrine/iqgen/bits/encoder_glo.py b/peregrine/iqgen/bits/encoder_glo.py new file mode 100644 index 0000000..2c53797 --- /dev/null +++ b/peregrine/iqgen/bits/encoder_glo.py @@ -0,0 +1,126 @@ +# 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_glo` module contains classes and +functions related to generating GLONASS signal output. + +""" + +from peregrine.iqgen.bits.encoder_1bit import BandBitEncoder +from peregrine.iqgen.bits.encoder_1bit import TwoBandsBitEncoder +from peregrine.iqgen.bits.encoder_2bits import BandTwoBitsEncoder +from peregrine.iqgen.bits.encoder_2bits import TwoBandsTwoBitsEncoder + + +class GLONASSL1BitEncoder(BandBitEncoder): + ''' + Generic single bit encoder for GLPNASS L1 signal + ''' + + def __init__(self, outputConfig): + ''' + Constructs GLONASS L1 band single bit encoder object. + + Parameters + ---------- + outputConfig : object + Output parameters object. + ''' + super(GLONASSL1BitEncoder, self).__init__(outputConfig.GLONASS.L1.INDEX) + + +class GLONASSL2BitEncoder(BandBitEncoder): + ''' + Generic single bit encoder for GLONASS L2 signal + ''' + + def __init__(self, outputConfig): + ''' + Constructs GLONASS L2 band single bit encoder object. + + Parameters + ---------- + outputConfig : object + Output parameters object. + ''' + super(GLONASSL2BitEncoder, self).__init__(outputConfig.GLONASS.L2.INDEX) + + +class GLONASSL1L2BitEncoder(TwoBandsBitEncoder): + ''' + Generic single bit encoder for GLONASS L1 and L2 signals + ''' + + def __init__(self, outputConfig): + ''' + Constructs GLONASS L1 and L2 dual band single bit encoder object. + + Parameters + ---------- + outputConfig : object + Output parameters object. + ''' + super(GLONASSL1L2BitEncoder, self).__init__(outputConfig.GLONASS.L1.INDEX, + outputConfig.GLONASS.L2.INDEX) + + +class GLONASSL1TwoBitsEncoder(BandTwoBitsEncoder): + ''' + Generic single bit encoder for GLONASS L1 signal + ''' + + def __init__(self, outputConfig): + ''' + Constructs GLONASS L1 band single bit encoder object. + + Parameters + ---------- + outputConfig : object + Output parameters object. + ''' + super(GLONASSL1TwoBitsEncoder, self).__init__( + outputConfig.GLONASS.L1.INDEX) + + +class GLONASSL2TwoBitsEncoder(BandTwoBitsEncoder): + ''' + Generic single bit encoder for GLONASS L2 Civil signal + ''' + + def __init__(self, outputConfig): + ''' + Constructs GLONASS L2 C band single bit encoder object. + + Parameters + ---------- + outputConfig : object + Output parameters object. + ''' + super(GLONASSL2TwoBitsEncoder, self).__init__( + outputConfig.GLONASS.L2.INDEX) + + +class GLONASSL1L2TwoBitsEncoder(TwoBandsTwoBitsEncoder): + ''' + Generic single bit encoder for GLONASS L1 and L2 signals + ''' + + def __init__(self, outputConfig): + ''' + Constructs GLONASS L1 and L2 dual band single bit encoder object. + + Parameters + ---------- + outputConfig : object + Output parameters object. + ''' + super(GLONASSL1L2TwoBitsEncoder, self).__init__(outputConfig.GLONASS.L1.INDEX, + outputConfig.GLONASS.L2.INDEX) diff --git a/peregrine/iqgen/bits/encoder_gps.py b/peregrine/iqgen/bits/encoder_gps.py index 99b46e1..41dc47e 100644 --- a/peregrine/iqgen/bits/encoder_gps.py +++ b/peregrine/iqgen/bits/encoder_gps.py @@ -16,7 +16,9 @@ from peregrine.iqgen.bits.encoder_base import Encoder from peregrine.iqgen.bits.encoder_1bit import BandBitEncoder +from peregrine.iqgen.bits.encoder_1bit import TwoBandsBitEncoder from peregrine.iqgen.bits.encoder_2bits import BandTwoBitsEncoder +from peregrine.iqgen.bits.encoder_2bits import TwoBandsTwoBitsEncoder class GPSL1BitEncoder(BandBitEncoder): @@ -53,7 +55,7 @@ def __init__(self, outputConfig): super(GPSL2BitEncoder, self).__init__(outputConfig.GPS.L2.INDEX) -class GPSL1L2BitEncoder(Encoder): +class GPSL1L2BitEncoder(TwoBandsBitEncoder): ''' Generic single bit encoder for GPS L1 C/A and L2 Civil signals ''' @@ -67,40 +69,8 @@ def __init__(self, outputConfig): 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 + super(GPSL1L2BitEncoder, self).__init__(outputConfig.GPS.L1.INDEX, + outputConfig.GPS.L2.INDEX) class GPSL1TwoBitsEncoder(BandTwoBitsEncoder): @@ -137,7 +107,7 @@ def __init__(self, outputConfig): super(GPSL2TwoBitsEncoder, self).__init__(outputConfig.GPS.L2.INDEX) -class GPSL1L2TwoBitsEncoder(Encoder): +class GPSL1L2TwoBitsEncoder(TwoBandsTwoBitsEncoder): ''' Generic single bit encoder for GPS L1 C/A and L2 Civil signals ''' @@ -151,44 +121,5 @@ def __init__(self, outputConfig): 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 + super(GPSL1L2TwoBitsEncoder, self).__init__(outputConfig.GPS.L1.INDEX, + outputConfig.GPS.L2.INDEX) diff --git a/peregrine/iqgen/bits/encoder_other.py b/peregrine/iqgen/bits/encoder_other.py new file mode 100644 index 0000000..f41738a --- /dev/null +++ b/peregrine/iqgen/bits/encoder_other.py @@ -0,0 +1,58 @@ +# 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_other` module contains classes and +functions related to generating combined signal output. + +""" + +from peregrine.iqgen.bits.encoder_1bit import FourBandsBitEncoder +from peregrine.iqgen.bits.encoder_2bits import FourBandsTwoBitsEncoder + + +class GPSGLONASSBitEncoder(FourBandsBitEncoder): + ''' + Generic single bit encoder for GPS and GLINASS signals (4 bands) + ''' + + def __init__(self, outputConfig): + ''' + Constructs four band single bit encoder object. + + Parameters + ---------- + outputConfig : object + Output parameters object. + ''' + super(GPSGLONASSBitEncoder, self).__init__(outputConfig.GPS.L2.INDEX, + outputConfig.GLONASS.L2.INDEX, + outputConfig.GLONASS.L1.INDEX, + outputConfig.GPS.L1.INDEX) + + +class GPSGLONASSTwoBitsEncoder(FourBandsTwoBitsEncoder): + ''' + Generic dual bit encoder for GPS and GLONASS signals (four bands) + ''' + + def __init__(self, outputConfig): + ''' + Constructs four band dual bit encoder object. + + Parameters + ---------- + outputConfig : object + Output parameters object. + ''' + super(GPSGLONASSTwoBitsEncoder, self).__init__(outputConfig.GPS.L2.INDEX, + outputConfig.GLONASS.L2.INDEX, + outputConfig.GLONASS.L1.INDEX, + outputConfig.GPS.L1.INDEX) diff --git a/peregrine/iqgen/bits/prn_glo_l1l2.py b/peregrine/iqgen/bits/prn_glo_l1l2.py new file mode 100644 index 0000000..2237387 --- /dev/null +++ b/peregrine/iqgen/bits/prn_glo_l1l2.py @@ -0,0 +1,84 @@ +# 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_glo_l1l2` module contains classes and +functions related to GLONASS G1/G2 PRN processing + +""" +import numpy +from peregrine.include.glo_ca_code import value as GLONASS_CA_Code + +caCode = GLONASS_CA_Code[:] + + +class PrnCode(object): + ''' + GPS G1/G2 C/A code object + ''' + CODE_LENGTH = 511 + CODE_FREQUENCY_HZ = 511e3 + + def __init__(self, prnNo): + ''' + Initializes object. + + Parameters + ---------- + prnNo : int + SV identifier + ''' + super(PrnCode, self).__init__() + self.caCode = caCode[:] + 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 index e424e58..b1f9f6d 100644 --- a/peregrine/iqgen/bits/prn_gps_l2c.py +++ b/peregrine/iqgen/bits/prn_gps_l2c.py @@ -42,31 +42,11 @@ def __init__(self, prnNo): 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 + self.binCode = numpy.asarray(L2CMCodes[prnNo - 1], dtype=numpy.int8) < 0 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 @@ -86,9 +66,8 @@ def __init__(self, prnNo, codeType): 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) + 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) @@ -103,27 +82,6 @@ def __init__(self, prnNo, 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 @@ -139,13 +97,13 @@ def __init__(self, prnNo, clCodeType): Type of the code: '01', '1', '0' ''' super(PrnCode, self).__init__() - self.cl = PrnCode.CL_Code(prnNo, clCodeType) - self.cm = PrnCode.CM_Code(prnNo) + cl = PrnCode.CL_Code(prnNo, clCodeType) + 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() + tmp[1::2] = 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() + tmp[i:i + PrnCode.CM_Code.CODE_LENGTH * 2:2] = cm.getCodeBits() self.binCode = tmp self.prnNo = prnNo @@ -182,25 +140,8 @@ def combineData(self, chipIndex_all, dataBits): 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) + oddChipDataBits = dataBits & oddChips + combined = numpy.bitwise_xor(chipBits, oddChipDataBits) 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 index e5bfa0f..1e2f085 100644 --- a/peregrine/iqgen/bits/satellite_base.py +++ b/peregrine/iqgen/bits/satellite_base.py @@ -136,3 +136,27 @@ def isBandEnabled(self, bandIndex, outputConfig): True, if the band is supported and enabled; False otherwise. ''' return False + + def isCodeDopplerIgnored(self): + ''' + Checks if code doppler is ignored + + Returns + ------- + bool + True, when code doppler is ignored, False otherwise + ''' + return self.doppler.isCodeDopplerIgnored() + + def setCodeDopplerIgnored(self, flag): + ''' + Checks if code doppler is ignored + + Parameters + ---------- + flag : bool + Flag to control code doppler: True - to ignore code doppler, + False - normal operation + + ''' + self.doppler.setCodeDopplerIgnored(flag) diff --git a/peregrine/iqgen/bits/satellite_factory.py b/peregrine/iqgen/bits/satellite_factory.py index f62fc15..f1c39aa 100644 --- a/peregrine/iqgen/bits/satellite_factory.py +++ b/peregrine/iqgen/bits/satellite_factory.py @@ -16,6 +16,7 @@ """ from peregrine.iqgen.bits.satellite_gps import GPSSatellite +from peregrine.iqgen.bits.satellite_glo import GLOSatellite 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 @@ -33,6 +34,8 @@ def toMapForm(self, obj): t = type(obj) if t is GPSSatellite: return self.__GPSSatellite_ToMap(obj) + elif t is GLOSatellite: + return self.__GLOSatellite_ToMap(obj) else: raise ValueError("Invalid object type") @@ -40,6 +43,8 @@ def fromMapForm(self, data): t = data['type'] if t == 'GPSSatellite': return self.__MapTo_GPSSatellite(data) + elif t == 'GLOSatellite': + return self.__MapTo_GLOSatellite(data) else: raise ValueError("Invalid object type") @@ -57,6 +62,19 @@ def __GPSSatellite_ToMap(self, obj): } return data + def __GLOSatellite_ToMap(self, obj): + data = {'type': 'GLOSatellite', + 'prn': obj.prn, + 'amplitude': amplitudeOF.toMapForm(obj.getAmplitude()), + 'l1Enabled': obj.isL1Enabled(), + 'l2Enabled': obj.isL2Enabled(), + 'l1Message': messageOF.toMapForm(obj.getL1Message()), + 'l2Message': messageOF.toMapForm(obj.getL2Message()), + 'doppler': dopplerOF.toMapForm(obj.getDoppler()), + 'codeDopplerIgnored': obj.isCodeDopplerIgnored() + } + return data + def __MapTo_GPSSatellite(self, data): prn = data['prn'] doppler = dopplerOF.fromMapForm(data['doppler']) @@ -78,4 +96,23 @@ def __MapTo_GPSSatellite(self, data): satellite.setCodeDopplerIgnored(codeDopplerIgnored) return satellite + def __MapTo_GLOSatellite(self, data): + prn = data['prn'] + doppler = dopplerOF.fromMapForm(data['doppler']) + amplitude = amplitudeOF.fromMapForm(data['amplitude']) + l1caEnabled = data['l1Enabled'] + l2cEnabled = data['l2Enabled'] + l1caMessage = messageOF.fromMapForm(data['l1Message']) + l2cMessage = messageOF.fromMapForm(data['l2Message']) + codeDopplerIgnored = data['codeDopplerIgnored'] + satellite = GLOSatellite(prn) + satellite.setAmplitude(amplitude) + satellite.setDoppler(doppler) + satellite.setL1Enabled(l1caEnabled) + satellite.setL2Enabled(l2cEnabled) + satellite.setL1Message(l1caMessage) + satellite.setL2Message(l2cMessage) + satellite.setCodeDopplerIgnored(codeDopplerIgnored) + return satellite + factoryObject = ObjectFactory() diff --git a/peregrine/iqgen/bits/satellite_glo.py b/peregrine/iqgen/bits/satellite_glo.py new file mode 100644 index 0000000..281e532 --- /dev/null +++ b/peregrine/iqgen/bits/satellite_glo.py @@ -0,0 +1,218 @@ +# 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_glo` module contains classes and +functions related to GLONASS satellite configuration. + +""" +import peregrine.iqgen.bits.signals as signals +from peregrine.iqgen.bits.message_const import Message +from peregrine.iqgen.bits.prn_glo_l1l2 import PrnCode as GLO_CA_Code +from peregrine.iqgen.bits.satellite_base import Satellite + +import numpy + +DEFAULT_MESSAGE = Message(1) + + +class GLOSatellite(Satellite): + ''' + GLONASS satellite object. + ''' + + def __init__(self, prnNo): + ''' + Constructs satellite object + + Parameters + ---------- + prnNo : int + GPS satellite number for selecting PRN. + ''' + super(GLOSatellite, self).__init__("GLONASS{}".format(prnNo)) + self.prn = prnNo + self.caCode = GLO_CA_Code(prnNo) + self.l1Enabled = False + self.l2Enabled = False + self.l1Message = DEFAULT_MESSAGE + self.l2Message = DEFAULT_MESSAGE + self.time0S = 0. + self.pr0M = 0. + self.phaseShift = 0. + + def setL1Enabled(self, enableFlag): + ''' + Enables or disable GLONASS L1 C/A sample generation + + Parameters + ---------- + enableFlag : boolean + Flag to enable (True) or disable (False) GPS L1 C/A samples + ''' + self.l1Enabled = enableFlag + + def isL1Enabled(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.l1Enabled + + def setL2Enabled(self, enableFlag): + ''' + Enables or disable GLONASS L2 C sample generation + + Parameters + ---------- + enableFlag : boolean + Flag to enable (True) or disable (False) GPS L2 C samples + ''' + self.l2Enabled = enableFlag + + def isL2Enabled(self): + ''' + Tests if L2 C signal generation is enabled + + Returns + ------- + bool + True, when L2 C signal generation is enabled, False otherwise + ''' + return self.l2Enabled + + def setL1Message(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.l1Message = message + + def setL2Message(self, message): + ''' + Configures data source for L2 C signal. + + Parameters + ---------- + message : object + Message object that will provide symbols for L2 C signal. + ''' + self.l2Message = message + + def getL1Message(self): + ''' + Returns configured message object for GPS L1 C/A band + + Returns + ------- + object + L1 C/A message object + ''' + return self.l1Message + + def getL2Message(self): + ''' + Returns configured message object for GPS L2 C band + + Returns + ------- + object + L2 C message object + ''' + return self.l2Message + + 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.l1Enabled): + band = outputConfig.GLONASS.L1 + intermediateFrequency_hz = band.INTERMEDIATE_FREQUENCIES_HZ[self.prn] + frequencyIndex = band.INDEX + values = self.doppler.computeBatch(userTimeAll_s, + self.amplitude, + signals.GLONASS.L1S[self.prn], + intermediateFrequency_hz, + self.l1Message, + self.caCode, + outputConfig, + debug) + numpy.add(samples[frequencyIndex], + values[0], + out=samples[frequencyIndex]) + debugData = {'type': "GLOL1", 'doppler': values[1]} + result.append(debugData) + if (self.l2Enabled): + band = outputConfig.GLONASS.L2 + intermediateFrequency_hz = band.INTERMEDIATE_FREQUENCIES_HZ[self.prn] + frequencyIndex = band.INDEX + values = self.doppler.computeBatch(userTimeAll_s, + self.amplitude, + signals.GLONASS.L2S[self.prn], + intermediateFrequency_hz, + self.l2Message, + self.caCode, + outputConfig, + debug) + numpy.add(samples[frequencyIndex], + values[0], + out=samples[frequencyIndex]) + debugData = {'type': "GLOL2", '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.GLONASS.L1.INDEX: + result = self.isL1Enabled() + elif bandIndex == outputConfig.GLONASS.L2.INDEX: + result = self.isL2Enabled() + else: + result = False + return result diff --git a/peregrine/iqgen/bits/signals.py b/peregrine/iqgen/bits/signals.py index 246fd76..82b6edd 100644 --- a/peregrine/iqgen/bits/signals.py +++ b/peregrine/iqgen/bits/signals.py @@ -17,33 +17,33 @@ import scipy.constants -class GPS: +def _calcDopplerShiftHz(frequency_hz, distance_m, velocity_mps): ''' - GPS signal parameters and utilities. + 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 - @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 GPS: + ''' + GPS signal parameters and utilities. + ''' class L1CA: ''' @@ -71,7 +71,8 @@ def calcDopplerShiftHz(distance_m, velocity_mps): float Doppler shift in Hz. ''' - return GPS._calcDopplerShiftHz(GPS.L1CA.CENTER_FREQUENCY_HZ, distance_m, velocity_mps) + return _calcDopplerShiftHz(GPS.L1CA.CENTER_FREQUENCY_HZ, + distance_m, velocity_mps) @staticmethod def getSymbolIndex(svTime_s): @@ -134,7 +135,8 @@ def calcDopplerShiftHz(distance_m, velocity_mps): float Doppler shift in Hz. ''' - return GPS._calcDopplerShiftHz(GPS.L2C.CENTER_FREQUENCY_HZ, distance_m, velocity_mps) + return _calcDopplerShiftHz(GPS.L2C.CENTER_FREQUENCY_HZ, + distance_m, velocity_mps) @staticmethod def getSymbolIndex(svTime_s): @@ -169,3 +171,174 @@ def getCodeChipIndex(svTime_s): Code chip index ''' return long(svTime_s * GPS.L2C.CODE_CHIP_RATE_HZ) + +# GLONASS L1 +GLONASS_L1_CENTER_FREQUENCY_HZ = 1602000 +GLONASS_L1_FREQUENCY_STEP_HZ = 562500 + +# GLONASS L2 +GLONASS_L2_CENTER_FREQUENCY_HZ = 1246000 +GLONASS_L2_FREQUENCY_STEP_HZ = 437500 + +# GLONASS L1 and L2 common +GLONASS_SYMBOL_RATE_HZ = 100 +GLONASS_CODE_CHIP_RATE_HZ = 511000 +GLONASS_CHIP_TO_SYMBOL_DIVIDER = 5110 + + +class GLONASS: + ''' + GLONASS signal parameters and utilities. + ''' + + # See ICD L1, L2 GLONASS + # + # No. of | Nominal value of | Nominal value of + # channel | frequency in L1 | frequency in L2 + # | sub-band, MHz | sub-band, MHz + # --------+------------------+------------------ + # 06 | 1605.3750 | 1248.6250 + # 05 | 1604.8125 | 1248.1875 + # 04 | 1604.2500 | 1247.7500 + # 03 | 1603.6875 | 1247.3125 + # 02 | 1603.1250 | 1246.8750 + # 01 | 1602.5625 | 1246.4375 + # 00 | 1602.0000 | 1246.0000 + # -01 | 1601.4375 | 1245.5625 + # -02 | 1600.8750 | 1245.1250 + # -03 | 1600.3125 | 1244.6875 + # -04 | 1599.7500 | 1244.2500 + # -05 | 1599.1875 | 1243.8125 + # -06 | 1598.6250 | 1243.3750 + # -07 | 1598.0625 | 1242.9375 + + @staticmethod + def getSymbolIndex(svTime_s): + ''' + Computes symbol index. + + Parameters + ---------- + svTime_s : float + SV time in seconds + + Returns + ------- + long + Symbol index + ''' + return long(float(svTime_s) * float(GLONASS.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(float(svTime_s) * float(GLONASS.CODE_CHIP_RATE_HZ)) + + class _L1: + ''' + GLONASS L1 frequency object for a single sub-band + + Attributes + ---------- + SUB_BAND + Sub-band index in the range [-7, 6] + SYMBOL_RATE_HZ + Symbol rate for GLONASS L1 + CENTER_FREQUENCY_HZ + Center frequency for GLONASS L1 sub-band + CODE_CHIP_RATE_HZ + Code chip rate in Hz + CHIP_TO_SYMBOL_DIVIDER + Divider for converting chips to symbols + ''' + + def __init__(self, subBand): + assert subBand >= -7 and subBand < 7 + + self.SUB_BAND = subBand + self.SYMBOL_RATE_HZ = GLONASS_SYMBOL_RATE_HZ + self.CENTER_FREQUENCY_HZ = float(GLONASS_L1_CENTER_FREQUENCY_HZ + + subBand * GLONASS_L1_FREQUENCY_STEP_HZ) + self.CODE_CHIP_RATE_HZ = GLONASS_CODE_CHIP_RATE_HZ + self.CHIP_TO_SYMBOL_DIVIDER = GLONASS_CHIP_TO_SYMBOL_DIVIDER + + def calcDopplerShiftHz(self, 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 _calcDopplerShiftHz(self.CENTER_FREQUENCY_HZ, + distance_m, velocity_mps) + + class _L2: + ''' + GLONASS L2 frequency object for a single sub-band + + Attributes + ---------- + SUB_BAND + Sub-band index in the range [-7, 6] + SYMBOL_RATE_HZ + Symbol rate for GLONASS L2 + CENTER_FREQUENCY_HZ + Center frequency for GLONASS L2 sub-band + CODE_CHIP_RATE_HZ + Code chip rate in Hz + CHIP_TO_SYMBOL_DIVIDER + Divider for converting chips to symbols + ''' + + def __init__(self, subBand): + assert subBand >= -7 and subBand < 7 + + self.SUB_BAND = subBand + self.SYMBOL_RATE_HZ = GLONASS_SYMBOL_RATE_HZ + self.CENTER_FREQUENCY_HZ = float(GLONASS_L2_CENTER_FREQUENCY_HZ + + subBand * GLONASS_L2_FREQUENCY_STEP_HZ) + self.CODE_CHIP_RATE_HZ = GLONASS_CODE_CHIP_RATE_HZ + self.CHIP_TO_SYMBOL_DIVIDER = GLONASS_CHIP_TO_SYMBOL_DIVIDER + + def calcDopplerShiftHz(self, 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 _calcDopplerShiftHz(self.CENTER_FREQUENCY_HZ, + distance_m, velocity_mps) + + L1S = [_L1(b) for b in range(7)] + [_L1(b) for b in range(-7, 0)] + L2S = [_L2(b) for b in range(7)] + [_L2(b) for b in range(-7, 0)] diff --git a/peregrine/iqgen/generate.py b/peregrine/iqgen/generate.py index 4d9a826..b7e14ad 100644 --- a/peregrine/iqgen/generate.py +++ b/peregrine/iqgen/generate.py @@ -8,19 +8,22 @@ # 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.satellite_gps import GPSSatellite +from peregrine.iqgen.bits.satellite_glo import GLOSatellite from peregrine.iqgen.bits.filter_lowpass import LowPassFilter from peregrine.iqgen.bits.filter_bandpass import BandPassFilter from peregrine.iqgen.bits import signals +import sys +import traceback import logging import scipy import numpy @@ -321,8 +324,12 @@ def generateSamples(outputFile, _count = 0l # Check which bands are enabled, configure band-specific parameters - bands = [outputConfig.GPS.L1, outputConfig.GPS.L2] # Supported bands + bands = [outputConfig.GPS.L1, + outputConfig.GPS.L2, + outputConfig.GLONASS.L1, + outputConfig.GLONASS.L2] # Supported bands lpf = [None] * len(bands) + lpfFA_db = [0.] * len(bands) # Filter attenuation levels bandsEnabled = [False] * len(bands) bandPass = False @@ -341,14 +348,21 @@ def generateSamples(outputFile, bandsEnabled[band.INDEX] |= sv.isBandEnabled(band.INDEX, outputConfig) filterObject = None + ifHz = 0. + if hasattr(band, "INTERMEDIATE_FREQUENCY_HZ"): + ifHz = band.INTERMEDIATE_FREQUENCY_HZ + elif hasattr(band, "INTERMEDIATE_FREQUENCIES_HZ"): + ifHz = band.INTERMEDIATE_FREQUENCIES_HZ[0] + else: + raise ValueError("Unknown band type") + if lowPass: - filterObject = LowPassFilter(outputConfig, - band.INTERMEDIATE_FREQUENCY_HZ) + filterObject = LowPassFilter(outputConfig, ifHz) elif bandPass: - filterObject = BandPassFilter(outputConfig, - band.INTERMEDIATE_FREQUENCY_HZ) + filterObject = BandPassFilter(outputConfig, ifHz) if filterObject: lpf[band.INDEX] = filterObject + lpfFA_db[band.INDEX] = filterObject.getPassBandAtt() logger.debug("Band %d filter NBW is %s" % (band.INDEX, str(filterObject))) @@ -356,6 +370,14 @@ def generateSamples(outputFile, sourcePower = 0. for sv in sv_list: svMeanPower = sv.getAmplitude().computeMeanPower() + if isinstance(sv, GPSSatellite): + # GPS: 1023 Kilobits/second + svMeanPower /= 1023e3 + elif isinstance(sv, GLOSatellite): + # GLONASS: 511 Kilobits/second + svMeanPower /= 511e3 + else: + pass sourcePower += svMeanPower logger.debug("[%s] Estimated mean power is %f" % (sv.getSvName(), svMeanPower)) @@ -367,8 +389,8 @@ def generateSamples(outputFile, # 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 + # Number of samples for 1023/511 MHz + freqTimesTau = outputConfig.SAMPLE_RATE_HZ noiseVariance = freqTimesTau * meanPower / (4. * 10. ** (float(SNR) / 10.)) noiseSigma = numpy.sqrt(noiseVariance) logger.info("Selected noise sigma %f (variance %f) for SNR %f" % @@ -388,32 +410,54 @@ def generateSamples(outputFile, _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() + if isinstance(sv, GPSSatellite): + # GPS: 1023 Kilobits/second + powerDivider = 1023e3 + band1Index = outputConfig.GPS.L1.INDEX + band2Index = outputConfig.GPS.L2.INDEX + elif isinstance(sv, GLOSatellite): + # GLONASS: 511 Kilobits/second + powerDivider = 511e3 + band1Index = outputConfig.GLONASS.L1.INDEX + band2Index = outputConfig.GLONASS.L2.INDEX + else: + pass # SNR for a satellite. Depends on sampling rate. if noiseVariance: - svSNR = svMeanPower / (4. * noiseVariance) * freqTimesTau + svSNR = svMeanPower / (4. * noiseVariance) * freqTimesTau / powerDivider 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 + svCNoL1 = svSNR_db + 10. * numpy.log10(powerDivider) - lpfFA_db[band1Index] # CNo for L2, half power used (-3dB) - svCNoL2 = svSNR_db + 10. * numpy.log10(1.023e6) - 3. - l2FA_db + svCNoL2 = svSNR_db + 10. * \ + numpy.log10(powerDivider) - 3. - lpfFA_db[band2Index] + + _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) + + if isinstance(_sv, GPSSatellite): + _msg1 = _sv.getL1CAMessage() + _msg2 = _sv.getL2CMessage() + _l2ct = _sv.getL2CLCodeType() + elif isinstance(_sv, GLOSatellite): + _msg1 = _sv.getL1Message() + _msg2 = _sv.getL2Message() + _l2ct = "N/A" + else: + raise ValueError("Unknown SV type") 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 " .l1_message: {}".format(_msg1) + print " .l2_message: {}".format(_msg2) + print " .l2_cl_type: {}".format(_l2ct) print " .SNR (dBHz): {}".format(svSNR_db) print " .L1 CNo: {}".format(svCNoL1) print " .L2 CNo: {}".format(svCNoL2) diff --git a/peregrine/iqgen/if_iface.py b/peregrine/iqgen/if_iface.py index b432b35..0a8f005 100644 --- a/peregrine/iqgen/if_iface.py +++ b/peregrine/iqgen/if_iface.py @@ -52,15 +52,19 @@ class L2(object): INTERMEDIATE_FREQUENCY_HZ = 7.4e+5 INDEX = 1 - class Glonass(object): + class GLONASS(object): class L1(object): - INTERMEDIATE_FREQUENCY_HZ = 12e5 - INDEX = 1 + INTERMEDIATE_FREQUENCIES_HZ = \ + [float(1200000 + b * 562500) for b in range(7)] + \ + [float(1200000 + b * 562500) for b in range(-7, 0)] + INDEX = 2 class L2(object): - INTERMEDIATE_FREQUENCY_HZ = 11e5 - INDEX = 2 + INTERMEDIATE_FREQUENCIES_HZ = \ + [float(1100000 + b * 437500) for b in range(7)] + \ + [float(1100000 + b * 437500) for b in range(-7, 0)] + INDEX = 3 class Galileo(object): @@ -128,14 +132,18 @@ class L2(object): INTERMEDIATE_FREQUENCY_HZ = 7.4e+6 INDEX = 1 - class Glonass(object): + class GLONASS(object): class L1(object): - INTERMEDIATE_FREQUENCY_HZ = 12e6 + INTERMEDIATE_FREQUENCIES_HZ = \ + [float(12000000 + b * 562500) for b in range(7)] + \ + [float(12000000 + b * 562500) for b in range(-7, 0)] INDEX = 1 class L2(object): - INTERMEDIATE_FREQUENCY_HZ = 11e6 + INTERMEDIATE_FREQUENCIES_HZ = \ + [float(11000000 + b * 437500) for b in range(7)] + \ + [float(11000000 + b * 437500) for b in range(-7, 0)] INDEX = 2 class Galileo(object): @@ -187,7 +195,7 @@ class HighRateConfig(object): SAMPLE_BATCH_SIZE = 100000 GPS = NormalRateConfig.GPS - Glonass = NormalRateConfig.Glonass + GLONASS = NormalRateConfig.GLONASS Galileo = NormalRateConfig.Galileo Beidou = NormalRateConfig.Beidou diff --git a/peregrine/iqgen/iqgen_main.py b/peregrine/iqgen/iqgen_main.py old mode 100644 new mode 100755 index 009a57b..292be0d --- a/peregrine/iqgen/iqgen_main.py +++ b/peregrine/iqgen/iqgen_main.py @@ -1,4 +1,4 @@ -#!/bin/python +#!/usr/bin/env python # Copyright (C) 2016 Swift Navigation Inc. # Contact: Valeri Atamaniouk # @@ -27,6 +27,7 @@ hasProgressBar = False from peregrine.iqgen.bits.satellite_gps import GPSSatellite +from peregrine.iqgen.bits.satellite_glo import GLOSatellite # Doppler objects from peregrine.iqgen.bits.doppler_poly import zeroDoppler @@ -69,6 +70,16 @@ from peregrine.iqgen.bits.encoder_gps import GPSL2TwoBitsEncoder from peregrine.iqgen.bits.encoder_gps import GPSL1L2TwoBitsEncoder +from peregrine.iqgen.bits.encoder_glo import GLONASSL1BitEncoder +from peregrine.iqgen.bits.encoder_glo import GLONASSL2BitEncoder +from peregrine.iqgen.bits.encoder_glo import GLONASSL1L2BitEncoder +from peregrine.iqgen.bits.encoder_glo import GLONASSL1TwoBitsEncoder +from peregrine.iqgen.bits.encoder_glo import GLONASSL2TwoBitsEncoder +from peregrine.iqgen.bits.encoder_glo import GLONASSL1L2TwoBitsEncoder + +from peregrine.iqgen.bits.encoder_other import GPSGLONASSBitEncoder +from peregrine.iqgen.bits.encoder_other import GPSGLONASSTwoBitsEncoder + from peregrine.iqgen.generate import generateSamples from peregrine.iqgen.bits.satellite_factory import factoryObject as satelliteFO @@ -130,7 +141,12 @@ def __call__(self, parser, namespace, values, option_string=None): namespace.gps_sv = [] # Add SV to the tail of the list. - sv = GPSSatellite(int(values)) + if option_string == '--gps-sv': + sv = GPSSatellite(int(values)) + elif option_string == '--glo-sv': + sv = GLOSatellite(int(values)) + else: + raise ValueError("Option value is not supported: %s" % option_string) namespace.gps_sv.append(sv) # Reset all configuration parameters @@ -183,18 +199,24 @@ def __init__(self, option_strings, dest, nargs=None, **kwargs): def doUpdate(self, sv, parser, namespace, values, option_string): l1caEnabled = False l2cEnabled = False - if namespace.bands == "l1ca": + if namespace.bands == "l1ca" or namespace.bands == 'l1': l1caEnabled = True - elif namespace.bands == "l2c": + elif namespace.bands == "l2c" or namespace.bands == 'l2': l2cEnabled = True - elif namespace.bands == "l1ca+l2c": + elif namespace.bands == "l1ca+l2c" or namespace.bands == 'l1+l2': l1caEnabled = True l2cEnabled = True else: raise ValueError() - sv.setL2CLCodeType(namespace.l2cl_code_type) - sv.setL1CAEnabled(l1caEnabled) - sv.setL2CEnabled(l2cEnabled) + if isinstance(sv, GPSSatellite): + sv.setL2CLCodeType(namespace.l2cl_code_type) + sv.setL1CAEnabled(l1caEnabled) + sv.setL2CEnabled(l2cEnabled) + elif isinstance(sv, GLOSatellite): + sv.setL1Enabled(l1caEnabled) + sv.setL2Enabled(l2cEnabled) + else: + raise ValueError("Unsupported object type in SV list") class UpdateDopplerType(UpdateSv): @@ -327,12 +349,22 @@ def doUpdate(self, sv, parser, namespace, values, option_string): messageL1 = ZeroOneMessage() messageL2 = messageL1 elif namespace.message_type == "crc": - messageL1 = LNavMessage(sv.prn) - messageL2 = CNavMessage(sv.prn) + if isinstance(sv, GPSSatellite): + messageL1 = LNavMessage(sv.prn) + messageL2 = CNavMessage(sv.prn) + else: + raise ValueError( + "Message type is not supported for a satellite type") else: raise ValueError("Unsupported message type") - sv.setL1CAMessage(messageL1) - sv.setL2CMessage(messageL2) + if isinstance(sv, GPSSatellite): + sv.setL1CAMessage(messageL1) + sv.setL2CMessage(messageL2) + elif isinstance(sv, GLOSatellite): + sv.setL1Message(messageL1) + sv.setL2Message(messageL2) + else: + raise ValueError("Unsupported object type in SV list") class UpdateMessageFile(UpdateSv): @@ -349,8 +381,14 @@ def doUpdate(self, sv, parser, namespace, values, option_string): numpy.negative(data, out=data) message = BlockMessage(data) - sv.setL1CAMessage(message) - sv.setL2CMessage(message) + if isinstance(sv, GPSSatellite): + sv.setL1CAMessage(message) + sv.setL2CMessage(message) + elif isinstance(sv, GLOSatellite): + sv.setL1Message(message) + sv.setL2Message(message) + else: + raise ValueError("Unsupported object type in SV list") class SaveConfigAction(argparse.Action): @@ -403,90 +441,104 @@ def __call__(self, parser, namespace, values, option_string=None): default=[], help='Enable GPS satellite', action=AddSv) + parser.add_argument('--glo-sv', + default=[], + help='Enable GLONASS satellite', + action=AddSv) parser.add_argument('--bands', default="l1ca", - choices=["l1ca", "l2c", "l1ca+l2c"], + choices=["l1ca", "l2c", "l1ca+l2c", "l1", "l2", "l1+l2"], 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") + dopplerGrp = parser.add_argument_group("Doppler Control", + "Doppler control parameters") + dopplerGrp.add_argument('--doppler-type', + default="zero", + choices=["zero", "const", "linear", "sine"], + help="Configure doppler type", + action=UpdateDopplerType) + dopplerGrp.add_argument('--doppler-value', + type=float, + help="Doppler shift in hertz (initial)", + action=UpdateDopplerType) + dopplerGrp.add_argument('--doppler-speed', + type=float, + help="Doppler shift change in hertz/second", + action=UpdateDopplerType) + + delayGrp = parser.add_argument_group("Signal Delay Control", + "Signal delay control parameters") + + delayGrp.add_argument('--distance', + type=float, + help="Distance in meters for signal delay (initial)", + action=UpdateDopplerType) + delayGrp.add_argument('--tec', + type=float, + help="Ionosphere TEC for signal delay" + " (electrons per meter^2)", + action=UpdateDopplerType) + dopplerGrp.add_argument('--doppler-amplitude', + type=float, + help="Doppler change amplitude (hertz)", + action=UpdateDopplerType) + dopplerGrp.add_argument('--doppler-period', + type=float, + help="Doppler change period (seconds)", + action=UpdateDopplerType) + dopplerGrp.add_argument('--ignore-code-doppler', + help="Disable doppler for code and data processing", + action=DisableCodeDoppler) + amplitudeGrp = parser.add_argument_group("Amplitude Control", + "Amplitude control parameters") + amplitudeGrp.add_argument('--amplitude-type', + default="poly", + choices=["poly", "sine"], + help="Configure amplitude type: polynomial or sine.", + action=UpdateAmplitudeType) + amplitudeGrp.add_argument('--amplitude-a0', + type=float, + help="Amplitude coefficient (a0 for polynomial;" + " offset for sine)", + action=UpdateAmplitudeType) + amplitudeGrp.add_argument('--amplitude-a1', + type=float, + help="Amplitude coefficient (a1 for polynomial," + " amplitude for size)", + action=UpdateAmplitudeType) + amplitudeGrp.add_argument('--amplitude-a2', + type=float, + help="Amplitude coefficient (a2 for polynomial)", + action=UpdateAmplitudeType) + amplitudeGrp.add_argument('--amplitude-a3', + type=float, + help="Amplitude coefficient (a3 for polynomial)", + action=UpdateAmplitudeType) + amplitudeGrp.add_argument('--amplitude-period', + type=float, + help="Amplitude period in seconds for sine", + action=UpdateAmplitudeType) + messageGrp = parser.add_argument_group("Message Data Control", + "Message data control parameters") + messageGrp.add_argument('--message-type', default="zero", + choices=["zero", "one", "zero+one", "crc"], + help="Message type", + action=UpdateMessageType) + messageGrp.add_argument('--message-file', + type=argparse.FileType('rb'), + help="Source file for message contents.", + action=UpdateMessageFile) + messageGrp.add_argument('--l2cl-code-type', + default='01', + choices=['01', '1', '0'], + help="GPS L2 CL code type", + action=UpdateBands) + delayGrp.add_argument('--symbol_delay', + type=int, + help="Initial symbol index") + delayGrp.add_argument('--chip_delay', + type=int, + help="Initial chip index") parser.add_argument('--filter-type', default='none', choices=['none', 'lowpass', 'bandpass'], @@ -494,32 +546,35 @@ def __call__(self, parser, namespace, values, option_string=None): 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) + tcxoGrp = parser.add_argument_group("TCXO Control", + "TCXO control parameters") + + tcxoGrp.add_argument('--tcxo-type', + choices=["poly", "sine"], + help="TCXO drift type", + action=UpdateTcxoType) + tcxoGrp.add_argument('--tcxo-a0', + type=float, + help="TCXO a0 coefficient for polynomial TCXO drift" + " or initial shift for sine TCXO drift", + action=UpdateTcxoType) + tcxoGrp.add_argument('--tcxo-a1', + type=float, + help="TCXO a1 coefficient for polynomial TCXO drift" + " or amplitude for sine TCXO drift", + action=UpdateTcxoType) + tcxoGrp.add_argument('--tcxo-a2', + type=float, + help="TCXO a2 coefficient for polynomial TCXO drift", + action=UpdateTcxoType) + tcxoGrp.add_argument('--tcxo-a3', + type=float, + help="TCXO a3 coefficient for polynomial TCXO drift", + action=UpdateTcxoType) + tcxoGrp.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") @@ -604,26 +659,57 @@ def main(): # output encoder enabledGPSL1 = False enabledGPSL2 = False + enabledGPS = False + enabledGLONASSL1 = False + enabledGLONASSL2 = False + enabledGLONASS = False for sv in args.gps_sv: enabledGPSL1 |= sv.isBandEnabled(outputConfig.GPS.L1.INDEX, outputConfig) enabledGPSL2 |= sv.isBandEnabled(outputConfig.GPS.L2.INDEX, outputConfig) + enabledGLONASSL1 |= sv.isBandEnabled(outputConfig.GLONASS.L1.INDEX, + outputConfig) + enabledGLONASSL2 |= sv.isBandEnabled(outputConfig.GLONASS.L2.INDEX, + outputConfig) + + enabledGPS = enabledGPSL1 or enabledGPSL2 + enabledGLONASS = enabledGLONASSL1 or enabledGLONASSL2 # Configure data encoder if args.encoder == "1bit": - if enabledGPSL1 and enabledGPSL2: - encoder = GPSL1L2BitEncoder(outputConfig) - elif enabledGPSL2: - encoder = GPSL2BitEncoder(outputConfig) - else: - encoder = GPSL1BitEncoder(outputConfig) + if enabledGPS and enabledGLONASS: + encoder = GPSGLONASSBitEncoder(outputConfig) + elif enabledGPS: + if enabledGPSL1 and enabledGPSL2: + encoder = GPSL1L2BitEncoder(outputConfig) + elif enabledGPSL2: + encoder = GPSL2BitEncoder(outputConfig) + else: + encoder = GPSL1BitEncoder(outputConfig) + elif enabledGLONASS: + if enabledGLONASSL1 and enabledGLONASSL2: + encoder = GLONASSL1L2BitEncoder(outputConfig) + elif enabledGLONASSL2: + encoder = GLONASSL2BitEncoder(outputConfig) + else: + encoder = GLONASSL1BitEncoder(outputConfig) elif args.encoder == "2bits": - if enabledGPSL1 and enabledGPSL2: - encoder = GPSL1L2TwoBitsEncoder(outputConfig) - elif enabledGPSL2: - encoder = GPSL2TwoBitsEncoder(outputConfig) - else: - encoder = GPSL1TwoBitsEncoder(outputConfig) + if enabledGPS and enabledGLONASS: + encoder = GPSGLONASSTwoBitsEncoder(outputConfig) + elif enabledGPS: + if enabledGPSL1 and enabledGPSL2: + encoder = GPSL1L2TwoBitsEncoder(outputConfig) + elif enabledGPSL2: + encoder = GPSL2TwoBitsEncoder(outputConfig) + else: + encoder = GPSL1TwoBitsEncoder(outputConfig) + elif enabledGLONASS: + if enabledGLONASSL1 and enabledGLONASSL2 and not enabledGLONASS: + encoder = GLONASSL1L2TwoBitsEncoder(outputConfig) + elif enabledGLONASSL2: + encoder = GLONASSL2TwoBitsEncoder(outputConfig) + else: + encoder = GLONASSL1TwoBitsEncoder(outputConfig) else: raise ValueError("Encoder type is not supported") From 675ac0856bf37398e6da72f93bab4867fa0f2f1d Mon Sep 17 00:00:00 2001 From: Valeri Atamaniouk Date: Tue, 5 Apr 2016 15:28:36 +0300 Subject: [PATCH 58/67] iqgen: added GLONASS message stub Added generation of GLONASS message stub for message decoding and error correction verification. --- peregrine/iqgen/bits/doppler_base.py | 10 +- peregrine/iqgen/bits/message_factory.py | 23 ++ peregrine/iqgen/bits/message_glo.py | 318 ++++++++++++++++++++++++ peregrine/iqgen/bits/satellite_glo.py | 5 +- peregrine/iqgen/bits/signals.py | 211 ++++++++-------- peregrine/iqgen/iqgen_main.py | 4 + 6 files changed, 463 insertions(+), 108 deletions(-) create mode 100644 peregrine/iqgen/bits/message_glo.py diff --git a/peregrine/iqgen/bits/doppler_base.py b/peregrine/iqgen/bits/doppler_base.py index ebddc8e..b8f103d 100644 --- a/peregrine/iqgen/bits/doppler_base.py +++ b/peregrine/iqgen/bits/doppler_base.py @@ -198,7 +198,8 @@ def computeBatch(self, # Get doppler shift in meters doppler_m = self.computeDopplerShiftM(userTimeAll_s) # Doppler for carrier center frequency - carrFreqRatio = -carrierSignal.CENTER_FREQUENCY_HZ / scipy.constants.c + carrierCenterFreqHz = float(carrierSignal.CENTER_FREQUENCY_HZ) + carrFreqRatio = -carrierCenterFreqHz / scipy.constants.c phaseAll += doppler_m * (carrFreqRatio * twoPi) # Convert phase to signal value and multiply by amplitude @@ -208,12 +209,13 @@ def computeBatch(self, amplitude.applyAmplitude(signal, userTimeAll_s) # PRN and data index computation - chipAll_idx = userTimeAll_s * carrierSignal.CODE_CHIP_RATE_HZ + codeChipRateHz = float(carrierSignal.CODE_CHIP_RATE_HZ) + chipAll_idx = userTimeAll_s * codeChipRateHz if self.codeDopplerIgnored: pass else: # Computing doppler coefficients - chipFreqRatio = -carrierSignal.CODE_CHIP_RATE_HZ / scipy.constants.c + chipFreqRatio = -codeChipRateHz / scipy.constants.c chipAll_idx += doppler_m * chipFreqRatio chips = self.computeDataNChipVector(chipAll_idx, @@ -281,7 +283,7 @@ def computeDataNChipVector(self, chipAll_idx, carrierSignal, message, code): vector of chip phases carrierSignal : object Signal description object - messge : object + message : object Data bits source code : objects Code chips source diff --git a/peregrine/iqgen/bits/message_factory.py b/peregrine/iqgen/bits/message_factory.py index 67c5034..98b1aaf 100644 --- a/peregrine/iqgen/bits/message_factory.py +++ b/peregrine/iqgen/bits/message_factory.py @@ -20,6 +20,7 @@ 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 +from peregrine.iqgen.bits.message_glo import Message as GLOMessage class ObjectFactory(object): @@ -42,6 +43,8 @@ def toMapForm(self, obj): return self.__LNAVMessage_ToMap(obj) elif t is ZeroOneMessage: return self.__ZeroOneMessage_ToMap(obj) + elif t is GLOMessage: + return self.__GLOMessage_ToMap(obj) else: raise ValueError("Invalid object type") @@ -57,6 +60,8 @@ def fromMapForm(self, data): return self.__MapTo_LNAVMessage(data) elif t == 'ZeroOneMessage': return self.__MapTo_ZeroOneMessage(data) + elif t == 'GLOMessage': + return self.__MapTo_GLOMessage(data) else: raise ValueError("Invalid object type") @@ -90,6 +95,14 @@ def __ZeroOneMessage_ToMap(self, obj): data = {'type': 'ZeroOneMessage'} return data + def __GLOMessage_ToMap(self, obj): + data = {'type': 'GLOMessage', + 'prn': obj.prn, + 'n_prefixBits': obj.n_prefixBits, + 'n_msg0': obj.n_msg0, + 'tow0': obj.tow0} + return data + def __MapTo_BlockMessage(self, data): messageData = data['data'] return BlockMessage(messageData) @@ -121,4 +134,14 @@ def __MapTo_LNAVMessage(self, data): def __MapTo_ZeroOneMessage(self, data): return ZeroOneMessage() + def __MapTo_GLOMessage(self, data): + prn = data['prn'] + n_prefixBits = data['n_prefixBits'] + n_msg0 = data['n_msg0'] + tow0 = data['tow0'] + return GLOMessage(prn=prn, + tow0=tow0, + n_msg=n_msg0, + n_prefixBits=n_prefixBits) + factoryObject = ObjectFactory() diff --git a/peregrine/iqgen/bits/message_glo.py b/peregrine/iqgen/bits/message_glo.py new file mode 100644 index 0000000..d2e9ec3 --- /dev/null +++ b/peregrine/iqgen/bits/message_glo.py @@ -0,0 +1,318 @@ +# -*- coding: utf-8 -*- +# 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_glo` module contains classes and +functions related to generating stub GLONASS messages. +''' + +import numpy +import logging +from swiftnav.bits import parity + +logger = logging.getLogger(__name__) + + +def __computeHammingCoefficients(): + ''' + The method prepares bit masks for parity computations according to GLONASS + ICD. + + Returns + ------- + numpy.ndarray(shape=(8, 11), dtype=numpy.uint8) + Bit masks for message bytes + ''' + + B = [None] * 8 + # C1 = β1 ⊕ [ Σi bi] mod 2 + # i = 9, 10, 12, 13, 15, 17, 19, 20, 22, 24, 26, 28, 30, 32, 34, 35, 37, 39, + # 41, 43, 45, 47, 49, 51, 53, 55, 57, 59, 61, 63, 65, 66, 68, 70, 72, 74, + # 76, 78, 80, 82, 84. + B[0] = (9, 10, 12, 13, 15, 17, 19, 20, 22, 24, 26, 28, 30, 32, 34, 35, 37, 39, + 41, 43, 45, 47, 49, 51, 53, 55, 57, 59, 61, 63, 65, 66, 68, 70, 72, 74, + 76, 78, 80, 82, 84) + # C2 = β2 ⊕ [ Σj bj] mod 2 + # j = 9, 11, 12, 14, 15, 18, 19, 21, 22, 25, 26, 29, 30, 33, 34, 36, 37, 40, + # 41, 44, 45, 48, 49, 52, 53, 56, 57, 60, 61, 64, 65, 67, 68, 71, 72, 75, + # 76, 79, 80, 83, 84. + B[1] = (9, 11, 12, 14, 15, 18, 19, 21, 22, 25, 26, 29, 30, 33, 34, 36, 37, 40, + 41, 44, 45, 48, 49, 52, 53, 56, 57, 60, 61, 64, 65, 67, 68, 71, 72, 75, + 76, 79, 80, 83, 84) + # C3 = β3 ⊕ [Σ k b k ] mod 2 + # k = 10-12, 16-19, 23-26, 31-34, 38-41, 46-49, 54-57, 62-65, 69-72, 77-80, + # 85. + B[2] = tuple(range(10, 12 + 1) + range(16, 19 + 1) + range(23, 26 + 1) + + range(31, 34 + 1) + range(38, 41 + 1) + range(46, 49 + 1) + + range(54, 57 + 1) + range(62, 65 + 1) + range(69, 72 + 1) + + range(77, 80 + 1) + [85]) + # C4 = β4 ⊕ [Σl bl]mod 2 + # l = 13-19, 27-34, 42-49, 58-65, 73-80. + B[3] = tuple(range(13, 19 + 1) + range(27, 34 + 1) + range(42, 49 + 1) + + range(58, 65 + 1) + range(73, 80 + 1)) + # C5 = β5 ⊕ [Σ m b m ] mod 2 + # m = 20-34, 50-65, 81-85. + B[4] = tuple(range(20, 34 + 1) + range(50, 65 + 1) + range(81, 85 + 1)) + # 65 + # C6 = β6 ⊕ [Σ bn] mod 2 + # n=35 + B[5] = tuple(range(36, 65 + 1)) + # 85 + # C7 = β7 ⊕ [Σ bp] mod 2 + # p=66 + B[6] = tuple(range(66, 85 + 1)) + # 8 85 + # CΣ = [Σ βq ] mod 2 ⊕ [Σ bq] mod 2 + # q=1 q=9 + B[7] = tuple(range(2, 85 + 1)) + + data = numpy.ndarray(shape=(8, 85), dtype=numpy.uint8) + data.fill(0) + for j in range(8): + for i in B[j]: + data[j][-i] = 1 + return numpy.packbits(data, axis=1) + + +def __computeTimeMark(): + ''' + Method produces time mark array. + Time mark is a shortened PR code, of 30 bits, computed from polynomial: + 1 + x^3 + x^5, or 0b111110001101110101000010010110 + + Returns + ------- + numpy.array(30, dtype=numpy.uint8) + Bit array of GLONASS time mark + ''' + # 30 bits: 111110001101110101000010010110 + TM = [1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, + 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0] + return numpy.asarray(TM, dtype=numpy.uint8) + +# Hamming code masks, see +# Edition 5.1 2008 ICD L1, L2 GLONASS +# Russian Institute of Space Device Engineering +_HAMMING_COEFFS = __computeHammingCoefficients() +# Time mark: 30 bits, see +# Edition 5.1 2008 ICD L1, L2 GLONASS +# Russian Institute of Space Device Engineering +_TIME_MARK = __computeTimeMark() + + +class Message(object): + ''' + GLONASS 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.nextTk_h = tow0 / (60 * 60) % (60 * 60 * 24) + self.nextTk_m = tow0 / 60 % 60 + self.nextTk_30s = 1 if tow0 % 30 else 0 + + self.nextMsgId = 1 + self.messageBits = numpy.zeros(n_prefixBits, dtype=numpy.uint8) + self.messageBits[1::2] = 1 + 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 "GLONASSS: prn=%d pref=%d tod=%02d:%02d:%02d" % \ + (self.prn, self.n_prefixBits, self.nextTk_h, self.nextTk_m, + 30 if self.nextTk_30s else 0) + + 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 / 200 + if delta % 200: + 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 GLONASS 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 * 200 + self.messageLen + newMessageData = numpy.ndarray(newMessageLen, dtype=numpy.uint8) + newMessageData[:self.messageLen] = self.messageBits + for i in range(self.messageLen, newMessageLen, 200): + if self.nextMsgId == 1: + logger.info("Starting new GLONASS frame: prn=%d frame tod=%02d:%02d:%02d" % + (self.prn, + self.nextTk_h, self.nextTk_m, + 30 if self.nextTk_30s == 1 else 0)) + logger.debug("Generating GLONASS string: prn=%d msg=%d" % + (self.prn, self.nextMsgId)) + glo_msg = self.generateGloMessage() + # First 170 symbols are 85 bits of message + # Meander sequence goes here + newMessageData[i:i + 85 * 2:2] = glo_msg + newMessageData[i + 1:i + 85 * 2:2] = glo_msg + # Last 30 symbols is the time mark + newMessageData[i + 170:i + 200] = _TIME_MARK + self.messageLen = newMessageLen + self.messageBits = newMessageData + self.msgCount += newMsgCount + + def generateGloMessage(self): + ''' + Produces additional GLONASS message. + Currently the method generates only type 1 GLONASS strings with ToD. + + Returns + ------- + numpy.ndarray(shape=85, dtype=numpy.uint8) + Message bits. + ''' + msgData = numpy.zeros(85, dtype=numpy.uint8) + + if self.nextMsgId == 1: + self.fillString1(msgData) + else: + self.fillString2_15(msgData) + + self.nextMsgId += 1 + if self.nextMsgId == 16: + self.nextMsgId = 1 + + # Frame has changed - the frame length is 30 seconds + self.nextTk_30s += 1 + while self.nextTk_30s >= 2: + self.nextTk_30s -= 2 + self.nextTk_m += 1 + while self.nextTk_m >= 60: + self.nextTk_m -= 60 + self.nextTk_h += 1 + while self.nextTk_h >= 24: + self.nextTk_h -= 24 + + self.updateParity(msgData) + + return msgData + + def fillString1(self, msgData): + msgData[0] = 0 # idle chip + msgData[1:5] = self.getBits(0b0001, 4) # m[4] + # [2] - Reserved + msgData[7:9] = self.getBits(0b00, 2) # P1[2] + + msgData[9:14] = self.getBits(self.nextTk_h, 5) # Tk[12] + msgData[14:20] = self.getBits(self.nextTk_m, 6) # Tk[12] + msgData[26:27] = self.getBits(self.nextTk_30s, 1) # Tk[12] + msgData[28::2] = 1 # Zero + one everywhere + + def fillString2_15(self, msgData): + msgData[1::2] = 1 # Zero + one everywhere + + 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 updateParity(self, dataBits): + ''' + Updates data bits and computes parity. + + When 85 bits are provided, they are used for parity computation and for + bit inversion. + + Parameters + ---------- + dataBits : numpy.ndarray(dtype=numpy.uint8) + 85 element array + ''' + packed = numpy.packbits(dataBits) + assert len(packed) == 11 + + hc = _HAMMING_COEFFS + for bIdx in range(8): + p = 0 + for i in range(11): + p ^= parity(packed[i] & hc[bIdx][i]) + dataBits[-(bIdx + 1)] = p + packed[10] |= p << bIdx diff --git a/peregrine/iqgen/bits/satellite_glo.py b/peregrine/iqgen/bits/satellite_glo.py index 281e532..423743f 100644 --- a/peregrine/iqgen/bits/satellite_glo.py +++ b/peregrine/iqgen/bits/satellite_glo.py @@ -102,6 +102,7 @@ def setL1Message(self, message): Message object that will provide symbols for L1 C/A signal. ''' self.l1Message = message + self.l2Message = message def setL2Message(self, message): ''' @@ -112,7 +113,7 @@ def setL2Message(self, message): message : object Message object that will provide symbols for L2 C signal. ''' - self.l2Message = message + pass def getL1Message(self): ''' @@ -134,7 +135,7 @@ def getL2Message(self): object L2 C message object ''' - return self.l2Message + return self.l1Message def getBatchSignals(self, userTimeAll_s, samples, outputConfig, debug): ''' diff --git a/peregrine/iqgen/bits/signals.py b/peregrine/iqgen/bits/signals.py index 82b6edd..32584c7 100644 --- a/peregrine/iqgen/bits/signals.py +++ b/peregrine/iqgen/bits/signals.py @@ -36,7 +36,7 @@ def _calcDopplerShiftHz(frequency_hz, distance_m, velocity_mps): float Doppler shift in hertz ''' - doppler_hz = -velocity_mps * frequency_hz / scipy.constants.c + doppler_hz = -float(velocity_mps) * float(frequency_hz) / scipy.constants.c return doppler_hz @@ -50,7 +50,7 @@ class L1CA: GPS L1 C/A parameters and utilities. ''' SYMBOL_RATE_HZ = 50 - CENTER_FREQUENCY_HZ = 1575.42e6 + CENTER_FREQUENCY_HZ = 1575420000 CODE_CHIP_RATE_HZ = 1023000 CHIP_TO_SYMBOL_DIVIDER = 20460 @@ -114,7 +114,7 @@ class L2C: ''' SYMBOL_RATE_HZ = 50 - CENTER_FREQUENCY_HZ = 1227.60e6 + CENTER_FREQUENCY_HZ = 1227600000 CODE_CHIP_RATE_HZ = 1023000 CHIP_TO_SYMBOL_DIVIDER = 20460 @@ -183,7 +183,106 @@ def getCodeChipIndex(svTime_s): # GLONASS L1 and L2 common GLONASS_SYMBOL_RATE_HZ = 100 GLONASS_CODE_CHIP_RATE_HZ = 511000 -GLONASS_CHIP_TO_SYMBOL_DIVIDER = 5110 +GLONASS_CHIP_TO_SYMBOL_DIVIDER = 5110 # 10 ms * 511 + + +class __GLONASS_L1L2Base(object): + ''' + GLONASS L1/L2 frequency object for a single sub-band + + Attributes + ---------- + SUB_BAND + Sub-band index in the range [-7, 6] + SYMBOL_RATE_HZ + Symbol rate for GLONASS L1/L2 + CENTER_FREQUENCY_HZ + Center frequency for GLONASS L1 or L2 sub-band + CODE_CHIP_RATE_HZ + Code chip rate in Hz + CHIP_TO_SYMBOL_DIVIDER + Divider for converting chips to symbols + ''' + + def __init__(self, subBand, centerFrequencyHz): + assert subBand >= -7 and subBand < 7 + + self.SUB_BAND = subBand + self.SYMBOL_RATE_HZ = GLONASS_SYMBOL_RATE_HZ + self.CENTER_FREQUENCY_HZ = centerFrequencyHz + self.CODE_CHIP_RATE_HZ = GLONASS_CODE_CHIP_RATE_HZ + self.CHIP_TO_SYMBOL_DIVIDER = GLONASS_CHIP_TO_SYMBOL_DIVIDER + + def calcDopplerShiftHz(self, distance_m, velocity_mps): + ''' + Converts relative speed into doppler value for GPS L1 or L2 band. + + Parameters + ---------- + distance_m : float + Distance in meters + velocity_mps : float + Relative speed in meters per second. + + Returns + ------- + float + Doppler shift in Hz. + ''' + return _calcDopplerShiftHz(self.CENTER_FREQUENCY_HZ, + distance_m, velocity_mps) + + +class _GLONASS_L1(__GLONASS_L1L2Base): + ''' + GLONASS L1 frequency object for a single sub-band + + Attributes + ---------- + SUB_BAND + Sub-band index in the range [-7, 6] + SYMBOL_RATE_HZ + Symbol rate for GLONASS L1 + CENTER_FREQUENCY_HZ + Center frequency for GLONASS L1 sub-band + CODE_CHIP_RATE_HZ + Code chip rate in Hz + CHIP_TO_SYMBOL_DIVIDER + Divider for converting chips to symbols + ''' + + def __init__(self, subBand): + assert subBand >= -7 and subBand < 7 + + super(_GLONASS_L1, self).__init__( + subBand, + GLONASS_L1_CENTER_FREQUENCY_HZ + subBand * GLONASS_L1_FREQUENCY_STEP_HZ) + + +class _GLONASS_L2(__GLONASS_L1L2Base): + ''' + GLONASS L2 frequency object for a single sub-band + + Attributes + ---------- + SUB_BAND + Sub-band index in the range [-7, 6] + SYMBOL_RATE_HZ + Symbol rate for GLONASS L2 + CENTER_FREQUENCY_HZ + Center frequency for GLONASS L2 sub-band + CODE_CHIP_RATE_HZ + Code chip rate in Hz + CHIP_TO_SYMBOL_DIVIDER + Divider for converting chips to symbols + ''' + + def __init__(self, subBand): + assert subBand >= -7 and subBand < 7 + + super(_GLONASS_L2, self).__init__( + subBand, + GLONASS_L2_CENTER_FREQUENCY_HZ + subBand * GLONASS_L2_FREQUENCY_STEP_HZ) class GLONASS: @@ -227,7 +326,7 @@ def getSymbolIndex(svTime_s): long Symbol index ''' - return long(float(svTime_s) * float(GLONASS.SYMBOL_RATE_HZ)) + return long(float(svTime_s) * float(GLONASS_SYMBOL_RATE_HZ)) @staticmethod def getCodeChipIndex(svTime_s): @@ -244,101 +343,9 @@ def getCodeChipIndex(svTime_s): long Code chip index ''' - return long(float(svTime_s) * float(GLONASS.CODE_CHIP_RATE_HZ)) - - class _L1: - ''' - GLONASS L1 frequency object for a single sub-band - - Attributes - ---------- - SUB_BAND - Sub-band index in the range [-7, 6] - SYMBOL_RATE_HZ - Symbol rate for GLONASS L1 - CENTER_FREQUENCY_HZ - Center frequency for GLONASS L1 sub-band - CODE_CHIP_RATE_HZ - Code chip rate in Hz - CHIP_TO_SYMBOL_DIVIDER - Divider for converting chips to symbols - ''' - - def __init__(self, subBand): - assert subBand >= -7 and subBand < 7 - - self.SUB_BAND = subBand - self.SYMBOL_RATE_HZ = GLONASS_SYMBOL_RATE_HZ - self.CENTER_FREQUENCY_HZ = float(GLONASS_L1_CENTER_FREQUENCY_HZ + - subBand * GLONASS_L1_FREQUENCY_STEP_HZ) - self.CODE_CHIP_RATE_HZ = GLONASS_CODE_CHIP_RATE_HZ - self.CHIP_TO_SYMBOL_DIVIDER = GLONASS_CHIP_TO_SYMBOL_DIVIDER - - def calcDopplerShiftHz(self, 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 _calcDopplerShiftHz(self.CENTER_FREQUENCY_HZ, - distance_m, velocity_mps) - - class _L2: - ''' - GLONASS L2 frequency object for a single sub-band - - Attributes - ---------- - SUB_BAND - Sub-band index in the range [-7, 6] - SYMBOL_RATE_HZ - Symbol rate for GLONASS L2 - CENTER_FREQUENCY_HZ - Center frequency for GLONASS L2 sub-band - CODE_CHIP_RATE_HZ - Code chip rate in Hz - CHIP_TO_SYMBOL_DIVIDER - Divider for converting chips to symbols - ''' - - def __init__(self, subBand): - assert subBand >= -7 and subBand < 7 - - self.SUB_BAND = subBand - self.SYMBOL_RATE_HZ = GLONASS_SYMBOL_RATE_HZ - self.CENTER_FREQUENCY_HZ = float(GLONASS_L2_CENTER_FREQUENCY_HZ + - subBand * GLONASS_L2_FREQUENCY_STEP_HZ) - self.CODE_CHIP_RATE_HZ = GLONASS_CODE_CHIP_RATE_HZ - self.CHIP_TO_SYMBOL_DIVIDER = GLONASS_CHIP_TO_SYMBOL_DIVIDER - - def calcDopplerShiftHz(self, 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 _calcDopplerShiftHz(self.CENTER_FREQUENCY_HZ, - distance_m, velocity_mps) + return long(float(svTime_s) * float(GLONASS_CODE_CHIP_RATE_HZ)) - L1S = [_L1(b) for b in range(7)] + [_L1(b) for b in range(-7, 0)] - L2S = [_L2(b) for b in range(7)] + [_L2(b) for b in range(-7, 0)] + L1S = tuple([_GLONASS_L1(b) for b in range(7)] + + [_GLONASS_L1(b) for b in range(-7, 0)]) + L2S = tuple([_GLONASS_L2(b) for b in range(7)] + + [_GLONASS_L2(b) for b in range(-7, 0)]) diff --git a/peregrine/iqgen/iqgen_main.py b/peregrine/iqgen/iqgen_main.py index 292be0d..bf162c9 100755 --- a/peregrine/iqgen/iqgen_main.py +++ b/peregrine/iqgen/iqgen_main.py @@ -57,6 +57,7 @@ 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 +from peregrine.iqgen.bits.message_glo import Message as GLOMessage # PRN code generators from peregrine.iqgen.bits.prn_gps_l1ca import PrnCode as GPS_L1CA_Code @@ -352,6 +353,9 @@ def doUpdate(self, sv, parser, namespace, values, option_string): if isinstance(sv, GPSSatellite): messageL1 = LNavMessage(sv.prn) messageL2 = CNavMessage(sv.prn) + elif isinstance(sv, GLOSatellite): + messageL1 = GLOMessage(sv.prn) + messageL2 = GLOMessage(sv.prn) else: raise ValueError( "Message type is not supported for a satellite type") From c15002f13891ad1a2bd3c9fdf84c8e2af82f3dbf Mon Sep 17 00:00:00 2001 From: Valeri Atamaniouk Date: Wed, 6 Apr 2016 15:30:36 +0300 Subject: [PATCH 59/67] iqgen: add GLONASS meander sequence for dummy messages Added meander sequence support for GLONASS dummy messages. --- peregrine/iqgen/bits/message_glo.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/peregrine/iqgen/bits/message_glo.py b/peregrine/iqgen/bits/message_glo.py index d2e9ec3..82f02fd 100644 --- a/peregrine/iqgen/bits/message_glo.py +++ b/peregrine/iqgen/bits/message_glo.py @@ -212,8 +212,8 @@ def addMessages(self, newMsgCount): (self.prn, self.nextMsgId)) glo_msg = self.generateGloMessage() # First 170 symbols are 85 bits of message - # Meander sequence goes here - newMessageData[i:i + 85 * 2:2] = glo_msg + # Meander sequence: as per ICD, each data bit is added to 1/0 sequence + newMessageData[i:i + 85 * 2:2] = glo_msg ^ 1 newMessageData[i + 1:i + 85 * 2:2] = glo_msg # Last 30 symbols is the time mark newMessageData[i + 170:i + 200] = _TIME_MARK From ca2de18798db56665faa7251a29b57333b4a6e3a Mon Sep 17 00:00:00 2001 From: Pasi Miettinen Date: Fri, 1 Apr 2016 13:20:39 +0300 Subject: [PATCH 60/67] Add Glonass CA ranging code generation module and unit test --- peregrine/include/glo_ca_code.py | 70 ++++++++++++++++++++++++++++++++ tests/test_glo_ca_code.py | 53 ++++++++++++++++++++++++ 2 files changed, 123 insertions(+) create mode 100644 peregrine/include/glo_ca_code.py create mode 100644 tests/test_glo_ca_code.py diff --git a/peregrine/include/glo_ca_code.py b/peregrine/include/glo_ca_code.py new file mode 100644 index 0000000..3fd0639 --- /dev/null +++ b/peregrine/include/glo_ca_code.py @@ -0,0 +1,70 @@ +# -*- coding: utf-8 -*- +#!/usr/bin/env python + +# Copyright (C) 2016 Swift Navigation Inc. +# +# Contact: Pasi Miettinen +# 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 types +import sys +import numpy as np + + +def generate_glo_ca_code(): + """ + Function generates PRN sequence for Glonass. + All GLONASS satellites use the same C/A-code, + generated by a 9-bit shift register. + """ + code = '111111111' + output = np.zeros(511, np.int8) + for i in xrange(len(output)): + if '0' == code[6]: + output[i] = 1 + else: + output[i] = -1 + if int(code[4]) ^ int(code[8]): + code = '1' + code[:8] + else: + code = '0' + code[:8] + return output + + +def readonly(value): + return property(lambda self: value) + + +class glo_ca_code(types.ModuleType): + """ + Implement module level variable as readonly by imitating module with + this class. + """ + + value = readonly(generate_glo_ca_code()) + + def __dir__(self): + return ['__doc__', '__name__', 'value'] + +tmp = glo_ca_code(__name__) +tmp.__doc__ = """ + PR ranging code is a sequence of maximum length of shift register with a + period 1 millisecond and bit rate 511 kbps. PR ranging code is sampled at + the output of 7th stage of the 9-stage shift register. The initialization + vector to generate this sequence is (111111111). The first character of the + PR ranging code is the first character in the group 111111100, and it is + repeated every 1 millisecond. The generating polynomial, which corresponds + to the 9-stage shift register is G(X) = 1 + X^5 + X^9 + + Function outputs the bitstream as str and time advances from left to right + output[0] ... output[510] + 0ms ----------> 1ms + + In the sequence '0' is represented as '1', 1 as -1 + """ +sys.modules[__name__] = tmp diff --git a/tests/test_glo_ca_code.py b/tests/test_glo_ca_code.py new file mode 100644 index 0000000..ceddcce --- /dev/null +++ b/tests/test_glo_ca_code.py @@ -0,0 +1,53 @@ +# -*- coding: utf-8 -*- + +# Copyright (C) 2016 Swift Navigation Inc. +# +# Contact: Pasi Miettinen +# 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 peregrine.include.glo_ca_code import value + +import numpy as np + + +def test_glo_ca_code(): + + code = np.array( + [-1, -1, -1, -1, -1, -1, -1, 1, 1, 1, 1, 1, -1, -1, -1, -1, 1, + -1, -1, -1, -1, -1, 1, 1, 1, -1, 1, -1, -1, -1, 1, 1, -1, -1, + 1, 1, -1, 1, 1, 1, 1, 1, -1, 1, 1, -1, 1, -1, 1, 1, -1, + -1, -1, 1, -1, -1, 1, -1, 1, 1, 1, -1, -1, -1, -1, 1, 1, -1, + -1, -1, -1, -1, 1, 1, -1, -1, 1, -1, -1, 1, 1, 1, -1, 1, -1, + 1, -1, 1, 1, -1, 1, 1, 1, -1, -1, -1, 1, 1, 1, -1, -1, 1, + -1, -1, 1, -1, 1, -1, 1, -1, -1, -1, 1, 1, 1, -1, 1, 1, -1, + -1, 1, 1, 1, -1, 1, 1, 1, -1, 1, 1, 1, 1, 1, 1, 1, 1, + -1, 1, 1, 1, 1, -1, 1, 1, 1, -1, -1, 1, 1, 1, 1, -1, 1, + 1, -1, -1, -1, 1, 1, -1, 1, -1, 1, -1, 1, -1, -1, 1, 1, 1, + 1, -1, -1, 1, -1, -1, -1, -1, 1, -1, 1, 1, -1, -1, 1, -1, -1, + -1, 1, 1, -1, 1, 1, 1, -1, 1, -1, 1, 1, 1, 1, -1, 1, -1, + 1, -1, -1, 1, -1, 1, 1, -1, -1, -1, -1, -1, -1, 1, -1, -1, 1, + 1, -1, 1, 1, -1, 1, 1, -1, 1, -1, -1, 1, -1, -1, -1, -1, -1, + -1, 1, 1, -1, 1, 1, -1, -1, 1, -1, 1, -1, 1, 1, -1, -1, 1, + 1, -1, -1, 1, 1, 1, 1, 1, 1, 1, -1, -1, 1, 1, 1, -1, -1, + 1, 1, -1, 1, -1, 1, 1, 1, -1, -1, 1, -1, 1, 1, -1, 1, -1, + -1, -1, -1, -1, -1, -1, 1, -1, 1, 1, 1, -1, 1, -1, -1, 1, 1, + 1, -1, -1, -1, 1, -1, 1, -1, -1, 1, 1, -1, 1, -1, -1, 1, 1, + -1, -1, -1, -1, 1, 1, 1, -1, -1, -1, -1, -1, 1, -1, -1, -1, 1, + -1, 1, 1, 1, 1, 1, -1, -1, 1, -1, 1, -1, -1, 1, -1, -1, 1, + -1, -1, -1, 1, -1, -1, 1, 1, 1, 1, 1, -1, 1, -1, -1, 1, -1, + 1, -1, -1, -1, -1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, 1, 1, + 1, 1, 1, -1, 1, -1, 1, 1, -1, 1, -1, 1, -1, -1, -1, -1, 1, + 1, -1, 1, -1, -1, -1, 1, -1, -1, -1, 1, 1, 1, 1, 1, 1, -1, + -1, -1, 1, 1, -1, -1, -1, 1, -1, 1, 1, -1, 1, 1, -1, -1, -1, + -1, 1, -1, 1, -1, -1, -1, 1, -1, 1, -1, 1, 1, 1, -1, 1, 1, + -1, 1, 1, 1, 1, -1, -1, 1, 1, -1, -1, -1, 1, 1, 1, 1, -1, + 1, -1, -1, -1, -1, 1, -1, -1, 1, -1, -1, 1, 1, -1, -1, 1, -1, + 1, 1, 1, 1, -1, -1, -1, 1, -1, -1, -1, -1, 1, 1, 1, 1, -1, + -1], dtype=np.int8) + + assert((code == value).all()) From 8dd3715fa69f4aa774ec2694136337aa7e55c794 Mon Sep 17 00:00:00 2001 From: Valeri Atamaniouk Date: Fri, 8 Apr 2016 08:47:10 +0300 Subject: [PATCH 61/67] iqgen: GLONASS fix --- peregrine/iqgen/bits/encoder_1bit.py | 2 +- peregrine/iqgen/bits/signals.py | 8 ++++---- peregrine/iqgen/if_iface.py | 12 ++++++------ peregrine/iqgen/iqgen_main.py | 22 ++++++++++++++++------ 4 files changed, 27 insertions(+), 17 deletions(-) diff --git a/peregrine/iqgen/bits/encoder_1bit.py b/peregrine/iqgen/bits/encoder_1bit.py index 4cfea18..a6fb86b 100644 --- a/peregrine/iqgen/bits/encoder_1bit.py +++ b/peregrine/iqgen/bits/encoder_1bit.py @@ -171,7 +171,7 @@ def addSamples(self, sample_array): for band in range(4): bandIndex = self.bandIndexes[band] band_bits = BandBitEncoder.convertBand(sample_array[bandIndex]) - self.bits[start + band:end:2] = band_bits + self.bits[start + band:end:4] = band_bits self.n_bits = end diff --git a/peregrine/iqgen/bits/signals.py b/peregrine/iqgen/bits/signals.py index 32584c7..18f728f 100644 --- a/peregrine/iqgen/bits/signals.py +++ b/peregrine/iqgen/bits/signals.py @@ -173,12 +173,12 @@ def getCodeChipIndex(svTime_s): return long(svTime_s * GPS.L2C.CODE_CHIP_RATE_HZ) # GLONASS L1 -GLONASS_L1_CENTER_FREQUENCY_HZ = 1602000 -GLONASS_L1_FREQUENCY_STEP_HZ = 562500 +GLONASS_L1_CENTER_FREQUENCY_HZ = 1602000000l +GLONASS_L1_FREQUENCY_STEP_HZ = 562500l # GLONASS L2 -GLONASS_L2_CENTER_FREQUENCY_HZ = 1246000 -GLONASS_L2_FREQUENCY_STEP_HZ = 437500 +GLONASS_L2_CENTER_FREQUENCY_HZ = 1246000000l +GLONASS_L2_FREQUENCY_STEP_HZ = 437500l # GLONASS L1 and L2 common GLONASS_SYMBOL_RATE_HZ = 100 diff --git a/peregrine/iqgen/if_iface.py b/peregrine/iqgen/if_iface.py index 0a8f005..9e8398e 100644 --- a/peregrine/iqgen/if_iface.py +++ b/peregrine/iqgen/if_iface.py @@ -136,15 +136,15 @@ class GLONASS(object): class L1(object): INTERMEDIATE_FREQUENCIES_HZ = \ - [float(12000000 + b * 562500) for b in range(7)] + \ - [float(12000000 + b * 562500) for b in range(-7, 0)] - INDEX = 1 + [float(12000000l + b * 562500l) for b in range(7)] + \ + [float(12000000l + b * 562500l) for b in range(-7, 0)] + INDEX = 2 class L2(object): INTERMEDIATE_FREQUENCIES_HZ = \ - [float(11000000 + b * 437500) for b in range(7)] + \ - [float(11000000 + b * 437500) for b in range(-7, 0)] - INDEX = 2 + [float(11000000l + b * 437500l) for b in range(7)] + \ + [float(11000000l + b * 437500l) for b in range(-7, 0)] + INDEX = 3 class Galileo(object): diff --git a/peregrine/iqgen/iqgen_main.py b/peregrine/iqgen/iqgen_main.py index bf162c9..ad32e98 100755 --- a/peregrine/iqgen/iqgen_main.py +++ b/peregrine/iqgen/iqgen_main.py @@ -225,10 +225,20 @@ 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 + if isinstance(sv, GPSSatellite): + 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") + elif isinstance(sv, GLOSatellite): + if sv.isL1Enabled(): + frequency_hz = signals.GLONASS.L1S[sv.prn].CENTER_FREQUENCY_HZ + elif sv.isL2Enabled(): + frequency_hz = signals.GLONASS.L2S[sv.prn].CENTER_FREQUENCY_HZ + else: + raise ValueError("Signal band must be specified before doppler") else: raise ValueError("Signal band must be specified before doppler") @@ -676,8 +686,8 @@ def main(): enabledGLONASSL2 |= sv.isBandEnabled(outputConfig.GLONASS.L2.INDEX, outputConfig) - enabledGPS = enabledGPSL1 or enabledGPSL2 - enabledGLONASS = enabledGLONASSL1 or enabledGLONASSL2 + enabledGPS |= enabledGPSL1 or enabledGPSL2 + enabledGLONASS |= enabledGLONASSL1 or enabledGLONASSL2 # Configure data encoder if args.encoder == "1bit": From 9d4a184126172d4aad1365683a6ff79027574743 Mon Sep 17 00:00:00 2001 From: Adel Mamin Date: Wed, 13 Apr 2016 10:01:41 +0300 Subject: [PATCH 62/67] Fix code review comments --- peregrine/acquisition.py | 7 +- .../{print_res.py => print_track_res.py} | 0 peregrine/analysis/tracking_loop.py | 108 +---- peregrine/defaults.py | 18 +- peregrine/gps_constants.py | 14 +- peregrine/initSettings.py | 27 -- peregrine/navigation.py | 11 +- peregrine/run.py | 237 +++++++--- peregrine/samples.py | 42 +- peregrine/tracking.py | 403 ++++++++++++++++-- tests/test_run.py | 4 +- 11 files changed, 597 insertions(+), 274 deletions(-) rename peregrine/analysis/{print_res.py => print_track_res.py} (100%) delete mode 100644 peregrine/initSettings.py diff --git a/peregrine/acquisition.py b/peregrine/acquisition.py index f8052d7..fac1911 100644 --- a/peregrine/acquisition.py +++ b/peregrine/acquisition.py @@ -49,6 +49,7 @@ class Acquisition: Parameters ---------- + signal: The signal to acquire samples : :class:`numpy.ndarray` or `None` Array of samples to use for acquisition. Can be `None` but in this case `init_samples` *must* be called with an array of samples before any other @@ -76,15 +77,17 @@ class Acquisition: """ def __init__(self, + signal, samples, sampling_freq, IF, samples_per_code, - code_length=defaults.code_length, + code_length, n_codes_integrate=4, offsets=None, wisdom_file=DEFAULT_WISDOM_FILE): + self.signal = signal self.sampling_freq = sampling_freq self.IF = IF self.samples_per_code = int(round(samples_per_code)) @@ -484,7 +487,7 @@ def progress_callback(freq_num, num_freqs): code_phase, snr, status, - 'l1ca') + self.signal) # If the acquisition was successful, log it if (snr > threshold): diff --git a/peregrine/analysis/print_res.py b/peregrine/analysis/print_track_res.py similarity index 100% rename from peregrine/analysis/print_res.py rename to peregrine/analysis/print_track_res.py diff --git a/peregrine/analysis/tracking_loop.py b/peregrine/analysis/tracking_loop.py index 51fd650..6bd4377 100755 --- a/peregrine/analysis/tracking_loop.py +++ b/peregrine/analysis/tracking_loop.py @@ -18,7 +18,7 @@ from peregrine.log import default_logging_config from peregrine.tracking import Tracker from peregrine.gps_constants import L1CA, L2C -from peregrine.initSettings import initSettings +from peregrine.run import populate_peregrine_cmd_line_arguments def main(): @@ -26,61 +26,7 @@ def main(): parser = argparse.ArgumentParser() - 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 = populate_peregrine_cmd_line_arguments(parser) signalParam.add_argument("-P", "--prn", help="PRN to track. ") @@ -101,33 +47,6 @@ def main(): 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' @@ -139,6 +58,9 @@ def main(): args = parser.parse_args() + if args.no_run: + return 0 + if args.file is None: parser.print_help() return @@ -173,21 +95,13 @@ def main(): else: l2c_handover = False - 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 + sampling_freq = freq_profile['sampling_freq'] # [Hz] 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) + ms_to_process = int(args.ms_to_process) if args.pipelining is not None: tracker_options = {'mode': 'pipelining', @@ -224,15 +138,15 @@ def main(): filename=args.file, file_format=args.file_format) - if ms_to_track < 0: + if ms_to_process < 0: # use all available data - ms_to_track = int(1e3 * samples['samples_total'] / sampling_freq) + ms_to_process = int(1e3 * samples['samples_total'] / 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" % ms_to_track + print "Time to process [ms]: %s" % ms_to_process 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 @@ -245,7 +159,7 @@ def main(): tracker = Tracker(samples=samples, channels=[acq_result], - ms_to_track=ms_to_track, + ms_to_track=ms_to_process, sampling_freq=sampling_freq, # [Hz] l2c_handover=l2c_handover, stage2_coherent_ms=stage2_coherent_ms, diff --git a/peregrine/defaults.py b/peregrine/defaults.py index 7e521af..a683e20 100644 --- a/peregrine/defaults.py +++ b/peregrine/defaults.py @@ -8,16 +8,17 @@ # 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 -skip_samples = 1000 -file_format = 'piksi' +acqThreshold = 21.0 # SNR (unitless) +acqSanityCheck = True # Check for sats known to be below the horizon +navSanityMaxResid = 25.0 # meters per SV, normalized nav residuals +abortIfInsane = True # Abort the whole attempt if sanity check fails +useCache = True +cacheDir = 'cache' +ephemMaxAge = 4 * 3600.0 # Reject an ephemeris entry if older than this + +# the size of the sample data block processed at a time processing_block_size = 20e6 # [samples] -chipping_rate = 1.023e6 # Hz -code_length = 1023 # chips - -code_period = code_length / chipping_rate - # original sample_channel_GPS_L1 = 0 sample_channel_GPS_L2 = 1 @@ -237,7 +238,6 @@ # disable lock detect 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. diff --git a/peregrine/gps_constants.py b/peregrine/gps_constants.py index dcf2601..24c781a 100644 --- a/peregrine/gps_constants.py +++ b/peregrine/gps_constants.py @@ -12,13 +12,15 @@ # GPS system parameters: l1 = 1.57542e9 # Hz l2 = 1.22760e9 # Hz -chips_per_code = 1023 -chip_rate = 1.023e6 # Hz -nominal_range = 26000e3 # m -# Useful derived quantities: -code_period = chips_per_code / chip_rate -code_wavelength = code_period * c +l1ca_chip_rate = 1.023e6 # Hz +l1ca_code_length = 1023 +l1ca_code_period = l1ca_code_length / l1ca_chip_rate +l1ca_code_wavelength = l1ca_code_period * c + +l2c_chip_rate = 1.023e6 # Hz + +nominal_range = 26000e3 # m L1CA = 'l1ca' L2C = 'l2c' diff --git a/peregrine/initSettings.py b/peregrine/initSettings.py deleted file mode 100644 index 4d7d209..0000000 --- a/peregrine/initSettings.py +++ /dev/null @@ -1,27 +0,0 @@ -# Copyright (C) 2012 Swift Navigation Inc. -# -# This source is subject to the license found in the file 'LICENSE' which must -# be be distributed together with this source. All other rights reserved. -# -# THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, -# EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED -# WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. - -import defaults - -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['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.useCache = True - self.cacheDir = 'cache' - self.ephemMaxAge = 4 * 3600.0 # Reject an ephemeris entry if older than this diff --git a/peregrine/navigation.py b/peregrine/navigation.py index 06da6a7..9cc5f91 100755 --- a/peregrine/navigation.py +++ b/peregrine/navigation.py @@ -9,7 +9,6 @@ # EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED # WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. -from initSettings import initSettings import datetime import numpy as np import swiftnav.nav_msg @@ -37,7 +36,7 @@ def extract_ephemerides(track_results): ephems[tr.prn] = (nav_msgs[n], tow_indicies[n]) return ephems -def make_chan_meas(track_results, ms, ephems, sampling_freq=16.368e6, IF=4.092e6): +def make_chan_meas(track_results, ms, ephems, sampling_freq=16.368e6): cms = [] for tr in track_results: i, tow_e = ephems[tr.prn][1] @@ -47,7 +46,7 @@ def make_chan_meas(track_results, ms, ephems, sampling_freq=16.368e6, IF=4.092e6 tr.code_phase[ms], tr.code_freq[ms], 0, - tr.carr_freq[ms] - IF, + tr.carr_freq[ms] - tr.IF, tow, tr.absolute_sample[ms] / sampling_freq, 100 @@ -95,15 +94,15 @@ def make_solns(nms): return map(swiftnav.pvt.calc_PVT, nms) -def navigation(track_results, settings, ephems=None, mss=range(10000, 35000, 200)): +def navigation(track_results, sampling_freq, + ephems=None, mss=range(10000, 35000, 200)): if ephems is None: ephems = extract_ephemerides(track_results) track_results = [tr for tr in track_results if tr.status == 'T' and tr.prn in ephems] if len(track_results) < 4: raise Exception('Too few satellites to calculate nav solution') - cmss = [make_chan_meas(track_results, ms, ephems, - settings.samplingFreq, settings.IF) for ms in mss] + cmss = [make_chan_meas(track_results, ms, ephems, sampling_freq) for ms in mss] nms = make_nav_meas(cmss, ephems) ss = make_solns(nms) wn = ephems.values()[0][0].gps_week_num() diff --git a/peregrine/run.py b/peregrine/run.py index f998495..92e62c1 100755 --- a/peregrine/run.py +++ b/peregrine/run.py @@ -13,6 +13,7 @@ import argparse import cPickle import logging +import json import numpy as np from operator import attrgetter @@ -22,8 +23,30 @@ 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 +import peregrine.gps_constants as gps + +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, file_hnd, option_string=None): + data = vars(namespace) + + json.dump(data, file_hnd, indent=2) + file_hnd.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, file_hnd, option_string=None): + loaded = json.load(file_hnd) + for k, v in loaded.iteritems(): + setattr(namespace, k, v) + file_hnd.close() def unpickle_iter(filenames): try: @@ -39,43 +62,14 @@ 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("-a", "--skip-acquisition", - help="use previously saved acquisition results", - action="store_true") - parser.add_argument("-t", "--skip-tracking", - help="use previously saved tracking results", - action="store_true") - parser.add_argument("-n", "--skip-navigation", - help="use previously saved navigation results", - action="store_true") - parser.add_argument("--ms-to-process", - 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'/'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 " - "('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()) +def populate_peregrine_cmd_line_arguments(parser): 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'], @@ -83,26 +77,113 @@ def main(): help="Show progress bar. Default is '%s'" % progress_bar_default) + parser.add_argument("--file", + help="the sample data file to process") + + parser.add_argument('--no-run', + action="store_true", + default=False, + help="Do not generate output.") + + parser.add_argument('--save-config', + type=argparse.FileType('wt'), + metavar='FILE_NAME', + help="Store configuration into file (implies --no-run)", + action=SaveConfigAction) + + parser.add_argument('--load-config', + type=argparse.FileType('rt'), + metavar='FILE_NAME', + help="Restore configuration from file", + action=LoadConfigAction) + + inputCtrl = parser.add_argument_group('Data Source', + 'Data source configuration') + + inputCtrl.add_argument("--skip-samples", + default=0, + metavar='N_SAMPLES', + help="How many samples to skip") + + 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("--ms-to-process", + metavar='MS', + help="the number of milliseconds to process." + "(-1: use all available data)", + default="-1") + + 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') + 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) + + signalParam = parser.add_argument_group('Signal tracking', + 'Parameters for satellite vehicle' + ' signal') + + signalParam.add_argument('--l1ca-profile', + metavar='PROFILE', + help='L1 C/A stage profile. Controls coherent' + ' integration time and tuning parameters: %s.' % + str(defaults.l1ca_stage_profiles.keys()), + choices=defaults.l1ca_stage_profiles.keys()) + + return signalParam + +def main(): + default_logging_config() + + parser = argparse.ArgumentParser() + + parser.add_argument("-a", "--skip-acquisition", + help="use previously saved acquisition results", + action="store_true") + parser.add_argument("-t", "--skip-tracking", + help="use previously saved tracking results", + action="store_true") + parser.add_argument("-n", "--skip-navigation", + help="use previously saved navigation results", + action="store_true") + + populate_peregrine_cmd_line_arguments(parser) - 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.no_run: + return 0 + if args.file is None: parser.print_help() return @@ -131,18 +212,12 @@ def main(): else: tracker_options = None - settings = initSettings(freq_profile) - settings.fileName = args.file - ms_to_process = int(args.ms_to_process) - 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 = {gps.L1CA: {'IF': freq_profile['GPS_L1_IF']}, + gps.L2C: {'IF': freq_profile['GPS_L2_IF']}, 'samples_total': -1, - 'sample_index': settings.skipNumberOfBytes} + 'sample_index': int(args.skip_samples)} # Do acquisition acq_results_file = args.file + ".acq_results" @@ -155,17 +230,27 @@ def main(): acq_results_file) sys.exit(1) else: - # Get 11ms of acquisition samples for fine frequency estimation - load_samples(samples=samples, - num_samples=11 * samplesPerCode, - filename=args.file, - file_format=args.file_format) - - 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(progress_bar_output=args.progress_bar) + for signal in [gps.L1CA]: + + samplesPerCode = int(round(freq_profile['sampling_freq'] / + (gps.l1ca_chip_rate / gps.l1ca_code_length))) + + # Get 11ms of acquisition samples for fine frequency estimation + load_samples(samples=samples, + num_samples=11 * samplesPerCode, + filename=args.file, + file_format=args.file_format) + + acq = Acquisition(signal, + samples[signal]['samples'], + freq_profile['sampling_freq'], + freq_profile['GPS_L1_IF'], + gps.l1ca_code_period * freq_profile['sampling_freq'], + gps.l1ca_code_length) + # only one signal - L1CA is expected to be acquired at the moment + # TODO: add handling of acquisition results from GLONASS once GLONASS + # acquisition is supported. + acq_results = acq.acquisition(progress_bar_output=args.progress_bar) print "Acquisition is over!" @@ -205,6 +290,8 @@ def main(): ms_to_process = int( 1e3 * samples['samples_total'] / freq_profile['sampling_freq']) + # Create the tracker object, which also create one tracking + # channel per each acquisition result in 'acq_results' list. tracker = tracking.Tracker(samples=samples, channels=acq_results, ms_to_track=ms_to_process, @@ -215,9 +302,20 @@ def main(): tracker_options=tracker_options, output_file=args.file, progress_bar_output=args.progress_bar) + # The tracking channels are designed to support batch processing. + # In the batch processing mode the data samples are provided in + # batches (chunks) of 'defaults.processing_block_size' bytes size. + # The loop below runs all tracking channels for each batch as it + # reads it from the samples file. tracker.start() condition = True while condition: + # Each tracking channel remembers its own data samples offset within + # 'samples' such that when new batch of data is provided, it + # starts precisely, where it finished at the previous batch + # processing round. + # 'sample_index' is set to the smallest offset within 'samples' + # array across all tracking channels. sample_index = tracker.run_channels(samples) if sample_index == samples['sample_index']: condition = False @@ -235,7 +333,8 @@ def main(): if not args.skip_navigation: track_results_generator = lambda: unpickle_iter(fn_results) for track_results in track_results_generator(): - nav_solns = navigation(track_results_generator, settings) + nav_solns = navigation(track_results_generator, + freq_profile['sampling_freq']) nav_results = [] for s, t in nav_solns: nav_results += [(t, s.pos_llh, s.vel_ned)] diff --git a/peregrine/samples.py b/peregrine/samples.py index 973b5df..a015b71 100644 --- a/peregrine/samples.py +++ b/peregrine/samples.py @@ -17,10 +17,10 @@ __all__ = ['load_samples', 'save_samples'] -def __load_samples_n_bits(filename, num_samples, num_skip, n_rx, n_bits, - value_lookup, channel_lookup = None): +def __load_samples_n_bits(filename, num_samples, num_skip, n_bits, + value_lookup, channel_lookup): ''' - Helper method to load two-bit samples from a file. + Helper method to load N-bit samples from a file. Parameters ---------- @@ -30,24 +30,22 @@ def __load_samples_n_bits(filename, num_samples, num_skip, n_rx, n_bits, 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 - channel_lookup : array-like - Array to map channels value_lookup : array-like Array to map values + channel_lookup : array-like + Array to map channels 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 `value_lookup` table. + separates codes (bands) and indexes with the 'channel_lookup' table. + The second dimention contains samples indexed with the `value_lookup` + table. ''' - if not channel_lookup: - channel_lookup = range(n_rx) + n_rx = len(channel_lookup) sample_block_size = n_bits * n_rx byte_offset = int(math.floor(num_skip / (8 / sample_block_size))) @@ -79,8 +77,7 @@ def __load_samples_n_bits(filename, num_samples, num_skip, n_rx, n_bits, samples[channel_lookup[rx]][:] = chan return samples -def __load_samples_one_bit(filename, num_samples, num_skip, n_rx, - channel_lookup = None): +def __load_samples_one_bit(filename, num_samples, num_skip, channel_lookup): ''' Helper method to load single-bit samples from a file. @@ -92,8 +89,6 @@ def __load_samples_one_bit(filename, num_samples, num_skip, n_rx, 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 channel_lookup : array-like Array to map channels @@ -105,11 +100,10 @@ def __load_samples_one_bit(filename, num_samples, num_skip, n_rx, of the values: -1, 1 ''' value_lookup = np.asarray((1, -1), dtype=np.int8) - return __load_samples_n_bits(filename, num_samples, num_skip, n_rx, 1, + return __load_samples_n_bits(filename, num_samples, num_skip, 1, value_lookup, channel_lookup) -def __load_samples_two_bits(filename, num_samples, num_skip, n_rx, - channel_lookup = None): +def __load_samples_two_bits(filename, num_samples, num_skip, channel_lookup): ''' Helper method to load two-bit samples from a file. @@ -121,8 +115,6 @@ def __load_samples_two_bits(filename, num_samples, num_skip, n_rx, 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 channel_lookup : array-like Array to map channels @@ -136,7 +128,7 @@ 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. value_lookup = np.asarray((-1, -3, 1, 3), dtype=np.int8) - return __load_samples_n_bits(filename, num_samples, num_skip, n_rx, 2, + return __load_samples_n_bits(filename, num_samples, num_skip, 2, value_lookup, channel_lookup) def _load_samples(filename, @@ -290,18 +282,18 @@ def _load_samples(filename, 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, 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) + samples = __load_samples_two_bits(filename, num_samples, num_skip, [0]) elif file_format == '2bits_x2': # Interleaved two bit samples from two receivers: -3, -1, +1, +3 - samples = __load_samples_two_bits(filename, num_samples, num_skip, 2, + samples = __load_samples_two_bits(filename, num_samples, num_skip, defaults.file_encoding_2bits_x2) elif file_format == '2bits_x4': # Interleaved two bit samples from four receivers: -3, -1, +1, +3 - samples = __load_samples_two_bits(filename, num_samples, num_skip, 4, + samples = __load_samples_two_bits(filename, num_samples, num_skip, 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 15b88a8..57cab08 100644 --- a/peregrine/tracking.py +++ b/peregrine/tracking.py @@ -96,6 +96,25 @@ def update(self, e, p, l): def _tracking_channel_factory(parameters): + """ + Tracking channel factory. + The right tracking channel is created depending + on the type of signal provided in acquisition + results. + + Parameters + ---------- + parameters : dictionary + Combines all relevant tracking channel parameters + needed to create a tracking channel instance. + + Returns + ------- + out : TrackingChannel + Tracking channel instance + + """ + if parameters['acq'].signal == gps_constants.L1CA: return TrackingChannelL1CA(parameters) if parameters['acq'].signal == gps_constants.L2C: @@ -103,8 +122,40 @@ def _tracking_channel_factory(parameters): class TrackingChannel(object): + """ + Tracking channel base class. + Specialized signal tracking channel classes are subclassed from + this class. See TrackingChannelL1CA or TrackingChannelL2C as + examples. + + Sub-classes can optionally implement :meth:'_run_preprocess', + :meth:'_run_postprocess' and :meth:'_get_result' methods. + + The class is designed to support batch processing of sample data. + This is to help processing of large data sample files without the need + of loading the whole file into a memory. + The class instance keeps track of the next sample to be processed + in the form of an index within the original data file. + Each sample data batch comes with its starting index within the original + data file. Given the starting index of the batch and its own index + of the next sample to be processed, the code computes the offset + within the batch and starts/continues the tracking procedure from there. + + """ def __init__(self, params): + """ + Initialize the parameters, which are common across different + types of tracking channels. + + Parameters + ---------- + params : dictionary + The subset of tracking channel parameters that are deemed + to be common across different types of tracking channels. + + + """ for (key, value) in params.iteritems(): setattr(self, key, value) @@ -162,7 +213,6 @@ def __init__(self, params): self.code_phase_acc = 0.0 self.samples_tracked = 0 self.i = 0 - #self.samples_offset = self.samples['samples_offset'] self.pipelining = False # Flag if pipelining is used self.pipelining_k = 0. # Error prediction coefficient for pipelining @@ -181,11 +231,22 @@ def __init__(self, params): raise ValueError("Invalid tracker mode %s" % str(mode)) def dump(self): + """ + Append intermediate tracking results to a file. + + """ fn_analysis, fn_results = self.track_result.dump(self.output_file, self.i) self.i = 0 return fn_analysis, fn_results def start(self): + """ + Start tracking channel. + For the time being only prints an informative log message about + the initial parameters of the tracking channel. + + """ + logger.info("[PRN: %d (%s)] Tracking is started. " "IF: %.1f, Doppler: %.1f, code phase: %.1f, " "sample channel: %d sample index: %d" % @@ -198,25 +259,103 @@ def start(self): self.acq.sample_index)) def get_index(self): + """ + Return index of next sample to be processed by the tracking channel. + The tracking channel is designed to process the input data samples + in batches. A single batch is fed to multiple tracking channels. + To keep track of the order of samples within one tracking channel, + each channel maintains an index of the next sample to be processed. + This method is a getter method for the index. + + Returns + ------- + sample_index: integer + The next data sample to be processed. + + """ return self.sample_index - def _run_preprocess(self): # optionally redefined in subclasses + def _run_preprocess(self): + """ + Customize the tracking run procedure in a subclass. + The method can be optionally redefined in a subclass to perform + a subclass specific actions to happen before correlator runs + next integration round. + + """ pass - def _run_postprocess(self): # optionally redefine in subclasses + def _run_postprocess(self): + """ + Customize the tracking run procedure in a subclass. + The method can be optionally redefined in a subclass to perform + a subclass specific actions to happen after correlator runs + next integration round. + + """ pass - def _get_result(self): # optionally redefine in subclasses + def _get_result(self): + """ + Customize the tracking run procedure outcome in a subclass. + The method can be optionally redefined in a subclass to return + a subclass specific data as a result of the tracking procedure. + + Returns + ------- + out : + None is returned by default. + + """ return None def is_pickleable(self): - return True + """ + Check if object is pickleable. + The base class instance is always pickleable. + If a subclass is not pickleable, then it should redefine the method + and return False. + The need to know if an object is pickleable or not arises from the fact + that we try to run the tracking procedure for multiple tracking channels + on multiple CPU cores, if more than one core is available. + This is done to speed up the overall processing time. When a tracking + channel runs on a separate CPU core, it also runs on a separate + process. When the tracking of the given batch of data is over, the process + exits and the tracking channel state is returned to the parent process. + This requires serialization (pickling) of the tracking object state, + which might not be always trivial. This method essentially defines + if the tracking channels can be run in a separate processs. + If the object is not pickleable, then the tracking for the channel is + done on the same CPU, which runs the parent process. Therefore all + non-pickleable tracking channels are processed sequentially. + + Returns + ------- + out : bool + True if the object is pickleable, False - if not. - def run_parallel(self, samples): - handover = self.run(samples) - return (handover, self) + """ + return True def run(self, samples): + """ + Run tracking channel for the given batch of data. + This method is an entry point for the tracking procedure. + Subclasses normally will not redefine the method, but instead + redefine the customization methods '_run_preprocess', '_run_postprocess' + and '_get_result' to run signal specific tracking operations. + + Parameters + ---------- + sample : dictionary + Sample data. Sample data are provided in batches + + Return + ------ + The return value is determined by '_get_result' customization method, + which can be redefined in subclasses + + """ self.samples = samples @@ -389,7 +528,21 @@ def run(self, samples): class TrackingChannelL1CA(TrackingChannel): + """ + L1CA tracking channel. + """ + def __init__(self, params): + """ + Initialize L1C/A tracking channel with L1C/A specific data. + + Parameters + ---------- + params : dictionary + L1C/A tracking initialization parameters + + """ + # Convert acquisition SNR to C/N0 cn0_0 = 10 * np.log10(params['acq'].snr) cn0_0 += 10 * np.log10(defaults.L1CA_CHANNEL_BANDWIDTH_HZ) @@ -399,9 +552,10 @@ 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.l1ca_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 + params['chipping_rate'] = gps_constants.l1ca_chip_rate TrackingChannel.__init__(self, params) @@ -411,6 +565,12 @@ def __init__(self, params): self.l2c_handover_done = False def _run_preprocess(self): + """ + Run L1C/A tracking loop preprocessor operation. + It runs before every coherent integration round. + + """ + # For L1 C/A there are coherent and non-coherent tracking options. if self.stage1 and \ self.stage2_coherent_ms and \ @@ -436,12 +596,30 @@ def _run_preprocess(self): self.coherent_iter = self.coherent_ms def _get_result(self): + """ + Get L1C/A tracking results. + The possible outcome of L1C/A tracking operation is + the L1C/A handover to L2C in the form of an AcquisitionResult object. + + Returns + ------- + out : AcquisitionResult + L2C acquisition result or None + + """ + if self.l2c_handover_acq and not self.l2c_handover_done: self.l2c_handover_done = True return self.l2c_handover_acq return None def _run_postprocess(self): + """ + Run L1C/A coherent integration postprocessing. + Runs navigation bit sync decoding operation and + L1C/A to L2C handover. + """ + sync, bit = self.nav_bit_sync.update(np.real(self.P), self.coherent_ms) if sync: tow = self.nav_msg.update(bit) @@ -487,8 +665,20 @@ def _run_postprocess(self): class TrackingChannelL2C(TrackingChannel): + """ + L2C tracking channel. + """ def __init__(self, params): + """ + Initialize L2C tracking channel with L2C specific data. + + Parameters + ---------- + params : dictionary + L2C tracking initialization parameters + + """ # Convert acquisition SNR to C/N0 cn0_0 = 10 * np.log10(params['acq'].snr) cn0_0 += 10 * np.log10(defaults.L2C_CHANNEL_BANDWIDTH_HZ) @@ -500,7 +690,8 @@ 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.l2c_chip_rate / gps_constants.l2 + params['chipping_rate'] = gps_constants.l2c_chip_rate TrackingChannel.__init__(self, params) @@ -508,9 +699,22 @@ def __init__(self, params): self.cnav_msg_decoder = CNavMsgDecoder() def is_pickleable(self): - return False # could not pickle cnav_msg_decoder Cython object + """ + L2C tracking channel object is not pickleable due to complexity + of serializing cnav_msg_decoder Cython object. + + out : bool + False - the L2C tracking object is not pickleable + """ + return False def _run_postprocess(self): + """ + Run L2C coherent integration postprocessing. + Runs navigation bit sync decoding operation. + + """ + symbol = 0xFF if np.real(self.P) >= 0 else 0x00 res, delay = self.cnav_msg_decoder.decode(symbol, self.cnav_msg) if res: @@ -531,15 +735,18 @@ def _run_postprocess(self): self.track_result.tow[self.i] = self.track_result.tow[self.i - 1] + \ self.coherent_ms - class Tracker(object): + """ + Tracker class. + Encapsulates and manages the processing of tracking channels. + + """ def __init__(self, samples, channels, ms_to_track, sampling_freq, - chipping_rate=defaults.chipping_rate, l2c_handover=True, progress_bar_output='none', loop_filter_class=AidedTrackingLoop, @@ -549,13 +756,51 @@ def __init__(self, multi=False, tracker_options=None, output_file=None): + """ + Set up tracking environment. + 1. Check if multy CPU tracking is possible + 2. Set up progress bar + 3. Create tracking channels based on the provided acquistion results + + Parameters + ---------- + samples : dictionary + Samples data for all one or more data channels + channels : list + A list of acquisition results + ms_to_track : int + How many milliseconds to track [ms] + sampling_freq : float + Data sampling frequency [Hz] + l2c_handover : bool + Instructs if L1C/A to L2C handover is to be done + progress_bar_output : string + Where the progress bar updates are forwarded. + loop_filter_class : class + The type of the loop filter class to be used by tracker channels + correlator : class + The correlator class to be used by tracker channels + stage2_coherent_ms : dictionary + Stage 2 coherent integration parameters set. + stage2_loop_filter_params : dictionary + Stage 2 loop filter parameters set. + multi : bool + Enable multi core CPU utilization + tracker_options : dictionary + Enable piplining or short/long cycles tracking to simulate HW + output_file : string + The name of the output file, where the tracking results are stored. + The actual file name is a mangled version of this file name and + reflects the signal name and PRN number for which the tracking results + are generated. + + """ 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.correlator = correlator self.stage2_coherent_ms = stage2_coherent_ms @@ -601,17 +846,23 @@ def __init__(self, progressbar.Percentage(), ' ', progressbar.ETA(), ' ', progressbar.Bar()] - self.pbar = progressbar.ProgressBar(widgets=widgets, - maxval=samples['samples_total'], - attr={'samples': self.samples['samples_total'], - 'sample': 0l}, - fd=progress_fd) + self.pbar = progressbar.ProgressBar( \ + widgets=widgets, + maxval=samples['samples_total'], + attr={'samples': self.samples['samples_total'], + 'sample': 0l}, + fd=progress_fd) else: self.pbar = None self.tracking_channels = map(self._create_channel, channels) def start(self): + """ + Start tracking operation for all created tracking channels. + Print relevant log messages, start progress bar. + + """ logger.info("Number of CPUs: %d" % (mp.cpu_count())) logger.info("Tracking %.4fs of data (%d samples)" % @@ -625,27 +876,53 @@ def start(self): if self.pbar: self.pbar.start() - def _print_name(self, name): - print name - def stop(self): + """ + Stop tracking operation of all tracking channels. + 1. Stop progress bar. + 2. Complete logging tracking results for all tracking channels. + + Return + ------ + out : list + A list of file names - one file name for one tracking channel. + Each file contains pickled TrackingResults object + + """ + if self.pbar: self.pbar.finish() - # (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) + def _print_name(name): + print name + print "The tracking results were stored into:" - map(self._print_name, fn_analysis) + map(_print_name, fn_analysis) logger.info("Tracking finished") return fn_results def _create_channel(self, acq): + """ + Create a new channel for the given acquisition result. + + Parameters + ---------- + acq : AcquisitionResults + Acquisition results class object + + Return + ------ + out : TrackingChannel + The new tracking channel class object + + """ if not acq: return parameters = {'acq': acq, @@ -655,7 +932,6 @@ def _create_channel(self, acq): '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, @@ -664,14 +940,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): + """ + Run tracking channels. + + Parameters + ---------- + samples : dictionary + Sample data together with description data + + Return + ------ + out : int + The smallest data sample index across all tracking channels. + The index tells the offset, from which the next sample data batch + is to be read from the input data file. + + """ channels = self.tracking_channels self.tracking_channels = [] + def _run_parallel(i, samples): + """ + Run a tracking channel. + Expected to be run in a child process. + + Parameters + ---------- + i : int + Channel index within self.parallel_channels list + + Return + out : TrackingChannel, AcquisitionResult + Tracking channel state and handover result + + """ + handover = self.parallel_channels[i].run(samples) + return self.parallel_channels[i], handover + while channels and not all(v is None for v in channels): if self.multi: @@ -684,7 +990,7 @@ def run_channels(self, samples): handover = [] if self.parallel_channels: - res = pp.parmap(lambda i: self._run_parallel(i, samples), + res = pp.parmap(lambda i: _run_parallel(i, samples), range(len(self.parallel_channels)), nprocs = len(self.parallel_channels), show_progress=False, @@ -713,8 +1019,29 @@ def run_channels(self, samples): class TrackResults: + """ + Tracking results. + The class is designed to support accumulation of tracking + result up to a certain limit. Once the limit is reached + 'dump' method is expected to be called to store the accumulated + tracking results to the file system. + + """ def __init__(self, n_points, prn, signal): + """ + Init tracking results. + Paremeters + ---------- + n_points : int + How many tracking results can be accumulated until they are + stored into the file system + prn : int + PRN number, for which the tracking results object is created + signal : string + Signal for which the tracking results object is created. + + """ self.print_start = 1 self.status = '-' self.prn = prn @@ -743,12 +1070,24 @@ def __init__(self, n_points, prn, signal): 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() self.signal = signal self.ms_tracked = np.zeros(n_points) def dump(self, output_file, size): + """ + Store tracking result to file system. + The tracking results are stored in two different formats: + CSV (test) and Python pickle (binary) format. + + Parameters + ---------- + output_file : string + The name of the output file. The actual file name is a mangled + version of this name and includes the PRN and signal type. + size : int + How many entries of the tracking results are to be stored into the file. + + """ output_filename, output_file_extension = os.path.splitext(output_file) # mangle the analyses file name with the tracked signal name diff --git a/tests/test_run.py b/tests/test_run.py index 17c0783..c7d66aa 100644 --- a/tests/test_run.py +++ b/tests/test_run.py @@ -40,7 +40,9 @@ def test_acquisition(): # Replace argv with args to skip tracking and navigation. - with patch.object(sys, 'argv', ['peregrine', SAMPLES, '-t', '-n']): + with patch.object(sys, 'argv', + ['peregrine', '--file', SAMPLES, + '--file-format', 'piksi', '-t', '-n']): try: peregrine.run.main() From db4eb2897c30c29f2f3c9d7da65a4c069b1ca761 Mon Sep 17 00:00:00 2001 From: Valeri Atamaniouk Date: Tue, 12 Apr 2016 15:29:06 +0300 Subject: [PATCH 63/67] iqgen: reworked signal SNR control Refactored GPS/GLONASS SNR control, added possibility to define signal power in amplitude, power, SNR and SNR(dB) units. Updates SNR estimation due to encoder attenuation. Fixed minor parameter handling issues. New parameters added: '--amplitude-units', '--noise-sigma'. Parameters removed: '--snr' --- peregrine/iqgen/bits/amplitude_base.py | 198 +++++++++++++++++++++- peregrine/iqgen/bits/amplitude_factory.py | 13 +- peregrine/iqgen/bits/amplitude_poly.py | 34 ++-- peregrine/iqgen/bits/amplitude_sine.py | 33 ++-- peregrine/iqgen/bits/doppler_base.py | 3 +- peregrine/iqgen/bits/doppler_poly.py | 21 ++- peregrine/iqgen/bits/doppler_sine.py | 12 +- peregrine/iqgen/bits/encoder_1bit.py | 9 +- peregrine/iqgen/bits/encoder_2bits.py | 10 +- peregrine/iqgen/bits/encoder_base.py | 16 +- peregrine/iqgen/bits/satellite_base.py | 5 +- peregrine/iqgen/bits/satellite_glo.py | 4 +- peregrine/iqgen/bits/satellite_gps.py | 4 +- peregrine/iqgen/bits/signals.py | 32 ++++ peregrine/iqgen/generate.py | 166 ++++++++---------- peregrine/iqgen/iqgen_main.py | 32 +++- 16 files changed, 434 insertions(+), 158 deletions(-) diff --git a/peregrine/iqgen/bits/amplitude_base.py b/peregrine/iqgen/bits/amplitude_base.py index 67a070d..ca7e962 100644 --- a/peregrine/iqgen/bits/amplitude_base.py +++ b/peregrine/iqgen/bits/amplitude_base.py @@ -14,19 +14,130 @@ """ +import numpy + + +class NoiseParameters(object): + ''' + Container class for holding noise generation parameters. + ''' + + def __init__(self, samplingFreqHz, noiseSigma): + ''' + Parameters + ---------- + samplingFreqHz : float or long + Sampling frequency in Hz + noiseSigma : float + Noise Sigma value + ''' + super(NoiseParameters, self).__init__() + self.samplingFreqHz = samplingFreqHz + self.noiseSigma = noiseSigma + + # Compute coefficient for 1ms integration + self.signalK = noiseSigma * 2. * \ + numpy.sqrt(1000000. / float(samplingFreqHz)) + + self.freqTimeTau = 1e-6 * float(samplingFreqHz) + + def getSamplingFreqHz(self): + ''' + Get sampling frequency. + + Returns + ------- + float or long + Sampling frequency in Hz + ''' + return self.samplingFreqHz + + def getNoiseSigma(self): + ''' + Get noise Sigma. + + Returns + ------- + float + Noise sigma value + ''' + return self.noiseSigma + + def getFreqTimesTau(self): + ''' + Get sampling integration parameter. + + Returns + ------- + float + Integration parameter of the sampling frequency times integration time. + ''' + return self.freqTimeTau + + def getSignalK(self): + ''' + Get amplification coefficient for SNR at 0 dB. + + Returns + ------- + float + Signal amplification coefficient for SNR at 0 dB. + ''' + return self.signalK + class AmplitudeBase(object): ''' Amplitude control for a signal source. + + Attributes + ---------- + UNITS_AMPLITUDE : string + Type of object for measuring signal in amplitude. SNR is dependent on + amplitude square. + UNITS_POWER : string + Type of object for measuring signal in power. SNR is linearly dependent on + power. + UNITS_SNR : string + Type of object for measuring signal in SNR. + UNITS_SNR_DB : string + Type of object for measuring signal in SNR dB. + ''' - def __init__(self): + UNITS_AMPLITUDE = 'AMP' + UNITS_POWER = 'PWR' + UNITS_SNR = 'SNR' + UNITS_SNR_DB = 'SNR_DB' + + def __init__(self, units): ''' Constructs base object for amplitude control. + + Parameters + ---------- + units : string + Object units. Can be one of the supported values: + - AmplitudeBase::UNITS_AMPLITUDE -- Amplitude units + - AmplitudeBase::UNITS_SNR_DB -- SNR in dB + - AmplitudeBase::UNITS_SNR -- SNR + - AmplitudeBase::UNITS_POWER -- Power units ''' super(AmplitudeBase, self).__init__() + self.units = units + + def getUnits(self): + ''' + Provides access to units. + + Returns + ------- + string + Amplitude units + ''' + return self.units - def applyAmplitude(self, signal, userTimeAll_s): + def applyAmplitude(self, signal, userTimeAll_s, noiseParams=None): ''' Applies amplitude modulation to signal. @@ -37,6 +148,8 @@ def applyAmplitude(self, signal, userTimeAll_s): [-1; +1]. This vector is modified in place. userTimeAll_s : numpy.ndarray Sample time vector. Each element defines sample time in seconds. + noiseParams : NoiseParameters + Noise parameters to adjust signal amplitude level. Returns ------- @@ -45,13 +158,88 @@ def applyAmplitude(self, signal, userTimeAll_s): ''' raise NotImplementedError() - def computeMeanPower(self): + def computeSNR(self, noiseParams): ''' - Computes mean signal power. + Computes signal to noise ratio in dB. + + Parameters + ---------- + noiseParams : NoiseParameters + Noise parameter container Returns ------- float - Mean signal power for the configured amplitude + SNR in dB ''' raise NotImplementedError() + + @staticmethod + def convertUnits2SNR(value, units, noiseParams): + ''' + Converts signal units to SNR in dB + + Parameters + ---------- + noiseParams : NoiseParameters + Noise parameter container + + Returns + ------- + float + SNR in dB + ''' + + noiseSigma = noiseParams.getNoiseSigma() + freqTimesTau = noiseParams.getFreqTimesTau() + + if units == AmplitudeBase.UNITS_AMPLITUDE: + power = numpy.square(value) + snr = freqTimesTau * power / (4. * noiseSigma * noiseSigma) + snrDb = 10 * numpy.log10(snr) + elif units == AmplitudeBase.UNITS_POWER: + power = value + snr = freqTimesTau * power / (4. * noiseSigma * noiseSigma) + snrDb = 10 * numpy.log10(snr) + elif units == AmplitudeBase.UNITS_SNR: + snr = value + snrDb = 10 * numpy.log10(snr) + elif units == AmplitudeBase.UNITS_SNR_DB: + snrDb = value + else: + assert False + return snrDb + + @staticmethod + def convertUnits2Amp(value, units, noiseParams): + ''' + Converts signal units to amplitude + + Parameters + ---------- + noiseParams : NoiseParameters + Noise parameter container + + Returns + ------- + float + SNR in dB + ''' + + noiseSigma = noiseParams.getNoiseSigma() + freqTimesTau = noiseParams.getFreqTimesTau() + + if units == AmplitudeBase.UNITS_AMPLITUDE: + amp = value + elif units == AmplitudeBase.UNITS_POWER: + amp = numpy.sqrt(value) + elif units == AmplitudeBase.UNITS_SNR: + snr = value + amp = numpy.sqrt(4. * snr / freqTimesTau) * noiseSigma + elif units == AmplitudeBase.UNITS_SNR_DB: + snrDb = value + snr = 10. ** (0.1 * snrDb) + amp = numpy.sqrt(4. * snr / freqTimesTau) * noiseSigma + else: + assert False + return amp diff --git a/peregrine/iqgen/bits/amplitude_factory.py b/peregrine/iqgen/bits/amplitude_factory.py index 1c88ea3..201730f 100644 --- a/peregrine/iqgen/bits/amplitude_factory.py +++ b/peregrine/iqgen/bits/amplitude_factory.py @@ -45,24 +45,29 @@ def fromMapForm(self, data): raise ValueError("Invalid object type") def __PolyAmplitude_ToMap(self, obj): - data = {'type': 'PolyAmplitude', 'coeffs': obj.coeffs} + data = {'type': 'PolyAmplitude', + 'coeffs': obj.coeffs, + 'units': obj.units} return data def __SineAmplitude_ToMap(self, obj): data = {'type': 'SineAmplitude', 'initial': obj.initial, 'amplitude': obj.amplitude, - 'period': obj.period_s} + 'period': obj.period_s, + 'units': obj.units} return data def __MapTo_PolyAmplitude(self, data): coeffs = data['coeffs'] - return PolyAmplitude(coeffs) + units = data['units'] + return PolyAmplitude(units, coeffs) def __MapTo_SineAmplitude(self, data): initial = data['initial'] amplitude = data['amplitude'] period = data['period'] - return SineAmplitude(initial, amplitude, period) + units = data['units'] + return SineAmplitude(units, initial, amplitude, period) factoryObject = ObjectFactory() diff --git a/peregrine/iqgen/bits/amplitude_poly.py b/peregrine/iqgen/bits/amplitude_poly.py index 0f9275a..66af727 100644 --- a/peregrine/iqgen/bits/amplitude_poly.py +++ b/peregrine/iqgen/bits/amplitude_poly.py @@ -24,7 +24,7 @@ class AmplitudePoly(AmplitudeBase): Amplitude control with polynomial dependency over time. ''' - def __init__(self, coeffs): + def __init__(self, units, coeffs): ''' Constructs polynomial amplitude control object. @@ -32,7 +32,7 @@ def __init__(self, coeffs): coeffs : array-like Polynomial coefficients ''' - super(AmplitudePoly, self).__init__() + super(AmplitudePoly, self).__init__(units) self.coeffs = tuple([x for x in coeffs]) if len(coeffs) > 0: @@ -49,9 +49,9 @@ def __str__(self): string Literal presentation of object ''' - return "AmplitudePoly(c={})".format(self.coeffs) + return "AmplitudePoly(units={}, c={})".format(self.units, self.coeffs) - def applyAmplitude(self, signal, userTimeAll_s): + def applyAmplitude(self, signal, userTimeAll_s, noiseParams): ''' Applies amplitude modulation to signal. @@ -64,6 +64,8 @@ def applyAmplitude(self, signal, userTimeAll_s): [-1; +1]. This vector is modified in place. userTimeAll_s : numpy.ndarray Sample time vector. Each element defines sample time in seconds. + noiseParams : NoiseParameters + Noise parameters to adjust signal amplitude level. Returns ------- @@ -74,22 +76,34 @@ def applyAmplitude(self, signal, userTimeAll_s): poly = self.poly if poly is not None: amplitudeVector = poly(userTimeAll_s) + amplitudeVector = AmplitudeBase.convertUnits2Amp(amplitudeVector, + self.units, + noiseParams) signal *= amplitudeVector + else: + amplitude = AmplitudeBase.convertUnits2Amp(1., + self.units, + noiseParams) + signal *= amplitude return signal - def computeMeanPower(self): + def computeSNR(self, noiseParams): ''' - Computes mean signal power. + Computes signal to noise ratio in dB. + + noiseParams : NoiseParameters + Noise parameter container Returns ------- float - Mean signal power for the configured amplitude + SNR in dB ''' poly = self.poly if poly is not None: - result = numpy.square(poly(0.)) + value = poly(0.) else: - result = 1. - return result + value = 1. + + return AmplitudeBase.convertUnits2SNR(value, self.units, noiseParams) diff --git a/peregrine/iqgen/bits/amplitude_sine.py b/peregrine/iqgen/bits/amplitude_sine.py index ee18452..2352a21 100644 --- a/peregrine/iqgen/bits/amplitude_sine.py +++ b/peregrine/iqgen/bits/amplitude_sine.py @@ -25,7 +25,7 @@ class AmplitudeSine(AmplitudeBase): Amplitude control with sine modulation over time. ''' - def __init__(self, initial, amplitude, period_s): + def __init__(self, units, initial, amplitude, period_s): ''' Constructs sine amplitude control object. @@ -37,7 +37,7 @@ def __init__(self, initial, amplitude, period_s): period_s : float Period of change in seconds ''' - super(AmplitudeSine, self).__init__() + super(AmplitudeSine, self).__init__(units) self.initial = initial self.amplitude = amplitude self.period_s = period_s @@ -52,10 +52,10 @@ def __str__(self): string Literal presentation of object ''' - return "AmplitudeSine(base={}, amp={}, p={} s)".\ - format(self.initial, self.amplitude, self.period_s) + return "AmplitudeSine(units={}, base={}, amp={}, p={} s)".\ + format(self.units, self.initial, self.amplitude, self.period_s) - def applyAmplitude(self, signal, userTimeAll_s): + def applyAmplitude(self, signal, userTimeAll_s, noiseParams): ''' Applies amplitude modulation to signal. @@ -66,6 +66,8 @@ def applyAmplitude(self, signal, userTimeAll_s): [-1; +1]. This vector is modified in place. userTimeAll_s : numpy.ndarray Sample time vector. Each element defines sample time in seconds. + noiseParams : NoiseParameters + Noise parameters to adjust signal amplitude level. Returns ------- @@ -75,17 +77,24 @@ def applyAmplitude(self, signal, userTimeAll_s): ampAll = numpy.sin(userTimeAll_s * self.c) * self.amplitude + self.initial - return numpy.multiply(signal, ampAll, out=signal) + ampAll = AmplitudeBase.convertUnits2Amp(ampAll, + self.units, + noiseParams) + signal *= ampAll - def computeMeanPower(self): + return signal + + def computeSNR(self, noiseParams): ''' - Computes mean signal power. + Computes signal to noise ratio in dB. + + noiseParams : NoiseParameters + Noise parameter container Returns ------- float - Mean signal power for the configured amplitude + SNR in dB ''' - amplitude = self.amplitude * 0.707 + self.initial - result = numpy.square(amplitude) - return result + value = self.initial + return AmplitudeBase.convertUnits2SNR(value, self.units, noiseParams) diff --git a/peregrine/iqgen/bits/doppler_base.py b/peregrine/iqgen/bits/doppler_base.py index b8f103d..119ab69 100644 --- a/peregrine/iqgen/bits/doppler_base.py +++ b/peregrine/iqgen/bits/doppler_base.py @@ -150,6 +150,7 @@ def computeSpeedMps(self, svTime_s): def computeBatch(self, userTimeAll_s, amplitude, + noiseParams, carrierSignal, ifFrequency_hz, message, @@ -206,7 +207,7 @@ def computeBatch(self, signal = scipy.cos(phaseAll) if amplitude: - amplitude.applyAmplitude(signal, userTimeAll_s) + amplitude.applyAmplitude(signal, userTimeAll_s, noiseParams) # PRN and data index computation codeChipRateHz = float(carrierSignal.CODE_CHIP_RATE_HZ) diff --git a/peregrine/iqgen/bits/doppler_poly.py b/peregrine/iqgen/bits/doppler_poly.py index d897716..db22c42 100644 --- a/peregrine/iqgen/bits/doppler_poly.py +++ b/peregrine/iqgen/bits/doppler_poly.py @@ -161,7 +161,7 @@ def computeDopplerShiftHz(self, userTimeAll_s, carrierSignal): speedPoly = self.speedPoly if speedPoly is not None: # Slower, but simple - c0 = -carrierSignal.CENTER_FREQUENCY_HZ / scipy.constants.c + c0 = -float(carrierSignal.CENTER_FREQUENCY_HZ) / scipy.constants.c doppler_hz = speedPoly(userTimeAll_s) * c0 else: # No phase shift @@ -193,11 +193,12 @@ def linearDoppler(distance0_m, 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 + speed0_mps = -scipy.constants.c / float(frequency_hz) * float(doppler0_hz) + accel_mps2 = -scipy.constants.c / \ + float(frequency_hz) * float(dopplerChange_hzps) - return Doppler(distance0_m=distance0_m, - tec_epm2=tec_epm2, + return Doppler(distance0_m=float(distance0_m), + tec_epm2=float(tec_epm2), coeffs=(accel_mps2, speed0_mps)) @@ -219,8 +220,10 @@ def constDoppler(distance0_m, tec_epm2, frequency_hz, doppler_hz): 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,)) + speed_mps = -scipy.constants.c / float(frequency_hz) * float(doppler_hz) + return Doppler(distance0_m=float(distance0_m), + tec_epm2=float(tec_epm2), + coeffs=(speed_mps,)) def zeroDoppler(distance_m, tec_epm2, frequency_hz): @@ -243,4 +246,6 @@ def zeroDoppler(distance_m, tec_epm2, frequency_hz): Doppler object that implments constant acceleration logic. ''' - return Doppler(distance0_m=distance_m, tec_epm2=tec_epm2, coeffs=()) + return Doppler(distance0_m=float(distance_m), + tec_epm2=float(tec_epm2), + coeffs=()) diff --git a/peregrine/iqgen/bits/doppler_sine.py b/peregrine/iqgen/bits/doppler_sine.py index 95d85e3..7ae914a 100644 --- a/peregrine/iqgen/bits/doppler_sine.py +++ b/peregrine/iqgen/bits/doppler_sine.py @@ -166,7 +166,7 @@ def computeDopplerShiftHz(self, userTimeAll_s, carrierSignal): 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 + doppler_hz *= -float(carrierSignal.CENTER_FREQUENCY_HZ) / scipy.constants.c return doppler_hz @@ -192,8 +192,12 @@ def sineDoppler(distance0_m, tec_epm2, frequency_hz, doppler0_hz, dopplerAmplitu Doppler object that implments constant acceleration logic. ''' - dopplerCoeff = -scipy.constants.c / frequency_hz - speed0_mps = dopplerCoeff * doppler0_hz + dopplerCoeff = -scipy.constants.c / float(frequency_hz) + speed0_mps = dopplerCoeff * float(doppler0_hz) amplitude_mps = dopplerCoeff * dopplerAmplitude_hz - return Doppler(distance0_m, tec_epm2, speed0_mps, amplitude_mps, dopplerPeriod_s) + return Doppler(float(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 index a6fb86b..16754c5 100644 --- a/peregrine/iqgen/bits/encoder_1bit.py +++ b/peregrine/iqgen/bits/encoder_1bit.py @@ -21,6 +21,9 @@ class BandBitEncoder(Encoder): ''' Base class for single bit encoding. ''' + # Minimum is 1.96 dB. Can be up to 3.5 dB. + # See Global Positioning System: Theory and Applications + ATT_LVL_DB = 1.96 def __init__(self, bandIndex): ''' @@ -31,7 +34,7 @@ def __init__(self, bandIndex): bandIndex : int Index of the band in the generated sample matrix. ''' - super(BandBitEncoder, self).__init__() + super(BandBitEncoder, self).__init__(attDb=BandBitEncoder.ATT_LVL_DB) self.bandIndex = bandIndex def addSamples(self, sample_array): @@ -96,7 +99,7 @@ def __init__(self, band1, band2): outputConfig : object Output parameters object. ''' - super(TwoBandsBitEncoder, self).__init__() + super(TwoBandsBitEncoder, self).__init__(attDb=BandBitEncoder.ATT_LVL_DB) self.l1Index = band1 self.l2Index = band2 @@ -146,7 +149,7 @@ def __init__(self, band1, band2, band3, band4): outputConfig : object Output parameters object. ''' - super(FourBandsBitEncoder, self).__init__() + super(FourBandsBitEncoder, self).__init__(attDb=BandBitEncoder.ATT_LVL_DB) self.bandIndexes = [band1, band2, band3, band4] def addSamples(self, sample_array): diff --git a/peregrine/iqgen/bits/encoder_2bits.py b/peregrine/iqgen/bits/encoder_2bits.py index 0f543de..478821a 100644 --- a/peregrine/iqgen/bits/encoder_2bits.py +++ b/peregrine/iqgen/bits/encoder_2bits.py @@ -23,6 +23,9 @@ class BandTwoBitsEncoder(Encoder): ''' Base class for two bits encoding. ''' + # Minimum is 1.2 dB. Can be up to 3.5 dB. + # See Global Positioning System: Theory and Applications + ATT_LVL_DB = 1.2 def __init__(self, bandIndex): ''' @@ -33,7 +36,7 @@ def __init__(self, bandIndex): bandIndex : int Index of the band in the generated sample matrix. ''' - super(BandTwoBitsEncoder, self).__init__() + super(BandTwoBitsEncoder, self).__init__(BandTwoBitsEncoder.ATT_LVL_DB) self.bandIndex = bandIndex @staticmethod @@ -131,7 +134,7 @@ def __init__(self, bandIndex1, bandIndex2): outputConfig : object Output parameters object. ''' - super(TwoBandsTwoBitsEncoder, self).__init__() + super(TwoBandsTwoBitsEncoder, self).__init__(BandTwoBitsEncoder.ATT_LVL_DB) self.l1Index = bandIndex1 self.l2Index = bandIndex2 @@ -188,7 +191,8 @@ def __init__(self, band1, band2, band3, band4): outputConfig : object Output parameters object. ''' - super(FourBandsTwoBitsEncoder, self).__init__() + super(FourBandsTwoBitsEncoder, self).__init__( + BandTwoBitsEncoder.ATT_LVL_DB) self.bandIndexes = [band1, band2, band3, band4] def addSamples(self, sample_array): diff --git a/peregrine/iqgen/bits/encoder_base.py b/peregrine/iqgen/bits/encoder_base.py index 1bade6b..edf95bf 100644 --- a/peregrine/iqgen/bits/encoder_base.py +++ b/peregrine/iqgen/bits/encoder_base.py @@ -29,7 +29,7 @@ class Encoder(object): EMPTY_RESULT = numpy.ndarray(0, dtype=numpy.uint8) # Internal empty array - def __init__(self, bufferSize=1000): + def __init__(self, bufferSize=1000, attDb=0.): ''' Constructs encoder. @@ -37,9 +37,12 @@ def __init__(self, bufferSize=1000): ---------- bufferSize : int, optional Size of the internal buffer to batch-process samples + attDb : float, optional + Encoder attenuation level, optional ''' self.bits = numpy.ndarray(bufferSize, dtype=numpy.int8) self.n_bits = 0 + self.attDb = attDb def addSamples(self, sample_array): ''' @@ -103,3 +106,14 @@ def ensureExtraCapacity(self, extraBits): ''' if len(self.bits) < self.n_bits + extraBits: self.bits.resize(self.n_bits + extraBits) + + def getAttenuationLevel(self): + ''' + Method provides encoder attenuation level in dB. + + Returns + ------- + float + Encoder attenuation level. Positive value expected. + ''' + return self.attDb diff --git a/peregrine/iqgen/bits/satellite_base.py b/peregrine/iqgen/bits/satellite_base.py index 1e2f085..edeffcf 100644 --- a/peregrine/iqgen/bits/satellite_base.py +++ b/peregrine/iqgen/bits/satellite_base.py @@ -15,6 +15,7 @@ """ from peregrine.iqgen.bits.doppler_poly import zeroDoppler from peregrine.iqgen.bits.amplitude_poly import AmplitudePoly +from peregrine.iqgen.bits.amplitude_base import AmplitudeBase class Satellite(object): @@ -37,7 +38,7 @@ def __init__(self, svName): super(Satellite, self).__init__() self.svName = svName self.doppler = zeroDoppler(0., 0., 1.) - self.amplitude = AmplitudePoly(()) + self.amplitude = AmplitudePoly(AmplitudeBase.UNITS_AMPLITUDE, ()) def getDoppler(self): ''' @@ -100,7 +101,7 @@ def __str__(self): def __repr__(self): return self.getSvName() - def getBatchSignals(self, userTimeAll_s, samples, outputConfig): + def getBatchSignals(self, userTimeAll_s, samples, outputConfig, noiseParams): ''' Generates signal samples. diff --git a/peregrine/iqgen/bits/satellite_glo.py b/peregrine/iqgen/bits/satellite_glo.py index 423743f..bb586f7 100644 --- a/peregrine/iqgen/bits/satellite_glo.py +++ b/peregrine/iqgen/bits/satellite_glo.py @@ -137,7 +137,7 @@ def getL2Message(self): ''' return self.l1Message - def getBatchSignals(self, userTimeAll_s, samples, outputConfig, debug): + def getBatchSignals(self, userTimeAll_s, samples, outputConfig, noiseParams, debug): ''' Generates signal samples. @@ -164,6 +164,7 @@ def getBatchSignals(self, userTimeAll_s, samples, outputConfig, debug): frequencyIndex = band.INDEX values = self.doppler.computeBatch(userTimeAll_s, self.amplitude, + noiseParams, signals.GLONASS.L1S[self.prn], intermediateFrequency_hz, self.l1Message, @@ -181,6 +182,7 @@ def getBatchSignals(self, userTimeAll_s, samples, outputConfig, debug): frequencyIndex = band.INDEX values = self.doppler.computeBatch(userTimeAll_s, self.amplitude, + noiseParams, signals.GLONASS.L2S[self.prn], intermediateFrequency_hz, self.l2Message, diff --git a/peregrine/iqgen/bits/satellite_gps.py b/peregrine/iqgen/bits/satellite_gps.py index 410804d..c0f9bb1 100644 --- a/peregrine/iqgen/bits/satellite_gps.py +++ b/peregrine/iqgen/bits/satellite_gps.py @@ -147,7 +147,7 @@ def getL2CMessage(self): ''' return self.l2cMessage - def getBatchSignals(self, userTimeAll_s, samples, outputConfig, debug): + def getBatchSignals(self, userTimeAll_s, samples, outputConfig, noiseParams, debug): ''' Generates signal samples. @@ -173,6 +173,7 @@ def getBatchSignals(self, userTimeAll_s, samples, outputConfig, debug): frequencyIndex = outputConfig.GPS.L1.INDEX values = self.doppler.computeBatch(userTimeAll_s, self.amplitude, + noiseParams, signals.GPS.L1CA, intermediateFrequency_hz, self.l1caMessage, @@ -189,6 +190,7 @@ def getBatchSignals(self, userTimeAll_s, samples, outputConfig, debug): frequencyIndex = outputConfig.GPS.L2.INDEX values = self.doppler.computeBatch(userTimeAll_s, self.amplitude, + noiseParams, signals.GPS.L2C, intermediateFrequency_hz, self.l2cMessage, diff --git a/peregrine/iqgen/bits/signals.py b/peregrine/iqgen/bits/signals.py index 18f728f..1334d84 100644 --- a/peregrine/iqgen/bits/signals.py +++ b/peregrine/iqgen/bits/signals.py @@ -232,6 +232,38 @@ def calcDopplerShiftHz(self, distance_m, velocity_mps): return _calcDopplerShiftHz(self.CENTER_FREQUENCY_HZ, distance_m, velocity_mps) + def getSymbolIndex(self, svTime_s): + ''' + Computes symbol index. + + Parameters + ---------- + svTime_s : float + SV time in seconds + + Returns + ------- + long + Symbol index + ''' + return svTime_s * self.SYMBOL_RATE_HZ + + def getCodeChipIndex(self, svTime_s): + ''' + Computes code chip index. + + Parameters + ---------- + svTime_s : float + SV time in seconds + + Returns + ------- + long + Code chip index + ''' + return svTime_s * self.CODE_CHIP_RATE_HZ + class _GLONASS_L1(__GLONASS_L1L2Base): ''' diff --git a/peregrine/iqgen/generate.py b/peregrine/iqgen/generate.py index b7e14ad..052754a 100644 --- a/peregrine/iqgen/generate.py +++ b/peregrine/iqgen/generate.py @@ -20,6 +20,8 @@ from peregrine.iqgen.bits.filter_lowpass import LowPassFilter from peregrine.iqgen.bits.filter_bandpass import BandPassFilter +from peregrine.iqgen.bits.amplitude_base import NoiseParameters + from peregrine.iqgen.bits import signals import sys @@ -43,7 +45,7 @@ class Task(object): def __init__(self, outputConfig, signalSources, - noiseSigma, + noiseParams, tcxo, signalFilters, generateDebug): @@ -54,8 +56,8 @@ def __init__(self, Output profile signalSources : array-like List of satellites - noiseSigma : float - Noise sigma value + noiseParams : NoiseParameters + Noise parameters container tcxo : object TCXO control object signalFilters : array-like @@ -68,7 +70,7 @@ def __init__(self, self.signalSources = signalSources self.signalFilters = signalFilters self.generateDebug = generateDebug - self.noiseSigma = noiseSigma + self.noiseParams = noiseParams self.tcxo = tcxo self.signals = scipy.ndarray(shape=(4, outputConfig.SAMPLE_BATCH_SIZE), dtype=numpy.float) @@ -115,19 +117,12 @@ def createNoise(self, nSamples): numpy.ndarray(shape=(4, nSamples), dtype=numpy.float) Noise values ''' - noiseSigma = self.noiseSigma - if noiseSigma is not None: + noiseParams = self.noiseParams + noise = None + if noiseParams 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 + noiseSigma = noiseParams.getNoiseSigma() + noise = noiseSigma * scipy.randn(4, nSamples) if noiseSigma else None return noise def perform(self): @@ -155,6 +150,7 @@ def perform(self): if tcxoTimeDrift_s: userTimeAll_s += tcxoTimeDrift_s + noiseParams = self.noiseParams noise = self.noise sigs = self.signals sigs.fill(0.) @@ -174,6 +170,7 @@ def perform(self): t = signalSource.getBatchSignals(userTimeAll_s, sigs, outputConfig, + noiseParams, generateDebug) # Debugging output if generateDebug: @@ -197,7 +194,7 @@ class Worker(multiprocessing.Process): def __init__(self, outputConfig, signalSources, - noiseSigma, + noiseParams, tcxo, signalFilters, generateDebug): @@ -208,7 +205,7 @@ def __init__(self, self.totalExecTime_s = 0. self.outputConfig = outputConfig self.signalSources = signalSources - self.noiseSigma = noiseSigma + self.noiseParams = noiseParams self.tcxo = tcxo self.signalFilters = signalFilters self.generateDebug = generateDebug @@ -216,7 +213,7 @@ def __init__(self, def run(self): task = Task(self.outputConfig, self.signalSources, - noiseSigma=self.noiseSigma, + noiseParams=self.noiseParams, tcxo=self.tcxo, signalFilters=self.signalFilters, generateDebug=self.generateDebug) @@ -228,7 +225,7 @@ def run(self): # 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() @@ -248,6 +245,7 @@ def run(self): 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() @@ -280,7 +278,7 @@ def generateSamples(outputFile, time0S, nSamples, outputConfig, - SNR=None, + noiseSigma=None, tcxo=None, filterType="none", logFile=None, @@ -303,7 +301,7 @@ def generateSamples(outputFile, Total number of samples to generate. outputConfig : object Output parameters - SNR : float, optional + noiseSigma : float, optional When specified, adds random noise to the output. tcxo : object, optional When specified, controls TCXO drift @@ -316,9 +314,9 @@ def generateSamples(outputFile, # # 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 + logger.info("Generating samples, sample rate={} Hz, interval={} seconds".format( + outputConfig.SAMPLE_RATE_HZ, nSamples / outputConfig.SAMPLE_RATE_HZ)) + logger.debug("Jobs: %d" % threadCount) _t0 = time.clock() _count = 0l @@ -346,6 +344,7 @@ def generateSamples(outputFile, for band in bands: for sv in sv_list: bandsEnabled[band.INDEX] |= sv.isBandEnabled(band.INDEX, outputConfig) + sv = None filterObject = None ifHz = 0. @@ -366,39 +365,16 @@ def generateSamples(outputFile, 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() - if isinstance(sv, GPSSatellite): - # GPS: 1023 Kilobits/second - svMeanPower /= 1023e3 - elif isinstance(sv, GLOSatellite): - # GLONASS: 511 Kilobits/second - svMeanPower /= 511e3 - else: - pass - 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/511 MHz - freqTimesTau = outputConfig.SAMPLE_RATE_HZ - 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))) + if noiseSigma is not None: + noiseVariance = noiseSigma * noiseSigma + noiseParams = NoiseParameters(outputConfig.SAMPLE_RATE_HZ, noiseSigma) + logger.info("Selected noise sigma %f (variance %f)" % + (noiseSigma, noiseVariance)) else: - noiseVariance = None - noiseSigma = None + noiseVariance = 0. + noiseSigma = 0. + noiseParams = NoiseParameters(outputConfig.SAMPLE_RATE_HZ, 0.) logger.info("SNR is not provided, noise is not generated.") # @@ -410,63 +386,63 @@ def generateSamples(outputFile, _svTime0_s = 0 _dist0_m = _sv.doppler.computeDistanceM(_svTime0_s) _speed_mps = _sv.doppler.computeSpeedMps(_svTime0_s) - svMeanPower = _sv.getAmplitude().computeMeanPower() - if isinstance(sv, GPSSatellite): - # GPS: 1023 Kilobits/second - powerDivider = 1023e3 + # svMeanPower = _sv.getAmplitude().computeMeanPower() + if isinstance(_sv, GPSSatellite): band1Index = outputConfig.GPS.L1.INDEX band2Index = outputConfig.GPS.L2.INDEX - elif isinstance(sv, GLOSatellite): - # GLONASS: 511 Kilobits/second - powerDivider = 511e3 + band1IncreaseDb = 60. - lpfFA_db[band1Index] # GPS L1 C/A + # GPS L2C CM - only half of power is used: -3dB + band2IncreaseDb = 60. - 3. - lpfFA_db[band2Index] + signal1 = signals.GPS.L1CA + signal2 = signals.GPS.L2C + _msg1 = _sv.getL1CAMessage() + _msg2 = _sv.getL2CMessage() + _l2ct = _sv.getL2CLCodeType() + elif isinstance(_sv, GLOSatellite): band1Index = outputConfig.GLONASS.L1.INDEX band2Index = outputConfig.GLONASS.L2.INDEX + band1IncreaseDb = 60. - lpfFA_db[band1Index] # GLONASS L1 + band2IncreaseDb = 60. - lpfFA_db[band2Index] # GLONASS L2 + signal1 = signals.GLONASS.L1S[_sv.prn] + signal2 = signals.GLONASS.L2S[_sv.prn] + _msg1 = _sv.getL1Message() + _msg2 = _sv.getL2Message() + _l2ct = None else: pass # SNR for a satellite. Depends on sampling rate. if noiseVariance: - svSNR = svMeanPower / (4. * noiseVariance) * freqTimesTau / powerDivider + svSNR_db = _sv.getAmplitude().computeSNR(noiseParams) + svCNoL1 = svSNR_db + band1IncreaseDb - encoder.getAttenuationLevel() + svCNoL2 = svSNR_db + band2IncreaseDb - encoder.getAttenuationLevel() else: - svSNR = 1e6 - svSNR_db = 10. * numpy.log10(svSNR) - # CNo for L1 - svCNoL1 = svSNR_db + 10. * numpy.log10(powerDivider) - lpfFA_db[band1Index] - # CNo for L2, half power used (-3dB) - svCNoL2 = svSNR_db + 10. * \ - numpy.log10(powerDivider) - 3. - lpfFA_db[band2Index] - - _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) + svSNR_db = 60. + svCNoL1 = svCNoL2 = 120 - if isinstance(_sv, GPSSatellite): - _msg1 = _sv.getL1CAMessage() - _msg2 = _sv.getL2CMessage() - _l2ct = _sv.getL2CLCodeType() - elif isinstance(_sv, GLOSatellite): - _msg1 = _sv.getL1Message() - _msg2 = _sv.getL2Message() - _l2ct = "N/A" - else: - raise ValueError("Unknown SV type") + _d1 = signal1.calcDopplerShiftHz(_dist0_m, _speed_mps) + _d2 = signal2.calcDopplerShiftHz(_dist0_m, _speed_mps) + _f1 = signal1.CENTER_FREQUENCY_HZ + _f2 = signal2.CENTER_FREQUENCY_HZ + _bit = signal1.getSymbolIndex(_svTime0_s) + _c1 = signal1.getCodeChipIndex(_svTime0_s) + _c2 = signal2.getCodeChipIndex(_svTime0_s) print "{} = {{".format(_svNo) print " .amplitude: {}".format(_amp) print " .doppler: {}".format(_sv.doppler) print " .l1_message: {}".format(_msg1) print " .l2_message: {}".format(_msg2) - print " .l2_cl_type: {}".format(_l2ct) - print " .SNR (dBHz): {}".format(svSNR_db) - print " .L1 CNo: {}".format(svCNoL1) - print " .L2 CNo: {}".format(svCNoL2) + if _l2ct: + print " .l2_cl_type: {}".format(_l2ct) print " .epoc:" + print " .SNR (dB): {}".format(svSNR_db) + print " .L1 CNo: {}".format(svCNoL1) + print " .L2 CNo: {}".format(svCNoL2) print " .distance: {} m".format(_dist0_m) print " .speed: {} m/s".format(_speed_mps) + print " .l1_doppler: {} hz @ {}".format(_d1, _f1) + print " .l2_doppler: {} hz @ {}".format(_d2, _f2) 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 "}" @@ -490,7 +466,7 @@ def generateSamples(outputFile, if threadCount > 0: workerPool = [Worker(outputConfig, sv_list, - noiseSigma, + noiseParams, tcxo, lpf, debugFlag) for _ in range(threadCount)] @@ -502,7 +478,7 @@ def generateSamples(outputFile, workerPool = None task = Task(outputConfig, sv_list, - noiseSigma=noiseSigma, + noiseParams=noiseParams, tcxo=tcxo, signalFilters=lpf, generateDebug=debugFlag) diff --git a/peregrine/iqgen/iqgen_main.py b/peregrine/iqgen/iqgen_main.py index ad32e98..177476d 100755 --- a/peregrine/iqgen/iqgen_main.py +++ b/peregrine/iqgen/iqgen_main.py @@ -38,6 +38,7 @@ # Amplitude objects from peregrine.iqgen.bits.amplitude_poly import AmplitudePoly from peregrine.iqgen.bits.amplitude_sine import AmplitudeSine +from peregrine.iqgen.bits.amplitude_base import AmplitudeBase # TCXO objects from peregrine.iqgen.bits.tcxo_poly import TCXOPoly @@ -88,6 +89,11 @@ logger = logging.getLogger(__name__) +AMP_MAP = {'amplitude': AmplitudeBase.UNITS_AMPLITUDE, + 'power': AmplitudeBase.UNITS_POWER, + 'snr': AmplitudeBase.UNITS_SNR, + 'snr-db': AmplitudeBase.UNITS_SNR_DB} + def computeTimeDelay(doppler, symbol_index, chip_index, signal, code): ''' @@ -169,6 +175,7 @@ def __call__(self, parser, namespace, values, option_string=None): # Amplitude parameters namespace.amplitude_type = "poly" + namespace.amplitude_unis = "snr-db" namespace.amplitude_a0 = None namespace.amplitude_a1 = None namespace.amplitude_a2 = None @@ -280,6 +287,8 @@ 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): + amplitude_units = AMP_MAP[namespace.amplitude_units] + if namespace.amplitude_type == "poly": coeffs = [] hasHighOrder = False @@ -292,7 +301,7 @@ def doUpdate(self, sv, parser, namespace, values, option_string): hasHighOrder = True elif hasHighOrder: coeffs.append(0.) - amplitude = AmplitudePoly(tuple(coeffs)) + amplitude = AmplitudePoly(amplitude_units, tuple(coeffs)) elif namespace.amplitude_type == "sine": initial = 1. ampl = 0.5 @@ -304,7 +313,7 @@ def doUpdate(self, sv, parser, namespace, values, option_string): if namespace.amplitude_period is not None: period_s = namespace.amplitude_period - amplitude = AmplitudeSine(initial, ampl, period_s) + amplitude = AmplitudeSine(amplitude_units, initial, ampl, period_s) else: raise ValueError("Unsupported amplitude type") sv.setAmplitude(amplitude) @@ -422,7 +431,7 @@ def __call__(self, parser, namespace, values, option_string=None): 'chip_delay': namespace.chip_delay, 'symbol_delay': namespace.symbol_delay, 'generate': namespace.generate, - 'snr': namespace.snr, + 'noise_sigma': namespace.noise_sigma, 'filter_type': namespace.filter_type, 'tcxo': tcxoFO.toMapForm(namespace.tcxo) } @@ -442,7 +451,7 @@ def __call__(self, parser, namespace, values, option_string=None): namespace.chip_delay = loaded['chip_delay'] namespace.symbol_delay = loaded['symbol_delay'] namespace.generate = loaded['generate'] - namespace.snr = loaded['snr'] + namespace.noise_sigma = loaded['noise_sigma'] namespace.filter_type = loaded['filter_type'] namespace.tcxo = tcxoFO.fromMapForm(loaded['tcxo']) namespace.gps_sv = [ @@ -510,6 +519,12 @@ def __call__(self, parser, namespace, values, option_string=None): choices=["poly", "sine"], help="Configure amplitude type: polynomial or sine.", action=UpdateAmplitudeType) + amplitudeGrp.add_argument('--amplitude-units', + default="snr-db", + choices=["snr-db", "snr", "amplitude", "power"], + help="Configure amplitude units: SNR in dB; SNR;" + " amplitude; power.", + action=UpdateAmplitudeType) amplitudeGrp.add_argument('--amplitude-a0', type=float, help="Amplitude coefficient (a0 for polynomial;" @@ -557,9 +572,10 @@ def __call__(self, parser, namespace, values, option_string=None): default='none', choices=['none', 'lowpass', 'bandpass'], help="Enable filter") - parser.add_argument('--snr', + parser.add_argument('--noise-sigma', type=float, - help="SNR for noise generation") + default=1., + help="Noise sigma for noise generation") tcxoGrp = parser.add_argument_group("TCXO Control", "TCXO control parameters") @@ -666,7 +682,7 @@ def main(): print " GPS L2 IF: ", outputConfig.GPS.L2.INTERMEDIATE_FREQUENCY_HZ print "Other parameters:" print " TCXO: ", args.tcxo - print " SNR: ", args.snr + print " noise sigma: ", args.noise_sigma print " tSatellites: ", args.gps_sv # Check which signals are enabled on each of satellite to select proper @@ -777,7 +793,7 @@ def main(): n_samples, outputConfig, tcxo=args.tcxo, - SNR=args.snr, + noiseSigma=args.noise_sigma, filterType=args.filter_type, logFile=args.debug, threadCount=args.jobs, From 0f44dde87ab404741bb450f4dc91e98bb22e178a Mon Sep 17 00:00:00 2001 From: Valeri Atamaniouk Date: Wed, 13 Apr 2016 14:59:40 +0300 Subject: [PATCH 64/67] iqgen: added group delay simulation support Added group delay simulation support. New parameter: '--group-delays ' --- peregrine/iqgen/bits/satellite_base.py | 22 +- peregrine/iqgen/bits/satellite_glo.py | 38 ++- peregrine/iqgen/bits/satellite_gps.py | 40 ++- peregrine/iqgen/generate.py | 421 ++++++++++++++++--------- peregrine/iqgen/if_iface.py | 34 +- peregrine/iqgen/iqgen_main.py | 31 +- 6 files changed, 380 insertions(+), 206 deletions(-) diff --git a/peregrine/iqgen/bits/satellite_base.py b/peregrine/iqgen/bits/satellite_base.py index edeffcf..1ebb7ba 100644 --- a/peregrine/iqgen/bits/satellite_base.py +++ b/peregrine/iqgen/bits/satellite_base.py @@ -62,7 +62,7 @@ def setDoppler(self, doppler): ''' self.doppler = doppler - def getSvName(self): + def getName(self): ''' Returns satellite name. @@ -101,7 +101,13 @@ def __str__(self): def __repr__(self): return self.getSvName() - def getBatchSignals(self, userTimeAll_s, samples, outputConfig, noiseParams): + def getBatchSignals(self, + userTimeAll_s, + samples, + outputConfig, + noiseParams, + band, + debug): ''' Generates signal samples. @@ -113,6 +119,12 @@ def getBatchSignals(self, userTimeAll_s, samples, outputConfig, noiseParams): Array to which samples are added. outputConfig : object Output configuration object. + noiseParams : NoiseParameters + Noise parameters object. + band : Band + Band description object. + debug : bool + Debug flag Returns ------- @@ -121,14 +133,14 @@ def getBatchSignals(self, userTimeAll_s, samples, outputConfig, noiseParams): ''' raise NotImplementedError() - def isBandEnabled(self, bandIndex, outputConfig): + def isBandEnabled(self, band, outputConfig): ''' Checks if particular band is supported and enabled. Parameters ---------- - bandIndex : int - Signal band index + band : Band + Band description object. outputConfig : object Output configuration diff --git a/peregrine/iqgen/bits/satellite_glo.py b/peregrine/iqgen/bits/satellite_glo.py index bb586f7..908e93a 100644 --- a/peregrine/iqgen/bits/satellite_glo.py +++ b/peregrine/iqgen/bits/satellite_glo.py @@ -137,7 +137,13 @@ def getL2Message(self): ''' return self.l1Message - def getBatchSignals(self, userTimeAll_s, samples, outputConfig, noiseParams, debug): + def getBatchSignals(self, + userTimeAll_s, + samples, + outputConfig, + noiseParams, + band, + debug): ''' Generates signal samples. @@ -149,6 +155,10 @@ def getBatchSignals(self, userTimeAll_s, samples, outputConfig, noiseParams, deb Array to which samples are added. outputConfig : object Output configuration object. + noiseParams : NoiseParameters + Noise parameters object + band : Band + Band description object. debug : bool Debug flag @@ -158,10 +168,8 @@ def getBatchSignals(self, userTimeAll_s, samples, outputConfig, noiseParams, deb Debug information ''' result = [] - if (self.l1Enabled): - band = outputConfig.GLONASS.L1 + if (self.l1Enabled and band == outputConfig.GLONASS.L1): intermediateFrequency_hz = band.INTERMEDIATE_FREQUENCIES_HZ[self.prn] - frequencyIndex = band.INDEX values = self.doppler.computeBatch(userTimeAll_s, self.amplitude, noiseParams, @@ -171,15 +179,13 @@ def getBatchSignals(self, userTimeAll_s, samples, outputConfig, noiseParams, deb self.caCode, outputConfig, debug) - numpy.add(samples[frequencyIndex], + numpy.add(samples[band.INDEX], values[0], - out=samples[frequencyIndex]) + out=samples[band.INDEX]) debugData = {'type': "GLOL1", 'doppler': values[1]} result.append(debugData) - if (self.l2Enabled): - band = outputConfig.GLONASS.L2 + if (self.l2Enabled and band == outputConfig.GLONASS.L2): intermediateFrequency_hz = band.INTERMEDIATE_FREQUENCIES_HZ[self.prn] - frequencyIndex = band.INDEX values = self.doppler.computeBatch(userTimeAll_s, self.amplitude, noiseParams, @@ -189,21 +195,21 @@ def getBatchSignals(self, userTimeAll_s, samples, outputConfig, noiseParams, deb self.caCode, outputConfig, debug) - numpy.add(samples[frequencyIndex], + numpy.add(samples[band.INDEX], values[0], - out=samples[frequencyIndex]) + out=samples[band.INDEX]) debugData = {'type': "GLOL2", 'doppler': values[1]} result.append(debugData) return result - def isBandEnabled(self, bandIndex, outputConfig): + def isBandEnabled(self, band, outputConfig): ''' Checks if particular band is supported and enabled. Parameters ---------- - bandIndex : int - Signal band index + band : Band + Band description object. outputConfig : object Output configuration @@ -212,9 +218,9 @@ def isBandEnabled(self, bandIndex, outputConfig): True, if the band is supported and enabled; False otherwise. ''' result = None - if bandIndex == outputConfig.GLONASS.L1.INDEX: + if band == outputConfig.GLONASS.L1: result = self.isL1Enabled() - elif bandIndex == outputConfig.GLONASS.L2.INDEX: + elif band == outputConfig.GLONASS.L2: result = self.isL2Enabled() else: result = False diff --git a/peregrine/iqgen/bits/satellite_gps.py b/peregrine/iqgen/bits/satellite_gps.py index c0f9bb1..c821d5d 100644 --- a/peregrine/iqgen/bits/satellite_gps.py +++ b/peregrine/iqgen/bits/satellite_gps.py @@ -147,7 +147,13 @@ def getL2CMessage(self): ''' return self.l2cMessage - def getBatchSignals(self, userTimeAll_s, samples, outputConfig, noiseParams, debug): + def getBatchSignals(self, + userTimeAll_s, + samples, + outputConfig, + noiseParams, + band, + debug): ''' Generates signal samples. @@ -159,6 +165,10 @@ def getBatchSignals(self, userTimeAll_s, samples, outputConfig, noiseParams, deb Array to which samples are added. outputConfig : object Output configuration object. + noiseParams : NoiseParameters + Noise parameters object + band : Band + Band description object. debug : bool Debug flag @@ -168,9 +178,8 @@ def getBatchSignals(self, userTimeAll_s, samples, outputConfig, noiseParams, deb Debug information ''' result = [] - if (self.l1caEnabled): - intermediateFrequency_hz = outputConfig.GPS.L1.INTERMEDIATE_FREQUENCY_HZ - frequencyIndex = outputConfig.GPS.L1.INDEX + if (self.l1caEnabled and band == outputConfig.GPS.L1): + intermediateFrequency_hz = band.INTERMEDIATE_FREQUENCY_HZ values = self.doppler.computeBatch(userTimeAll_s, self.amplitude, noiseParams, @@ -180,14 +189,13 @@ def getBatchSignals(self, userTimeAll_s, samples, outputConfig, noiseParams, deb self.l1caCode, outputConfig, debug) - numpy.add(samples[frequencyIndex], + numpy.add(samples[band.INDEX], values[0], - out=samples[frequencyIndex]) + out=samples[band.INDEX]) 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 + if (self.l2cEnabled and band == outputConfig.GPS.L2): + intermediateFrequency_hz = band.INTERMEDIATE_FREQUENCY_HZ values = self.doppler.computeBatch(userTimeAll_s, self.amplitude, noiseParams, @@ -197,21 +205,21 @@ def getBatchSignals(self, userTimeAll_s, samples, outputConfig, noiseParams, deb self.l2cCode, outputConfig, debug) - numpy.add(samples[frequencyIndex], + numpy.add(samples[band.INDEX], values[0], - out=samples[frequencyIndex]) + out=samples[band.INDEX]) debugData = {'type': "GPSL2", 'doppler': values[1]} result.append(debugData) return result - def isBandEnabled(self, bandIndex, outputConfig): + def isBandEnabled(self, band, outputConfig): ''' Checks if particular band is supported and enabled. Parameters ---------- - bandIndex : int - Signal band index + band : Band + Band description object. outputConfig : object Output configuration @@ -220,9 +228,9 @@ def isBandEnabled(self, bandIndex, outputConfig): True, if the band is supported and enabled; False otherwise. ''' result = None - if bandIndex == outputConfig.GPS.L1.INDEX: + if band == outputConfig.GPS.L1: result = self.isL1CAEnabled() - elif bandIndex == outputConfig.GPS.L2.INDEX: + elif band == outputConfig.GPS.L2: result = self.isL2CEnabled() else: result = False diff --git a/peregrine/iqgen/generate.py b/peregrine/iqgen/generate.py index 052754a..95adcb8 100644 --- a/peregrine/iqgen/generate.py +++ b/peregrine/iqgen/generate.py @@ -48,6 +48,8 @@ def __init__(self, noiseParams, tcxo, signalFilters, + groupDelays, + bands, generateDebug): ''' Parameters @@ -62,6 +64,10 @@ def __init__(self, TCXO control object signalFilters : array-like Output signal filter objects + groupDelays : bool + Flag if group delays are enabled + bands : list + List of bands to generate generateDebug : bool Flag if additional debug output is required ''' @@ -72,10 +78,13 @@ def __init__(self, self.generateDebug = generateDebug self.noiseParams = noiseParams self.tcxo = tcxo - self.signals = scipy.ndarray(shape=(4, outputConfig.SAMPLE_BATCH_SIZE), + self.signals = scipy.ndarray(shape=(outputConfig.N_GROUPS, + outputConfig.SAMPLE_BATCH_SIZE), dtype=numpy.float) self.noise = self.createNoise(outputConfig.SAMPLE_BATCH_SIZE) self.nSamples = outputConfig.SAMPLE_BATCH_SIZE + self.groupDelays = groupDelays + self.bands = bands def update(self, userTime0_s, nSamples, firstSampleIndex): ''' @@ -97,7 +106,8 @@ def update(self, userTime0_s, nSamples, firstSampleIndex): self.firstSampleIndex = firstSampleIndex if (self.nSamples != nSamples): - newSignals = numpy.ndarray((4, nSamples), dtype=float) + newSignals = numpy.ndarray((self.outputConfig.N_GROUPS, + nSamples), dtype=float) newNoise = self.createNoise(nSamples) self.nSamples = nSamples self.signals = newSignals @@ -114,7 +124,7 @@ def createNoise(self, nSamples): Returns ------- - numpy.ndarray(shape=(4, nSamples), dtype=numpy.float) + numpy.ndarray(shape=(outputConfig.N_GROUPS, nSamples), dtype=numpy.float) Noise values ''' noiseParams = self.noiseParams @@ -122,19 +132,44 @@ def createNoise(self, nSamples): if noiseParams is not None: # Initialize signal array with noise noiseSigma = noiseParams.getNoiseSigma() - noise = noiseSigma * scipy.randn(4, nSamples) if noiseSigma else None + noise = noiseSigma * scipy.randn(self.outputConfig.N_GROUPS, + nSamples) if noiseSigma else None return noise - def perform(self): - outputConfig = self.outputConfig - signalSources = self.signalSources - signalFilters = self.signalFilters + def computeTcxoVector(self): + ''' + Computes TCXO time drift vector if enabled. + + Returns + ------- + numpy.array or None + Computed TCXO time drift as a vector or None if TCXO is not enabled + ''' tcxo = self.tcxo - firstSampleIndex = self.firstSampleIndex - finalSampleIndex = firstSampleIndex + self.nSamples + if tcxo: + firstSampleIndex = self.firstSampleIndex + finalSampleIndex = firstSampleIndex + self.nSamples + outputConfig = self.outputConfig + tcxoTimeDrift_s = tcxo.computeTcxoTime(firstSampleIndex, + finalSampleIndex, + outputConfig) + else: + tcxoTimeDrift_s = None + return tcxoTimeDrift_s - generateDebug = self.generateDebug + def computeTimeVector(self): + ''' + Computes time vector for the batch. + + Returns + ------- + numpy.array + Computed time vector for computing sampling time + ''' + outputConfig = self.outputConfig + # Group delay shifts all time stamps backwards, this shift is performed + # before TCXO drift is applied, as group delays are not controlled by TCXO userTime0_s = self.userTime0_s userTimeX_s = userTime0_s + float(self.nSamples) / \ float(outputConfig.SAMPLE_RATE_HZ) @@ -142,22 +177,64 @@ def perform(self): userTimeX_s, self.nSamples, endpoint=False) + return userTimeAll_s - if tcxo: - tcxoTimeDrift_s = tcxo.computeTcxoTime(firstSampleIndex, - finalSampleIndex, - outputConfig) - if tcxoTimeDrift_s: - userTimeAll_s += tcxoTimeDrift_s + def computeGroupTimeVectors(self, userTimeAll_s, outputConfig): + ''' + Computes group time vector from a single source and output configuration. + Parameters + ---------- + userTimeAll_s : numpy.array + Time vector + outputConfig : object + Output configuration with group delay parameters + + Returns + ------- + list[numpy.array] * outputConfig.N_GROUPS + If the group delays are enabled, each element offsets initial time vector + by an appropriate group delay, otherwise all entries point to original + time vector without modifications. + ''' + if self.groupDelays: + # In case of group delays the time vector shall be adjusted for each + # signal group. This makes impossible parallel processing of multiple + # signals with the same time vector. + bandTimeAll_s = [userTimeAll_s + outputConfig.GROUP_DELAYS[x] + for x in range(outputConfig.N_GROUPS)] + else: + bandTimeAll_s = [userTimeAll_s] * outputConfig.N_GROUPS + + return bandTimeAll_s + + def perform(self): + outputConfig = self.outputConfig + signalSources = self.signalSources + signalFilters = self.signalFilters noiseParams = self.noiseParams - noise = self.noise - sigs = self.signals + generateDebug = self.generateDebug + noise = self.noise # Noise matrix if present + sigs = self.signals # Signal matrix + + # Compute time stamps in linear time space + userTimeAll_s = self.computeTimeVector() + + # Compute TCXO time drift and apply if appropriate + tcxoTimeDrift_s = self.computeTcxoVector() + if tcxoTimeDrift_s: + userTimeAll_s += tcxoTimeDrift_s + + # Compute band time vectors with group delays + bandTimeAll_s = self.computeGroupTimeVectors(userTimeAll_s, outputConfig) + + # Prepare signal matrix sigs.fill(0.) if noise is not None: # Initialize signal array with noise sigs += noise + # Debug data if generateDebug: signalData = [] debugData = {'time': userTimeAll_s, 'signalData': signalData} @@ -166,21 +243,25 @@ def perform(self): # 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, - noiseParams, - generateDebug) - # Debugging output - if generateDebug: - svDebug = {'name': signalSource.getSvName(), 'data': t} - signalData.append(svDebug) - t = None + for band in self.bands: + if signalSource.isBandEnabled(band, outputConfig): + # Add signal from source (satellite) to signal accumulator + t = signalSource.getBatchSignals(bandTimeAll_s[band.INDEX], + sigs, + outputConfig, + noiseParams, + band, + 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)): + for i in range(outputConfig.N_GROUPS): filterObject = signalFilters[i] if filterObject is not None: sigs[i][:] = filterObject.filter(sigs[i]) @@ -190,6 +271,10 @@ def perform(self): class Worker(multiprocessing.Process): + ''' + Remote process worker. The object encapsulates Task logic for running in a + separate address space. + ''' def __init__(self, outputConfig, @@ -197,6 +282,8 @@ def __init__(self, noiseParams, tcxo, signalFilters, + groupDelays, + bands, generateDebug): super(Worker, self).__init__() self.queueIn = multiprocessing.Queue() @@ -208,6 +295,8 @@ def __init__(self, self.noiseParams = noiseParams self.tcxo = tcxo self.signalFilters = signalFilters + self.groupDelays = groupDelays + self.bands = bands self.generateDebug = generateDebug def run(self): @@ -216,6 +305,8 @@ def run(self): noiseParams=self.noiseParams, tcxo=self.tcxo, signalFilters=self.signalFilters, + groupDelays=self.groupDelays, + bands=self.bands, generateDebug=self.generateDebug) while True: @@ -253,23 +344,95 @@ def run(self): sys.exit(0) -def computeTimeIntervalS(outputConfig): +def printSvInfo(sv_list, outputConfig, lpfFA_db, noiseParams, encoder): ''' - Helper for computing generation interval duration in seconds. + Print some relevant information to console. Parameters ---------- + sv_list : list + List of signal sources outputConfig : object - Output configuration. - - Returns - ------- - float - Generation interval duration in seconds + Output configuration object + lpfFA_db : list + Filter attenuation levels for each band + encoder : Encoder + Encoder object ''' - deltaTime_s = float(outputConfig.SAMPLE_BATCH_SIZE) / \ - outputConfig.SAMPLE_RATE_HZ - return deltaTime_s + for _sv in sv_list: + _svNo = _sv.getName() + _amp = _sv.amplitude + _svTime0_s = 0 + _dist0_m = _sv.doppler.computeDistanceM(_svTime0_s) + _speed_mps = _sv.doppler.computeSpeedMps(_svTime0_s) + # svMeanPower = _sv.getAmplitude().computeMeanPower() + if isinstance(_sv, GPSSatellite): + band1 = outputConfig.GPS.L1 + band2 = outputConfig.GPS.L2 + band1IncreaseDb = 60. - lpfFA_db[band1.INDEX] # GPS L1 C/A + # GPS L2C CM - only half of power is used: -3dB + band2IncreaseDb = 60. - 3. - lpfFA_db[band2.INDEX] + signal1 = signals.GPS.L1CA + signal2 = signals.GPS.L2C + _msg1 = _sv.getL1CAMessage() + _msg2 = _sv.getL2CMessage() + _l2ct = _sv.getL2CLCodeType() + elif isinstance(_sv, GLOSatellite): + band1 = outputConfig.GLONASS.L1 + band2 = outputConfig.GLONASS.L2 + band1IncreaseDb = 60. - lpfFA_db[band1.INDEX] # GLONASS L1 + band2IncreaseDb = 60. - lpfFA_db[band2.INDEX] # GLONASS L2 + signal1 = signals.GLONASS.L1S[_sv.prn] + signal2 = signals.GLONASS.L2S[_sv.prn] + _msg1 = _sv.getL1Message() + _msg2 = _sv.getL2Message() + _l2ct = None + else: + pass + # SNR for a satellite. Depends on sampling rate. + if noiseParams.getNoiseSigma(): + svSNR_db = _sv.getAmplitude().computeSNR(noiseParams) + svCNoL1 = svSNR_db + band1IncreaseDb - encoder.getAttenuationLevel() + svCNoL2 = svSNR_db + band2IncreaseDb - encoder.getAttenuationLevel() + else: + svSNR_db = 60. + svCNoL1 = svCNoL2 = 120 + + _d1 = signal1.calcDopplerShiftHz(_dist0_m, _speed_mps) + _d2 = signal2.calcDopplerShiftHz(_dist0_m, _speed_mps) + _f1 = signal1.CENTER_FREQUENCY_HZ + _f2 = signal2.CENTER_FREQUENCY_HZ + _bit = signal1.getSymbolIndex(_svTime0_s) + _c1 = signal1.getCodeChipIndex(_svTime0_s) + _c2 = signal2.getCodeChipIndex(_svTime0_s) + + print "{} = {{".format(_svNo) + print " .amplitude: {}".format(_amp) + print " .doppler: {}".format(_sv.doppler) + if _sv.isBandEnabled(band1, outputConfig): + print " .l1_message: {}".format(_msg1) + if _sv.isBandEnabled(band2, outputConfig): + print " .l2_message: {}".format(_msg2) + if _l2ct: + print " .l2_cl_type: {}".format(_l2ct) + print " .epoc:" + print " .SNR (dB): {}".format(svSNR_db) + if _sv.isBandEnabled(band1, outputConfig): + print " .L1 CNo: {}".format(svCNoL1) + if _sv.isBandEnabled(band2, outputConfig): + print " .L2 CNo: {}".format(svCNoL2) + print " .distance: {} m".format(_dist0_m) + print " .speed: {} m/s".format(_speed_mps) + if _sv.isBandEnabled(band1, outputConfig): + print " .l1_doppler: {} hz @ {}".format(_d1, _f1) + if _sv.isBandEnabled(band2, outputConfig): + print " .l2_doppler: {} hz @ {}".format(_d2, _f2) + print " .symbol: {}".format(_bit) + if _sv.isBandEnabled(band1, outputConfig): + print " .l1_chip: {}".format(_c1) + if _sv.isBandEnabled(band2, outputConfig): + print " .l2_chip: {}".format(_c2) + print "}" def generateSamples(outputFile, @@ -281,6 +444,7 @@ def generateSamples(outputFile, noiseSigma=None, tcxo=None, filterType="none", + groupDelays=None, logFile=None, threadCount=0, pbar=None): @@ -307,17 +471,16 @@ def generateSamples(outputFile, 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. + groupDelays : bool + Flag if group delays are enabled. + logFile : object + Debug information destination file. + threadCount : int + Number of parallel threads for multi-process computation. + pbar : object + Progress bar object ''' - # - # Print out parameters - # - logger.info("Generating samples, sample rate={} Hz, interval={} seconds".format( - outputConfig.SAMPLE_RATE_HZ, nSamples / outputConfig.SAMPLE_RATE_HZ)) - logger.debug("Jobs: %d" % threadCount) - _t0 = time.clock() _count = 0l @@ -326,24 +489,16 @@ def generateSamples(outputFile, outputConfig.GPS.L2, outputConfig.GLONASS.L1, outputConfig.GLONASS.L2] # Supported bands - lpf = [None] * len(bands) - lpfFA_db = [0.] * len(bands) # Filter attenuation levels - 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)) + lpf = [None] * outputConfig.N_GROUPS + lpfFA_db = [0.] * outputConfig.N_GROUPS # Filter attenuation levels + bandsEnabled = [False] * outputConfig.N_GROUPS + + bandPass = filterType == 'bandpass' + lowPass = filterType == 'lowpass' for band in bands: for sv in sv_list: - bandsEnabled[band.INDEX] |= sv.isBandEnabled(band.INDEX, outputConfig) + bandsEnabled[band.INDEX] |= sv.isBandEnabled(band, outputConfig) sv = None filterObject = None @@ -377,79 +532,17 @@ def generateSamples(outputFile, noiseParams = NoiseParameters(outputConfig.SAMPLE_RATE_HZ, 0.) logger.info("SNR is not provided, noise is not generated.") - # + # Print out parameters + logger.info("Generating samples, sample rate={} Hz, interval={} seconds".format( + outputConfig.SAMPLE_RATE_HZ, nSamples / outputConfig.SAMPLE_RATE_HZ)) + logger.debug("Jobs: %d" % threadCount) # Print out SV parameters - # - 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) - # svMeanPower = _sv.getAmplitude().computeMeanPower() - if isinstance(_sv, GPSSatellite): - band1Index = outputConfig.GPS.L1.INDEX - band2Index = outputConfig.GPS.L2.INDEX - band1IncreaseDb = 60. - lpfFA_db[band1Index] # GPS L1 C/A - # GPS L2C CM - only half of power is used: -3dB - band2IncreaseDb = 60. - 3. - lpfFA_db[band2Index] - signal1 = signals.GPS.L1CA - signal2 = signals.GPS.L2C - _msg1 = _sv.getL1CAMessage() - _msg2 = _sv.getL2CMessage() - _l2ct = _sv.getL2CLCodeType() - elif isinstance(_sv, GLOSatellite): - band1Index = outputConfig.GLONASS.L1.INDEX - band2Index = outputConfig.GLONASS.L2.INDEX - band1IncreaseDb = 60. - lpfFA_db[band1Index] # GLONASS L1 - band2IncreaseDb = 60. - lpfFA_db[band2Index] # GLONASS L2 - signal1 = signals.GLONASS.L1S[_sv.prn] - signal2 = signals.GLONASS.L2S[_sv.prn] - _msg1 = _sv.getL1Message() - _msg2 = _sv.getL2Message() - _l2ct = None - else: - pass - # SNR for a satellite. Depends on sampling rate. - if noiseVariance: - svSNR_db = _sv.getAmplitude().computeSNR(noiseParams) - svCNoL1 = svSNR_db + band1IncreaseDb - encoder.getAttenuationLevel() - svCNoL2 = svSNR_db + band2IncreaseDb - encoder.getAttenuationLevel() - else: - svSNR_db = 60. - svCNoL1 = svCNoL2 = 120 - - _d1 = signal1.calcDopplerShiftHz(_dist0_m, _speed_mps) - _d2 = signal2.calcDopplerShiftHz(_dist0_m, _speed_mps) - _f1 = signal1.CENTER_FREQUENCY_HZ - _f2 = signal2.CENTER_FREQUENCY_HZ - _bit = signal1.getSymbolIndex(_svTime0_s) - _c1 = signal1.getCodeChipIndex(_svTime0_s) - _c2 = signal2.getCodeChipIndex(_svTime0_s) - - print "{} = {{".format(_svNo) - print " .amplitude: {}".format(_amp) - print " .doppler: {}".format(_sv.doppler) - print " .l1_message: {}".format(_msg1) - print " .l2_message: {}".format(_msg2) - if _l2ct: - print " .l2_cl_type: {}".format(_l2ct) - print " .epoc:" - print " .SNR (dB): {}".format(svSNR_db) - print " .L1 CNo: {}".format(svCNoL1) - print " .L2 CNo: {}".format(svCNoL2) - print " .distance: {} m".format(_dist0_m) - print " .speed: {} m/s".format(_speed_mps) - print " .l1_doppler: {} hz @ {}".format(_d1, _f1) - print " .l2_doppler: {} hz @ {}".format(_d2, _f2) - print " .symbol: {}".format(_bit) - print " .l1_chip: {}".format(_c1) - print " .l2_chip: {}".format(_c2) - print "}" + printSvInfo(sv_list, outputConfig, lpfFA_db, noiseParams, encoder) userTime_s = float(time0S) - deltaUserTime_s = computeTimeIntervalS(outputConfig) + deltaUserTime_s = (float(outputConfig.SAMPLE_BATCH_SIZE) / + float(outputConfig.SAMPLE_RATE_HZ)) debugFlag = logFile is not None if debugFlag: @@ -464,31 +557,38 @@ def generateSamples(outputFile, logFile.write("\n") if threadCount > 0: + # Parallel execution: create worker pool workerPool = [Worker(outputConfig, sv_list, noiseParams, tcxo, lpf, + groupDelays, + bands, debugFlag) for _ in range(threadCount)] for worker in workerPool: worker.start() + # Each worker in the pool permits 2 tasks in the queue. maxTaskListSize = threadCount * 2 else: + # Synchronous execution: single worker workerPool = None task = Task(outputConfig, sv_list, noiseParams=noiseParams, tcxo=tcxo, signalFilters=lpf, + groupDelays=groupDelays, + bands=bands, generateDebug=debugFlag) maxTaskListSize = 1 - workerPutIndex = 0 - workerGetIndex = 0 - activeTasks = 0 + workerPutIndex = 0 # Worker index for adding task parameters with RR policy + workerGetIndex = 0 # Worker index for getting task results with RR policy + activeTasks = 0 # Number of active generation tasks - totalSampleCounter = 0 + totalSampleCounter = 0l taskQueuedCounter = 0 taskReceivedCounter = 0 @@ -498,23 +598,29 @@ def generateSamples(outputFile, 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: + if totalSampleCounter + outputConfig.SAMPLE_BATCH_SIZE > 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 + userTimeX_s = userTime0_s + (float(sampleCount) / + float(outputConfig.SAMPLE_RATE_HZ)) + else: + # Normal internal: full batch size + userTimeX_s = userTime_s + deltaUserTime_s + sampleCount = outputConfig.SAMPLE_BATCH_SIZE + # Parameters: time interval start, number of samples, sample index + # counter for debug output params = (userTime0_s, sampleCount, totalSampleCounter) - # print ">>> ", userTime0_s, sampleCount, totalSampleCounter, - # workerPutIndex if workerPool is not None: + # Parallel execution: add the next task parameters into the worker's + # pool queue. Worker pool uses RR policy. workerPool[workerPutIndex].queueIn.put(params) workerPutIndex = (workerPutIndex + 1) % threadCount else: + # Synchronous execution: update task parameters for the next interval task.update(userTime0_s, sampleCount, totalSampleCounter) activeTasks += 1 @@ -532,16 +638,15 @@ def generateSamples(outputFile, try: if workerPool is not None: - # Wait for the first task + # Parallel execution: wait for the next task result 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: + # Synchronous execution: execute task and get result result = task.perform() except: exType, exValue, exTraceback = sys.exc_info() @@ -554,9 +659,9 @@ def generateSamples(outputFile, print "Error in processor; aborting." break + # Unpack result values. (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 @@ -567,10 +672,8 @@ def generateSamples(outputFile, 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 @@ -585,22 +688,24 @@ def generateSamples(outputFile, _count += len(encodedSamples) encodedSamples.tofile(outputFile) encodedSamples = None - encodeDuration_s = time.time() - encodeStartTime_s - totalEncodeTime_s += encodeDuration_s + + totalEncodeTime_s += time.time() - encodeStartTime_s if pbar: pbar.update(_firstSampleIndex + _sampleCount) - logger.debug("MAIN: Encode duration: %f" % totalEncodeTime_s) - logger.debug("MAIN: wait duration: %f" % totalWaitTime_s) + # Generation completed. + # Flush any pending data in encoder encodedSamples = encoder.flush() if len(encodedSamples) > 0: encodedSamples.tofile(outputFile) + # Close debug log file if debugFlag: logFile.close() + # Terminate all worker processes if workerPool is not None: for worker in workerPool: worker.queueIn.put(None) @@ -616,3 +721,7 @@ def generateSamples(outputFile, worker.queueOut.close() worker.terminate() worker.join() + + # Print some statistical debug information + logger.debug("MAIN: Encode duration: %f" % totalEncodeTime_s) + logger.debug("MAIN: wait duration: %f" % totalWaitTime_s) diff --git a/peregrine/iqgen/if_iface.py b/peregrine/iqgen/if_iface.py index 9e8398e..7aad225 100644 --- a/peregrine/iqgen/if_iface.py +++ b/peregrine/iqgen/if_iface.py @@ -29,18 +29,24 @@ class LowRateConfig(object): Sample rate in hertz for data generation. SAMPLE_BATCH_SIZE : int Size of the sample batch in samples. + N_GROUPS : int + Number of groups in the configuration + GROUP_DELAYS: tuple(float * 4) + Group delays for the configuration GPS : object GPS band information Galileo : object Galileo band information Beidou : object Beidou band information - Glonass : object + GLONASS : object Glonass band information ''' NAME = "Low rate configuration for fast tests" SAMPLE_RATE_HZ = 24.84375e5 SAMPLE_BATCH_SIZE = 100000 + N_GROUPS = 4 + GROUP_DELAYS = (0., 0., 0., 0.) class GPS(object): @@ -107,18 +113,24 @@ class NormalRateConfig(object): Sample rate in hertz for data generation. SAMPLE_BATCH_SIZE : int Size of the sample batch in samples. + N_GROUPS : int + Number of groups in the configuration + GROUP_DELAYS: tuple(float * 4) + Group delays for the configuration GPS : object GPS band information Galileo : object Galileo band information Beidou : object Beidou band information - Glonass : object + GLONASS : object Glonass band information ''' NAME = "Normal rate configuration equivalent to decimated data output" SAMPLE_RATE_HZ = 24.84375e6 SAMPLE_BATCH_SIZE = 100000 + N_GROUPS = LowRateConfig.N_GROUPS + GROUP_DELAYS = LowRateConfig.GROUP_DELAYS class GPS(object): ''' @@ -187,12 +199,24 @@ class HighRateConfig(object): Sample rate in hertz for data generation. SAMPLE_BATCH_SIZE : int Size of the sample batch in samples. + N_GROUPS : int + Number of groups in the configuration + GROUP_DELAYS: tuple(float * 4) + Group delays for the configuration GPS : object GPS band information + Galileo : object + Galileo band information + Beidou : object + Beidou band information + GLONASS : object + Glonass band information ''' NAME = "High rate configuration equivalent to full rate data output" SAMPLE_RATE_HZ = 99.375e6 SAMPLE_BATCH_SIZE = 100000 + N_GROUPS = NormalRateConfig.N_GROUPS + GROUP_DELAYS = NormalRateConfig.GROUP_DELAYS GPS = NormalRateConfig.GPS GLONASS = NormalRateConfig.GLONASS @@ -212,12 +236,18 @@ class CustomRateConfig(object): Sample rate in hertz for data generation. SAMPLE_BATCH_SIZE : int Size of the sample batch in samples. + N_GROUPS : int + Number of groups in the configuration + GROUP_DELAYS: tuple(float * 4) + Group delays for the configuration GPS : object GPS band information ''' NAME = "Custom configuration for fast tests" SAMPLE_RATE_HZ = freq_profile_peregrine['sampling_freq'] SAMPLE_BATCH_SIZE = 100000 + N_GROUPS = NormalRateConfig.N_GROUPS + GROUP_DELAYS = NormalRateConfig.GROUP_DELAYS class GPS(object): diff --git a/peregrine/iqgen/iqgen_main.py b/peregrine/iqgen/iqgen_main.py index 177476d..6bb9cea 100755 --- a/peregrine/iqgen/iqgen_main.py +++ b/peregrine/iqgen/iqgen_main.py @@ -433,7 +433,8 @@ def __call__(self, parser, namespace, values, option_string=None): 'generate': namespace.generate, 'noise_sigma': namespace.noise_sigma, 'filter_type': namespace.filter_type, - 'tcxo': tcxoFO.toMapForm(namespace.tcxo) + 'tcxo': tcxoFO.toMapForm(namespace.tcxo), + 'group_delays': namespace.group_delays } json.dump(data, values, indent=2) values.close() @@ -456,6 +457,7 @@ def __call__(self, parser, namespace, values, option_string=None): namespace.tcxo = tcxoFO.fromMapForm(loaded['tcxo']) namespace.gps_sv = [ satelliteFO.fromMapForm(sv) for sv in loaded['gps_sv']] + namespace.group_delays = loaded['group_delays'] values.close() parser = argparse.ArgumentParser( @@ -605,6 +607,9 @@ def __call__(self, parser, namespace, values, option_string=None): type=float, help="TCXO period in seconds for sine TCXO drift", action=UpdateTcxoType) + parser.add_argument('--group-delays', + type=bool, + help="Enable/disable group delays simulation between bands") parser.add_argument('--debug', type=argparse.FileType('wb'), help="Debug output file") @@ -675,15 +680,18 @@ def main(): 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 " 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 " GLONASS L1[0] IF:", outputConfig.GLONASS.L1.INTERMEDIATE_FREQUENCIES_HZ[0] + print " GLONASS L2[0] IF:", outputConfig.GLONASS.L2.INTERMEDIATE_FREQUENCIES_HZ[0] print "Other parameters:" print " TCXO: ", args.tcxo print " noise sigma: ", args.noise_sigma - print " tSatellites: ", args.gps_sv + print " satellites: ", [sv.getName() for sv in args.gps_sv] + print " group delays: ", args.group_delays # Check which signals are enabled on each of satellite to select proper # output encoder @@ -695,11 +703,11 @@ def main(): enabledGLONASS = False for sv in args.gps_sv: - enabledGPSL1 |= sv.isBandEnabled(outputConfig.GPS.L1.INDEX, outputConfig) - enabledGPSL2 |= sv.isBandEnabled(outputConfig.GPS.L2.INDEX, outputConfig) - enabledGLONASSL1 |= sv.isBandEnabled(outputConfig.GLONASS.L1.INDEX, + enabledGPSL1 |= sv.isBandEnabled(outputConfig.GPS.L1, outputConfig) + enabledGPSL2 |= sv.isBandEnabled(outputConfig.GPS.L2, outputConfig) + enabledGLONASSL1 |= sv.isBandEnabled(outputConfig.GLONASS.L1, outputConfig) - enabledGLONASSL2 |= sv.isBandEnabled(outputConfig.GLONASS.L2.INDEX, + enabledGLONASSL2 |= sv.isBandEnabled(outputConfig.GLONASS.L2, outputConfig) enabledGPS |= enabledGPSL1 or enabledGPSL2 @@ -795,6 +803,7 @@ def main(): tcxo=args.tcxo, noiseSigma=args.noise_sigma, filterType=args.filter_type, + groupDelays=args.group_delays, logFile=args.debug, threadCount=args.jobs, pbar=pbar) From a7f99251313ddb16deaa7a70935cf0622fb31872 Mon Sep 17 00:00:00 2001 From: Adel Mamin Date: Wed, 13 Apr 2016 18:05:28 +0300 Subject: [PATCH 65/67] Test L1C/A acquisition with IQgen data --- .travis.yml | 4 ++ peregrine/acquisition.py | 6 +- peregrine/run.py | 17 ++--- requirements.txt | 1 + setup.py | 1 + tests/test_run.py | 140 +++++++++++++++++++++++++++------------ 6 files changed, 116 insertions(+), 53 deletions(-) diff --git a/.travis.yml b/.travis.yml index a6fe55b..a6db16e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,6 +16,10 @@ addons: - python-numpy - python-cython - python-dev + - libopenblas-dev + - liblapack-dev + - gfortran + - g++ install: - export LD_LIBRARY_PATH=$TRAVIS_BUILD_DIR/libswiftnav/build/install/usr/local/lib diff --git a/peregrine/acquisition.py b/peregrine/acquisition.py index fac1911..9d4cc56 100644 --- a/peregrine/acquisition.py +++ b/peregrine/acquisition.py @@ -361,7 +361,11 @@ def find_peak(self, freqs, results, interpolation='gaussian'): code_phase = float(cp_samples) / self.samples_per_chip # Calculate SNR for the peak. - snr = np.max(results) / np.mean(results) + results_mean = np.mean(results) + if results_mean != 0: + snr = np.max(results) / results_mean + else: + snr = 0 return (code_phase, freq, snr) diff --git a/peregrine/run.py b/peregrine/run.py index 92e62c1..a50c0e0 100755 --- a/peregrine/run.py +++ b/peregrine/run.py @@ -273,14 +273,15 @@ def main(): # Track the acquired satellites track_results_file = args.file + ".track_results" if args.skip_tracking: - logging.info("Skipping tracking, loading saved tracking results.") - try: - with open(track_results_file, 'rb') as f: - track_results = cPickle.load(f) - except IOError: - logging.critical("Couldn't open tracking results file '%s'.", - track_results_file) - sys.exit(1) + if not args.skip_navigation: + logging.info("Skipping tracking, loading saved tracking results.") + try: + with open(track_results_file, 'rb') as f: + track_results = cPickle.load(f) + except IOError: + logging.critical("Couldn't open tracking results file '%s'.", + track_results_file) + sys.exit(1) else: load_samples(samples=samples, filename=args.file, diff --git a/requirements.txt b/requirements.txt index 9dd20d4..7a7ffbc 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,7 @@ numpy==1.10.4 pytest==2.8.7 mock==1.3.0 +scipy==0.13.3 # This is the default index. --index-url https://pypi.python.org/simple/ diff --git a/setup.py b/setup.py index 53e91cc..3356893 100755 --- a/setup.py +++ b/setup.py @@ -17,6 +17,7 @@ INSTALL_REQUIRES = ['numpy >= 1.9', 'pyFFTW >= 0.8.2', + 'scipy >= 0.13.3', 'swiftnav'] TEST_REQUIRES = ['pytest'] diff --git a/tests/test_run.py b/tests/test_run.py index c7d66aa..2d2f13d 100644 --- a/tests/test_run.py +++ b/tests/test_run.py @@ -8,11 +8,12 @@ # WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. import peregrine.run +import peregrine.iqgen.iqgen_main as iqgen import sys import cPickle import os +import peregrine.acquisition as acq -from peregrine.acquisition import load_acq_results from mock import patch from shutil import copyfile @@ -28,61 +29,112 @@ SAMPLES = SAMPLES_PATH + SAMPLES_FNAME -OLD_ACQ_RES = RES_PATH + SAMPLES_FNAME + '.acq_results' OLD_TRK_RES = RES_PATH + SAMPLES_FNAME + '.track_results' OLD_NAV_RES = RES_PATH + SAMPLES_FNAME + '.nav_results' # run.py deposits results in same location as samples -NEW_ACQ_RES = SAMPLES_PATH + SAMPLES_FNAME + '.acq_results' NEW_TRK_RES = SAMPLES_PATH + SAMPLES_FNAME + '.track_results' NEW_NAV_RES = SAMPLES_PATH + SAMPLES_FNAME + '.nav_results' -def test_acquisition(): - - # Replace argv with args to skip tracking and navigation. - with patch.object(sys, 'argv', - ['peregrine', '--file', SAMPLES, - '--file-format', 'piksi', '-t', '-n']): - - try: - peregrine.run.main() - except SystemExit: - # Thrown if track and nav results files are not present and we - # supplied command line args to skip tracking and navigation. - pass +def generate_sample_file(gps_sv_prn, init_doppler, init_code_phase): + sample_file = 'iqgen-data-samples.bin' + freq_profile = 'low_rate' + params = ['iqgen_main'] + params += ['--gps-sv', str(gps_sv_prn)] + params += ['--bands', 'l1ca+l2c'] + params += ['--doppler-type', 'const'] + params += ['--doppler-value', str(init_doppler) ] + params += ['--message-type', 'crc'] + params += ['--chip_delay', str(init_code_phase)] + params += ['--snr', '-5'] + params += ['--generate', '1'] + params += ['--encoder', '2bits'] + params += ['--output', sample_file] + params += ['--profile', freq_profile] + print params + with patch.object(sys, 'argv', params): + iqgen.main() + + return {'sample_file' : sample_file, + 'file_format' : '2bits_x2', + 'freq_profile' : freq_profile} + +def get_acq_result_file_name(sample_file): + return sample_file + '.acq_results' + +def run_acq_test(init_doppler, init_code_phase): + for prn in range(1, 33, 5): + samples = generate_sample_file(prn, init_doppler, init_code_phase) + + # Replace argv with args to skip tracking and navigation. + with patch.object(sys, 'argv', + ['peregrine', + '--file', samples['sample_file'], + '--file-format', samples['file_format'], + '--profile', samples['freq_profile'], + '-t', '-n']): + + try: + peregrine.run.main() + except SystemExit: + # Thrown if track and nav results files are not present and we + # supplied command line args to skip tracking and navigation. + pass + + acq_results = acq.load_acq_results( + get_acq_result_file_name(samples['sample_file'])) + + acq_results = sorted(acq_results, lambda x, y: -1 if x.snr > y.snr else 1) + + assert len(acq_results) != 0 + + result = acq_results[0] + print "result = ", result + assert (result.prn + 1) == prn + + # check doppler phase estimation + doppler_diff = abs(abs(result.doppler) - abs(init_doppler)) + print "doppler_diff = ", doppler_diff + assert doppler_diff < 70.0 + + # check code phase estimation + code_phase_diff = abs(abs(result.code_phase) - abs(init_code_phase)) + print "code_phase_diff = ", code_phase_diff + assert code_phase_diff < 1.0 + + # Clean-up. + os.remove(get_acq_result_file_name(samples['sample_file'])) + os.remove(samples['sample_file']) - new_acq_results = load_acq_results(NEW_ACQ_RES) - old_acq_results = load_acq_results(OLD_ACQ_RES) - - assert new_acq_results == old_acq_results - - # Clean-up. - os.remove(NEW_ACQ_RES) +def test_acquisition(): + run_acq_test(1000, 0) -def test_tracking(): +# def test_tracking(): - # Replace argv with args to skip acquisition and navigation. - with patch.object(sys, 'argv', ['peregrine', SAMPLES, '-a', '-n']): +# # Replace argv with args to skip acquisition and navigation. +# with patch.object(sys, 'argv', ['peregrine', SAMPLES, '-a', '-n']): - # Copy reference acq results to use in order to skip acquisition. - copyfile(OLD_ACQ_RES, NEW_ACQ_RES) +# # Copy reference acq results to use in order to skip acquisition. +# copyfile(OLD_ACQ_RES, NEW_ACQ_RES) - try: - peregrine.run.main() - except SystemExit: - # Thrown if nav results file is not present and we supplied - # command line arg to skip navigation. - pass +# try: +# peregrine.run.main() +# except SystemExit: +# # Thrown if nav results file is not present and we supplied +# # command line arg to skip navigation. +# pass - # Comparison not working on Travis at the moment, needs further debugging. - # Simply make sure tracking runs successfully for now. - #with open(NEW_TRK_RES, 'rb') as f: - # new_trk_results = cPickle.load(f) - #with open(OLD_TRK_RES, 'rb') as f: - # old_trk_results = cPickle.load(f) - #assert new_trk_results == old_trk_results +# # Comparison not working on Travis at the moment, needs further debugging. +# # Simply make sure tracking runs successfully for now. +# #with open(NEW_TRK_RES, 'rb') as f: +# # new_trk_results = cPickle.load(f) +# #with open(OLD_TRK_RES, 'rb') as f: +# # old_trk_results = cPickle.load(f) +# #assert new_trk_results == old_trk_results - # Clean-up. - os.remove(NEW_ACQ_RES) - #os.remove(NEW_TRK_RES) +# # Clean-up. +# os.remove(NEW_ACQ_RES) +# #os.remove(NEW_TRK_RES) +# if __name__ == '__main__': +# test_acquisition() From e1a24a0c38b36365eb931e3900da8b1676720a20 Mon Sep 17 00:00:00 2001 From: Valeri Atamaniouk Date: Wed, 13 Apr 2016 14:59:40 +0300 Subject: [PATCH 66/67] iqgen: added unit tests Added unit tests. Fixed issues with PRN code handling and TCXO. --- .gitignore | 5 + peregrine/iqgen/bits/amplitude_base.py | 4 +- peregrine/iqgen/bits/doppler_base.py | 8 +- peregrine/iqgen/bits/doppler_sine.py | 15 - peregrine/iqgen/bits/prn_glo_l1l2.py | 11 +- peregrine/iqgen/bits/prn_gps_l1ca.py | 10 +- peregrine/iqgen/bits/prn_gps_l2c.py | 4 +- peregrine/iqgen/bits/satellite_base.py | 8 +- peregrine/iqgen/bits/tcxo_poly.py | 6 - peregrine/iqgen/bits/tcxo_sine.py | 15 +- tests/test_iqgen_amplitude.py | 248 +++++++++++ tests/test_iqgen_doppler.py | 595 +++++++++++++++++++++++++ tests/test_iqgen_tcxo.py | 142 ++++++ 13 files changed, 1012 insertions(+), 59 deletions(-) create mode 100644 tests/test_iqgen_amplitude.py create mode 100644 tests/test_iqgen_doppler.py create mode 100644 tests/test_iqgen_tcxo.py diff --git a/.gitignore b/.gitignore index 0409b13..dd4cb5c 100644 --- a/.gitignore +++ b/.gitignore @@ -56,3 +56,8 @@ tests/test_data_old* .project .cproject .settings/ + +# Coverage +.coverage +htmlcov/ + diff --git a/peregrine/iqgen/bits/amplitude_base.py b/peregrine/iqgen/bits/amplitude_base.py index ca7e962..b0b750e 100644 --- a/peregrine/iqgen/bits/amplitude_base.py +++ b/peregrine/iqgen/bits/amplitude_base.py @@ -206,7 +206,7 @@ def convertUnits2SNR(value, units, noiseParams): snrDb = 10 * numpy.log10(snr) elif units == AmplitudeBase.UNITS_SNR_DB: snrDb = value - else: + else: # pragma: no cover assert False return snrDb @@ -240,6 +240,6 @@ def convertUnits2Amp(value, units, noiseParams): snrDb = value snr = 10. ** (0.1 * snrDb) amp = numpy.sqrt(4. * snr / freqTimesTau) * noiseSigma - else: + else: # pragma: no cover assert False return amp diff --git a/peregrine/iqgen/bits/doppler_base.py b/peregrine/iqgen/bits/doppler_base.py index 119ab69..c40ef82 100644 --- a/peregrine/iqgen/bits/doppler_base.py +++ b/peregrine/iqgen/bits/doppler_base.py @@ -181,12 +181,8 @@ def computeBatch(self, ------- 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 + dopplerAll_hz : numpy.ndarray(n_samples, dtype=float) + Doppler values in Hz if debug is enabled ''' userTimeAll_s = self.applySignalDelays(userTimeAll_s, carrierSignal) diff --git a/peregrine/iqgen/bits/doppler_sine.py b/peregrine/iqgen/bits/doppler_sine.py index 7ae914a..e9a19e9 100644 --- a/peregrine/iqgen/bits/doppler_sine.py +++ b/peregrine/iqgen/bits/doppler_sine.py @@ -65,21 +65,6 @@ def __str__(self): 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. diff --git a/peregrine/iqgen/bits/prn_glo_l1l2.py b/peregrine/iqgen/bits/prn_glo_l1l2.py index 2237387..74c2d08 100644 --- a/peregrine/iqgen/bits/prn_glo_l1l2.py +++ b/peregrine/iqgen/bits/prn_glo_l1l2.py @@ -14,9 +14,10 @@ """ import numpy -from peregrine.include.glo_ca_code import value as GLONASS_CA_Code +from peregrine.include.glo_ca_code import value as GLONASS_CACode -caCode = GLONASS_CA_Code[:] +# Binary CA code (0/1) +caCode = (GLONASS_CACode < 0).astype(numpy.uint8) class PrnCode(object): @@ -36,11 +37,7 @@ def __init__(self, prnNo): SV identifier ''' super(PrnCode, self).__init__() - self.caCode = caCode[:] - tmp = numpy.asarray(self.caCode, dtype=numpy.int8) - tmp -= 1 - tmp /= -2 - self.binCode = tmp + self.binCode = caCode self.prnNo = prnNo self.bitLookup = numpy.asarray([1, -1], dtype=numpy.int8) diff --git a/peregrine/iqgen/bits/prn_gps_l1ca.py b/peregrine/iqgen/bits/prn_gps_l1ca.py index 96893d6..515cc7c 100644 --- a/peregrine/iqgen/bits/prn_gps_l1ca.py +++ b/peregrine/iqgen/bits/prn_gps_l1ca.py @@ -16,9 +16,9 @@ """ -import peregrine.include.generateCAcode +from peregrine.include.generateCAcode import caCodes as L1CACodes -caCodes = peregrine.include.generateCAcode.caCodes +caCodes = (L1CACodes < 0).astype(numpy.uint8) class PrnCode(object): @@ -38,11 +38,7 @@ def __init__(self, prnNo): 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.binCode = caCodes[prnNo - 1] self.prnNo = prnNo self.bitLookup = numpy.asarray([1, -1], dtype=numpy.int8) diff --git a/peregrine/iqgen/bits/prn_gps_l2c.py b/peregrine/iqgen/bits/prn_gps_l2c.py index b1f9f6d..5130f6c 100644 --- a/peregrine/iqgen/bits/prn_gps_l2c.py +++ b/peregrine/iqgen/bits/prn_gps_l2c.py @@ -19,6 +19,8 @@ from peregrine.include.generateL2CMcode import L2CMCodes +caCodes = (L2CMCodes < 0).astype(numpy.uint8) + class PrnCode(object): ''' @@ -42,7 +44,7 @@ def __init__(self, prnNo): SV identifier ''' super(PrnCode.CM_Code, self).__init__() - self.binCode = numpy.asarray(L2CMCodes[prnNo - 1], dtype=numpy.int8) < 0 + self.binCode = caCodes[prnNo - 1] def getCodeBits(self): return self.binCode diff --git a/peregrine/iqgen/bits/satellite_base.py b/peregrine/iqgen/bits/satellite_base.py index 1ebb7ba..d9072a4 100644 --- a/peregrine/iqgen/bits/satellite_base.py +++ b/peregrine/iqgen/bits/satellite_base.py @@ -96,10 +96,10 @@ def getAmplitude(self): return self.amplitude def __str__(self): - return self.getSvName() - - def __repr__(self): - return self.getSvName() + ''' + Returns string representation of SV object + ''' + return self.getName() def getBatchSignals(self, userTimeAll_s, diff --git a/peregrine/iqgen/bits/tcxo_poly.py b/peregrine/iqgen/bits/tcxo_poly.py index f7fcbe5..1add2d5 100644 --- a/peregrine/iqgen/bits/tcxo_poly.py +++ b/peregrine/iqgen/bits/tcxo_poly.py @@ -53,12 +53,6 @@ def __str__(self, *args, **kwargs): ''' 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 diff --git a/peregrine/iqgen/bits/tcxo_sine.py b/peregrine/iqgen/bits/tcxo_sine.py index cc0b3b3..5b002c7 100644 --- a/peregrine/iqgen/bits/tcxo_sine.py +++ b/peregrine/iqgen/bits/tcxo_sine.py @@ -43,7 +43,7 @@ def __init__(self, initial_ppm, amplitude_ppm, period_s): 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.c0 = -amplitude_ppm * 1e-6 * self.period_s / (2. * scipy.constants.pi) self.c1 = 2. * scipy.constants.pi / period_s self.c2 = initial_ppm * 1e-6 @@ -54,13 +54,6 @@ def __str__(self, *args, **kwargs): 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 @@ -86,13 +79,13 @@ def computeTcxoTime(self, fromSample, toSample, outputConfig): time0_s = fromSample / outputConfig.SAMPLE_RATE_HZ timeX_s = toSample / outputConfig.SAMPLE_RATE_HZ - timeAll_s = numpy.linspace(time0_s * c1, - timeX_s * c1, + timeAll_s = numpy.linspace(time0_s, + timeX_s, toSample - fromSample, endpoint=False, dtype=numpy.float) - result = numpy.cos(timeAll_s) + result = numpy.cos(timeAll_s * c1) result += -1. result *= c0 if c2: diff --git a/tests/test_iqgen_amplitude.py b/tests/test_iqgen_amplitude.py new file mode 100644 index 0000000..ed1a562 --- /dev/null +++ b/tests/test_iqgen_amplitude.py @@ -0,0 +1,248 @@ +# 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. + +''' +Unit tests for IQgen amplitude controls +''' + +from peregrine.iqgen.bits.amplitude_base import AmplitudeBase +from peregrine.iqgen.bits.amplitude_base import NoiseParameters +from peregrine.iqgen.bits.amplitude_poly import AmplitudePoly +from peregrine.iqgen.bits.amplitude_sine import AmplitudeSine +import numpy + +EPSILON = 1e-9 + + +def test_AmplitudeBase_units(): + ''' + Generic tests for AmplitudeBase methods + ''' + ampl = AmplitudeBase(AmplitudeBase.UNITS_SNR) + assert AmplitudeBase.UNITS_SNR == ampl.getUnits() + ampl = AmplitudeBase(AmplitudeBase.UNITS_SNR_DB) + assert AmplitudeBase.UNITS_SNR_DB == ampl.getUnits() + + +def test_AmplitudeBase_abstract(): + ''' + Generic tests for AmplitudeBase abstract methods + ''' + ampl = AmplitudeBase(AmplitudeBase.UNITS_SNR_DB) + noiseParams = NoiseParameters(1e6, 1.) + userTimeAll_s = numpy.asarray([0., 1.], dtype=numpy.float) + signal = numpy.asarray([0., 1.], dtype=numpy.float) + try: + ampl.computeSNR(noiseParams) + assert False + except NotImplementedError: + pass + try: + ampl.applyAmplitude(signal, userTimeAll_s, noiseParams) + assert False + except NotImplementedError: + pass + + +def test_AmplitudeBase_convertVolts(): + ''' + Generic tests for AmplitudeBase conversion to volts + ''' + noiseParams = NoiseParameters(1e6, 1.) + assert 4. == AmplitudeBase.convertUnits2Amp(4., + AmplitudeBase.UNITS_AMPLITUDE, + noiseParams) + assert 2. == AmplitudeBase.convertUnits2Amp(4., + AmplitudeBase.UNITS_POWER, + noiseParams) + assert 20. == AmplitudeBase.convertUnits2Amp(100, + AmplitudeBase.UNITS_SNR, + noiseParams) + assert 2. == AmplitudeBase.convertUnits2Amp(0., + AmplitudeBase.UNITS_SNR_DB, + noiseParams) + + +def test_AmplitudeBase_convertSNR(): + ''' + Generic tests for AmplitudeBase conversion to volts + ''' + noiseParams = NoiseParameters(1e6, 1.) + assert 10. * numpy.log10(4.) == AmplitudeBase.convertUnits2SNR(4., + AmplitudeBase.UNITS_AMPLITUDE, + noiseParams) + assert 10. * numpy.log10(1.) == AmplitudeBase.convertUnits2SNR(4., + AmplitudeBase.UNITS_POWER, + noiseParams) + assert 20. == AmplitudeBase.convertUnits2SNR(100, + AmplitudeBase.UNITS_SNR, + noiseParams) + assert 15. == AmplitudeBase.convertUnits2SNR(15., + AmplitudeBase.UNITS_SNR_DB, + noiseParams) + + +def test_NoiseParameters(): + ''' + Generic tests for NoiseParameters + ''' + noiseParams = NoiseParameters(1e6, 1.) + assert 1e6 == noiseParams.getSamplingFreqHz() + assert 1. == noiseParams.getNoiseSigma() + assert 1. == noiseParams.getFreqTimesTau() + assert 2. == noiseParams.getSignalK() + + +def test_AmplitudePoly_SNR0(): + ''' + Test AmplitudePoly SNR_0 computation (empty polynomial) + ''' + noiseParams = NoiseParameters(1e6, 1.) + ampl = AmplitudePoly(AmplitudeBase.UNITS_AMPLITUDE, ()) + SNR = 10. * numpy.log10(noiseParams.getFreqTimesTau() / 4.) + assert SNR == ampl.computeSNR(noiseParams) + + +def test_AmplitudePoly_SNR1(): + ''' + Test AmplitudePoly SNR_0 computation (first order polynomial) + ''' + noiseParams = NoiseParameters(1e6, 1.) + ampl = AmplitudePoly(AmplitudeBase.UNITS_AMPLITUDE, (1.,)) + SNR = 10. * numpy.log10(noiseParams.getFreqTimesTau() / 4.) + assert SNR == ampl.computeSNR(noiseParams) + + +def test_AmplitudePoly_SNR2(): + ''' + Test AmplitudePoly SNR_0 computation (second order polynomial) + ''' + noiseParams = NoiseParameters(1e6, 1.) + ampl = AmplitudePoly(AmplitudeBase.UNITS_AMPLITUDE, (1., 1.)) + SNR = 10. * numpy.log10(noiseParams.getFreqTimesTau() / 4.) + assert SNR == ampl.computeSNR(noiseParams) + + +def test_AmplitudePoly_SNR3(): + ''' + Test AmplitudePoly SNR_0 computation (second order polynomial, power units) + ''' + noiseParams = NoiseParameters(1e6, 1.) + ampl = AmplitudePoly(AmplitudeBase.UNITS_POWER, (1., 1.)) + SNR = 10. * numpy.log10(noiseParams.getFreqTimesTau() / 4.) + assert SNR == ampl.computeSNR(noiseParams) + + +def test_AmplitudePoly_SNR4(): + ''' + Test AmplitudePoly SNR_0 computation (second order polynomial, SNR units) + ''' + noiseParams = NoiseParameters(1e6, 1.) + ampl = AmplitudePoly(AmplitudeBase.UNITS_SNR, (1., 1.)) + SNR = 10. * numpy.log10(1.) + assert SNR == ampl.computeSNR(noiseParams) + + +def test_AmplitudePoly_SNR5(): + ''' + Test AmplitudePoly SNR_0 computation (second order polynomial, SNR dB units) + ''' + noiseParams = NoiseParameters(1e6, 1.) + ampl = AmplitudePoly(AmplitudeBase.UNITS_SNR_DB, (1., 1.)) + SNR = 1. + assert SNR == ampl.computeSNR(noiseParams) + + +def test_AmplitudePoly_apply0(): + ''' + Test AmplitudePoly computation (empty polynomial) + ''' + noiseParams = NoiseParameters(1e6, 1.) + ampl = AmplitudePoly(AmplitudeBase.UNITS_AMPLITUDE, ()) + userTimeAll_s = numpy.asarray([0., 1.], dtype=numpy.float) + signal = numpy.asarray([0., 1.], dtype=numpy.float) + signal = ampl.applyAmplitude(signal, userTimeAll_s, noiseParams) + assert (numpy.abs(signal - numpy.asarray([0., 1.], dtype=numpy.float)) + < EPSILON).all() + + +def test_AmplitudePoly_apply1(): + ''' + Test AmplitudePoly computation (zero order polynomial: 1.0) + ''' + noiseParams = NoiseParameters(1e6, 1.) + ampl = AmplitudePoly(AmplitudeBase.UNITS_AMPLITUDE, (1.,)) + userTimeAll_s = numpy.asarray([0., 1.], dtype=numpy.float) + signal = numpy.asarray([0., 1.], dtype=numpy.float) + signal = ampl.applyAmplitude(signal, userTimeAll_s, noiseParams) + assert (numpy.abs(signal - numpy.asarray([0., 1.], dtype=numpy.float)) + < EPSILON).all() + + +def test_AmplitudePoly_apply2(): + ''' + Test AmplitudePoly computation (first order polynomial: 1.0*t+1.0) + ''' + noiseParams = NoiseParameters(1e6, 1.) + ampl = AmplitudePoly(AmplitudeBase.UNITS_AMPLITUDE, (1., 1.)) + userTimeAll_s = numpy.asarray([0., 1.], dtype=numpy.float) + signal = numpy.asarray([0., 1.], dtype=numpy.float) + signal = ampl.applyAmplitude(signal, userTimeAll_s, noiseParams) + assert (numpy.abs(signal - numpy.asarray([0., 2.], dtype=numpy.float)) + < EPSILON).all() + + +def test_AmplitudeSine_SNR0(): + ''' + Test AmplitudeSine SNR_0 computation (1.+2.*sin(2.*pi*t/1.)) + ''' + noiseParams = NoiseParameters(1e6, 1.) + ampl = AmplitudeSine(AmplitudeBase.UNITS_AMPLITUDE, 1., 2., 1.) + SNR = 10. * numpy.log10(noiseParams.getFreqTimesTau() / 4.) + assert numpy.abs(SNR - ampl.computeSNR(noiseParams)) < EPSILON + + +def test_AmplitudeSine_apply0(): + ''' + Test AmplitudeSine computation (1.+2.*sin(2.*pi*t/4.)) + ''' + noiseParams = NoiseParameters(1e6, 1.) + ampl = AmplitudeSine(AmplitudeBase.UNITS_AMPLITUDE, 1., 2., 4.) + userTimeAll_s = numpy.asarray([0., 1., 2.], dtype=numpy.float) + signal = numpy.asarray([0., 1., 1.], dtype=numpy.float) + signal = ampl.applyAmplitude(signal, userTimeAll_s, noiseParams) + assert (numpy.abs(signal - numpy.asarray([0., 3., 1.], dtype=numpy.float)) + < EPSILON).all() + + +def test_AmplitudePoly_str0(): + ''' + String representation test for polynomial amplitude object + ''' + value = str(AmplitudePoly(AmplitudeBase.UNITS_SNR, ())) + assert value.find('=SNR') >= 0 + assert value.find('()') >= 0 + assert value.find('Poly') >= 0 + value = str(AmplitudePoly(AmplitudeBase.UNITS_AMPLITUDE, (1.,))) + assert value.find('=AMP') >= 0 + assert value.find('(1.0,)') >= 0 + assert value.find('Poly') >= 0 + + +def test_AmplitudeSine_str0(): + ''' + String representation test for sine amplitude object + ''' + value = str(AmplitudeSine(AmplitudeBase.UNITS_SNR, 4., 3., 5.)) + assert value.find('SNR') >= 0 + assert value.find('4.') >= 0 + assert value.find('3.') >= 0 + assert value.find('5.') >= 0 + assert value.find('Sine') >= 0 diff --git a/tests/test_iqgen_doppler.py b/tests/test_iqgen_doppler.py new file mode 100644 index 0000000..69436ec --- /dev/null +++ b/tests/test_iqgen_doppler.py @@ -0,0 +1,595 @@ +# 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. + +''' +Unit tests for IQgen doppler controls +''' + +from peregrine.iqgen.bits.amplitude_poly import AmplitudePoly +from peregrine.iqgen.bits.amplitude_base import NoiseParameters, AmplitudeBase +from peregrine.iqgen.bits.doppler_base import DopplerBase +from peregrine.iqgen.bits.doppler_poly import Doppler as DopplerPoly +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 Doppler as DopplerSine +from peregrine.iqgen.bits.doppler_sine import sineDoppler +from peregrine.iqgen.bits.message_const import Message +from peregrine.iqgen.bits.signals import GPS +from peregrine.iqgen.bits.prn_gps_l1ca import PrnCode +from peregrine.iqgen.if_iface import NormalRateConfig +import numpy +import scipy.constants + +EPSILON = 1e-9 + + +def test_DopplerBase_CDI(): + ''' + Test CDI flag manipulation + ''' + doppler = DopplerBase() + assert not doppler.isCodeDopplerIgnored() + doppler.setCodeDopplerIgnored(True) + assert doppler.isCodeDopplerIgnored() + + +def test_DopplerBase_TEC(): + ''' + Test TEC value manipulation + ''' + doppler = DopplerBase(tec_epm2=50.) + assert doppler.computeSignalDelayS(1e9) == 40.3 * 50. / 1e18 + doppler.tec_epm2 = 25. + assert doppler.computeSignalDelayS(1e9) == 40.3 * 25. / 1e18 + + +def test_DopplerBase_Distance(): + ''' + Test distance value manipulation + ''' + doppler = DopplerBase(distance0_m=0., tec_epm2=0.) + assert doppler.computeSignalDelayS(1e9) == 0. + doppler = DopplerBase(distance0_m=1e6, tec_epm2=0.) + assert doppler.computeSignalDelayS(1e9) == 1e6 / scipy.constants.c + + +def test_DopplerBase_applySignalDelay(): + ''' + Test signal delay computation + ''' + doppler = DopplerBase(tec_epm2=50.) + userTimeAll_s = numpy.asanyarray([1., 2.], dtype=numpy.float) + res = doppler.applySignalDelays(userTimeAll_s, GPS.L1CA) + delay_s = 40.3 * 50. / 1e18 + assert (numpy.abs(res + delay_s - userTimeAll_s) < EPSILON).all() + + +def test_DopplerBase_computeDistance(): + ''' + Test distance computation + ''' + doppler = DopplerBase(distance0_m=0., tec_epm2=0.) + + try: + doppler.computeDistanceM(0.) + assert False + except NotImplementedError: + pass + + +def test_DopplerBase_computeSpeed(): + ''' + Test speed computation + ''' + doppler = DopplerBase(distance0_m=0., tec_epm2=0.) + try: + doppler.computeSpeedMps(0.) + assert False + except NotImplementedError: + pass + + +def test_DopplerBase_computeBatch(): + ''' + Test signal generation + ''' + doppler = DopplerBase(distance0_m=0., tec_epm2=0.) + userTimeAll_s = numpy.asarray([0.]) + amplitude = AmplitudePoly(AmplitudeBase.UNITS_AMPLITUDE, ()) + noiseParams = NoiseParameters(NormalRateConfig.SAMPLE_RATE_HZ, 0.) + message = Message(1) + code = PrnCode(1) + try: + doppler.computeBatch(userTimeAll_s, + amplitude, + noiseParams, + GPS.L1CA, + NormalRateConfig.GPS.L1.INTERMEDIATE_FREQUENCY_HZ, + message, + code, + NormalRateConfig, + False) + assert False + except NotImplementedError: + pass + + +def test_DopplerBase_computeDeltaUserTimeS(): + ''' + Test time delay + ''' + time_s = DopplerBase.computeDeltaUserTimeS(0., 24.84375e6, NormalRateConfig) + assert time_s == 1. + + +def test_DopplerBase_computeDopplerHz(): + ''' + Test doppler in Hz computation + ''' + dopplerHz = DopplerBase.computeDopplerHz(1e9, 100.) + assert numpy.abs(dopplerHz == -1e11 / scipy.constants.c) < EPSILON + + +def test_DopplerBase_computeDataNChipVector0(): + ''' + Test combination of data and code + ''' + doppler = DopplerBase() + chipAll_idx = numpy.linspace(0, 1022, 1023, dtype=numpy.long) + message = Message(1) + code = PrnCode(1) + vect = doppler.computeDataNChipVector(chipAll_idx, + GPS.L1CA, + message, + code) + + assert len(vect) == 1023 + assert ((vect < 0) == code.getCodeBits(chipAll_idx)).all() + + +def test_DopplerBase_computeDataNChipVector1(): + ''' + Test combination of data and code + ''' + doppler = DopplerBase() + chipAll_idx = numpy.linspace(0, 1022, 1023, dtype=numpy.long) + message = Message(-1) + code = PrnCode(1) + vect = doppler.computeDataNChipVector(chipAll_idx, + GPS.L1CA, + message, + code) + + assert len(vect) == 1023 + assert ((vect > 0) == code.getCodeBits(chipAll_idx)).all() + + +def test_DopplerBase_computeDopplerShiftM(): + ''' + Test computation of phase shift in m for a time + ''' + doppler = DopplerBase() + userTimeAll_s = numpy.asarray([0.], dtype=numpy.float) + try: + doppler.computeDopplerShiftHz(userTimeAll_s, GPS.L1CA) + assert False + except NotImplementedError: + pass + + +def test_DopplerBase_computeDopplerShiftHz(): + ''' + Test computation of doppler shift for a time + ''' + doppler = DopplerBase() + userTimeAll_s = numpy.asarray([0.], dtype=numpy.float) + try: + doppler.computeDopplerShiftM(userTimeAll_s) + assert False + except NotImplementedError: + pass + + +def test_Helper_zeroDoppler(): + ''' + Helper method test + ''' + doppler = zeroDoppler(1000., 77., 1e9) + assert isinstance(doppler, DopplerPoly) + assert doppler.distance0_m == 1000. + assert doppler.tec_epm2 == 77. + assert doppler.distanceCoeffs is None + assert doppler.speedPoly is None + + +def test_Helper_constDoppler(): + ''' + Helper method test + ''' + doppler = constDoppler(1000., 77., 1e9, 100.) + assert isinstance(doppler, DopplerPoly) + assert doppler.distance0_m == 1000. + assert doppler.tec_epm2 == 77. + assert len(doppler.distancePoly.coeffs) == 2 + assert len(doppler.speedPoly.coeffs) == 1 + + speed_mps = -scipy.constants.c / 1e7 + speedCoeffs = numpy.asarray([speed_mps], dtype=numpy.float) + distCoeffs = numpy.asarray([speed_mps, 0.], dtype=numpy.float) + + assert (numpy.abs(doppler.distancePoly.coeffs - distCoeffs) < EPSILON).all() + assert (numpy.abs(doppler.speedPoly.coeffs == speedCoeffs) < EPSILON).all() + + +def test_Helper_linearDoppler(): + ''' + Helper method test + ''' + doppler = linearDoppler(1000., 77., 1e9, 100., 100.) + assert isinstance(doppler, DopplerPoly) + assert doppler.distance0_m == 1000. + assert doppler.tec_epm2 == 77. + assert len(doppler.distancePoly.coeffs) == 3 + assert len(doppler.speedPoly.coeffs) == 2 + + speed_mps = -scipy.constants.c / 1e7 + speedCoeffs = numpy.asarray([speed_mps, speed_mps], dtype=numpy.float) + distCoeffs = numpy.asarray([speed_mps / 2, + speed_mps, + 0.], dtype=numpy.float) + + assert (numpy.abs(doppler.speedPoly.coeffs - speedCoeffs) < EPSILON).all() + assert (numpy.abs(doppler.distancePoly.coeffs - distCoeffs) < EPSILON).all() + + +def test_Helper_sineDoppler(): + ''' + Helper method test + ''' + doppler = sineDoppler(1000., 77., 1e9, 100., 50., 3.) + assert isinstance(doppler, DopplerSine) + assert doppler.distance0_m == 1000. + assert doppler.tec_epm2 == 77. + assert doppler.period_s == 3. + assert numpy.abs(doppler.speed0_mps - -scipy.constants.c / 1e7) < EPSILON + assert numpy.abs(doppler.amplutude_mps - -scipy.constants.c / 2e7) < EPSILON + + +def test_DopplerSine_computeDistance(): + ''' + Test distance for sine doppler + ''' + doppler = DopplerSine(1000., 77., 100., 50., 3.) + assert abs(1000. - doppler.computeDistanceM(0.)) < EPSILON + assert abs(1250. - doppler.computeDistanceM(1.5)) < EPSILON + assert abs(1300. - doppler.computeDistanceM(3.)) < EPSILON + + +def test_DopplerSine_computeSpeed(): + ''' + Test speed for sine doppler + ''' + doppler = DopplerSine(1000., 77., 100., 50., 4.) + assert abs(100. - doppler.computeSpeedMps(0.)) < EPSILON + assert abs(150. - doppler.computeSpeedMps(1.)) < EPSILON + assert abs(100. - doppler.computeSpeedMps(2.)) < EPSILON + assert abs(50. - doppler.computeSpeedMps(3.)) < EPSILON + assert abs(100. - doppler.computeSpeedMps(4.)) < EPSILON + + +def test_DopplerSine_computeDopplerShift0(): + ''' + Test distance for sine doppler + ''' + doppler = DopplerSine(1000., 77., 0., 50., 4.) + userTimeAll_s = numpy.asarray([0., 1., 2., 3.]) + shift = doppler.computeDopplerShiftM(userTimeAll_s) + + pi = scipy.constants.pi + assert abs(0. - shift[0]) < EPSILON + assert abs(0. + 50. * 4. / (2. * pi) - shift[1]) < EPSILON + assert abs(0. + 2. * 50. * 4. / (2. * pi) - shift[2]) < EPSILON + assert abs(0. + 50. * 4. / (2. * pi) - shift[3]) < EPSILON + + +def test_DopplerSine_computeDopplerShift1(): + ''' + Test distance for sine doppler + ''' + doppler = DopplerSine(1000., 77., 1., 50., 4.) + userTimeAll_s = numpy.asarray([0., 1., 2., 3.]) + shift = doppler.computeDopplerShiftM(userTimeAll_s) + + pi = scipy.constants.pi + assert abs(0. - shift[0]) < EPSILON + assert abs(1. + 50. * 4. / (2. * pi) - shift[1]) < EPSILON + assert abs(2. + 2. * 50. * 4. / (2. * pi) - shift[2]) < EPSILON + assert abs(3. + 50. * 4. / (2. * pi) - shift[3]) < EPSILON + + +def test_DopplerSine_computeDopplerShiftHz(): + ''' + Test distance for sine doppler + ''' + doppler = sineDoppler(1000., # Distance + 45., # TEC + GPS.L1CA.CENTER_FREQUENCY_HZ, # F + 100., # Offset Hz + 50., # Amplitude Hz + 4.) # Period s + userTimeAll_s = numpy.asarray([0., 1., 2., 3.]) + shift = doppler.computeDopplerShiftHz(userTimeAll_s, GPS.L1CA) + + assert abs(100. - shift[0]) < EPSILON + assert abs(150. - shift[1]) < EPSILON + assert abs(100. - shift[2]) < EPSILON + assert abs(50. - shift[3]) < EPSILON + + +def test_DopplerPoly_computeDistance0(): + ''' + Test distance for empty polynomial doppler + ''' + doppler = DopplerPoly(1000., 77., ()) + assert abs(0. - doppler.computeDistanceM(0.)) < EPSILON + assert abs(0. - doppler.computeDistanceM(1.5)) < EPSILON + assert abs(0. - doppler.computeDistanceM(3.)) < EPSILON + + +def test_DopplerPoly_computeDistance1(): + ''' + Test distance for zero order polynomial doppler + ''' + doppler = DopplerPoly(1000., 77., (1.,)) + assert abs(0. - doppler.computeDistanceM(0.)) < EPSILON + assert abs(1.5 - doppler.computeDistanceM(1.5)) < EPSILON + assert abs(3. - doppler.computeDistanceM(3.)) < EPSILON + + +def test_DopplerPoly_computeDistance2(): + ''' + Test distance for first order polynomial doppler + ''' + doppler = DopplerPoly(1000., 77., (1., 1.)) + assert abs(0. - doppler.computeDistanceM(0.)) < EPSILON + assert abs(1.5 - doppler.computeDistanceM(1.)) < EPSILON + assert abs(7.5 - doppler.computeDistanceM(3.)) < EPSILON + + +def test_DopplerPoly_computeSpeed0(): + ''' + Test speed for empty polynomial doppler + ''' + doppler = DopplerPoly(1000., 77., ()) + assert abs(0. - doppler.computeSpeedMps(0.)) < EPSILON + assert abs(0. - doppler.computeSpeedMps(1.)) < EPSILON + assert abs(0. - doppler.computeSpeedMps(2.)) < EPSILON + + +def test_DopplerPoly_computeSpeed1(): + ''' + Test speed for zero order polynomial doppler + ''' + doppler = DopplerPoly(1000., 77., (1.,)) + assert abs(1. - doppler.computeSpeedMps(0.)) < EPSILON + assert abs(1. - doppler.computeSpeedMps(1.)) < EPSILON + assert abs(1. - doppler.computeSpeedMps(2.)) < EPSILON + + +def test_DopplerPoly_computeSpeed2(): + ''' + Test speed for first order polynomial doppler + ''' + doppler = DopplerPoly(1000., 77., (1., 1.)) + assert abs(1. - doppler.computeSpeedMps(0.)) < EPSILON + assert abs(2. - doppler.computeSpeedMps(1.)) < EPSILON + assert abs(3. - doppler.computeSpeedMps(2.)) < EPSILON + + +def test_DopplerPoly_computeDopplerShift0(): + ''' + Test distance for empty polynomial doppler + ''' + doppler = DopplerPoly(1000., 77., ()) + userTimeAll_s = numpy.asarray([0., 1., 2., 3.]) + shift = doppler.computeDopplerShiftM(userTimeAll_s) + + assert abs(0. - shift[0]) < EPSILON + assert abs(0. - shift[1]) < EPSILON + assert abs(0. - shift[2]) < EPSILON + assert abs(0. - shift[3]) < EPSILON + + +def test_DopplerPoly_computeDopplerShift1(): + ''' + Test distance for zero order polynomial doppler + ''' + doppler = DopplerPoly(1000., 77., (1.,)) + userTimeAll_s = numpy.asarray([0., 1., 2., 3.]) + shift = doppler.computeDopplerShiftM(userTimeAll_s) + + assert abs(0. - shift[0]) < EPSILON + assert abs(1. - shift[1]) < EPSILON + assert abs(2. - shift[2]) < EPSILON + assert abs(3. - shift[3]) < EPSILON + + +def test_DopplerPoly_computeDopplerShift2(): + ''' + Test distance for first order polynomial doppler + ''' + doppler = DopplerPoly(1000., 77., (1., 1.)) + userTimeAll_s = numpy.asarray([0., 1., 2., 3.]) + shift = doppler.computeDopplerShiftM(userTimeAll_s) + + assert abs(0. - shift[0]) < EPSILON + assert abs(1.5 - shift[1]) < EPSILON + assert abs(4. - shift[2]) < EPSILON + assert abs(7.5 - shift[3]) < EPSILON + + +def test_DopplerPoly_computeDopplerShiftHz0(): + ''' + Test phase shift for empty polynomial doppler + ''' + doppler = DopplerPoly(1000., 50., ()) + userTimeAll_s = numpy.asarray([0., 1., 2., 3.]) + shift = doppler.computeDopplerShiftHz(userTimeAll_s, GPS.L1CA) + + assert abs(0. - shift[0]) < EPSILON + assert abs(0. - shift[1]) < EPSILON + assert abs(0. - shift[2]) < EPSILON + assert abs(0. - shift[3]) < EPSILON + + +def test_DopplerPoly_computeDopplerShiftHz1(): + ''' + Test phase shift for zero order polynomial doppler + ''' + doppler = constDoppler(1000., # Distance + 45., # TEC + GPS.L1CA.CENTER_FREQUENCY_HZ, # F + 1.) # constant Hz + userTimeAll_s = numpy.asarray([0., 1., 2., 3.]) + shift = doppler.computeDopplerShiftHz(userTimeAll_s, GPS.L1CA) + + assert abs(1. - shift[0]) < EPSILON + assert abs(1. - shift[1]) < EPSILON + assert abs(1. - shift[2]) < EPSILON + assert abs(1. - shift[3]) < EPSILON + + +def test_DopplerPoly_computeDopplerShiftHz2(): + ''' + Test phase shift for first order polynomial doppler + ''' + doppler = linearDoppler(1000., # Distance + 45., # TEC + GPS.L1CA.CENTER_FREQUENCY_HZ, # F + 1., # constant Hz + 1.) # acceleration Hz/s + userTimeAll_s = numpy.asarray([0., 1., 2., 3.]) + shift = doppler.computeDopplerShiftHz(userTimeAll_s, GPS.L1CA) + + assert abs(1. - shift[0]) < EPSILON + assert abs(2. - shift[1]) < EPSILON + assert abs(3. - shift[2]) < EPSILON + assert abs(4. - shift[3]) < EPSILON + + +def test_DopplerPoly_str0(): + ''' + String representation test for polynomial doppler object + ''' + value = str(DopplerPoly(1000., 55., ())) + assert value.find('1000.') >= 0 + assert value.find('55.') >= 0 + assert value.find('()') >= 0 + assert value.find('Poly') >= 0 + value = str(DopplerPoly(1000., 55., (1.,))) + assert value.find('1000.') >= 0 + assert value.find('55.') >= 0 + assert value.find('(1.0,)') >= 0 + + +def test_DopplerSine_str0(): + ''' + String representation test for sine doppler object + ''' + value = str(DopplerSine(1000., 55., 4., 3., 5.)) + assert value.find('1000.') >= 0 + assert value.find('55.') >= 0 + assert value.find('4.') >= 0 + assert value.find('3.') >= 0 + assert value.find('5.') >= 0 + assert value.find('Sine') >= 0 + + +def test_DopplerZero_batch(): + ''' + Verifies execution of the batch computation with zero doppler. + ''' + doppler = zeroDoppler(1000., 50., GPS.L1CA.CENTER_FREQUENCY_HZ) + userTimeAll_s = numpy.linspace(0., + NormalRateConfig.SAMPLE_BATCH_SIZE / + NormalRateConfig.SAMPLE_RATE_HZ, + NormalRateConfig.SAMPLE_BATCH_SIZE, + endpoint=False) + amplitude = AmplitudePoly(AmplitudeBase.UNITS_AMPLITUDE, ()) + noiseParams = NoiseParameters(GPS.L1CA.CENTER_FREQUENCY_HZ, 0.) + message = Message(1) + code = PrnCode(1) + res = doppler.computeBatch(userTimeAll_s, + amplitude, + noiseParams, + GPS.L1CA, + NormalRateConfig.GPS.L1.INTERMEDIATE_FREQUENCY_HZ, + message, + code, + NormalRateConfig, + True) + + signal1, doppler1 = res + + doppler.setCodeDopplerIgnored(True) + res = doppler.computeBatch(userTimeAll_s, + amplitude, + noiseParams, + GPS.L1CA, + NormalRateConfig.GPS.L1.INTERMEDIATE_FREQUENCY_HZ, + message, + code, + NormalRateConfig, + True) + signal2, doppler2 = res + + assert (signal1 == signal2).all() + assert (doppler1 == doppler2).all() + + +def test_DopplerConst_batch(): + ''' + Verifies execution of the batch computation with const doppler. + ''' + doppler = constDoppler(1000., 50., GPS.L1CA.CENTER_FREQUENCY_HZ, 100.) + userTimeAll_s = numpy.linspace(10., + 10. + + NormalRateConfig.SAMPLE_BATCH_SIZE / + NormalRateConfig.SAMPLE_RATE_HZ, + NormalRateConfig.SAMPLE_BATCH_SIZE, + endpoint=False) + amplitude = AmplitudePoly(AmplitudeBase.UNITS_AMPLITUDE, ()) + noiseParams = NoiseParameters(GPS.L1CA.CENTER_FREQUENCY_HZ, 0.) + message = Message(1) + code = PrnCode(1) + res = doppler.computeBatch(userTimeAll_s, + amplitude, + noiseParams, + GPS.L1CA, + NormalRateConfig.GPS.L1.INTERMEDIATE_FREQUENCY_HZ, + message, + code, + NormalRateConfig, + True) + signal1, doppler1 = res + doppler.setCodeDopplerIgnored(True) + res = doppler.computeBatch(userTimeAll_s, + amplitude, + noiseParams, + GPS.L1CA, + NormalRateConfig.GPS.L1.INTERMEDIATE_FREQUENCY_HZ, + message, + code, + NormalRateConfig, + True) + signal2, doppler2 = res + assert (doppler1 == doppler2).all() + assert (signal1 != signal2).any() diff --git a/tests/test_iqgen_tcxo.py b/tests/test_iqgen_tcxo.py new file mode 100644 index 0000000..e88ad53 --- /dev/null +++ b/tests/test_iqgen_tcxo.py @@ -0,0 +1,142 @@ +# 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. + +''' +Unit tests for IQgen TCXO controls +''' + +from peregrine.iqgen.bits.tcxo_base import TCXOBase +from peregrine.iqgen.bits.tcxo_poly import TCXOPoly +from peregrine.iqgen.bits.tcxo_sine import TCXOSine +from peregrine.iqgen.if_iface import NormalRateConfig + +import numpy +from scipy.constants import pi + +EPSILON = 1e-10 + + +def test_TCXOBase_abstract(): + ''' + Unit test for abstract methods in TCXOBase + ''' + tcxo = TCXOBase() + try: + tcxo.computeTcxoTime(10, 20, NormalRateConfig) + assert False + except NotImplementedError: + pass + + +def test_TCXOPoly_compute0(): + ''' + Unit test for empty TCXOPoly object + ''' + tcxo = TCXOPoly(()) + time = tcxo.computeTcxoTime(0, 10, NormalRateConfig) + assert time is None + + +def test_TCXOPoly_compute1(): + ''' + Unit test for TCXOPoly with linear time shift (10e-6) + ''' + tcxo = TCXOPoly((1.,)) + time = tcxo.computeTcxoTime(0, 10, NormalRateConfig) + test_vector = numpy.linspace(0., + 10. * 1e-6 / NormalRateConfig.SAMPLE_RATE_HZ, + 10., + endpoint=False) + assert (time == test_vector).all() + + +def test_TCXOPoly_compute2(): + ''' + Unit test for TCXOPoly with linear time shift (10e-6) + ''' + tcxo = TCXOPoly((1., 1.)) + time = tcxo.computeTcxoTime(0, 10, NormalRateConfig) + test_vector = numpy.linspace(0., + 10. * 1e-6 / NormalRateConfig.SAMPLE_RATE_HZ, + 10., + endpoint=False) + test_vector = test_vector * test_vector / 2. + test_vector + assert (numpy.abs(time - test_vector) < EPSILON).all() + + +def test_TCXOSine_compute0(): + ''' + Unit test for TCXOSine object: 0.+sin(2*pi*t/0.004) + + The integral output is: (1. - cos(2*pi*t/0.004))*0.004/(2*pi); + Minimum value: 0 + Maximum value: 0.002/pi + ''' + tcxo = TCXOSine(0., 1e6, 0.004) + time = tcxo.computeTcxoTime( + 0, NormalRateConfig.SAMPLE_RATE_HZ * 0.004 + 1, NormalRateConfig) + assert time[0] == 0. + assert time[-1] == 0. + _max = numpy.max(time) + _min = numpy.min(time) + assert numpy.abs(_min) < EPSILON + assert numpy.abs(_max - 0.004 / pi) < EPSILON + assert time[NormalRateConfig.SAMPLE_RATE_HZ * 0.002] == _max + + +def test_TCXOSine_compute1(): + ''' + Unit test for TCXOSine object: 1.+sin(2*pi*t/0.004) + + The integral output is: 1.*t + (1. - cos(2*pi*t/0.004))*0.004/(2*pi); + After removing the time component: + Minimum value: 0 + Maximum value: 0.002/pi + ''' + tcxo = TCXOSine(1e6, 1e6, 0.004) + time = tcxo.computeTcxoTime( + 0, NormalRateConfig.SAMPLE_RATE_HZ * 0.004 + 1, NormalRateConfig) + + # Remove linear time component + timeX_s = (NormalRateConfig.SAMPLE_RATE_HZ * 0.004 + 1) / \ + NormalRateConfig.SAMPLE_RATE_HZ + time -= numpy.linspace(0, timeX_s, + NormalRateConfig.SAMPLE_RATE_HZ * 0.004 + 1, + endpoint=False) + assert time[0] == 0. + assert time[-1] == 0. + _max = numpy.max(time) + _min = numpy.min(time) + assert numpy.abs(_min) < EPSILON + assert numpy.abs(_max - 0.004 / pi) < EPSILON + assert time[NormalRateConfig.SAMPLE_RATE_HZ * 0.002] == _max + + +def test_TCXOPoly_str0(): + ''' + String representation test for polynomial amplitude object + ''' + value = str(TCXOPoly(())) + assert value.find('()') >= 0 + assert value.find('Poly') >= 0 + value = str(TCXOPoly((1.,))) + assert value.find('(1.0,)') >= 0 + assert value.find('Poly') >= 0 + + +def test_TXOSine_str0(): + ''' + String representation test for sine amplitude object + ''' + value = str(TCXOSine(4., 3., 5.)) + assert value.find('4.') >= 0 + assert value.find('3.') >= 0 + assert value.find('5.') >= 0 + assert value.find('Sine') >= 0 From f51452e40f1848bfe8da8a1742af51ae68cad72b Mon Sep 17 00:00:00 2001 From: Pasi Miettinen Date: Mon, 18 Apr 2016 10:48:20 +0300 Subject: [PATCH 67/67] Add GLO acquisition support --- peregrine/acquisition.py | 130 ++++++++++++++++++++++++++----------- peregrine/defaults.py | 7 +- peregrine/glo_constants.py | 13 ++++ peregrine/gps_constants.py | 16 ++--- peregrine/run.py | 47 ++++++++++---- peregrine/samples.py | 22 +++++-- tests/test_run.py | 103 ++++++++++++++++++++--------- 7 files changed, 242 insertions(+), 96 deletions(-) create mode 100644 peregrine/glo_constants.py diff --git a/peregrine/acquisition.py b/peregrine/acquisition.py index 9d4cc56..57ddbb3 100644 --- a/peregrine/acquisition.py +++ b/peregrine/acquisition.py @@ -17,9 +17,11 @@ import numpy as np import pyfftw import cPickle -import defaults from include.generateCAcode import caCodes +from include.generateGLOcode import GLOCode +from peregrine.gps_constants import L1CA +from peregrine.glo_constants import GLO_L1, glo_l1_step import logging logger = logging.getLogger(__name__) @@ -191,13 +193,15 @@ def interpolate(self, S_0, S_1, S_2, interpolation='gaussian'): **Parabolic interpolation:** - .. math:: \Delta = \\frac{1}{2} \\frac{S[k+1] - S[k-1]}{2S[k] - S[k-1] - S[k+1]} + .. math:: \Delta = \\frac{1}{2} \\frac{S[k+1] - + S[k-1]}{2S[k] - S[k-1] - S[k+1]} Where :math:`S[n]` is the magnitude of FFT bin :math:`n`. **Gaussian interpolation:** - .. math:: \Delta = \\frac{1}{2} \\frac{\ln(S[k+1]) - \ln(S[k-1])}{2\ln(S[k]) - \ln(S[k-1]) - \ln(S[k+1])} + .. math:: \Delta = \\frac{1}{2} \\frac{\ln(S[k+1]) - + \ln(S[k-1])}{2\ln(S[k]) - \ln(S[k-1]) - \ln(S[k+1])} The Gaussian interpolation method gives better results, especially when used with a Gaussian window function, at the expense of computational @@ -342,6 +346,8 @@ def find_peak(self, freqs, results, interpolation='gaussian'): freq_index, cp_samples = np.unravel_index(results.argmax(), results.shape) + code_phase = float(cp_samples) / self.samples_per_chip + if freq_index > 1 and freq_index < len(freqs) - 1: delta = self.interpolate( results[freq_index - 1][cp_samples], @@ -358,8 +364,6 @@ def find_peak(self, freqs, results, interpolation='gaussian'): else: freq = freqs[freq_index] - code_phase = float(cp_samples) / self.samples_per_chip - # Calculate SNR for the peak. results_mean = np.mean(results) if results_mean != 0: @@ -370,7 +374,8 @@ def find_peak(self, freqs, results, interpolation='gaussian'): return (code_phase, freq, snr) def acquisition(self, - prns=range(32), + prns=xrange(32), + channels=[x - 7 for x in xrange(14)], doppler_priors=None, doppler_search=7000, doppler_step=None, @@ -379,10 +384,10 @@ def acquisition(self, multi=True ): """ - Perform an acquisition for a given list of PRNs. + Perform an acquisition for a given list of PRNs/channels. - Perform an acquisition for a given list of PRNs across a range of Doppler - frequencies. + Perform an acquisition for a given list of PRNs/channels across a range of + Doppler frequencies. This function returns :class:`AcquisitionResult` objects containing the location of the acquisition peak for PRNs that have an acquisition @@ -394,8 +399,13 @@ def acquisition(self, Parameters ---------- + bandcode : optional + String defining the acquisition code. Default: L1CA + choices: L1CA, GLO_L1 (in gps_constants.py) prns : iterable, optional List of PRNs to acquire. Default: 0..31 (0-indexed) + channels : iterable, optional + List of channels to acquire. Default: -7..6 doppler_prior: list of floats, optional List of expected Doppler frequencies in Hz (one per PRN). Search will be centered about these. If None, will search around 0 for all PRNs. @@ -413,10 +423,11 @@ def acquisition(self, Returns ------- out : [AcquisitionResult] - A list of :class:`AcquisitionResult` objects, one per PRN in `prns`. + A list of :class:`AcquisitionResult` objects, one per PRN in `prns` or + channel in 'channels'. """ - logger.info("Acquisition starting") + logger.info("Acquisition starting for " + self.signal) from peregrine.parallel_processing import parmap # If the Doppler step is not specified, compute it from the coarse @@ -428,9 +439,6 @@ def acquisition(self, # magnitude. doppler_step = self.sampling_freq / self.n_integrate - if doppler_priors is None: - doppler_priors = np.zeros_like(prns) - if progress_bar_output == 'stdout': show_progress = True progress_fd = sys.stdout @@ -446,33 +454,55 @@ def acquisition(self, show_progress = False logger.warning("show_progress = True but progressbar module not found.") + if self.signal == L1CA: + input_len = len(prns) + offset = 1 + pb_attr = progressbar.Attribute('prn', '(PRN: %02d)', '(PRN --)') + if doppler_priors is None: + doppler_priors = np.zeros_like(prns) + else: + input_len = len(channels) + offset = 0 + pb_attr = progressbar.Attribute('ch', '(CH: %02d)', '(CH --)') + if doppler_priors is None: + doppler_priors = np.zeros_like(channels) + # Setup our progress bar if we need it if show_progress and not multi: widgets = [' Acquisition ', - progressbar.Attribute('prn', '(PRN: %02d)', '(PRN --)'), ' ', + pb_attr, ' ', progressbar.Percentage(), ' ', progressbar.ETA(), ' ', progressbar.Bar()] pbar = progressbar.ProgressBar(widgets=widgets, - maxval=int(len(prns) * - (2 * doppler_search / doppler_step + 1)), + maxval=int(input_len * + (2 * doppler_search / doppler_step + 1)), fd=progress_fd) pbar.start() else: pbar = None def do_acq(n): - prn = prns[n] + if self.signal == L1CA: + prn = prns[n] + code = caCodes[prn] + int_f = self.IF + attr = {'prn': prn + 1} + else: + ch = channels[n] + code = GLOCode + int_f = self.IF + ch * glo_l1_step + attr = {'ch': ch} doppler_prior = doppler_priors[n] freqs = np.arange(doppler_prior - doppler_search, - doppler_prior + doppler_search, doppler_step) + self.IF + doppler_prior + doppler_search, doppler_step) + int_f if pbar: def progress_callback(freq_num, num_freqs): - pbar.update(n * len(freqs) + freq_num, attr={'prn': prn + 1}) + pbar.update(n * len(freqs) + freq_num, attr=attr) else: progress_callback = None - coarse_results = self.acquire(caCodes[prn], freqs, + coarse_results = self.acquire(code, freqs, progress_callback=progress_callback) code_phase, carr_freq, snr = self.find_peak(freqs, coarse_results, @@ -485,13 +515,22 @@ def progress_callback(freq_num, num_freqs): status = 'A' # Save properties of the detected satellite signal - acq_result = AcquisitionResult(prn, - carr_freq, - carr_freq - self.IF, - code_phase, - snr, - status, - self.signal) + if self.signal == L1CA: + acq_result = AcquisitionResult(prn, + carr_freq, + carr_freq - int_f, + code_phase, + snr, + status, + L1CA) + else: + acq_result = GloAcquisitionResult(ch, + carr_freq, + carr_freq - int_f, + code_phase, + snr, + status, + GLO_L1) # If the acquisition was successful, log it if (snr > threshold): @@ -501,9 +540,9 @@ def progress_callback(freq_num, num_freqs): if multi: acq_results = parmap( - do_acq, range(len(prns)), show_progress=show_progress) + do_acq, xrange(input_len), show_progress=show_progress) else: - acq_results = map(do_acq, range(len(prns))) + acq_results = map(do_acq, xrange(input_len)) # Acquisition is finished @@ -512,9 +551,11 @@ def progress_callback(freq_num, num_freqs): pbar.finish() logger.info("Acquisition finished") - acquired_prns = [ar.prn + 1 for ar in acq_results if ar.status == 'A'] - logger.info("Acquired %d satellites, PRNs: %s.", - len(acquired_prns), acquired_prns) + acq = [ar.prn + offset for ar in acq_results if ar.status == 'A'] + if self.signal == L1CA: + logger.info("Acquired %d satellites, PRNs: %s.", len(acq), acq) + else: + logger.info("Acquired %d channels: %s.", len(acq), acq) return acq_results @@ -531,7 +572,7 @@ def save_wisdom(self, wisdom_file=DEFAULT_WISDOM_FILE): pyfftw.export_wisdom(), f, protocol=cPickle.HIGHEST_PROTOCOL) -class AcquisitionResult: +class AcquisitionResult(object): """ Stores the acquisition parameters of a single satellite. @@ -560,7 +601,7 @@ class AcquisitionResult: """ __slots__ = ('prn', 'carr_freq', 'doppler', - 'code_phase', 'snr', 'status', 'signal') + 'code_phase', 'snr', 'status', 'signal', 'sample_index') def __init__(self, prn, carr_freq, doppler, code_phase, snr, status, signal, sample_index=0): @@ -574,7 +615,7 @@ def __init__(self, prn, carr_freq, doppler, code_phase, snr, status, signal, self.sample_index = sample_index def __str__(self): - return "PRN %2d (%s) SNR %6.2f @ CP %6.1f, %+8.2f Hz %s" % \ + return "PRN %2d (%s) SNR %6.2f @ CP %6.3f, %+8.2f Hz %s" % \ (self.prn + 1, self.signal, self.snr, self.code_phase, self.doppler, self.status) @@ -615,6 +656,20 @@ def _equal(self, other): return True +class GloAcquisitionResult(AcquisitionResult): + + def __init__(self, channel, carr_freq, doppler, code_phase, snr, status, + signal, sample_index=0): + super(GloAcquisitionResult, self).__init__(channel, carr_freq, doppler, + code_phase, snr, status, + signal, sample_index) + + def __str__(self): + return "CH %2d (%s) SNR %6.2f @ CP %6.3f, %+8.2f Hz %s" % \ + (self.prn, self.signal, self.snr, self.code_phase, self.doppler, + self.status) + + def save_acq_results(filename, acq_results): """ Save a set of acquisition results to a file. @@ -676,4 +731,5 @@ def print_scores(acq_results, pred, pred_dopp=None): print "Found %d of %d, mean doppler error = %+5.0f Hz, mean abs err = %4.0f Hz, worst = %+5.0f Hz"\ % (n_match, len(pred), - sum_dopp_err / max(1, n_match), sum_abs_dopp_err / max(1, n_match), worst_dopp_err) + sum_dopp_err / max(1, n_match), sum_abs_dopp_err / + max(1, n_match), worst_dopp_err) diff --git a/peregrine/defaults.py b/peregrine/defaults.py index a683e20..6cbe3da 100644 --- a/peregrine/defaults.py +++ b/peregrine/defaults.py @@ -17,7 +17,7 @@ ephemMaxAge = 4 * 3600.0 # Reject an ephemeris entry if older than this # the size of the sample data block processed at a time -processing_block_size = 20e6 # [samples] +processing_block_size = 20e6 # [samples] # original sample_channel_GPS_L1 = 0 @@ -75,18 +75,21 @@ freq_profile_low_rate = { 'GPS_L1_IF': 14.58e5, 'GPS_L2_IF': 7.4e5, + 'GLO_L1_IF': 12e5, 'sampling_freq': 24.84375e5} # 'normal_rate' frequencies profile 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/glo_constants.py b/peregrine/glo_constants.py new file mode 100644 index 0000000..d35319d --- /dev/null +++ b/peregrine/glo_constants.py @@ -0,0 +1,13 @@ +# -*- coding: utf-8 -*- +from gps_constants import c +# GLO system parameters +glo_l1 = 1.602e9 # Hz +glo_l2 = 1.246e9 # Hz +glo_code_len = 511 +glo_chip_rate = 0.511e6 # Hz +glo_l1_step = 0.5625e6 # Hz + +glo_code_period = glo_code_len / glo_chip_rate +glo_code_wavelength = glo_code_period * c + +GLO_L1 = 'glo_l1' diff --git a/peregrine/gps_constants.py b/peregrine/gps_constants.py index 24c781a..cfa2b13 100644 --- a/peregrine/gps_constants.py +++ b/peregrine/gps_constants.py @@ -2,25 +2,25 @@ # 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 -l1ca_chip_rate = 1.023e6 # Hz +l1ca_chip_rate = 1.023e6 # Hz l1ca_code_length = 1023 l1ca_code_period = l1ca_code_length / l1ca_chip_rate l1ca_code_wavelength = l1ca_code_period * c -l2c_chip_rate = 1.023e6 # Hz +l2c_chip_rate = 1.023e6 # Hz -nominal_range = 26000e3 # m +nominal_range = 26000e3 # m L1CA = 'l1ca' L2C = 'l2c' diff --git a/peregrine/run.py b/peregrine/run.py index a50c0e0..a652a43 100755 --- a/peregrine/run.py +++ b/peregrine/run.py @@ -18,12 +18,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 import peregrine.gps_constants as gps +import peregrine.glo_constants as glo + class SaveConfigAction(argparse.Action): @@ -37,6 +40,7 @@ def __call__(self, parser, namespace, file_hnd, option_string=None): file_hnd.close() namespace.no_run = True + class LoadConfigAction(argparse.Action): def __init__(self, option_strings, dest, nargs=None, **kwargs): @@ -48,6 +52,7 @@ def __call__(self, parser, namespace, file_hnd, option_string=None): setattr(namespace, k, v) file_hnd.close() + def unpickle_iter(filenames): try: f = [open(filename, "r") for filename in filenames] @@ -62,6 +67,7 @@ def unpickle_iter(filenames): for fh in f: fh.close() + def populate_peregrine_cmd_line_arguments(parser): if sys.stdout.isatty(): progress_bar_default = 'stdout' @@ -162,6 +168,7 @@ def populate_peregrine_cmd_line_arguments(parser): return signalParam + def main(): default_logging_config() @@ -176,6 +183,9 @@ def main(): parser.add_argument("-n", "--skip-navigation", help="use previously saved navigation results", action="store_true") + parser.add_argument("--skip-glonass", + help="skip glonass", + action="store_true") populate_peregrine_cmd_line_arguments(parser) @@ -216,6 +226,7 @@ def main(): samples = {gps.L1CA: {'IF': freq_profile['GPS_L1_IF']}, gps.L2C: {'IF': freq_profile['GPS_L2_IF']}, + glo.GLO_L1: {'IF': freq_profile['GLO_L1_IF']}, 'samples_total': -1, 'sample_index': int(args.skip_samples)} @@ -230,10 +241,22 @@ def main(): acq_results_file) sys.exit(1) else: - for signal in [gps.L1CA]: - - samplesPerCode = int(round(freq_profile['sampling_freq'] / - (gps.l1ca_chip_rate / gps.l1ca_code_length))) + acq_results = [] + for signal in [gps.L1CA, glo.GLO_L1]: + if signal == gps.L1CA: + code_period = gps.l1ca_code_period + code_len = gps.l1ca_code_length + i_f = freq_profile['GPS_L1_IF'] + samplesPerCode = int(round(freq_profile['sampling_freq'] / + (gps.l1ca_chip_rate / gps.l1ca_code_length))) + else: + if args.skip_glonass: + continue + code_period = glo.glo_code_period + code_len = glo.glo_code_len + i_f = freq_profile['GLO_L1_IF'] + samplesPerCode = int(round(freq_profile['sampling_freq'] / + (glo.glo_chip_rate / glo.glo_code_len))) # Get 11ms of acquisition samples for fine frequency estimation load_samples(samples=samples, @@ -244,13 +267,10 @@ def main(): acq = Acquisition(signal, samples[signal]['samples'], freq_profile['sampling_freq'], - freq_profile['GPS_L1_IF'], - gps.l1ca_code_period * freq_profile['sampling_freq'], - gps.l1ca_code_length) - # only one signal - L1CA is expected to be acquired at the moment - # TODO: add handling of acquisition results from GLONASS once GLONASS - # acquisition is supported. - acq_results = acq.acquisition(progress_bar_output=args.progress_bar) + i_f, + code_period * freq_profile['sampling_freq'], + code_len) + acq_results += acq.acquisition(progress_bar_output=args.progress_bar) print "Acquisition is over!" @@ -347,7 +367,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 a015b71..56ed343 100644 --- a/peregrine/samples.py +++ b/peregrine/samples.py @@ -14,9 +14,11 @@ import math import defaults from peregrine.gps_constants import L1CA, L2C +from peregrine.glo_constants import GLO_L1 __all__ = ['load_samples', 'save_samples'] + def __load_samples_n_bits(filename, num_samples, num_skip, n_bits, value_lookup, channel_lookup): ''' @@ -77,6 +79,7 @@ def __load_samples_n_bits(filename, num_samples, num_skip, n_bits, samples[channel_lookup[rx]][:] = chan return samples + def __load_samples_one_bit(filename, num_samples, num_skip, channel_lookup): ''' Helper method to load single-bit samples from a file. @@ -103,6 +106,7 @@ def __load_samples_one_bit(filename, num_samples, num_skip, channel_lookup): return __load_samples_n_bits(filename, num_samples, num_skip, 1, value_lookup, channel_lookup) + def __load_samples_two_bits(filename, num_samples, num_skip, channel_lookup): ''' Helper method to load two-bit samples from a file. @@ -131,10 +135,11 @@ def __load_samples_two_bits(filename, num_samples, num_skip, channel_lookup): return __load_samples_n_bits(filename, num_samples, num_skip, 2, value_lookup, channel_lookup) + def _load_samples(filename, - num_samples = defaults.processing_block_size, - num_skip = 0, - file_format = 'piksi'): + num_samples=defaults.processing_block_size, + num_skip=0, + file_format='piksi'): """ Load sample data from a file. @@ -272,7 +277,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] @@ -300,6 +305,7 @@ def _load_samples(filename, return samples + def __get_samples_total(filename, file_format, sample_index): if file_format == 'int8': samples_block_size = 8 @@ -341,10 +347,11 @@ def __get_samples_total(filename, file_format, sample_index): return samples_total + def load_samples(samples, filename, - num_samples = defaults.processing_block_size, - file_format = 'piksi'): + num_samples=defaults.processing_block_size, + file_format='piksi'): if samples['samples_total'] == -1: samples['samples_total'] = __get_samples_total(filename, @@ -357,9 +364,12 @@ 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 + def save_samples(filename, samples, file_format='int8'): """ Save sample data to a file. diff --git a/tests/test_run.py b/tests/test_run.py index 2d2f13d..229ed81 100644 --- a/tests/test_run.py +++ b/tests/test_run.py @@ -10,12 +10,12 @@ import peregrine.run import peregrine.iqgen.iqgen_main as iqgen import sys -import cPickle import os import peregrine.acquisition as acq +import peregrine.gps_constants as gps +import peregrine.glo_constants as glo from mock import patch -from shutil import copyfile SAMPLES_PATH = 'tests/test_data/' # todo: the gpsl1ca_ci_samples.piksi_format.acq_results @@ -36,35 +36,57 @@ NEW_TRK_RES = SAMPLES_PATH + SAMPLES_FNAME + '.track_results' NEW_NAV_RES = SAMPLES_PATH + SAMPLES_FNAME + '.nav_results' -def generate_sample_file(gps_sv_prn, init_doppler, init_code_phase): + +def generate_sample_file(gps_sv_prn, glo_ch, init_doppler, init_code_phase): sample_file = 'iqgen-data-samples.bin' - freq_profile = 'low_rate' + freq_profile = 'normal_rate' params = ['iqgen_main'] + # GPS params += ['--gps-sv', str(gps_sv_prn)] - params += ['--bands', 'l1ca+l2c'] + params += ['--bands', 'l1ca'] + params += ['--doppler-type', 'const'] + params += ['--doppler-value', str(init_doppler)] + params += ['--tec', '0'] + params += ['--distance', '0'] + params += ['--chip_delay', str(init_code_phase)] + params += ['--amplitude-type', 'poly'] + params += ['--amplitude-units', 'snr-db'] + params += ['--amplitude-a0', '-17'] + # GLO + params += ['--glo-sv', str(glo_ch)] + params += ['--bands', 'l1'] params += ['--doppler-type', 'const'] - params += ['--doppler-value', str(init_doppler) ] + params += ['--doppler-value', str(init_doppler)] + params += ['--tec', '0'] + params += ['--distance', '0'] params += ['--message-type', 'crc'] params += ['--chip_delay', str(init_code_phase)] - params += ['--snr', '-5'] + params += ['--amplitude-type', 'poly'] + params += ['--amplitude-units', 'snr-db'] + params += ['--amplitude-a0', '-17'] + # common params += ['--generate', '1'] params += ['--encoder', '2bits'] params += ['--output', sample_file] params += ['--profile', freq_profile] + params += ['-j', '4'] print params with patch.object(sys, 'argv', params): iqgen.main() - return {'sample_file' : sample_file, - 'file_format' : '2bits_x2', - 'freq_profile' : freq_profile} + return {'sample_file': sample_file, + 'file_format': '2bits_x4', + 'freq_profile': freq_profile} + def get_acq_result_file_name(sample_file): return sample_file + '.acq_results' + def run_acq_test(init_doppler, init_code_phase): - for prn in range(1, 33, 5): - samples = generate_sample_file(prn, init_doppler, init_code_phase) + for ch in xrange(-7, 6): + prn = (ch + 8) * 2 + samples = generate_sample_file(prn, ch, init_doppler, init_code_phase) # Replace argv with args to skip tracking and navigation. with patch.object(sys, 'argv', @@ -84,30 +106,51 @@ def run_acq_test(init_doppler, init_code_phase): acq_results = acq.load_acq_results( get_acq_result_file_name(samples['sample_file'])) - acq_results = sorted(acq_results, lambda x, y: -1 if x.snr > y.snr else 1) - - assert len(acq_results) != 0 - - result = acq_results[0] - print "result = ", result - assert (result.prn + 1) == prn - - # check doppler phase estimation - doppler_diff = abs(abs(result.doppler) - abs(init_doppler)) - print "doppler_diff = ", doppler_diff - assert doppler_diff < 70.0 - - # check code phase estimation - code_phase_diff = abs(abs(result.code_phase) - abs(init_code_phase)) - print "code_phase_diff = ", code_phase_diff - assert code_phase_diff < 1.0 + glo_res = [] + gps_res = [] + for res in acq_results: + if isinstance(res, acq.GloAcquisitionResult): + glo_res.append(res) + else: + gps_res.append(res) + glo_res = sorted(glo_res, lambda x, y: -1 if x.snr > y.snr else 1) + gps_res = sorted(gps_res, lambda x, y: -1 if x.snr > y.snr else 1) + + def check_result(res): + assert len(res) != 0 + + result = res[0] + print "result = ", result + if isinstance(result, acq.GloAcquisitionResult): + assert (result.prn) == ch + code_length = glo.glo_code_len + else: + assert (result.prn + 1) == prn + code_length = gps.l1ca_code_length + + # check doppler phase estimation + doppler_diff = abs(abs(result.doppler) - abs(init_doppler)) + print "doppler_diff = ", doppler_diff + assert doppler_diff < 200.0 + + # check code phase estimation + code_phase = result.code_phase + if code_phase > code_length / 2: + code_phase = code_phase - code_length + code_phase_diff = abs(abs(code_phase) - abs(init_code_phase)) + print "code_phase_diff = ", code_phase_diff + assert code_phase_diff < 1.0 + + check_result(glo_res) + check_result(gps_res) # Clean-up. os.remove(get_acq_result_file_name(samples['sample_file'])) os.remove(samples['sample_file']) + def test_acquisition(): - run_acq_test(1000, 0) + run_acq_test(775, 0) # def test_tracking():