/*! * \file main.cc * \brief Main file of the Front-end calibration program. * \author Javier Arribas, 2013. jarribas(at)cttc.es * * * ----------------------------------------------------------------------------- * * GNSS-SDR is a Global Navigation Satellite System software-defined receiver. * This file is part of GNSS-SDR. * * Copyright (C) 2010-2020 (see AUTHORS file for a list of contributors) * SPDX-License-Identifier: GPL-3.0-or-later * * ----------------------------------------------------------------------------- */ #ifndef FRONT_END_CAL_VERSION #define FRONT_END_CAL_VERSION "0.0.1" #endif #include "GPS_L1_CA.h" // for GPS_L1_CA_COD... #include "concurrent_map.h" #include "concurrent_queue.h" #include "configuration_interface.h" // for Configuration... #include "file_configuration.h" #include "front_end_cal.h" #include "gnss_block_factory.h" #include "gnss_block_interface.h" // for GNSSBlockInte... #include "gnss_sdr_filesystem.h" #include "gnss_sdr_flags.h" #include "gnss_synchro.h" #include "gps_acq_assist.h" // for Gps_Acq_Assist #include "gps_almanac.h" #include "gps_ephemeris.h" #include "gps_iono.h" #include "gps_l1_ca_pcps_acquisition_fine_doppler.h" #include "gps_utc_model.h" #include "signal_source_interface.h" // for SignalSourceInterface #include // for bad_any_cast #include #include #include #include #include // for block #include #include #include #include #include #include // for gr_complex #include // for io_signature #include // for block_sptr #include #include // for pmt_t, to_long #include // for mp #include #include // for round #include #include #include // for ctime #include #include // for std::setiosflags, std::setprecision #include #include #include #include // for logic_error #include #include #include #include #if HAS_GENERIC_LAMBDA #else #include #endif #if PMT_USES_BOOST_ANY namespace wht = boost; #else namespace wht = std; #endif #if GFLAGS_OLD_NAMESPACE namespace gflags { using namespace google; } #endif DECLARE_string(log_dir); Concurrent_Map global_gps_ephemeris_map; Concurrent_Map global_gps_iono_map; Concurrent_Map global_gps_utc_model_map; Concurrent_Map global_gps_almanac_map; Concurrent_Map global_gps_acq_assist_map; Concurrent_Queue global_gps_acq_assist_queue; bool stop; Concurrent_Queue channel_internal_queue; std::vector gnss_sync_vector; Gnss_Synchro gnss_synchro{}; // ######## GNURADIO BLOCK MESSAGE RECEVER ######### class FrontEndCal_msg_rx; using FrontEndCal_msg_rx_sptr = gnss_shared_ptr; FrontEndCal_msg_rx_sptr FrontEndCal_msg_rx_make(); class FrontEndCal_msg_rx : public gr::block { private: friend FrontEndCal_msg_rx_sptr FrontEndCal_msg_rx_make(); void msg_handler_channel_events(const pmt::pmt_t& msg); FrontEndCal_msg_rx(); public: int rx_message{0}; }; FrontEndCal_msg_rx_sptr FrontEndCal_msg_rx_make() { return FrontEndCal_msg_rx_sptr(new FrontEndCal_msg_rx()); } void FrontEndCal_msg_rx::msg_handler_channel_events(const pmt::pmt_t& msg) { try { int64_t message = pmt::to_long(msg); rx_message = message; channel_internal_queue.push(rx_message); } catch (const wht::bad_any_cast& e) { LOG(WARNING) << "msg_handler_telemetry Bad any cast!\n"; rx_message = 0; } } FrontEndCal_msg_rx::FrontEndCal_msg_rx() : gr::block("FrontEndCal_msg_rx", gr::io_signature::make(0, 0, 0), gr::io_signature::make(0, 0, 0)) { this->message_port_register_in(pmt::mp("events")); this->set_msg_handler(pmt::mp("events"), #if HAS_GENERIC_LAMBDA [this](auto&& PH1) { msg_handler_channel_events(PH1); }); #else #if USE_BOOST_BIND_PLACEHOLDERS boost::bind(&FrontEndCal_msg_rx::msg_handler_channel_events, this, boost::placeholders::_1)); #else boost::bind(&FrontEndCal_msg_rx::msg_handler_channel_events, this, _1)); #endif #endif } void wait_message() { while (!stop) { int message; channel_internal_queue.wait_and_pop(message); // std::cout<<"Acq message rx="<reset(); break; case 2: // negative acq // acquisition->reset(); break; case 3: stop = true; break; default: break; } } } bool front_end_capture(const std::shared_ptr& configuration) { auto success = false; std::string trace_step; gr::top_block_sptr top_block; GNSSBlockFactory block_factory; std::shared_ptr> queue; queue = std::make_shared>(); top_block = gr::make_top_block("Acquisition test"); try { // Note: the block_factory returns a unique_ptr (what you would get with an "auto" // declaration), but the flowgraph uses shared_ptr. Without further understanding of why // it should matter in this context, used shared_ptr throughout std::shared_ptr source; std::shared_ptr conditioner; trace_step = "creating source"; source = block_factory.GetSignalSource(configuration.get(), queue.get()); trace_step = "creating signal conditioner"; conditioner = block_factory.GetSignalConditioner(configuration.get()); trace_step = "unexpected in setup code"; gr::block_sptr sink; sink = gr::blocks::file_sink::make(sizeof(gr_complex), "tmp_capture.dat"); // -- Find number of samples per spreading code --- int64_t fs_in_ = configuration->property("GNSS-SDR.internal_fs_sps", 2048000); int samples_per_code = round(fs_in_ / (GPS_L1_CA_CODE_RATE_CPS / GPS_L1_CA_CODE_LENGTH_CHIPS)); int nsamples = samples_per_code * 50; int skip_samples = fs_in_ * 5; // skip 5 seconds gr::block_sptr head = gr::blocks::head::make(sizeof(gr_complex), nsamples); gr::block_sptr skiphead = gr::blocks::skiphead::make(sizeof(gr_complex), skip_samples); trace_step = "connecting the GNU Radio blocks"; source->connect(top_block); conditioner->connect(top_block); top_block->connect(source->get_right_block(), 0, conditioner->get_left_block(), 0); top_block->connect(conditioner->get_right_block(), 0, skiphead, 0); top_block->connect(skiphead, 0, head, 0); top_block->connect(head, 0, sink, 0); top_block->run(); success = true; } catch (std::exception const& e) { std::cout << "Exception caught " << trace_step << ": " << e.what() << std::endl; } return success; } static time_t utc_time(int week, int64_t tow) { time_t t; /* Jan 5/6 midnight 1980 - beginning of GPS time as Unix time */ t = 315964801; /* soon week will wrap again, uh oh... */ /* TS 44.031: GPSTOW, range 0-604799.92, resolution 0.08 sec, 23-bit presentation */ /* The factor 0.08 was applied in the ephemeris SUPL class */ /* here the tow is in [s] */ t += (1024 + week) * 604800 + tow; return t; } int main(int argc, char** argv) { try { const std::string intro_help( std::string("\n RTL-SDR E4000 RF front-end center frequency and sampling rate calibration tool that uses GPS signals\n") + "Copyright (C) 2010-2019 (see AUTHORS file for a list of contributors)\n" + "This program comes with ABSOLUTELY NO WARRANTY;\n" + "See COPYING file to see a copy of the General Public License\n \n"); gflags::SetUsageMessage(intro_help); google::SetVersionString(FRONT_END_CAL_VERSION); gflags::ParseCommandLineFlags(&argc, &argv, true); std::cout << "Initializing... Please wait.\n"; } catch (const std::exception& e) { std::cerr << e.what() << '\n'; std::cout << "front-end-cal program ended.\n"; return 1; } google::InitGoogleLogging(argv[0]); if (FLAGS_log_dir.empty()) { std::cout << "Logging will be done at " << "/tmp" << '\n' << "Use front-end-cal --log_dir=/path/to/log to change that." << '\n'; } else { try { const fs::path p(FLAGS_log_dir); if (!fs::exists(p)) { std::cout << "The path " << FLAGS_log_dir << " does not exist, attempting to create it" << '\n'; errorlib::error_code ec; if (!fs::create_directory(p, ec)) { std::cerr << "Could not create the " << FLAGS_log_dir << " folder. Front-end-cal program ended.\n"; gflags::ShutDownCommandLineFlags(); return 1; } } std::cout << "Logging with be done at " << FLAGS_log_dir << '\n'; } catch (const std::exception& e) { std::cerr << e.what() << '\n'; std::cerr << "Could not create the " << FLAGS_log_dir << " folder. Front-end-cal program ended.\n"; gflags::ShutDownCommandLineFlags(); return 1; } } // 0. Instantiate the FrontEnd Calibration class try { FrontEndCal front_end_cal; // 1. Load configuration parameters from config file std::shared_ptr configuration = std::make_shared(FLAGS_config_file); front_end_cal.set_configuration(configuration); // 2. Get SUPL information from server: Ephemeris record, assistance info and TOW try { if (front_end_cal.get_ephemeris() == true) { std::cout << "SUPL data received OK!\n"; } else { std::cout << "Failure connecting to SUPL server\n"; } } catch (const boost::exception& e) { std::cout << "Failure connecting to SUPL server\n"; } // 3. Capture some front-end samples to hard disk try { if (front_end_capture(configuration)) { std::cout << "Front-end RAW samples captured\n"; } else { std::cout << "Failure capturing front-end samples\n"; } } catch (const boost::bad_lexical_cast& e) { std::cout << "Exception caught while capturing samples (bad lexical cast)\n"; } catch (const std::exception& e) { std::cout << "Exception caught while capturing samples: " << e.what() << '\n'; } catch (...) { std::cout << "Unexpected exception\n"; } // 4. Setup GNU Radio flowgraph (file_source -> Acquisition_10m) gr::top_block_sptr top_block; top_block = gr::make_top_block("Acquisition test"); // Satellite signal definition gnss_synchro = Gnss_Synchro(); gnss_synchro.Channel_ID = 0; gnss_synchro.System = 'G'; std::string signal = "1C"; signal.copy(gnss_synchro.Signal, 2, 0); gnss_synchro.PRN = 1; int64_t fs_in_ = configuration->property("GNSS-SDR.internal_fs_sps", 2048000); configuration->set_property("Acquisition.max_dwells", "10"); auto acquisition = std::make_shared(configuration.get(), "Acquisition", 1, 1); acquisition->set_channel(1); acquisition->set_gnss_synchro(&gnss_synchro); acquisition->set_threshold(configuration->property("Acquisition.threshold", 2.0)); acquisition->set_doppler_max(configuration->property("Acquisition.doppler_max", 10000)); acquisition->set_doppler_step(configuration->property("Acquisition.doppler_step", 250)); gr::block_sptr source; source = gr::blocks::file_source::make(sizeof(gr_complex), "tmp_capture.dat"); #if GNURADIO_USES_STD_POINTERS std::shared_ptr msg_rx; #else boost::shared_ptr msg_rx; #endif try { msg_rx = FrontEndCal_msg_rx_make(); } catch (const std::exception& e) { std::cout << "Failure connecting the message port system: " << e.what() << '\n'; exit(0); } try { acquisition->connect(top_block); top_block->connect(source, 0, acquisition->get_left_block(), 0); top_block->msg_connect(acquisition->get_right_block(), pmt::mp("events"), msg_rx, pmt::mp("events")); } catch (const std::exception& e) { std::cout << "Failure connecting the GNU Radio blocks: " << e.what() << '\n'; } // 5. Run the flowgraph // Get visible GPS satellites (positive acquisitions with Doppler measurements) // Compute Doppler estimations // todo: Fix the front-end cal to support new channel internal message system (no more external queues) std::map doppler_measurements_map; std::map cn0_measurements_map; std::thread ch_thread; // record startup time std::chrono::time_point start; std::chrono::time_point end; std::chrono::duration elapsed_seconds{}; start = std::chrono::system_clock::now(); bool start_msg = true; for (unsigned int PRN = 1; PRN < 33; PRN++) { gnss_synchro.PRN = PRN; acquisition->set_gnss_synchro(&gnss_synchro); acquisition->init(); acquisition->set_local_code(); acquisition->reset(); stop = false; try { ch_thread = std::thread(wait_message); } catch (const std::exception& e) { LOG(INFO) << "Exception caught (thread resource error)"; } top_block->run(); if (start_msg == true) { std::cout << "Searching for GPS Satellites in L1 band...\n"; std::cout << "["; start_msg = false; } if (!gnss_sync_vector.empty()) { std::cout << " " << PRN << " "; double doppler_measurement_hz = 0; for (auto& it : gnss_sync_vector) { doppler_measurement_hz += it.Acq_doppler_hz; } doppler_measurement_hz = doppler_measurement_hz / gnss_sync_vector.size(); doppler_measurements_map.insert(std::pair(PRN, doppler_measurement_hz)); } else { std::cout << " . "; } try { channel_internal_queue.push(3); } catch (const boost::exception& e) { LOG(INFO) << "Exception caught while pushing to the internal queue."; } try { ch_thread.join(); } catch (const std::exception& e) { LOG(INFO) << "Exception caught while joining threads."; } gnss_sync_vector.clear(); #if GNURADIO_USES_STD_POINTERS std::dynamic_pointer_cast(source)->seek(0, 0); #else boost::dynamic_pointer_cast(source)->seek(0, 0); #endif std::cout.flush(); } std::cout << "]\n"; // report the elapsed time end = std::chrono::system_clock::now(); elapsed_seconds = end - start; std::cout << "Total signal acquisition run time " << elapsed_seconds.count() << " [seconds]\n"; // 6. find TOW from SUPL assistance double current_TOW = 0; try { if (global_gps_ephemeris_map.size() > 0) { std::map Eph_map; Eph_map = global_gps_ephemeris_map.get_map_copy(); current_TOW = Eph_map.begin()->second.tow; time_t t = utc_time(Eph_map.begin()->second.WN, static_cast(current_TOW)); std::cout << "Reference Time:\n"; std::cout << " GPS Week: " << Eph_map.begin()->second.WN << '\n'; std::cout << " GPS TOW: " << static_cast(current_TOW) << " " << static_cast(current_TOW) * 0.08 << '\n'; std::cout << " ~ UTC: " << ctime(&t) << '\n'; std::cout << "Current TOW obtained from SUPL assistance = " << current_TOW << '\n'; } else { std::cout << "Unable to get Ephemeris SUPL assistance. TOW is unknown!\n"; gflags::ShutDownCommandLineFlags(); std::cout << "GNSS-SDR Front-end calibration program ended.\n"; return 0; } } catch (const boost::exception& e) { std::cout << "Exception in getting Global ephemeris map\n"; gflags::ShutDownCommandLineFlags(); std::cout << "GNSS-SDR Front-end calibration program ended.\n"; return 0; } // Get user position from config file (or from SUPL using GSM Cell ID) double lat_deg = configuration->property("GNSS-SDR.init_latitude_deg", 41.0); double lon_deg = configuration->property("GNSS-SDR.init_longitude_deg", 2.0); double altitude_m = configuration->property("GNSS-SDR.init_altitude_m", 100); std::cout << "Reference location (defined in config file):\n"; std::cout << "Latitude=" << lat_deg << " [º]\n"; std::cout << "Longitude=" << lon_deg << " [º]\n"; std::cout << "Altitude=" << altitude_m << " [m]\n"; if (doppler_measurements_map.empty()) { std::cout << "Sorry, no GPS satellites detected in the front-end capture, please check the antenna setup...\n"; gflags::ShutDownCommandLineFlags(); std::cout << "GNSS-SDR Front-end calibration program ended.\n"; return 0; } std::map f_if_estimation_Hz_map; std::map f_fs_estimation_Hz_map; std::map f_ppm_estimation_Hz_map; std::cout << std::setiosflags(std::ios::fixed) << std::setprecision(2) << "Doppler analysis results:\n"; std::cout << "SV ID Measured [Hz] Predicted [Hz]\n"; for (auto& it : doppler_measurements_map) { try { double doppler_estimated_hz; doppler_estimated_hz = front_end_cal.estimate_doppler_from_eph(it.first, current_TOW, lat_deg, lon_deg, altitude_m); std::cout << " " << it.first << " " << it.second << " " << doppler_estimated_hz << '\n'; // 7. Compute front-end IF and sampling frequency estimation // Compare with the measurements and compute clock drift using FE model double estimated_fs_Hz; double estimated_f_if_Hz; double f_osc_err_ppm; front_end_cal.GPS_L1_front_end_model_E4000(doppler_estimated_hz, it.second, fs_in_, &estimated_fs_Hz, &estimated_f_if_Hz, &f_osc_err_ppm); f_if_estimation_Hz_map.insert(std::pair(it.first, estimated_f_if_Hz)); f_fs_estimation_Hz_map.insert(std::pair(it.first, estimated_fs_Hz)); f_ppm_estimation_Hz_map.insert(std::pair(it.first, f_osc_err_ppm)); } catch (const std::logic_error& e) { std::cout << "Logic error caught: " << e.what() << '\n'; } catch (const boost::lock_error& e) { std::cout << "Exception caught while reading ephemeris\n"; } catch (const std::exception& ex) { std::cout << " " << it.first << " " << it.second << " (Eph not found)\n"; } } // FINAL FE estimations double mean_f_if_Hz = 0; double mean_fs_Hz = 0; double mean_osc_err_ppm = 0; int n_elements = f_if_estimation_Hz_map.size(); for (auto& it : f_if_estimation_Hz_map) { mean_f_if_Hz += it.second; const auto est_fs = f_fs_estimation_Hz_map.find(it.first); if (est_fs != f_fs_estimation_Hz_map.cend()) { mean_fs_Hz += est_fs->second; } const auto est_ppm = f_ppm_estimation_Hz_map.find(it.first); if (est_ppm != f_ppm_estimation_Hz_map.cend()) { mean_osc_err_ppm += est_ppm->second; } } mean_f_if_Hz /= n_elements; mean_fs_Hz /= n_elements; mean_osc_err_ppm /= n_elements; std::cout << std::setiosflags(std::ios::fixed) << std::setprecision(2) << "Parameters estimation for Elonics E4000 Front-End:\n"; std::cout << "Sampling frequency =" << mean_fs_Hz << " [Hz]\n"; std::cout << "IF bias present in baseband=" << mean_f_if_Hz << " [Hz]\n"; std::cout << "Reference oscillator error =" << mean_osc_err_ppm << " [ppm]\n"; std::cout << std::setiosflags(std::ios::fixed) << std::setprecision(2) << "Corrected Doppler vs. Predicted\n"; std::cout << "SV ID Corrected [Hz] Predicted [Hz]\n"; for (auto& it : doppler_measurements_map) { try { double doppler_estimated_hz; doppler_estimated_hz = front_end_cal.estimate_doppler_from_eph(it.first, current_TOW, lat_deg, lon_deg, altitude_m); std::cout << " " << it.first << " " << it.second - mean_f_if_Hz << " " << doppler_estimated_hz << '\n'; } catch (const std::logic_error& e) { std::cout << "Logic error caught: " << e.what() << '\n'; } catch (const boost::lock_error& e) { std::cout << "Exception caught while reading ephemeris\n"; } catch (const std::exception& ex) { std::cout << " " << it.first << " " << it.second - mean_f_if_Hz << " (Eph not found)\n"; } } } catch (const std::exception& e) { std::cerr << "Exception: " << e.what(); gflags::ShutDownCommandLineFlags(); return 1; } catch (...) { std::cerr << "Unknown error\n"; gflags::ShutDownCommandLineFlags(); return 1; } gflags::ShutDownCommandLineFlags(); std::cout << "GNSS-SDR Front-end calibration program ended.\n"; return 0; }