From bcd7c25cd1eff66583116acde1bed1d742400c15 Mon Sep 17 00:00:00 2001 From: Carles Fernandez Date: Sat, 7 Nov 2020 21:33:26 +0100 Subject: [PATCH] Add Galileo E6 signal structure based on E6-B/C Codes Technical Note, Issue 1, Jan 2019. Add Acquisition, Tracking and TLM blocks for Galileo E6 B/C. The decoder does nothing --- docs/changelog.md | 7 + src/algorithms/PVT/adapters/rtklib_pvt.cc | 145 ++++-- .../PVT/gnuradio_blocks/rtklib_pvt_gs.cc | 375 ++++++++++++++- .../PVT/gnuradio_blocks/rtklib_pvt_gs.h | 1 + .../acquisition/adapters/CMakeLists.txt | 2 + .../adapters/galileo_e6_pcps_acquisition.cc | 253 ++++++++++ .../adapters/galileo_e6_pcps_acquisition.h | 189 ++++++++ .../pcps_opencl_acquisition_cc.h | 2 +- src/algorithms/libs/CMakeLists.txt | 2 + .../libs/galileo_e6_signal_processing.cc | 253 ++++++++++ .../libs/galileo_e6_signal_processing.h | 111 +++++ src/algorithms/libs/gnss_signal_processing.cc | 108 +++++ src/algorithms/libs/gnss_signal_processing.h | 8 + .../gnuradio_blocks/hybrid_observables_gs.cc | 4 + .../gnuradio_blocks/hybrid_observables_gs.h | 1 + .../adapters/signal_generator.cc | 5 + .../gnuradio_blocks/signal_generator_c.cc | 67 +++ .../telemetry_decoder/adapters/CMakeLists.txt | 2 + .../adapters/galileo_e6_telemetry_decoder.cc | 93 ++++ .../adapters/galileo_e6_telemetry_decoder.h | 117 +++++ .../galileo_telemetry_decoder_gs.cc | 97 ++-- .../galileo_telemetry_decoder_gs.h | 1 + .../tracking/adapters/CMakeLists.txt | 2 + .../adapters/galileo_e6_dll_pll_tracking.cc | 140 ++++++ .../adapters/galileo_e6_dll_pll_tracking.h | 117 +++++ .../gnuradio_blocks/dll_pll_veml_tracking.cc | 50 +- src/core/receiver/gnss_block_factory.cc | 73 ++- src/core/receiver/gnss_flowgraph.cc | 92 ++++ src/core/receiver/gnss_flowgraph.h | 2 + src/core/system_parameters/CMakeLists.txt | 1 + src/core/system_parameters/Galileo_E6.h | 214 +++++++++ src/tests/test_main.cc | 1 + .../arithmetic/code_generation_test.cc | 44 ++ .../galileo_e5b_pcps_acquisition_test.cc | 2 +- .../galileo_e6_pcps_acquisition_test.cc | 445 ++++++++++++++++++ 35 files changed, 2912 insertions(+), 114 deletions(-) create mode 100644 src/algorithms/acquisition/adapters/galileo_e6_pcps_acquisition.cc create mode 100644 src/algorithms/acquisition/adapters/galileo_e6_pcps_acquisition.h create mode 100644 src/algorithms/libs/galileo_e6_signal_processing.cc create mode 100644 src/algorithms/libs/galileo_e6_signal_processing.h create mode 100644 src/algorithms/telemetry_decoder/adapters/galileo_e6_telemetry_decoder.cc create mode 100644 src/algorithms/telemetry_decoder/adapters/galileo_e6_telemetry_decoder.h create mode 100644 src/algorithms/tracking/adapters/galileo_e6_dll_pll_tracking.cc create mode 100644 src/algorithms/tracking/adapters/galileo_e6_dll_pll_tracking.h create mode 100644 src/core/system_parameters/Galileo_E6.h create mode 100644 src/tests/unit-tests/signal-processing-blocks/acquisition/galileo_e6_pcps_acquisition_test.cc diff --git a/docs/changelog.md b/docs/changelog.md index 923f59f04..16bdd54d9 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -23,6 +23,13 @@ SPDX-FileCopyrightText: 2011-2020 Carles Fernandez-Prades property("Channels_1B.count", 0); const int gal_E5a_count = configuration->property("Channels_5X.count", 0); const int gal_E5b_count = configuration->property("Channels_7X.count", 0); + const int gal_E6_count = configuration->property("Channels_E6.count", 0); const int glo_1G_count = configuration->property("Channels_1G.count", 0); const int glo_2G_count = configuration->property("Channels_2G.count", 0); const int bds_B1_count = configuration->property("Channels_B1.count", 0); const int bds_B3_count = configuration->property("Channels_B3.count", 0); - if ((gps_1C_count != 0) && (gps_2S_count == 0) && (gps_L5_count == 0) && (gal_1B_count == 0) && (gal_E5a_count == 0) && (gal_E5b_count == 0) && (glo_1G_count == 0) && (glo_2G_count == 0) && (bds_B1_count == 0) && (bds_B3_count == 0)) + if ((gps_1C_count != 0) && (gps_2S_count == 0) && (gps_L5_count == 0) && (gal_1B_count == 0) && (gal_E5a_count == 0) && (gal_E5b_count == 0) && (gal_E6_count == 0) && (glo_1G_count == 0) && (glo_2G_count == 0) && (bds_B1_count == 0) && (bds_B3_count == 0)) { pvt_output_parameters.type_of_receiver = 1; // L1 } - if ((gps_1C_count == 0) && (gps_2S_count != 0) && (gps_L5_count == 0) && (gal_1B_count == 0) && (gal_E5a_count == 0) && (gal_E5b_count == 0) && (glo_1G_count == 0) && (glo_2G_count == 0) && (bds_B1_count == 0) && (bds_B3_count == 0)) + if ((gps_1C_count == 0) && (gps_2S_count != 0) && (gps_L5_count == 0) && (gal_1B_count == 0) && (gal_E5a_count == 0) && (gal_E5b_count == 0) && (gal_E6_count == 0) && (glo_1G_count == 0) && (glo_2G_count == 0) && (bds_B1_count == 0) && (bds_B3_count == 0)) { pvt_output_parameters.type_of_receiver = 2; // GPS L2C } - if ((gps_1C_count == 0) && (gps_2S_count == 0) && (gps_L5_count != 0) && (gal_1B_count == 0) && (gal_E5a_count == 0) && (gal_E5b_count == 0) && (glo_1G_count == 0) && (glo_2G_count == 0) && (bds_B1_count == 0) && (bds_B3_count == 0)) + if ((gps_1C_count == 0) && (gps_2S_count == 0) && (gps_L5_count != 0) && (gal_1B_count == 0) && (gal_E5a_count == 0) && (gal_E5b_count == 0) && (gal_E6_count == 0) && (glo_1G_count == 0) && (glo_2G_count == 0) && (bds_B1_count == 0) && (bds_B3_count == 0)) { pvt_output_parameters.type_of_receiver = 3; // L5 } - if ((gps_1C_count == 0) && (gps_2S_count == 0) && (gps_L5_count == 0) && (gal_1B_count != 0) && (gal_E5a_count == 0) && (gal_E5b_count == 0) && (glo_1G_count == 0) && (glo_2G_count == 0) && (bds_B1_count == 0) && (bds_B3_count == 0)) + if ((gps_1C_count == 0) && (gps_2S_count == 0) && (gps_L5_count == 0) && (gal_1B_count != 0) && (gal_E5a_count == 0) && (gal_E5b_count == 0) && (gal_E6_count == 0) && (glo_1G_count == 0) && (glo_2G_count == 0) && (bds_B1_count == 0) && (bds_B3_count == 0)) { pvt_output_parameters.type_of_receiver = 4; // E1 } - if ((gps_1C_count == 0) && (gps_2S_count == 0) && (gps_L5_count == 0) && (gal_1B_count == 0) && (gal_E5a_count != 0) && (gal_E5b_count == 0) && (glo_1G_count == 0) && (glo_2G_count == 0) && (bds_B1_count == 0) && (bds_B3_count == 0)) + if ((gps_1C_count == 0) && (gps_2S_count == 0) && (gps_L5_count == 0) && (gal_1B_count == 0) && (gal_E5a_count != 0) && (gal_E5b_count == 0) && (gal_E6_count == 0) && (glo_1G_count == 0) && (glo_2G_count == 0) && (bds_B1_count == 0) && (bds_B3_count == 0)) { pvt_output_parameters.type_of_receiver = 5; // E5a } - if ((gps_1C_count == 0) && (gps_2S_count == 0) && (gps_L5_count == 0) && (gal_1B_count == 0) && (gal_E5a_count == 0) && (gal_E5b_count != 0) && (glo_1G_count == 0) && (glo_2G_count == 0) && (bds_B1_count == 0) && (bds_B3_count == 0)) + if ((gps_1C_count == 0) && (gps_2S_count == 0) && (gps_L5_count == 0) && (gal_1B_count == 0) && (gal_E5a_count == 0) && (gal_E5b_count != 0) && (gal_E6_count == 0) && (glo_1G_count == 0) && (glo_2G_count == 0) && (bds_B1_count == 0) && (bds_B3_count == 0)) { pvt_output_parameters.type_of_receiver = 6; } - if ((gps_1C_count != 0) && (gps_2S_count != 0) && (gps_L5_count == 0) && (gal_1B_count == 0) && (gal_E5a_count == 0) && (gal_E5b_count == 0) && (glo_1G_count == 0) && (glo_2G_count == 0) && (bds_B1_count == 0) && (bds_B3_count == 0)) + if ((gps_1C_count != 0) && (gps_2S_count != 0) && (gps_L5_count == 0) && (gal_1B_count == 0) && (gal_E5a_count == 0) && (gal_E5b_count == 0) && (gal_E6_count == 0) && (glo_1G_count == 0) && (glo_2G_count == 0) && (bds_B1_count == 0) && (bds_B3_count == 0)) { pvt_output_parameters.type_of_receiver = 7; // GPS L1 C/A + GPS L2C } - if ((gps_1C_count != 0) && (gps_2S_count == 0) && (gps_L5_count != 0) && (gal_1B_count == 0) && (gal_E5a_count == 0) && (gal_E5b_count == 0) && (glo_1G_count == 0) && (glo_2G_count == 0) && (bds_B1_count == 0) && (bds_B3_count == 0)) + if ((gps_1C_count != 0) && (gps_2S_count == 0) && (gps_L5_count != 0) && (gal_1B_count == 0) && (gal_E5a_count == 0) && (gal_E5b_count == 0) && (gal_E6_count == 0) && (glo_1G_count == 0) && (glo_2G_count == 0) && (bds_B1_count == 0) && (bds_B3_count == 0)) { pvt_output_parameters.type_of_receiver = 8; // L1+L5 } - if ((gps_1C_count != 0) && (gps_2S_count == 0) && (gps_L5_count == 0) && (gal_1B_count != 0) && (gal_E5a_count == 0) && (gal_E5b_count == 0) && (glo_1G_count == 0) && (glo_2G_count == 0) && (bds_B1_count == 0) && (bds_B3_count == 0)) + if ((gps_1C_count != 0) && (gps_2S_count == 0) && (gps_L5_count == 0) && (gal_1B_count != 0) && (gal_E5a_count == 0) && (gal_E5b_count == 0) && (gal_E6_count == 0) && (glo_1G_count == 0) && (glo_2G_count == 0) && (bds_B1_count == 0) && (bds_B3_count == 0)) { pvt_output_parameters.type_of_receiver = 9; // L1+E1 } - if ((gps_1C_count != 0) && (gps_2S_count == 0) && (gps_L5_count == 0) && (gal_1B_count == 0) && (gal_E5a_count != 0) && (gal_E5b_count == 0) && (glo_1G_count == 0) && (glo_2G_count == 0) && (bds_B1_count == 0) && (bds_B3_count == 0)) + if ((gps_1C_count != 0) && (gps_2S_count == 0) && (gps_L5_count == 0) && (gal_1B_count == 0) && (gal_E5a_count != 0) && (gal_E5b_count == 0) && (gal_E6_count == 0) && (glo_1G_count == 0) && (glo_2G_count == 0) && (bds_B1_count == 0) && (bds_B3_count == 0)) { pvt_output_parameters.type_of_receiver = 10; // GPS L1 C/A + Galileo E5a } - if ((gps_1C_count != 0) && (gps_2S_count == 0) && (gps_L5_count == 0) && (gal_1B_count == 0) && (gal_E5a_count == 0) && (gal_E5b_count != 0) && (glo_1G_count == 0) && (glo_2G_count == 0) && (bds_B1_count == 0) && (bds_B3_count == 0)) + if ((gps_1C_count != 0) && (gps_2S_count == 0) && (gps_L5_count == 0) && (gal_1B_count == 0) && (gal_E5a_count == 0) && (gal_E5b_count != 0) && (gal_E6_count == 0) && (glo_1G_count == 0) && (glo_2G_count == 0) && (bds_B1_count == 0) && (bds_B3_count == 0)) { pvt_output_parameters.type_of_receiver = 11; } - if ((gps_1C_count == 0) && (gps_2S_count != 0) && (gps_L5_count == 0) && (gal_1B_count != 0) && (gal_E5a_count == 0) && (gal_E5b_count == 0) && (glo_1G_count == 0) && (glo_2G_count == 0) && (bds_B1_count == 0) && (bds_B3_count == 0)) + if ((gps_1C_count == 0) && (gps_2S_count != 0) && (gps_L5_count == 0) && (gal_1B_count != 0) && (gal_E5a_count == 0) && (gal_E5b_count == 0) && (gal_E6_count == 0) && (glo_1G_count == 0) && (glo_2G_count == 0) && (bds_B1_count == 0) && (bds_B3_count == 0)) { pvt_output_parameters.type_of_receiver = 12; // Galileo E1B + GPS L2C } - if ((gps_1C_count == 0) && (gps_2S_count == 0) && (gps_L5_count != 0) && (gal_1B_count == 0) && (gal_E5a_count != 0) && (gal_E5b_count == 0) && (glo_1G_count == 0) && (glo_2G_count == 0) && (bds_B1_count == 0) && (bds_B3_count == 0)) + if ((gps_1C_count == 0) && (gps_2S_count == 0) && (gps_L5_count != 0) && (gal_1B_count == 0) && (gal_E5a_count != 0) && (gal_E5b_count == 0) && (gal_E6_count == 0) && (glo_1G_count == 0) && (glo_2G_count == 0) && (bds_B1_count == 0) && (bds_B3_count == 0)) { pvt_output_parameters.type_of_receiver = 13; // L5+E5a } - if ((gps_1C_count == 0) && (gps_2S_count == 0) && (gps_L5_count == 0) && (gal_1B_count != 0) && (gal_E5a_count != 0) && (gal_E5b_count == 0) && (glo_1G_count == 0) && (glo_2G_count == 0) && (bds_B1_count == 0) && (bds_B3_count == 0)) + if ((gps_1C_count == 0) && (gps_2S_count == 0) && (gps_L5_count == 0) && (gal_1B_count != 0) && (gal_E5a_count != 0) && (gal_E5b_count == 0) && (gal_E6_count == 0) && (glo_1G_count == 0) && (glo_2G_count == 0) && (bds_B1_count == 0) && (bds_B3_count == 0)) { pvt_output_parameters.type_of_receiver = 14; // Galileo E1B + Galileo E5a } - if ((gps_1C_count == 0) && (gps_2S_count == 0) && (gps_L5_count == 0) && (gal_1B_count != 0) && (gal_E5a_count == 0) && (gal_E5b_count != 0) && (glo_1G_count == 0) && (glo_2G_count == 0) && (bds_B1_count == 0) && (bds_B3_count == 0)) + if ((gps_1C_count == 0) && (gps_2S_count == 0) && (gps_L5_count == 0) && (gal_1B_count != 0) && (gal_E5a_count == 0) && (gal_E5b_count != 0) && (gal_E6_count == 0) && (glo_1G_count == 0) && (glo_2G_count == 0) && (bds_B1_count == 0) && (bds_B3_count == 0)) { pvt_output_parameters.type_of_receiver = 15; } - if ((gps_1C_count == 0) && (gps_2S_count != 0) && (gps_L5_count != 0) && (gal_1B_count == 0) && (gal_E5a_count == 0) && (gal_E5b_count == 0) && (glo_1G_count == 0) && (glo_2G_count == 0) && (bds_B1_count == 0) && (bds_B3_count == 0)) + if ((gps_1C_count == 0) && (gps_2S_count != 0) && (gps_L5_count != 0) && (gal_1B_count == 0) && (gal_E5a_count == 0) && (gal_E5b_count == 0) && (gal_E6_count == 0) && (glo_1G_count == 0) && (glo_2G_count == 0) && (bds_B1_count == 0) && (bds_B3_count == 0)) { pvt_output_parameters.type_of_receiver = 16; // GPS L2C + GPS L5 } - if ((gps_1C_count == 0) && (gps_2S_count != 0) && (gps_L5_count == 0) && (gal_1B_count == 0) && (gal_E5a_count != 0) && (gal_E5b_count == 0) && (glo_1G_count == 0) && (glo_2G_count == 0) && (bds_B1_count == 0) && (bds_B3_count == 0)) + if ((gps_1C_count == 0) && (gps_2S_count != 0) && (gps_L5_count == 0) && (gal_1B_count == 0) && (gal_E5a_count != 0) && (gal_E5b_count == 0) && (gal_E6_count == 0) && (glo_1G_count == 0) && (glo_2G_count == 0) && (bds_B1_count == 0) && (bds_B3_count == 0)) { pvt_output_parameters.type_of_receiver = 17; // GPS L2C + Galileo E5a } - if ((gps_1C_count == 0) && (gps_2S_count != 0) && (gps_L5_count == 0) && (gal_1B_count == 0) && (gal_E5a_count == 0) && (gal_E5b_count != 0) && (glo_1G_count == 0) && (glo_2G_count == 0) && (bds_B1_count == 0) && (bds_B3_count == 0)) + if ((gps_1C_count == 0) && (gps_2S_count != 0) && (gps_L5_count == 0) && (gal_1B_count == 0) && (gal_E5a_count == 0) && (gal_E5b_count != 0) && (gal_E6_count == 0) && (glo_1G_count == 0) && (glo_2G_count == 0) && (bds_B1_count == 0) && (bds_B3_count == 0)) { pvt_output_parameters.type_of_receiver = 18; } - // if( (gps_1C_count == 0) && (gps_2S_count == 0) && (gps_L5_count == 0) && (gal_1B_count == 0) && (gal_E5a_count == 0) && (gal_E5b_count == 0)) pvt_output_parameters.type_of_receiver = 19; - // if( (gps_1C_count == 0) && (gps_2S_count == 0) && (gps_L5_count == 0) && (gal_1B_count == 0) && (gal_E5a_count == 0) && (gal_E5b_count == 0)) pvt_output_parameters.type_of_receiver = 20; - if ((gps_1C_count != 0) && (gps_2S_count != 0) && (gps_L5_count == 0) && (gal_1B_count != 0) && (gal_E5a_count == 0) && (gal_E5b_count == 0) && (glo_1G_count == 0) && (glo_2G_count == 0) && (bds_B1_count == 0) && (bds_B3_count == 0)) + // if( (gps_1C_count == 0) && (gps_2S_count == 0) && (gps_L5_count == 0) && (gal_1B_count == 0) && (gal_E5a_count == 0) && (gal_E5b_count == 0) && (gal_E6_count == 0)) pvt_output_parameters.type_of_receiver = 19; + // if( (gps_1C_count == 0) && (gps_2S_count == 0) && (gps_L5_count == 0) && (gal_1B_count == 0) && (gal_E5a_count == 0) && (gal_E5b_count == 0) && (gal_E6_count == 0)) pvt_output_parameters.type_of_receiver = 20; + if ((gps_1C_count != 0) && (gps_2S_count != 0) && (gps_L5_count == 0) && (gal_1B_count != 0) && (gal_E5a_count == 0) && (gal_E5b_count == 0) && (gal_E6_count == 0) && (glo_1G_count == 0) && (glo_2G_count == 0) && (bds_B1_count == 0) && (bds_B3_count == 0)) { pvt_output_parameters.type_of_receiver = 21; // GPS L1 C/A + Galileo E1B + GPS L2C } - if ((gps_1C_count != 0) && (gps_2S_count == 0) && (gps_L5_count != 0) && (gal_1B_count != 0) && (gal_E5a_count == 0) && (gal_E5b_count == 0) && (glo_1G_count == 0) && (glo_2G_count == 0) && (bds_B1_count == 0) && (bds_B3_count == 0)) + if ((gps_1C_count != 0) && (gps_2S_count == 0) && (gps_L5_count != 0) && (gal_1B_count != 0) && (gal_E5a_count == 0) && (gal_E5b_count == 0) && (gal_E6_count == 0) && (glo_1G_count == 0) && (glo_2G_count == 0) && (bds_B1_count == 0) && (bds_B3_count == 0)) { pvt_output_parameters.type_of_receiver = 22; // GPS L1 C/A + Galileo E1B + GPS L5 } - if ((gps_1C_count == 0) && (gps_2S_count == 0) && (gps_L5_count == 0) && (gal_1B_count == 0) && (gal_E5a_count == 0) && (gal_E5b_count == 0) && (glo_1G_count != 0) && (bds_B1_count == 0) && (bds_B3_count == 0)) + if ((gps_1C_count == 0) && (gps_2S_count == 0) && (gps_L5_count == 0) && (gal_1B_count == 0) && (gal_E5a_count == 0) && (gal_E5b_count == 0) && (gal_E6_count == 0) && (glo_1G_count != 0) && (bds_B1_count == 0) && (bds_B3_count == 0)) { pvt_output_parameters.type_of_receiver = 23; // GLONASS L1 C/A } - if ((gps_1C_count == 0) && (gps_2S_count == 0) && (gps_L5_count == 0) && (gal_1B_count == 0) && (gal_E5a_count == 0) && (gal_E5b_count == 0) && (glo_1G_count == 0) && (glo_2G_count != 0) && (bds_B1_count == 0) && (bds_B3_count == 0)) + if ((gps_1C_count == 0) && (gps_2S_count == 0) && (gps_L5_count == 0) && (gal_1B_count == 0) && (gal_E5a_count == 0) && (gal_E5b_count == 0) && (gal_E6_count == 0) && (glo_1G_count == 0) && (glo_2G_count != 0) && (bds_B1_count == 0) && (bds_B3_count == 0)) { pvt_output_parameters.type_of_receiver = 24; // GLONASS L2 C/A } - if ((gps_1C_count == 0) && (gps_2S_count == 0) && (gps_L5_count == 0) && (gal_1B_count == 0) && (gal_E5a_count == 0) && (gal_E5b_count == 0) && (glo_1G_count != 0) && (glo_2G_count != 0) && (bds_B1_count == 0) && (bds_B3_count == 0)) + if ((gps_1C_count == 0) && (gps_2S_count == 0) && (gps_L5_count == 0) && (gal_1B_count == 0) && (gal_E5a_count == 0) && (gal_E5b_count == 0) && (gal_E6_count == 0) && (glo_1G_count != 0) && (glo_2G_count != 0) && (bds_B1_count == 0) && (bds_B3_count == 0)) { pvt_output_parameters.type_of_receiver = 25; // GLONASS L1 C/A + GLONASS L2 C/A } - if ((gps_1C_count != 0) && (gps_2S_count == 0) && (gps_L5_count == 0) && (gal_1B_count == 0) && (gal_E5a_count == 0) && (gal_E5b_count == 0) && (glo_1G_count != 0) && (glo_2G_count == 0) && (bds_B1_count == 0) && (bds_B3_count == 0)) + if ((gps_1C_count != 0) && (gps_2S_count == 0) && (gps_L5_count == 0) && (gal_1B_count == 0) && (gal_E5a_count == 0) && (gal_E5b_count == 0) && (gal_E6_count == 0) && (glo_1G_count != 0) && (glo_2G_count == 0) && (bds_B1_count == 0) && (bds_B3_count == 0)) { pvt_output_parameters.type_of_receiver = 26; // GPS L1 C/A + GLONASS L1 C/A } - if ((gps_1C_count == 0) && (gps_2S_count == 0) && (gps_L5_count == 0) && (gal_1B_count != 0) && (gal_E5a_count == 0) && (gal_E5b_count == 0) && (glo_1G_count != 0) && (glo_2G_count == 0) && (bds_B1_count == 0) && (bds_B3_count == 0)) + if ((gps_1C_count == 0) && (gps_2S_count == 0) && (gps_L5_count == 0) && (gal_1B_count != 0) && (gal_E5a_count == 0) && (gal_E5b_count == 0) && (gal_E6_count == 0) && (glo_1G_count != 0) && (glo_2G_count == 0) && (bds_B1_count == 0) && (bds_B3_count == 0)) { pvt_output_parameters.type_of_receiver = 27; // Galileo E1B + GLONASS L1 C/A } - if ((gps_1C_count == 0) && (gps_2S_count != 0) && (gps_L5_count == 0) && (gal_1B_count == 0) && (gal_E5a_count == 0) && (gal_E5b_count == 0) && (glo_1G_count != 0) && (glo_2G_count == 0) && (bds_B1_count == 0) && (bds_B3_count == 0)) + if ((gps_1C_count == 0) && (gps_2S_count != 0) && (gps_L5_count == 0) && (gal_1B_count == 0) && (gal_E5a_count == 0) && (gal_E5b_count == 0) && (gal_E6_count == 0) && (glo_1G_count != 0) && (glo_2G_count == 0) && (bds_B1_count == 0) && (bds_B3_count == 0)) { pvt_output_parameters.type_of_receiver = 28; // GPS L2C + GLONASS L1 C/A } - if ((gps_1C_count != 0) && (gps_2S_count == 0) && (gps_L5_count == 0) && (gal_1B_count == 0) && (gal_E5a_count == 0) && (gal_E5b_count == 0) && (glo_1G_count == 0) && (glo_2G_count != 0) && (bds_B1_count == 0) && (bds_B3_count == 0)) + if ((gps_1C_count != 0) && (gps_2S_count == 0) && (gps_L5_count == 0) && (gal_1B_count == 0) && (gal_E5a_count == 0) && (gal_E5b_count == 0) && (gal_E6_count == 0) && (glo_1G_count == 0) && (glo_2G_count != 0) && (bds_B1_count == 0) && (bds_B3_count == 0)) { pvt_output_parameters.type_of_receiver = 29; // GPS L1 C/A + GLONASS L2 C/A } - if ((gps_1C_count == 0) && (gps_2S_count == 0) && (gps_L5_count == 0) && (gal_1B_count != 0) && (gal_E5a_count == 0) && (gal_E5b_count == 0) && (glo_1G_count == 0) && (glo_2G_count != 0) && (bds_B1_count == 0) && (bds_B3_count == 0)) + if ((gps_1C_count == 0) && (gps_2S_count == 0) && (gps_L5_count == 0) && (gal_1B_count != 0) && (gal_E5a_count == 0) && (gal_E5b_count == 0) && (gal_E6_count == 0) && (glo_1G_count == 0) && (glo_2G_count != 0) && (bds_B1_count == 0) && (bds_B3_count == 0)) { pvt_output_parameters.type_of_receiver = 30; // Galileo E1B + GLONASS L2 C/A } - if ((gps_1C_count == 0) && (gps_2S_count != 0) && (gps_L5_count == 0) && (gal_1B_count == 0) && (gal_E5a_count == 0) && (gal_E5b_count == 0) && (glo_1G_count == 0) && (glo_2G_count != 0) && (bds_B1_count == 0) && (bds_B3_count == 0)) + if ((gps_1C_count == 0) && (gps_2S_count != 0) && (gps_L5_count == 0) && (gal_1B_count == 0) && (gal_E5a_count == 0) && (gal_E5b_count == 0) && (gal_E6_count == 0) && (glo_1G_count == 0) && (glo_2G_count != 0) && (bds_B1_count == 0) && (bds_B3_count == 0)) { pvt_output_parameters.type_of_receiver = 31; // GPS L2C + GLONASS L2 C/A } - - if ((gps_1C_count != 0) && (gps_2S_count == 0) && (gps_L5_count != 0) && (gal_1B_count != 0) && (gal_E5a_count != 0) && (gal_E5b_count == 0) && (glo_1G_count == 0) && (glo_2G_count == 0) && (bds_B1_count == 0) && (bds_B3_count == 0)) + if ((gps_1C_count != 0) && (gps_2S_count == 0) && (gps_L5_count != 0) && (gal_1B_count != 0) && (gal_E5a_count != 0) && (gal_E5b_count == 0) && (gal_E6_count == 0) && (glo_1G_count == 0) && (glo_2G_count == 0) && (bds_B1_count == 0) && (bds_B3_count == 0)) { pvt_output_parameters.type_of_receiver = 32; // L1+E1+L5+E5a } + if ((gps_1C_count != 0) && (gps_2S_count == 0) && (gps_L5_count == 0) && (gal_1B_count != 0) && (gal_E5a_count != 0) && (gal_E5b_count == 0) && (gal_E6_count == 0) && (glo_1G_count == 0) && (glo_2G_count == 0) && (bds_B1_count == 0) && (bds_B3_count == 0)) + { + pvt_output_parameters.type_of_receiver = 33; // L1+E1+E5a + } + // Galileo E6 + if ((gps_1C_count == 0) && (gps_2S_count == 0) && (gps_L5_count == 0) && (gal_1B_count == 0) && (gal_E5a_count == 0) && (gal_E5b_count == 0) && (gal_E6_count != 0) && (glo_1G_count == 0) && (glo_2G_count == 0) && (bds_B1_count == 0) && (bds_B3_count == 0)) + { + pvt_output_parameters.type_of_receiver = 100; // Galileo E6B + } + if ((gps_1C_count == 0) && (gps_2S_count == 0) && (gps_L5_count == 0) && (gal_1B_count != 0) && (gal_E5a_count == 0) && (gal_E5b_count == 0) && (gal_E6_count != 0) && (glo_1G_count == 0) && (glo_2G_count == 0) && (bds_B1_count == 0) && (bds_B3_count == 0)) + { + pvt_output_parameters.type_of_receiver = 101; // Galileo E1B + Galileo E6B + } + if ((gps_1C_count == 0) && (gps_2S_count == 0) && (gps_L5_count == 0) && (gal_1B_count == 0) && (gal_E5a_count != 0) && (gal_E5b_count == 0) && (gal_E6_count != 0) && (glo_1G_count == 0) && (glo_2G_count == 0) && (bds_B1_count == 0) && (bds_B3_count == 0)) + { + pvt_output_parameters.type_of_receiver = 102; // Galileo E5a + Galileo E6B + } + if ((gps_1C_count == 0) && (gps_2S_count == 0) && (gps_L5_count == 0) && (gal_1B_count == 0) && (gal_E5a_count == 0) && (gal_E5b_count != 0) && (gal_E6_count != 0) && (glo_1G_count == 0) && (glo_2G_count == 0) && (bds_B1_count == 0) && (bds_B3_count == 0)) + { + pvt_output_parameters.type_of_receiver = 103; // Galileo E5b + Galileo E6B + } + if ((gps_1C_count == 0) && (gps_2S_count == 0) && (gps_L5_count == 0) && (gal_1B_count != 0) && (gal_E5a_count != 0) && (gal_E5b_count == 0) && (gal_E6_count != 0) && (glo_1G_count == 0) && (glo_2G_count == 0) && (bds_B1_count == 0) && (bds_B3_count == 0)) + { + pvt_output_parameters.type_of_receiver = 104; // Galileo E1B + Galileo E5a + Galileo E6B + } + if ((gps_1C_count == 0) && (gps_2S_count == 0) && (gps_L5_count == 0) && (gal_1B_count != 0) && (gal_E5a_count == 0) && (gal_E5b_count != 0) && (gal_E6_count != 0) && (glo_1G_count == 0) && (glo_2G_count == 0) && (bds_B1_count == 0) && (bds_B3_count == 0)) + { + pvt_output_parameters.type_of_receiver = 105; // Galileo E1B + Galileo E5b + Galileo E6B + } + if ((gps_1C_count != 0) && (gps_2S_count == 0) && (gps_L5_count == 0) && (gal_1B_count != 0) && (gal_E5a_count == 0) && (gal_E5b_count == 0) && (gal_E6_count != 0) && (glo_1G_count == 0) && (glo_2G_count == 0) && (bds_B1_count == 0) && (bds_B3_count == 0)) + { + pvt_output_parameters.type_of_receiver = 106; // GPS L1 C/A + Galileo E1B + Galileo E6B + } // BeiDou B1I Receiver - if ((gps_1C_count == 0) && (gps_2S_count == 0) && (gps_L5_count == 0) && (gal_1B_count == 0) && (gal_E5a_count == 0) && (gal_E5b_count == 0) && (glo_1G_count == 0) && (glo_2G_count == 0) && (bds_B1_count != 0) && (bds_B3_count == 0)) + if ((gps_1C_count == 0) && (gps_2S_count == 0) && (gps_L5_count == 0) && (gal_1B_count == 0) && (gal_E5a_count == 0) && (gal_E5b_count == 0) && (gal_E6_count == 0) && (glo_1G_count == 0) && (glo_2G_count == 0) && (bds_B1_count != 0) && (bds_B3_count == 0)) { pvt_output_parameters.type_of_receiver = 500; // Beidou B1I } - if ((gps_1C_count != 0) && (gps_2S_count == 0) && (gps_L5_count == 0) && (gal_1B_count == 0) && (gal_E5a_count == 0) && (gal_E5b_count == 0) && (glo_1G_count == 0) && (glo_2G_count == 0) && (bds_B1_count != 0) && (bds_B3_count == 0)) + if ((gps_1C_count != 0) && (gps_2S_count == 0) && (gps_L5_count == 0) && (gal_1B_count == 0) && (gal_E5a_count == 0) && (gal_E5b_count == 0) && (gal_E6_count == 0) && (glo_1G_count == 0) && (glo_2G_count == 0) && (bds_B1_count != 0) && (bds_B3_count == 0)) { pvt_output_parameters.type_of_receiver = 501; // Beidou B1I + GPS L1 C/A } - if ((gps_1C_count == 0) && (gps_2S_count == 0) && (gps_L5_count == 0) && (gal_1B_count != 0) && (gal_E5a_count == 0) && (gal_E5b_count == 0) && (glo_1G_count == 0) && (glo_2G_count == 0) && (bds_B1_count != 0) && (bds_B3_count == 0)) + if ((gps_1C_count == 0) && (gps_2S_count == 0) && (gps_L5_count == 0) && (gal_1B_count != 0) && (gal_E5a_count == 0) && (gal_E5b_count == 0) && (gal_E6_count == 0) && (glo_1G_count == 0) && (glo_2G_count == 0) && (bds_B1_count != 0) && (bds_B3_count == 0)) { pvt_output_parameters.type_of_receiver = 502; // Beidou B1I + Galileo E1B } - if ((gps_1C_count == 0) && (gps_2S_count == 0) && (gps_L5_count == 0) && (gal_1B_count == 0) && (gal_E5a_count == 0) && (gal_E5b_count == 0) && (glo_1G_count != 0) && (glo_2G_count == 0) && (bds_B1_count != 0) && (bds_B3_count == 0)) + if ((gps_1C_count == 0) && (gps_2S_count == 0) && (gps_L5_count == 0) && (gal_1B_count == 0) && (gal_E5a_count == 0) && (gal_E5b_count == 0) && (gal_E6_count == 0) && (glo_1G_count != 0) && (glo_2G_count == 0) && (bds_B1_count != 0) && (bds_B3_count == 0)) { pvt_output_parameters.type_of_receiver = 503; // Beidou B1I + GLONASS L1 C/A } - if ((gps_1C_count != 0) && (gps_2S_count == 0) && (gps_L5_count == 0) && (gal_1B_count != 0) && (gal_E5a_count == 0) && (gal_E5b_count == 0) && (glo_1G_count == 0) && (glo_2G_count == 0) && (bds_B1_count != 0) && (bds_B3_count == 0)) + if ((gps_1C_count != 0) && (gps_2S_count == 0) && (gps_L5_count == 0) && (gal_1B_count != 0) && (gal_E5a_count == 0) && (gal_E5b_count == 0) && (gal_E6_count == 0) && (glo_1G_count == 0) && (glo_2G_count == 0) && (bds_B1_count != 0) && (bds_B3_count == 0)) { pvt_output_parameters.type_of_receiver = 504; // Beidou B1I + GPS L1 C/A + Galileo E1B } - if ((gps_1C_count != 0) && (gps_2S_count == 0) && (gps_L5_count == 0) && (gal_1B_count != 0) && (gal_E5a_count == 0) && (gal_E5b_count == 0) && (glo_1G_count != 0) && (glo_2G_count == 0) && (bds_B1_count != 0) && (bds_B3_count == 0)) + if ((gps_1C_count != 0) && (gps_2S_count == 0) && (gps_L5_count == 0) && (gal_1B_count != 0) && (gal_E5a_count == 0) && (gal_E5b_count == 0) && (gal_E6_count == 0) && (glo_1G_count != 0) && (glo_2G_count == 0) && (bds_B1_count != 0) && (bds_B3_count == 0)) { pvt_output_parameters.type_of_receiver = 505; // Beidou B1I + GPS L1 C/A + GLONASS L1 C/A + Galileo E1B } - if ((gps_1C_count == 0) && (gps_2S_count == 0) && (gps_L5_count == 0) && (gal_1B_count == 0) && (gal_E5a_count == 0) && (gal_E5b_count == 0) && (glo_1G_count == 0) && (glo_2G_count == 0) && (bds_B1_count != 0) && (bds_B3_count != 0)) + if ((gps_1C_count == 0) && (gps_2S_count == 0) && (gps_L5_count == 0) && (gal_1B_count == 0) && (gal_E5a_count == 0) && (gal_E5b_count == 0) && (gal_E6_count == 0) && (glo_1G_count == 0) && (glo_2G_count == 0) && (bds_B1_count != 0) && (bds_B3_count != 0)) { pvt_output_parameters.type_of_receiver = 506; // Beidou B1I + Beidou B3I } // BeiDou B3I Receiver - if ((gps_1C_count == 0) && (gps_2S_count == 0) && (gps_L5_count == 0) && (gal_1B_count == 0) && (gal_E5a_count == 0) && (gal_E5b_count == 0) && (glo_1G_count == 0) && (glo_2G_count == 0) && (bds_B1_count == 0) && (bds_B3_count != 0)) + if ((gps_1C_count == 0) && (gps_2S_count == 0) && (gps_L5_count == 0) && (gal_1B_count == 0) && (gal_E5a_count == 0) && (gal_E5b_count == 0) && (gal_E6_count == 0) && (glo_1G_count == 0) && (glo_2G_count == 0) && (bds_B1_count == 0) && (bds_B3_count != 0)) { pvt_output_parameters.type_of_receiver = 600; // Beidou B3I } - if ((gps_1C_count != 0) && (gps_2S_count != 0) && (gps_L5_count == 0) && (gal_1B_count == 0) && (gal_E5a_count == 0) && (gal_E5b_count == 0) && (glo_1G_count == 0) && (glo_2G_count == 0) && (bds_B1_count == 0) && (bds_B3_count != 0)) + if ((gps_1C_count != 0) && (gps_2S_count != 0) && (gps_L5_count == 0) && (gal_1B_count == 0) && (gal_E5a_count == 0) && (gal_E5b_count == 0) && (gal_E6_count == 0) && (glo_1G_count == 0) && (glo_2G_count == 0) && (bds_B1_count == 0) && (bds_B3_count != 0)) { pvt_output_parameters.type_of_receiver = 601; // Beidou B3I + GPS L2C } - if ((gps_1C_count == 0) && (gps_2S_count == 0) && (gps_L5_count == 0) && (gal_1B_count != 0) && (gal_E5a_count == 0) && (gal_E5b_count == 0) && (glo_1G_count == 0) && (glo_2G_count != 0) && (bds_B1_count == 0) && (bds_B3_count != 0)) + if ((gps_1C_count == 0) && (gps_2S_count == 0) && (gps_L5_count == 0) && (gal_1B_count != 0) && (gal_E5a_count == 0) && (gal_E5b_count == 0) && (gal_E6_count == 0) && (glo_1G_count == 0) && (glo_2G_count != 0) && (bds_B1_count == 0) && (bds_B3_count != 0)) { pvt_output_parameters.type_of_receiver = 602; // Beidou B3I + GLONASS L2 C/A } - if ((gps_1C_count == 0) && (gps_2S_count != 0) && (gps_L5_count == 0) && (gal_1B_count == 0) && (gal_E5a_count == 0) && (gal_E5b_count == 0) && (glo_1G_count != 0) && (glo_2G_count != 0) && (bds_B1_count == 0) && (bds_B3_count != 0)) + if ((gps_1C_count == 0) && (gps_2S_count != 0) && (gps_L5_count == 0) && (gal_1B_count == 0) && (gal_E5a_count == 0) && (gal_E5b_count == 0) && (gal_E6_count == 0) && (glo_1G_count != 0) && (glo_2G_count != 0) && (bds_B1_count == 0) && (bds_B3_count != 0)) { pvt_output_parameters.type_of_receiver = 603; // Beidou B3I + GPS L2C + GLONASS L2 C/A } - if ((gps_1C_count != 0) && (gps_2S_count != 0) && (gps_L5_count != 0) && (gal_1B_count == 0) && (gal_E5a_count == 0) && (gal_E5b_count == 0) && (glo_1G_count == 0) && (glo_2G_count == 0) && (bds_B1_count == 0) && (bds_B3_count == 0)) + if ((gps_1C_count != 0) && (gps_2S_count != 0) && (gps_L5_count != 0) && (gal_1B_count == 0) && (gal_E5a_count == 0) && (gal_E5b_count == 0) && (gal_E6_count == 0) && (glo_1G_count == 0) && (glo_2G_count == 0) && (bds_B1_count == 0) && (bds_B3_count == 0)) { pvt_output_parameters.type_of_receiver = 1000; // GPS L1 + GPS L2C + GPS L5 } - if ((gps_1C_count != 0) && (gps_2S_count != 0) && (gps_L5_count != 0) && (gal_1B_count != 0) && (gal_E5a_count != 0) && (gal_E5b_count == 0) && (glo_1G_count == 0) && (glo_2G_count == 0) && (bds_B1_count == 0) && (bds_B3_count == 0)) + if ((gps_1C_count != 0) && (gps_2S_count != 0) && (gps_L5_count != 0) && (gal_1B_count != 0) && (gal_E5a_count != 0) && (gal_E5b_count == 0) && (gal_E6_count == 0) && (glo_1G_count == 0) && (glo_2G_count == 0) && (bds_B1_count == 0) && (bds_B3_count == 0)) { pvt_output_parameters.type_of_receiver = 1001; // GPS L1 + Galileo E1B + GPS L2C + GPS L5 + Galileo E5a } @@ -411,7 +452,7 @@ Rtklib_Pvt::Rtklib_Pvt(const ConfigurationInterface* configuration, int num_bands = 0; - if ((gps_1C_count > 0) || (gal_1B_count > 0) || (glo_1G_count > 0) || (bds_B1_count > 0)) + if ((gps_1C_count > 0) || (gal_1B_count > 0) || (gal_E6_count > 0) || (glo_1G_count > 0) || (bds_B1_count > 0)) { num_bands = 1; } @@ -423,6 +464,14 @@ Rtklib_Pvt::Rtklib_Pvt(const ConfigurationInterface* configuration, { num_bands = 2; } + if ((gal_1B_count > 0) && (gal_E6_count > 0)) + { + num_bands = 2; + } + if ((gal_1B_count > 0) && (gal_E6_count > 0) && (gal_E5a_count > 0) || (gal_E5b_count > 0)) + { + num_bands = 3; + } if (((gps_1C_count > 0) || (gal_1B_count > 0) || (glo_1G_count > 0) || (bds_B1_count > 0)) && ((gps_2S_count > 0) || (glo_2G_count > 0) || (bds_B3_count > 0)) && ((gal_E5a_count > 0) || (gal_E5b_count > 0) || (gps_L5_count > 0))) { num_bands = 3; diff --git a/src/algorithms/PVT/gnuradio_blocks/rtklib_pvt_gs.cc b/src/algorithms/PVT/gnuradio_blocks/rtklib_pvt_gs.cc index 9c4c3f5c4..1f628832c 100644 --- a/src/algorithms/PVT/gnuradio_blocks/rtklib_pvt_gs.cc +++ b/src/algorithms/PVT/gnuradio_blocks/rtklib_pvt_gs.cc @@ -134,6 +134,7 @@ rtklib_pvt_gs::rtklib_pvt_gs(uint32_t nchannels, d_mapStringValues["L5"] = evGPS_L5; d_mapStringValues["1B"] = evGAL_1B; d_mapStringValues["5X"] = evGAL_5X; + d_mapStringValues["E6"] = evGAL_E6; d_mapStringValues["7X"] = evGAL_7X; d_mapStringValues["1G"] = evGLO_1G; d_mapStringValues["2G"] = evGLO_2G; @@ -1140,6 +1141,9 @@ void rtklib_pvt_gs::msg_handler_telemetry(const pmt::pmt_t& msg) case 33: // L1+E1+E5a d_rp->log_rinex_nav(d_rp->navMixFile, new_eph, new_gal_eph); break; + case 106: // GPS L1 C/A + Galileo E1B + Galileo E6B + d_rp->log_rinex_nav(d_rp->navMixFile, new_eph, new_gal_eph); + break; case 1000: // L1+L2+L5 d_rp->log_rinex_nav(d_rp->navFile, new_eph); break; @@ -1319,8 +1323,20 @@ void rtklib_pvt_gs::msg_handler_telemetry(const pmt::pmt_t& msg) case 30: // Galileo E1B + GLONASS L2 C/A d_rp->log_rinex_nav(d_rp->navMixFile, new_gal_eph, new_glo_eph); break; - case 32: // L1+E1+L5+E5a - case 33: // L1+E1+E5a + case 32: // L1+E1+L5+E5a + case 33: // L1+E1+E5a + d_rp->log_rinex_nav(d_rp->navMixFile, new_eph, new_gal_eph); + break; + case 101: // E1B + E6B + case 102: // Galileo E5a + Galileo E6B + case 103: // Galileo E5b + Galileo E6B + case 104: // Galileo E1B + Galileo E5a + Galileo E6B + case 105: // Galileo E1B + Galileo E5b + Galileo E6B + d_rp->log_rinex_nav(d_rp->navGalFile, new_gal_eph); + break; + case 106: // GPS L1 C/A + Galileo E1B + Galileo E6B + d_rp->log_rinex_nav(d_rp->navMixFile, new_eph, new_gal_eph); + break; case 1001: // L1+E1+L2+L5+E5a d_rp->log_rinex_nav(d_rp->navMixFile, new_eph, new_gal_eph); break; @@ -1803,6 +1819,9 @@ void rtklib_pvt_gs::apply_rx_clock_offset(std::map& observabl case evGAL_5X: observables_iter->second.Carrier_phase_rads -= rx_clock_offset_s * FREQ5 * TWO_PI; break; + case evGAL_E6: + observables_iter->second.Carrier_phase_rads -= rx_clock_offset_s * FREQ6 * TWO_PI; + break; case evGAL_7X: observables_iter->second.Carrier_phase_rads -= rx_clock_offset_s * FREQ7 * TWO_PI; break; @@ -1902,6 +1921,9 @@ void rtklib_pvt_gs::initialize_and_apply_carrier_phase_offset() case evGAL_5X: wavelength_m = SPEED_OF_LIGHT_M_S / FREQ5; break; + case evGAL_E6: + wavelength_m = SPEED_OF_LIGHT_M_S / FREQ6; + break; case evGAL_7X: wavelength_m = SPEED_OF_LIGHT_M_S / FREQ7; break; @@ -2301,7 +2323,14 @@ int rtklib_pvt_gs::work(int noutput_items, gr_vector_const_void_star& input_item * 30 | Galileo E1B + GLONASS L2 C/A * 31 | GPS L2C + GLONASS L2 C/A * 32 | GPS L1 C/A + Galileo E1B + GPS L5 + Galileo E5a - * 500 | BeiDou B1I + * 33 | GPS L1 C/A + Galileo E1B + Galileo E5a + = 100 | Galileo E6B + = 101 | Galileo E1B + Galileo E6B + * 102 | Galileo E5a + Galileo E6B + * 103 | Galileo E5b + Galileo E6B + * 104 | Galileo E1B + Galileo E5a + Galileo E6B + * 105 | Galileo E1B + Galileo E5b + Galileo E6B + * 106 | GPS L1 C/A + Galileo E1B + Galileo E6B * 501 | BeiDou B1I + GPS L1 C/A * 502 | BeiDou B1I + Galileo E1B * 503 | BeiDou B1I + GLONASS L1 C/A @@ -2615,6 +2644,65 @@ int rtklib_pvt_gs::work(int noutput_items, gr_vector_const_void_star& input_item d_rinex_header_written = true; // do not write header anymore } break; + case 101: // Galileo E1B + Galileo E6B + if (galileo_ephemeris_iter != d_user_pvt_solver->galileo_ephemeris_map.cend()) + { + d_rp->rinex_obs_header(d_rp->obsFile, galileo_ephemeris_iter->second, d_rx_time); + d_rp->rinex_nav_header(d_rp->navGalFile, d_user_pvt_solver->galileo_iono, d_user_pvt_solver->galileo_utc_model); + d_rp->log_rinex_nav(d_rp->navGalFile, d_user_pvt_solver->galileo_ephemeris_map); + d_rinex_header_written = true; // do not write header anymore + } + break; + case 102: // Galileo E5a + Galileo E6B + if (galileo_ephemeris_iter != d_user_pvt_solver->galileo_ephemeris_map.cend()) + { + const std::string signal("5X"); + d_rp->rinex_obs_header(d_rp->obsFile, galileo_ephemeris_iter->second, d_rx_time, signal); + d_rp->rinex_nav_header(d_rp->navGalFile, d_user_pvt_solver->galileo_iono, d_user_pvt_solver->galileo_utc_model); + d_rp->log_rinex_nav(d_rp->navGalFile, d_user_pvt_solver->galileo_ephemeris_map); + d_rinex_header_written = true; // do not write header anymore + } + break; + case 103: // Galileo E5b + Galileo E6B + if (galileo_ephemeris_iter != d_user_pvt_solver->galileo_ephemeris_map.cend()) + { + const std::string signal("7X"); + d_rp->rinex_obs_header(d_rp->obsFile, galileo_ephemeris_iter->second, d_rx_time, signal); + d_rp->rinex_nav_header(d_rp->navGalFile, d_user_pvt_solver->galileo_iono, d_user_pvt_solver->galileo_utc_model); + d_rp->log_rinex_nav(d_rp->navGalFile, d_user_pvt_solver->galileo_ephemeris_map); + d_rinex_header_written = true; // do not write header anymore + } + break; + case 104: // Galileo E1B + Galileo E5a + Galileo E6B + if ((galileo_ephemeris_iter != d_user_pvt_solver->galileo_ephemeris_map.cend())) + { + const std::string gal_signal("1B 5X"); + d_rp->rinex_obs_header(d_rp->obsFile, galileo_ephemeris_iter->second, d_rx_time, gal_signal); + d_rp->rinex_nav_header(d_rp->navGalFile, d_user_pvt_solver->galileo_iono, d_user_pvt_solver->galileo_utc_model); + d_rp->log_rinex_nav(d_rp->navGalFile, d_user_pvt_solver->galileo_ephemeris_map); + d_rinex_header_written = true; // do not write header anymore + } + break; + case 105: // Galileo E1B + Galileo E5b + Galileo E6B + if ((galileo_ephemeris_iter != d_user_pvt_solver->galileo_ephemeris_map.cend())) + { + const std::string gal_signal("1B 7X"); + d_rp->rinex_obs_header(d_rp->obsFile, galileo_ephemeris_iter->second, d_rx_time, gal_signal); + d_rp->rinex_nav_header(d_rp->navGalFile, d_user_pvt_solver->galileo_iono, d_user_pvt_solver->galileo_utc_model); + d_rp->log_rinex_nav(d_rp->navGalFile, d_user_pvt_solver->galileo_ephemeris_map); + d_rinex_header_written = true; // do not write header anymore + } + break; + case 106: // GPS L1 C/A + Galileo E1B + Galileo E6B + if ((galileo_ephemeris_iter != d_user_pvt_solver->galileo_ephemeris_map.cend()) and (gps_ephemeris_iter != d_user_pvt_solver->gps_ephemeris_map.cend())) + { + const std::string gal_signal("1B"); + d_rp->rinex_obs_header(d_rp->obsFile, gps_ephemeris_iter->second, galileo_ephemeris_iter->second, d_rx_time, gal_signal); + d_rp->rinex_nav_header(d_rp->navMixFile, d_user_pvt_solver->gps_iono, d_user_pvt_solver->gps_utc_model, gps_ephemeris_iter->second, d_user_pvt_solver->galileo_iono, d_user_pvt_solver->galileo_utc_model); + d_rp->log_rinex_nav(d_rp->navMixFile, d_user_pvt_solver->gps_ephemeris_map, d_user_pvt_solver->galileo_ephemeris_map); + d_rinex_header_written = true; // do not write header anymore + } + break; case 500: // BDS B1I only if (beidou_dnav_ephemeris_iter != d_user_pvt_solver->beidou_dnav_ephemeris_map.cend()) { @@ -3002,6 +3090,78 @@ int rtklib_pvt_gs::work(int noutput_items, gr_vector_const_void_star& input_item } } break; + case 101: // Galileo E1B + Galileo E6B + if (galileo_ephemeris_iter != d_user_pvt_solver->galileo_ephemeris_map.cend()) + { + d_rp->log_rinex_obs(d_rp->obsFile, galileo_ephemeris_iter->second, d_rx_time, d_gnss_observables_map, "1B"); + } + if (!d_rinex_header_updated and (d_user_pvt_solver->galileo_utc_model.A0_6 != 0)) + { + d_rp->update_nav_header(d_rp->navGalFile, d_user_pvt_solver->galileo_iono, d_user_pvt_solver->galileo_utc_model); + d_rp->update_obs_header(d_rp->obsFile, d_user_pvt_solver->galileo_utc_model); + d_rinex_header_updated = true; + } + break; + case 102: // Galileo E5a + Galileo E6B + if (galileo_ephemeris_iter != d_user_pvt_solver->galileo_ephemeris_map.cend()) + { + d_rp->log_rinex_obs(d_rp->obsFile, galileo_ephemeris_iter->second, d_rx_time, d_gnss_observables_map, "5X"); + } + if (!d_rinex_header_updated and (d_user_pvt_solver->galileo_utc_model.A0_6 != 0)) + { + d_rp->update_nav_header(d_rp->navGalFile, d_user_pvt_solver->galileo_iono, d_user_pvt_solver->galileo_utc_model); + d_rp->update_obs_header(d_rp->obsFile, d_user_pvt_solver->galileo_utc_model); + d_rinex_header_updated = true; + } + break; + case 103: // Galileo E5b + Galileo E6B + if (galileo_ephemeris_iter != d_user_pvt_solver->galileo_ephemeris_map.cend()) + { + d_rp->log_rinex_obs(d_rp->obsFile, galileo_ephemeris_iter->second, d_rx_time, d_gnss_observables_map, "5X"); + } + if (!d_rinex_header_updated and (d_user_pvt_solver->galileo_utc_model.A0_6 != 0)) + { + d_rp->update_nav_header(d_rp->navGalFile, d_user_pvt_solver->galileo_iono, d_user_pvt_solver->galileo_utc_model); + d_rp->update_obs_header(d_rp->obsFile, d_user_pvt_solver->galileo_utc_model); + d_rinex_header_updated = true; + } + break; + case 104: // Galileo E1B + Galileo E5a + Galileo E6B + if (galileo_ephemeris_iter != d_user_pvt_solver->galileo_ephemeris_map.cend()) + { + d_rp->log_rinex_obs(d_rp->obsFile, galileo_ephemeris_iter->second, d_rx_time, d_gnss_observables_map, "1B 5X"); + } + if (!d_rinex_header_updated and (d_user_pvt_solver->galileo_utc_model.A0_6 != 0)) + { + d_rp->update_nav_header(d_rp->navGalFile, d_user_pvt_solver->galileo_iono, d_user_pvt_solver->galileo_utc_model); + d_rp->update_obs_header(d_rp->obsFile, d_user_pvt_solver->galileo_utc_model); + d_rinex_header_updated = true; + } + break; + case 105: // Galileo E1B + Galileo E5b + Galileo E6B + if (galileo_ephemeris_iter != d_user_pvt_solver->galileo_ephemeris_map.cend()) + { + d_rp->log_rinex_obs(d_rp->obsFile, galileo_ephemeris_iter->second, d_rx_time, d_gnss_observables_map, "1B 7X"); + } + if (!d_rinex_header_updated and (d_user_pvt_solver->galileo_utc_model.A0_6 != 0)) + { + d_rp->update_nav_header(d_rp->navGalFile, d_user_pvt_solver->galileo_iono, d_user_pvt_solver->galileo_utc_model); + d_rp->update_obs_header(d_rp->obsFile, d_user_pvt_solver->galileo_utc_model); + d_rinex_header_updated = true; + } + break; + case 106: // GPS L1 C/A + Galileo E1B + Galileo E6B + if ((galileo_ephemeris_iter != d_user_pvt_solver->galileo_ephemeris_map.cend()) and (gps_ephemeris_iter != d_user_pvt_solver->gps_ephemeris_map.cend())) + { + d_rp->log_rinex_obs(d_rp->obsFile, gps_ephemeris_iter->second, galileo_ephemeris_iter->second, d_rx_time, d_gnss_observables_map); + if (!d_rinex_header_updated and (d_user_pvt_solver->gps_utc_model.d_A0 != 0)) + { + d_rp->update_obs_header(d_rp->obsFile, d_user_pvt_solver->gps_utc_model); + d_rp->update_nav_header(d_rp->navMixFile, d_user_pvt_solver->gps_iono, d_user_pvt_solver->gps_utc_model, gps_ephemeris_iter->second, d_user_pvt_solver->galileo_iono, d_user_pvt_solver->galileo_utc_model); + d_rinex_header_updated = true; + } + } + break; case 500: // BDS B1I only if (beidou_dnav_ephemeris_iter != d_user_pvt_solver->beidou_dnav_ephemeris_map.cend()) { @@ -3084,9 +3244,9 @@ int rtklib_pvt_gs::work(int noutput_items, gr_vector_const_void_star& input_item } } break; - case 4: - case 5: - case 6: + case 4: // Galileo E1B + case 5: // Galileo E5a + case 6: // Galileo E5b if (flag_write_RTCM_1045_output == true) { for (const auto& gal_eph_iter : d_user_pvt_solver->galileo_ephemeris_map) @@ -3250,8 +3410,8 @@ int rtklib_pvt_gs::work(int noutput_items, gr_vector_const_void_star& input_item } } break; - case 14: - case 15: + case 14: // Galileo E1B + Galileo E5a + case 15: // Galileo E1B + Galileo E5b if (flag_write_RTCM_1045_output == true) { for (const auto& gal_eph_iter : d_user_pvt_solver->galileo_ephemeris_map) @@ -3268,9 +3428,9 @@ int rtklib_pvt_gs::work(int noutput_items, gr_vector_const_void_star& input_item } } break; - case 23: - case 24: - case 25: + case 23: // GLONASS L1 C/A + case 24: // GLONASS L2 C/A + case 25: // GLONASS L1 C/A + GLONASS L2 C/A if (flag_write_RTCM_1020_output == true) { for (const auto& glonass_gnav_ephemeris_iter : d_user_pvt_solver->glonass_gnav_ephemeris_map) @@ -3579,6 +3739,85 @@ int rtklib_pvt_gs::work(int noutput_items, gr_vector_const_void_star& input_item } } break; + case 101: // Galileo E1B + Galileo E6B + case 102: // Galileo E5a + Galileo E6B + case 103: // Galileo E5b + Galileo E6B + case 104: // Galileo E1B + Galileo E5a + Galileo E6B + case 105: // Galileo E1B + Galileo E5b + Galileo E6B + if (flag_write_RTCM_1045_output == true) + { + for (const auto& gal_eph_iter : d_user_pvt_solver->galileo_ephemeris_map) + { + d_rtcm_printer->Print_Rtcm_MT1045(gal_eph_iter.second); + } + } + if (flag_write_RTCM_MSM_output == true) + { + const auto gal_eph_iter = d_user_pvt_solver->galileo_ephemeris_map.cbegin(); + if (gal_eph_iter != d_user_pvt_solver->galileo_ephemeris_map.cend()) + { + d_rtcm_printer->Print_Rtcm_MSM(7, {}, {}, gal_eph_iter->second, {}, d_rx_time, d_gnss_observables_map, d_enable_rx_clock_correction, 0, 0, false, false); + } + } + break; + case 106: // GPS L1 C/A + Galileo E1B + Galileo E6B + if (flag_write_RTCM_1019_output == true) + { + for (const auto& gps_eph_iter : d_user_pvt_solver->gps_ephemeris_map) + { + d_rtcm_printer->Print_Rtcm_MT1019(gps_eph_iter.second); + } + } + if (flag_write_RTCM_1045_output == true) + { + for (const auto& gal_eph_iter : d_user_pvt_solver->galileo_ephemeris_map) + { + d_rtcm_printer->Print_Rtcm_MT1045(gal_eph_iter.second); + } + } + if (flag_write_RTCM_MSM_output == true) + { + auto gps_eph_iter = d_user_pvt_solver->gps_ephemeris_map.cbegin(); + auto gal_eph_iter = d_user_pvt_solver->galileo_ephemeris_map.cbegin(); + int gps_channel = 0; + int gal_channel = 0; + for (const auto& gnss_observables_iter : d_gnss_observables_map) + { + const std::string system(gnss_observables_iter.second.System, 1); + if (gps_channel == 0) + { + if (system == "G") + { + // This is a channel with valid GPS signal + gps_eph_iter = d_user_pvt_solver->gps_ephemeris_map.find(gnss_observables_iter.second.PRN); + if (gps_eph_iter != d_user_pvt_solver->gps_ephemeris_map.cend()) + { + gps_channel = 1; + } + } + } + if (gal_channel == 0) + { + if (system == "E") + { + gal_eph_iter = d_user_pvt_solver->galileo_ephemeris_map.find(gnss_observables_iter.second.PRN); + if (gal_eph_iter != d_user_pvt_solver->galileo_ephemeris_map.cend()) + { + gal_channel = 1; + } + } + } + } + if (gps_eph_iter != d_user_pvt_solver->gps_ephemeris_map.cend()) + { + d_rtcm_printer->Print_Rtcm_MSM(7, gps_eph_iter->second, {}, {}, {}, d_rx_time, d_gnss_observables_map, d_enable_rx_clock_correction, 0, 0, false, false); + } + if (gal_eph_iter != d_user_pvt_solver->galileo_ephemeris_map.cend()) + { + d_rtcm_printer->Print_Rtcm_MSM(7, {}, {}, gal_eph_iter->second, {}, d_rx_time, d_gnss_observables_map, d_enable_rx_clock_correction, 0, 0, false, false); + } + } + break; default: break; } @@ -3606,9 +3845,9 @@ int rtklib_pvt_gs::work(int noutput_items, gr_vector_const_void_star& input_item } d_rtcm_writing_started = true; break; - case 4: - case 5: - case 6: + case 4: // Galileo E1B + case 5: // Galileo E5a + case 6: // Galileo E5b if (d_rtcm_MT1045_rate_ms != 0) // allows deactivating messages by setting rate = 0 { for (const auto& gal_eph_iter : d_user_pvt_solver->galileo_ephemeris_map) @@ -3759,8 +3998,8 @@ int rtklib_pvt_gs::work(int noutput_items, gr_vector_const_void_star& input_item } d_rtcm_writing_started = true; break; - case 14: - case 15: + case 14: // Galileo E1B + Galileo E5a + case 15: // Galileo E1B + Galileo E5b if (d_rtcm_MT1045_rate_ms != 0) // allows deactivating messages by setting rate = 0 { for (const auto& gal_eph_iter : d_user_pvt_solver->galileo_ephemeris_map) @@ -3778,9 +4017,9 @@ int rtklib_pvt_gs::work(int noutput_items, gr_vector_const_void_star& input_item } d_rtcm_writing_started = true; break; - case 23: - case 24: - case 25: + case 23: // GLONASS L1 C/A + case 24: // GLONASS L2 C/A + case 25: // GLONASS L1 C/A + GLONASS L2 C/A if (d_rtcm_MT1020_rate_ms != 0) // allows deactivating messages by setting rate = 0 { for (const auto& glonass_gnav_eph_iter : d_user_pvt_solver->glonass_gnav_ephemeris_map) @@ -4094,6 +4333,104 @@ int rtklib_pvt_gs::work(int noutput_items, gr_vector_const_void_star& input_item } d_rtcm_writing_started = true; break; + case 101: // Galileo E1B + Galileo E6B + case 102: // Galileo E5a + Galileo E6B + case 103: // Galileo E5b + Galileo E6B + if (d_rtcm_MT1045_rate_ms != 0) // allows deactivating messages by setting rate = 0 + { + for (const auto& gal_eph_iter : d_user_pvt_solver->galileo_ephemeris_map) + { + d_rtcm_printer->Print_Rtcm_MT1045(gal_eph_iter.second); + } + } + if (d_rtcm_MSM_rate_ms != 0) + { + const auto gal_eph_iter = d_user_pvt_solver->galileo_ephemeris_map.cbegin(); + if (gal_eph_iter != d_user_pvt_solver->galileo_ephemeris_map.cend()) + { + d_rtcm_printer->Print_Rtcm_MSM(7, {}, {}, gal_eph_iter->second, {}, d_rx_time, d_gnss_observables_map, d_enable_rx_clock_correction, 0, 0, false, false); + } + } + d_rtcm_writing_started = true; + break; + case 104: // Galileo E1B + Galileo E5a + Galileo E6B + case 105: // Galileo E1B + Galileo E5b + Galileo E6B + if (d_rtcm_MT1045_rate_ms != 0) // allows deactivating messages by setting rate = 0 + { + for (const auto& gal_eph_iter : d_user_pvt_solver->galileo_ephemeris_map) + { + d_rtcm_printer->Print_Rtcm_MT1045(gal_eph_iter.second); + } + } + if (d_rtcm_MSM_rate_ms != 0) + { + const auto gal_eph_iter = d_user_pvt_solver->galileo_ephemeris_map.cbegin(); + if (gal_eph_iter != d_user_pvt_solver->galileo_ephemeris_map.cend()) + { + d_rtcm_printer->Print_Rtcm_MSM(7, {}, {}, gal_eph_iter->second, {}, d_rx_time, d_gnss_observables_map, d_enable_rx_clock_correction, 0, 0, false, false); + } + } + d_rtcm_writing_started = true; + break; + case 106: // GPS L1 C/A + Galileo E1B + Galileo E6B + if (d_rtcm_MT1019_rate_ms != 0) // allows deactivating messages by setting rate = 0 + { + for (const auto& gps_eph_iter : d_user_pvt_solver->gps_ephemeris_map) + { + d_rtcm_printer->Print_Rtcm_MT1019(gps_eph_iter.second); + } + } + if (d_rtcm_MT1045_rate_ms != 0) + { + for (const auto& gal_eph_iter : d_user_pvt_solver->galileo_ephemeris_map) + { + d_rtcm_printer->Print_Rtcm_MT1045(gal_eph_iter.second); + } + } + if (d_rtcm_MSM_rate_ms != 0) + { + auto gal_eph_iter = d_user_pvt_solver->galileo_ephemeris_map.cbegin(); + auto gps_eph_iter = d_user_pvt_solver->gps_ephemeris_map.cbegin(); + int gps_channel = 0; + int gal_channel = 0; + for (const auto& gnss_observables_iter : d_gnss_observables_map) + { + const std::string system(gnss_observables_iter.second.System, 1); + if (gps_channel == 0) + { + if (system == "G") + { + // This is a channel with valid GPS signal + gps_eph_iter = d_user_pvt_solver->gps_ephemeris_map.find(gnss_observables_iter.second.PRN); + if (gps_eph_iter != d_user_pvt_solver->gps_ephemeris_map.cend()) + { + gps_channel = 1; + } + } + } + if (gal_channel == 0) + { + if (system == "E") + { + gal_eph_iter = d_user_pvt_solver->galileo_ephemeris_map.find(gnss_observables_iter.second.PRN); + if (gal_eph_iter != d_user_pvt_solver->galileo_ephemeris_map.cend()) + { + gal_channel = 1; + } + } + } + } + if (gps_eph_iter != d_user_pvt_solver->gps_ephemeris_map.cend()) + { + d_rtcm_printer->Print_Rtcm_MSM(7, gps_eph_iter->second, {}, {}, {}, d_rx_time, d_gnss_observables_map, d_enable_rx_clock_correction, 0, 0, false, false); + } + if (gal_eph_iter != d_user_pvt_solver->galileo_ephemeris_map.cend()) + { + d_rtcm_printer->Print_Rtcm_MSM(7, {}, {}, gal_eph_iter->second, {}, d_rx_time, d_gnss_observables_map, d_enable_rx_clock_correction, 0, 0, false, false); + } + } + d_rtcm_writing_started = true; + break; default: break; } diff --git a/src/algorithms/PVT/gnuradio_blocks/rtklib_pvt_gs.h b/src/algorithms/PVT/gnuradio_blocks/rtklib_pvt_gs.h index ac79c0c61..b986b541c 100644 --- a/src/algorithms/PVT/gnuradio_blocks/rtklib_pvt_gs.h +++ b/src/algorithms/PVT/gnuradio_blocks/rtklib_pvt_gs.h @@ -190,6 +190,7 @@ private: evSBAS_1C, evGAL_1B, evGAL_5X, + evGAL_E6, evGAL_7X, evGLO_1G, evGLO_2G, diff --git a/src/algorithms/acquisition/adapters/CMakeLists.txt b/src/algorithms/acquisition/adapters/CMakeLists.txt index 82ad3d01c..48168cf77 100644 --- a/src/algorithms/acquisition/adapters/CMakeLists.txt +++ b/src/algorithms/acquisition/adapters/CMakeLists.txt @@ -24,6 +24,7 @@ set(ACQ_ADAPTER_SOURCES galileo_e5a_noncoherent_iq_acquisition_caf.cc galileo_e5a_pcps_acquisition.cc galileo_e5b_pcps_acquisition.cc + galileo_e6_pcps_acquisition.cc glonass_l1_ca_pcps_acquisition.cc glonass_l2_ca_pcps_acquisition.cc beidou_b1i_pcps_acquisition.cc @@ -46,6 +47,7 @@ set(ACQ_ADAPTER_HEADERS galileo_e5a_noncoherent_iq_acquisition_caf.h galileo_e5a_pcps_acquisition.h galileo_e5b_pcps_acquisition.h + galileo_e6_pcps_acquisition.h glonass_l1_ca_pcps_acquisition.h glonass_l2_ca_pcps_acquisition.h beidou_b1i_pcps_acquisition.h diff --git a/src/algorithms/acquisition/adapters/galileo_e6_pcps_acquisition.cc b/src/algorithms/acquisition/adapters/galileo_e6_pcps_acquisition.cc new file mode 100644 index 000000000..2a31b7d18 --- /dev/null +++ b/src/algorithms/acquisition/adapters/galileo_e6_pcps_acquisition.cc @@ -0,0 +1,253 @@ +/*! + * \file galileo_e6_pcps_acquisition.cc + * \brief Adapts a PCPS acquisition block to an AcquisitionInterface for + * Galileo E6 B/C Signals + * \author Carles Fernandez-Prades, 2020. cfernandez(at)cttc.es + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2020 (see AUTHORS file for a list of contributors) + * + * GNSS-SDR is a software defined Global Navigation + * Satellite Systems receiver + * + * This file is part of GNSS-SDR. + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + * ----------------------------------------------------------------------------- + */ + +#include "galileo_e6_pcps_acquisition.h" +#include "Galileo_E6.h" +#include "acq_conf.h" +#include "configuration_interface.h" +#include "galileo_e6_signal_processing.h" +#include "gnss_sdr_flags.h" +#include +#include + +#if HAS_STD_SPAN +#include +namespace own = std; +#else +#include +namespace own = gsl; +#endif + +GalileoE6PcpsAcquisition::GalileoE6PcpsAcquisition( + const ConfigurationInterface* configuration, + const std::string& role, + unsigned int in_streams, + unsigned int out_streams) : role_(role), + in_streams_(in_streams), + out_streams_(out_streams) +{ + configuration_ = configuration; + acq_parameters_.ms_per_code = 1; + acq_parameters_.SetFromConfiguration(configuration_, role, GALILEO_E6_B_CODE_CHIP_RATE_CPS, GALILEO_E6_OPT_ACQ_FS_SPS); + + DLOG(INFO) << "role " << role; + + if (FLAGS_doppler_max != 0) + { + acq_parameters_.doppler_max = FLAGS_doppler_max; + } + doppler_max_ = acq_parameters_.doppler_max; + doppler_step_ = static_cast(acq_parameters_.doppler_step); + item_type_ = acq_parameters_.item_type; + item_size_ = acq_parameters_.it_size; + fs_in_ = acq_parameters_.fs_in; + + code_length_ = static_cast(std::floor(static_cast(acq_parameters_.resampled_fs) / (GALILEO_E6_B_CODE_CHIP_RATE_CPS / GALILEO_E6_B_CODE_LENGTH_CHIPS))); + vector_length_ = static_cast(std::floor(acq_parameters_.sampled_ms * acq_parameters_.samples_per_ms) * (acq_parameters_.bit_transition_flag ? 2.0 : 1.0)); + code_ = std::vector>(vector_length_); + + sampled_ms_ = acq_parameters_.sampled_ms; + + acquisition_ = pcps_make_acquisition(acq_parameters_); + DLOG(INFO) << "acquisition(" << acquisition_->unique_id() << ")"; + + if (item_type_ == "cbyte") + { + cbyte_to_float_x2_ = make_complex_byte_to_float_x2(); + float_to_complex_ = gr::blocks::float_to_complex::make(); + } + + channel_ = 0; + threshold_ = 0.0; + doppler_center_ = 0; + gnss_synchro_ = nullptr; + + if (in_streams_ > 1) + { + LOG(ERROR) << "This implementation only supports one input stream"; + } + if (out_streams_ > 0) + { + LOG(ERROR) << "This implementation does not provide an output stream"; + } +} + + +void GalileoE6PcpsAcquisition::stop_acquisition() +{ + acquisition_->set_active(false); +} + + +void GalileoE6PcpsAcquisition::set_threshold(float threshold) +{ + threshold_ = threshold; + + acquisition_->set_threshold(threshold_); +} + + +void GalileoE6PcpsAcquisition::set_doppler_max(unsigned int doppler_max) +{ + doppler_max_ = doppler_max; + + acquisition_->set_doppler_max(doppler_max_); +} + + +void GalileoE6PcpsAcquisition::set_doppler_step(unsigned int doppler_step) +{ + doppler_step_ = doppler_step; + + acquisition_->set_doppler_step(doppler_step_); +} + + +void GalileoE6PcpsAcquisition::set_doppler_center(int doppler_center) +{ + doppler_center_ = doppler_center; + + acquisition_->set_doppler_center(doppler_center_); +} + + +void GalileoE6PcpsAcquisition::set_gnss_synchro(Gnss_Synchro* gnss_synchro) +{ + gnss_synchro_ = gnss_synchro; + + acquisition_->set_gnss_synchro(gnss_synchro_); +} + + +signed int GalileoE6PcpsAcquisition::mag() +{ + return acquisition_->mag(); +} + + +void GalileoE6PcpsAcquisition::init() +{ + acquisition_->init(); +} + + +void GalileoE6PcpsAcquisition::set_local_code() +{ + std::vector> code(code_length_); + + if (acq_parameters_.use_automatic_resampler) + { + galileo_e6_b_code_gen_complex_sampled(code, + gnss_synchro_->PRN, acq_parameters_.resampled_fs, 0); + } + else + { + galileo_e6_b_code_gen_complex_sampled(code, + gnss_synchro_->PRN, fs_in_, 0); + } + + own::span code__span(code_.data(), vector_length_); + for (unsigned int i = 0; i < sampled_ms_; i++) + { + std::copy_n(code.data(), code_length_, code__span.subspan(i * code_length_, code_length_).data()); + } + + acquisition_->set_local_code(code_.data()); +} + + +void GalileoE6PcpsAcquisition::reset() +{ + acquisition_->set_active(true); +} + + +void GalileoE6PcpsAcquisition::set_state(int state) +{ + acquisition_->set_state(state); +} + + +void GalileoE6PcpsAcquisition::connect(gr::top_block_sptr top_block) +{ + if (item_type_ == "gr_complex" || item_type_ == "cshort") + { + // nothing to connect + } + else if (item_type_ == "cbyte") + { + // Since a byte-based acq implementation is not available, + // we just convert cshorts to gr_complex + top_block->connect(cbyte_to_float_x2_, 0, float_to_complex_, 0); + top_block->connect(cbyte_to_float_x2_, 1, float_to_complex_, 1); + top_block->connect(float_to_complex_, 0, acquisition_, 0); + } + else + { + LOG(WARNING) << item_type_ << " unknown acquisition item type"; + } +} + + +void GalileoE6PcpsAcquisition::disconnect(gr::top_block_sptr top_block) +{ + if (item_type_ == "gr_complex" || item_type_ == "cshort") + { + // nothing to disconnect + } + else if (item_type_ == "cbyte") + { + top_block->disconnect(cbyte_to_float_x2_, 0, float_to_complex_, 0); + top_block->disconnect(cbyte_to_float_x2_, 1, float_to_complex_, 1); + top_block->disconnect(float_to_complex_, 0, acquisition_, 0); + } + else + { + LOG(WARNING) << item_type_ << " unknown acquisition item type"; + } +} + + +gr::basic_block_sptr GalileoE6PcpsAcquisition::get_left_block() +{ + if (item_type_ == "gr_complex" || item_type_ == "cshort") + { + return acquisition_; + } + if (item_type_ == "cbyte") + { + return cbyte_to_float_x2_; + } + + LOG(WARNING) << item_type_ << " unknown acquisition item type"; + return nullptr; +} + + +gr::basic_block_sptr GalileoE6PcpsAcquisition::get_right_block() +{ + return acquisition_; +} + + +void GalileoE6PcpsAcquisition::set_resampler_latency(uint32_t latency_samples) +{ + acquisition_->set_resampler_latency(latency_samples); +} diff --git a/src/algorithms/acquisition/adapters/galileo_e6_pcps_acquisition.h b/src/algorithms/acquisition/adapters/galileo_e6_pcps_acquisition.h new file mode 100644 index 000000000..4fcd77228 --- /dev/null +++ b/src/algorithms/acquisition/adapters/galileo_e6_pcps_acquisition.h @@ -0,0 +1,189 @@ +/*! + * \file galileo_e6_pcps_acquisition.h + * \brief Adapts a PCPS acquisition block to an AcquisitionInterface for + * Galileo E6 B/C Signals + * \author Carles Fernandez-Prades, 2020. cfernandez(at)cttc.es + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2020 (see AUTHORS file for a list of contributors) + * + * GNSS-SDR is a software defined Global Navigation + * Satellite Systems receiver + * + * This file is part of GNSS-SDR. + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + * ----------------------------------------------------------------------------- + */ + +#ifndef GNSS_SDR_GALILEO_E6_PCPS_ACQUISITION_H +#define GNSS_SDR_GALILEO_E6_PCPS_ACQUISITION_H + +#include "acq_conf.h" +#include "channel_fsm.h" +#include "complex_byte_to_float_x2.h" +#include "gnss_synchro.h" +#include "pcps_acquisition.h" +#include +#include +#include +#include + +/** \addtogroup Acquisition + * \{ */ +/** \addtogroup Acq_adapters + * \{ */ + + +class ConfigurationInterface; + +/*! + * \brief This class adapts a PCPS acquisition block to an + * AcquisitionInterface for Galileo E6 Signals + */ +class GalileoE6PcpsAcquisition : public AcquisitionInterface +{ +public: + GalileoE6PcpsAcquisition( + const ConfigurationInterface* configuration, + const std::string& role, + unsigned int in_streams, + unsigned int out_streams); + + ~GalileoE6PcpsAcquisition() = default; + + inline std::string role() override + { + return role_; + } + + /*! + * \brief Returns "Galileo_E6_PCPS_Acquisition" + */ + inline std::string implementation() override + { + return "Galileo_E6_PCPS_Acquisition"; + } + + size_t item_size() override + { + return item_size_; + } + + void connect(gr::top_block_sptr top_block) override; + void disconnect(gr::top_block_sptr top_block) override; + gr::basic_block_sptr get_left_block() override; + gr::basic_block_sptr get_right_block() override; + + /*! + * \brief Set acquisition/tracking common Gnss_Synchro object pointer + * to efficiently exchange synchronization data between acquisition and + * tracking blocks + */ + void set_gnss_synchro(Gnss_Synchro* p_gnss_synchro) override; + + /*! + * \brief Set acquisition channel unique ID + */ + inline void set_channel(unsigned int channel) override + { + channel_ = channel; + acquisition_->set_channel(channel_); + } + + /*! + * \brief Set channel fsm associated to this acquisition instance + */ + inline void set_channel_fsm(std::weak_ptr channel_fsm) override + { + channel_fsm_ = channel_fsm; + acquisition_->set_channel_fsm(channel_fsm); + } + + /*! + * \brief Set statistics threshold of PCPS algorithm + */ + void set_threshold(float threshold) override; + + /*! + * \brief Set maximum Doppler off grid search + */ + void set_doppler_max(unsigned int doppler_max) override; + + /*! + * \brief Set Doppler steps for the grid search + */ + void set_doppler_step(unsigned int doppler_step) override; + + /*! + * \brief Set Doppler center for the grid search + */ + void set_doppler_center(int doppler_center) override; + + /*! + * \brief Initializes acquisition algorithm. + */ + void init() override; + + /*! + * \brief Sets local code for Galileo E1 PCPS acquisition algorithm. + */ + void set_local_code() override; + + /*! + * \brief Returns the maximum peak of grid search + */ + signed int mag() override; + + /*! + * \brief Restart acquisition algorithm + */ + void reset() override; + + /*! + * \brief If state = 1, it forces the block to start acquiring from the first sample + */ + void set_state(int state) override; + + /*! + * \brief Stop running acquisition + */ + void stop_acquisition() override; + + /*! + * \brief Sets the resampler latency to account it in the acquisition code delay estimation + */ + void set_resampler_latency(uint32_t latency_samples) override; + +private: + pcps_acquisition_sptr acquisition_; + std::vector> code_; + std::weak_ptr channel_fsm_; + gr::blocks::float_to_complex::sptr float_to_complex_; + complex_byte_to_float_x2_sptr cbyte_to_float_x2_; + Gnss_Synchro* gnss_synchro_; + const ConfigurationInterface* configuration_; + Acq_Conf acq_parameters_; + std::string item_type_; + std::string dump_filename_; + std::string role_; + int64_t fs_in_; + size_t item_size_; + float threshold_; + int doppler_center_; + unsigned int vector_length_; + unsigned int code_length_; + unsigned int channel_; + unsigned int doppler_max_; + unsigned int doppler_step_; + unsigned int sampled_ms_; + unsigned int in_streams_; + unsigned int out_streams_; +}; + + +/** \} */ +/** \} */ +#endif // GNSS_SDR_GALILEO_E6_PCPS_ACQUISITION_H diff --git a/src/algorithms/acquisition/gnuradio_blocks/pcps_opencl_acquisition_cc.h b/src/algorithms/acquisition/gnuradio_blocks/pcps_opencl_acquisition_cc.h index c1d652000..0e053c48b 100644 --- a/src/algorithms/acquisition/gnuradio_blocks/pcps_opencl_acquisition_cc.h +++ b/src/algorithms/acquisition/gnuradio_blocks/pcps_opencl_acquisition_cc.h @@ -42,8 +42,8 @@ #define CL_SILENCE_DEPRECATION #include "channel_fsm.h" -#include "gnss_synchro.h" #include "gnss_block_interface.h" +#include "gnss_synchro.h" #include "opencl/fft_internal.h" #include #include diff --git a/src/algorithms/libs/CMakeLists.txt b/src/algorithms/libs/CMakeLists.txt index 1c5a3c827..9281832cb 100644 --- a/src/algorithms/libs/CMakeLists.txt +++ b/src/algorithms/libs/CMakeLists.txt @@ -19,6 +19,7 @@ set(GNSS_SPLIBS_SOURCES glonass_l2_signal_processing.cc pass_through.cc galileo_e5_signal_processing.cc + galileo_e6_signal_processing.cc beidou_b1i_signal_processing.cc beidou_b3i_signal_processing.cc complex_byte_to_float_x2.cc @@ -44,6 +45,7 @@ set(GNSS_SPLIBS_HEADERS glonass_l2_signal_processing.h pass_through.h galileo_e5_signal_processing.h + galileo_e6_signal_processing.h beidou_b1i_signal_processing.h beidou_b3i_signal_processing.h complex_byte_to_float_x2.h diff --git a/src/algorithms/libs/galileo_e6_signal_processing.cc b/src/algorithms/libs/galileo_e6_signal_processing.cc new file mode 100644 index 000000000..d6ef9e5d2 --- /dev/null +++ b/src/algorithms/libs/galileo_e6_signal_processing.cc @@ -0,0 +1,253 @@ +/*! + * \file galileo_e6_signal_processing.cc + * \brief This library implements various functions for Galileo E6 signals such + * as replica code generation + * \author Carles Fernandez-Prades, 2020. cfernandez(at)cttc.es + * + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2020 (see AUTHORS file for a list of contributors) + * + * GNSS-SDR is a software defined Global Navigation + * Satellite Systems receiver + * + * This file is part of GNSS-SDR. + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + * ----------------------------------------------------------------------------- + */ + + +#include "galileo_e6_signal_processing.h" +#include "Galileo_E6.h" +#include "gnss_signal_processing.h" +#include +#include + +void galileo_e6_b_code_gen_complex_primary(own::span> _dest, + int32_t _prn) +{ + const uint32_t prn = _prn - 1; + uint32_t index = 0; + std::array a{}; + if ((_prn < 1) || (_prn > 50)) + { + return; + } + + for (size_t i = 0; i < GALILEO_E6_B_PRIMARY_CODE_STR_LENGTH - 1; i++) + { + hex_to_binary_converter(a, GALILEO_E6_B_PRIMARY_CODE[prn][i]); + _dest[index] = std::complex(static_cast(a[0]), 0.0); + _dest[index + 1] = std::complex(static_cast(a[1]), 0.0); + _dest[index + 2] = std::complex(static_cast(a[2]), 0.0); + _dest[index + 3] = std::complex(static_cast(a[3]), 0.0); + index = index + 4; + } + // last bit is filled up with a zero + hex_to_binary_converter(a, GALILEO_E6_B_PRIMARY_CODE[prn][GALILEO_E6_B_PRIMARY_CODE_STR_LENGTH - 1]); + _dest[index] = std::complex(static_cast(a[0]), 0.0); + _dest[index + 1] = std::complex(static_cast(a[1]), 0.0); + _dest[index + 2] = std::complex(static_cast(a[2]), 0.0); +} + + +void galileo_e6_b_code_gen_float_primary(own::span _dest, int32_t _prn) +{ + const uint32_t prn = _prn - 1; + uint32_t index = 0; + std::array a{}; + if ((_prn < 1) || (_prn > 50)) + { + return; + } + + for (size_t i = 0; i < GALILEO_E6_B_PRIMARY_CODE_STR_LENGTH - 1; i++) + { + hex_to_binary_converter(a, GALILEO_E6_B_PRIMARY_CODE[prn][i]); + _dest[index] = static_cast(a[0]); + _dest[index + 1] = static_cast(a[1]); + _dest[index + 2] = static_cast(a[2]); + _dest[index + 3] = static_cast(a[3]); + index = index + 4; + } + // last bit is filled up with a zero + hex_to_binary_converter(a, GALILEO_E6_B_PRIMARY_CODE[prn][GALILEO_E6_B_PRIMARY_CODE_STR_LENGTH - 1]); + _dest[index] = static_cast(a[0]); + _dest[index + 1] = static_cast(a[1]); + _dest[index + 2] = static_cast(a[2]); +} + + +void galileo_e6_b_code_gen_complex_sampled(own::span> _dest, + uint32_t _prn, + int32_t _fs, + uint32_t _chip_shift) +{ + constexpr uint32_t _codeLength = GALILEO_E6_B_CODE_LENGTH_CHIPS; + constexpr int32_t _codeFreqBasis = GALILEO_E6_B_CODE_CHIP_RATE_CPS; + + const auto _samplesPerCode = static_cast(static_cast(_fs) / (static_cast(_codeFreqBasis) / static_cast(_codeLength))); + const uint32_t delay = ((_codeLength - _chip_shift) % _codeLength) * _samplesPerCode / _codeLength; + + std::vector> _code(_codeLength); + galileo_e6_b_code_gen_complex_primary(_code, _prn); + + if (_fs != _codeFreqBasis) + { + std::vector> _resampled_signal(_samplesPerCode); + resampler(_code, _resampled_signal, _codeFreqBasis, _fs); // resamples code to fs + _code = std::move(_resampled_signal); + } + + for (uint32_t i = 0; i < _samplesPerCode; i++) + { + _dest[(i + delay) % _samplesPerCode] = _code[i]; + } +} + + +void galileo_e6_c_code_gen_complex_primary(own::span> _dest, + int32_t _prn) +{ + const uint32_t prn = _prn - 1; + uint32_t index = 0; + std::array a{}; + if ((_prn < 1) || (_prn > 50)) + { + return; + } + for (size_t i = 0; i < GALILEO_E6_C_PRIMARY_CODE_STR_LENGTH - 1; i++) + { + hex_to_binary_converter(a, GALILEO_E6_C_PRIMARY_CODE[prn][i]); + _dest[index] = std::complex(static_cast(a[0]), 0.0); + _dest[index + 1] = std::complex(static_cast(a[1]), 0.0); + _dest[index + 2] = std::complex(static_cast(a[2]), 0.0); + _dest[index + 3] = std::complex(static_cast(a[3]), 0.0); + index = index + 4; + } + // last bit is filled up with a zero + hex_to_binary_converter(a, GALILEO_E6_C_PRIMARY_CODE[prn][GALILEO_E6_C_PRIMARY_CODE_STR_LENGTH - 1]); + _dest[index] = std::complex(static_cast(a[0]), 0.0); + _dest[index + 1] = std::complex(static_cast(a[1]), 0.0); + _dest[index + 2] = std::complex(static_cast(a[2]), 0.0); +} + + +void galileo_e6_c_code_gen_float_primary(own::span _dest, int32_t _prn) +{ + const uint32_t prn = _prn - 1; + uint32_t index = 0; + std::array a{}; + if ((_prn < 1) || (_prn > 50)) + { + return; + } + for (size_t i = 0; i < GALILEO_E6_C_PRIMARY_CODE_STR_LENGTH - 1; i++) + { + hex_to_binary_converter(a, GALILEO_E6_C_PRIMARY_CODE[prn][i]); + _dest[index] = static_cast(a[0]); + _dest[index + 1] = static_cast(a[1]); + _dest[index + 2] = static_cast(a[2]); + _dest[index + 3] = static_cast(a[3]); + index = index + 4; + } + // last bit is filled up with a zero + hex_to_binary_converter(a, GALILEO_E6_C_PRIMARY_CODE[prn][GALILEO_E6_C_PRIMARY_CODE_STR_LENGTH - 1]); + _dest[index] = static_cast(a[0]); + _dest[index + 1] = static_cast(a[1]); + _dest[index + 2] = static_cast(a[2]); +} + + +void galileo_e6_c_code_gen_complex_sampled(own::span> _dest, + uint32_t _prn, + int32_t _fs, + uint32_t _chip_shift) +{ + constexpr uint32_t _codeLength = GALILEO_E6_C_CODE_LENGTH_CHIPS; + constexpr int32_t _codeFreqBasis = GALILEO_E6_C_CODE_CHIP_RATE_CPS; + + const auto _samplesPerCode = static_cast(static_cast(_fs) / (static_cast(_codeFreqBasis) / static_cast(_codeLength))); + const uint32_t delay = ((_codeLength - _chip_shift) % _codeLength) * _samplesPerCode / _codeLength; + + std::vector> _code(_codeLength); + galileo_e6_c_code_gen_complex_primary(_code, _prn); + + if (_fs != _codeFreqBasis) + { + std::vector> _resampled_signal(_samplesPerCode); + resampler(_code, _resampled_signal, _codeFreqBasis, _fs); // resamples code to fs + _code = std::move(_resampled_signal); + } + + for (uint32_t i = 0; i < _samplesPerCode; i++) + { + _dest[(i + delay) % _samplesPerCode] = _code[i]; + } +} + + +void galileo_e6_c_secondary_code_gen_complex(own::span> _dest, + int32_t _prn) +{ + const uint32_t prn = _prn - 1; + uint32_t index = 0; + std::array a{}; + if ((_prn < 1) || (_prn > 50)) + { + return; + } + for (size_t i = 0; i < GALILEO_E6_C_SECONDARY_CODE_STR_LENGTH; i++) + { + hex_to_binary_converter(a, GALILEO_E6_C_SECONDARY_CODE[prn][i]); + _dest[index] = std::complex(static_cast(a[0]), 0.0); + _dest[index + 1] = std::complex(static_cast(a[1]), 0.0); + _dest[index + 2] = std::complex(static_cast(a[2]), 0.0); + _dest[index + 3] = std::complex(static_cast(a[3]), 0.0); + index = index + 4; + } +} + + +void galileo_e6_c_secondary_code_gen_float(own::span _dest, + int32_t _prn) +{ + const uint32_t prn = _prn - 1; + uint32_t index = 0; + std::array a{}; + if ((_prn < 1) || (_prn > 50)) + { + return; + } + for (size_t i = 0; i < GALILEO_E6_C_SECONDARY_CODE_STR_LENGTH; i++) + { + hex_to_binary_converter(a, GALILEO_E6_C_SECONDARY_CODE[prn][i]); + _dest[index] = static_cast(a[0]); + _dest[index + 1] = static_cast(a[1]); + _dest[index + 2] = static_cast(a[2]); + _dest[index + 3] = static_cast(a[3]); + index = index + 4; + } +} + + +std::string galileo_e6_c_secondary_code(int32_t _prn) +{ + std::string dest(static_cast(GALILEO_E6_C_SECONDARY_CODE_LENGTH_CHIPS), '0'); + const uint32_t prn = _prn - 1; + uint32_t index = 0; + for (size_t i = 0; i < GALILEO_E6_C_SECONDARY_CODE_STR_LENGTH; i++) + { + std::string aux = hex_to_binary_string(GALILEO_E6_C_SECONDARY_CODE[prn][i]); + dest[index] = aux[0]; + dest[index + 1] = aux[1]; + dest[index + 2] = aux[2]; + dest[index + 3] = aux[3]; + index = index + 4; + } + + return dest; +} diff --git a/src/algorithms/libs/galileo_e6_signal_processing.h b/src/algorithms/libs/galileo_e6_signal_processing.h new file mode 100644 index 000000000..809f852f0 --- /dev/null +++ b/src/algorithms/libs/galileo_e6_signal_processing.h @@ -0,0 +1,111 @@ +/*! + * \file galileo_e6_signal_processing.h + * \brief This library implements various functions for Galileo E6 signals such + * as replica code generation + * \author Carles Fernandez-Prades, 2020. cfernandez(at)cttc.es + * + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2020 (see AUTHORS file for a list of contributors) + * + * GNSS-SDR is a software defined Global Navigation + * Satellite Systems receiver + * + * This file is part of GNSS-SDR. + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + * ----------------------------------------------------------------------------- + */ + +#ifndef GNSS_SDR_GALILEO_E6_SIGNAL_PROCESSING_H +#define GNSS_SDR_GALILEO_E6_SIGNAL_PROCESSING_H + +#include +#include +#include +#include +#if HAS_STD_SPAN +#include +namespace own = std; +#else +#include +namespace own = gsl; +#endif + +/** \addtogroup Algorithms_Library + * \{ */ +/** \addtogroup Algorithm_libs algorithms_libs + * \{ */ + + +/*! + * \brief Generates Galileo E6B code at 1 sample/chip + */ +void galileo_e6_b_code_gen_complex_primary(own::span> _dest, + int32_t _prn); + + +/*! + * \brief Generates Galileo E6B code at 1 sample/chip + */ +void galileo_e6_b_code_gen_float_primary(own::span _dest, int32_t _prn); + + +/*! + * \brief Generates Galileo E6B complex code, shifted to the desired chip and + * sampled at a frequency fs + */ +void galileo_e6_b_code_gen_complex_sampled(own::span> _dest, + uint32_t _prn, + int32_t _fs, + uint32_t _chip_shift); + + +/*! + * \brief Generates Galileo E6C codes at 1 sample/chip + */ +void galileo_e6_c_code_gen_complex_primary(own::span> _dest, + int32_t _prn); + + +/*! + * \brief Generates Galileo E6C codes at 1 sample/chip + */ +void galileo_e6_c_code_gen_float_primary(own::span _dest, int32_t _prn); + + +/*! + * \brief Generates Galileo E6C complex codes, shifted to the desired chip and + * sampled at a frequency fs + */ +void galileo_e6_c_code_gen_complex_sampled(own::span> _dest, + uint32_t _prn, + int32_t _fs, + uint32_t _chip_shift); + + +/*! + * \brief Generates Galileo E6C secondary codes at 1 sample/chip + */ +void galileo_e6_c_secondary_code_gen_complex(own::span> _dest, + int32_t _prn); + + +/*! + * \brief Generates Galileo E6C secondary codes at 1 sample/chip + */ +void galileo_e6_c_secondary_code_gen_float(own::span _dest, + int32_t _prn); + + +/*! + * \brief Generates a string with Galileo E6C secondary codes at 1 sample/chip + */ +std::string galileo_e6_c_secondary_code(int32_t _prn); + + +/** \} */ +/** \} */ +#endif // GNSS_SDR_GALILEO_E6_SIGNAL_PROCESSING_H diff --git a/src/algorithms/libs/gnss_signal_processing.cc b/src/algorithms/libs/gnss_signal_processing.cc index 51463b5d2..f090fc9f6 100644 --- a/src/algorithms/libs/gnss_signal_processing.cc +++ b/src/algorithms/libs/gnss_signal_processing.cc @@ -149,6 +149,114 @@ void hex_to_binary_converter(own::span _dest, char _from) } +std::string hex_to_binary_string(char _from) +{ + std::string _dest("0000"); + switch (_from) + { + case '0': + _dest[0] = '0'; + _dest[1] = '0'; + _dest[2] = '0'; + _dest[3] = '0'; + break; + case '1': + _dest[0] = '0'; + _dest[1] = '0'; + _dest[2] = '0'; + _dest[3] = '1'; + break; + case '2': + _dest[0] = '0'; + _dest[1] = '0'; + _dest[2] = '1'; + _dest[3] = '0'; + break; + case '3': + _dest[0] = '0'; + _dest[1] = '0'; + _dest[2] = '1'; + _dest[3] = '1'; + break; + case '4': + _dest[0] = '0'; + _dest[1] = '1'; + _dest[2] = '0'; + _dest[3] = '0'; + break; + case '5': + _dest[0] = '0'; + _dest[1] = '1'; + _dest[2] = '0'; + _dest[3] = '1'; + break; + case '6': + _dest[0] = '0'; + _dest[1] = '1'; + _dest[2] = '1'; + _dest[3] = '0'; + break; + case '7': + _dest[0] = '0'; + _dest[1] = '1'; + _dest[2] = '1'; + _dest[3] = '1'; + break; + case '8': + _dest[0] = '1'; + _dest[1] = '0'; + _dest[2] = '0'; + _dest[3] = '0'; + break; + case '9': + _dest[0] = '1'; + _dest[1] = '0'; + _dest[2] = '0'; + _dest[3] = '1'; + break; + case 'A': + _dest[0] = '1'; + _dest[1] = '0'; + _dest[2] = '1'; + _dest[3] = '0'; + break; + case 'B': + _dest[0] = '1'; + _dest[1] = '0'; + _dest[2] = '1'; + _dest[3] = '1'; + break; + case 'C': + _dest[0] = '1'; + _dest[1] = '1'; + _dest[2] = '0'; + _dest[3] = '0'; + break; + case 'D': + _dest[0] = '1'; + _dest[1] = '1'; + _dest[2] = '0'; + _dest[3] = '1'; + break; + case 'E': + _dest[0] = '1'; + _dest[1] = '1'; + _dest[2] = '1'; + _dest[3] = '0'; + break; + case 'F': + _dest[0] = '1'; + _dest[1] = '1'; + _dest[2] = '1'; + _dest[3] = '1'; + break; + default: + break; + } + return _dest; +} + + void resampler(const own::span _from, own::span _dest, float _fs_in, float _fs_out) { diff --git a/src/algorithms/libs/gnss_signal_processing.h b/src/algorithms/libs/gnss_signal_processing.h index 4a677699a..2e6f3e6be 100644 --- a/src/algorithms/libs/gnss_signal_processing.h +++ b/src/algorithms/libs/gnss_signal_processing.h @@ -25,6 +25,7 @@ #include #include +#include #if HAS_STD_SPAN #include namespace own = std; @@ -58,6 +59,13 @@ void complex_exp_gen_conj(own::span> _dest, double _f, doubl */ void hex_to_binary_converter(own::span _dest, char _from); +/*! + * \brief This function makes a conversion from hex (the input is a char) + * to binary (the output is a string of 4 char with 0 or 1 values). + * + */ +std::string hex_to_binary_string(char _from); + /*! * \brief This function resamples a sequence of float values. * diff --git a/src/algorithms/observables/gnuradio_blocks/hybrid_observables_gs.cc b/src/algorithms/observables/gnuradio_blocks/hybrid_observables_gs.cc index 3d06b1a67..656ac7e33 100644 --- a/src/algorithms/observables/gnuradio_blocks/hybrid_observables_gs.cc +++ b/src/algorithms/observables/gnuradio_blocks/hybrid_observables_gs.cc @@ -155,6 +155,7 @@ hybrid_observables_gs::hybrid_observables_gs(const Obs_Conf &conf_) : gr::block( d_mapStringValues["L5"] = evGPS_L5; d_mapStringValues["1B"] = evGAL_1B; d_mapStringValues["5X"] = evGAL_5X; + d_mapStringValues["E6"] = evGAL_E6; d_mapStringValues["7X"] = evGAL_7X; d_mapStringValues["1G"] = evGLO_1G; d_mapStringValues["2G"] = evGLO_2G; @@ -572,6 +573,9 @@ void hybrid_observables_gs::smooth_pseudoranges(std::vector &data) case evGAL_5X: wavelength_m = SPEED_OF_LIGHT_M_S / FREQ5; break; + case evGAL_E6: + wavelength_m = SPEED_OF_LIGHT_M_S / FREQ6; + break; case evGAL_7X: wavelength_m = SPEED_OF_LIGHT_M_S / FREQ7; break; diff --git a/src/algorithms/observables/gnuradio_blocks/hybrid_observables_gs.h b/src/algorithms/observables/gnuradio_blocks/hybrid_observables_gs.h index 5d188a5f3..c5a448c37 100644 --- a/src/algorithms/observables/gnuradio_blocks/hybrid_observables_gs.h +++ b/src/algorithms/observables/gnuradio_blocks/hybrid_observables_gs.h @@ -90,6 +90,7 @@ private: evSBAS_1C, evGAL_1B, evGAL_5X, + evGAL_E6, evGAL_7X, evGLO_1G, evGLO_2G, diff --git a/src/algorithms/signal_generator/adapters/signal_generator.cc b/src/algorithms/signal_generator/adapters/signal_generator.cc index a385ffe9a..40767c30c 100644 --- a/src/algorithms/signal_generator/adapters/signal_generator.cc +++ b/src/algorithms/signal_generator/adapters/signal_generator.cc @@ -26,6 +26,7 @@ #include "Galileo_E1.h" #include "Galileo_E5a.h" #include "Galileo_E5b.h" +#include "Galileo_E6.h" #include "configuration_interface.h" #include #include @@ -93,6 +94,10 @@ SignalGenerator::SignalGenerator(const ConfigurationInterface* configuration, { vector_length = static_cast(round(static_cast(fs_in) / (GALILEO_E5B_CODE_CHIP_RATE_CPS / GALILEO_E5B_CODE_LENGTH_CHIPS))); } + else if (signal1[0].at(1) == '6') + { + vector_length = static_cast(round(static_cast(fs_in) / (GALILEO_E6_C_CODE_CHIP_RATE_CPS / GALILEO_E6_C_CODE_LENGTH_CHIPS)) * GALILEO_E6_C_SECONDARY_CODE_LENGTH_CHIPS); + } else { vector_length = static_cast(round(static_cast(fs_in) / (GALILEO_E1_CODE_CHIP_RATE_CPS / GALILEO_E1_B_CODE_LENGTH_CHIPS)) * GALILEO_E1_C_SECONDARY_CODE_LENGTH); diff --git a/src/algorithms/signal_generator/gnuradio_blocks/signal_generator_c.cc b/src/algorithms/signal_generator/gnuradio_blocks/signal_generator_c.cc index 387d25787..5c37e866f 100644 --- a/src/algorithms/signal_generator/gnuradio_blocks/signal_generator_c.cc +++ b/src/algorithms/signal_generator/gnuradio_blocks/signal_generator_c.cc @@ -23,8 +23,10 @@ #include "Galileo_E1.h" #include "Galileo_E5a.h" #include "Galileo_E5b.h" +#include "Galileo_E6.h" #include "galileo_e1_signal_processing.h" #include "galileo_e5_signal_processing.h" +#include "galileo_e6_signal_processing.h" #include "glonass_l1_signal_processing.h" #include "gps_sdr_signal_processing.h" #include @@ -142,6 +144,13 @@ void signal_generator_c::init() data_bit_duration_ms_.push_back(1e3 / GALILEO_E5B_SYMBOL_RATE_BPS); } + else if (signal_[sat].at(1) == '6') + { + samples_per_code_.push_back(round(static_cast(fs_in_) / (GALILEO_E6_B_CODE_CHIP_RATE_CPS / GALILEO_E6_B_CODE_LENGTH_CHIPS))); + + num_of_codes_per_vector_.push_back(static_cast(GALILEO_E6_C_SECONDARY_CODE_LENGTH_CHIPS)); + data_bit_duration_ms_.push_back(1); + } else { samples_per_code_.push_back(round(static_cast(fs_in_) / (GALILEO_E1_CODE_CHIP_RATE_CPS / GALILEO_E1_B_CODE_LENGTH_CHIPS))); @@ -239,6 +248,37 @@ void signal_generator_c::generate_codes() } } } + else if (signal_[sat].at(1) == '6') + { + // Generate one code-period of E&B signal + galileo_e6_b_code_gen_complex_sampled(code, PRN_[sat], fs_in_, + static_cast(GALILEO_E6_B_CODE_LENGTH_CHIPS) - delay_chips_[sat]); + // Obtain the desired CN0 assuming that Pn = 1. + if (noise_flag_) + { + for (unsigned int i = 0; i < samples_per_code_[sat]; i++) + { + code[i] *= std::sqrt(std::pow(10.0F, CN0_dB_[sat] / 10.0F) / BW_BB_ / 2.0F); + } + } + // Concatenate "num_of_codes_per_vector_" codes + for (unsigned int i = 0; i < num_of_codes_per_vector_[sat]; i++) + { + memcpy(&(sampled_code_data_[sat][i * samples_per_code_[sat]]), + code.data(), sizeof(gr_complex) * samples_per_code_[sat]); + } + // Generate E6C signal (100 code-periods, with secondary code) + galileo_e6_c_code_gen_complex_sampled(sampled_code_pilot_[sat], PRN_[sat], fs_in_, + static_cast(GALILEO_E6_C_CODE_LENGTH_CHIPS) - delay_chips_[sat]); + // Obtain the desired CN0 assuming that Pn = 1. + if (noise_flag_) + { + for (unsigned int i = 0; i < vector_length_; i++) + { + sampled_code_pilot_[sat][i] *= sqrt(std::pow(10.0F, CN0_dB_[sat] / 10.0F) / BW_BB_ / 2.0F); + } + } + } else { // Generate one code-period of E1B signal @@ -442,6 +482,33 @@ int signal_generator_c::general_work(int noutput_items __attribute__((unused)), out_idx++; } } + else if (signal_[sat].at(1) == '6') + { + // EACH WORK outputs 1 modulated primary code + int codelen = static_cast(GALILEO_E6_C_CODE_LENGTH_CHIPS); + unsigned int delay_samples = (delay_chips_[sat] % codelen) * samples_per_code_[sat] / codelen; + for (i = 0; i < num_of_codes_per_vector_[sat]; i++) + { + for (k = 0; k < delay_samples; k++) + { + out[out_idx] += (sampled_code_data_[sat][out_idx] * current_data_bits_[sat] - sampled_code_pilot_[sat][out_idx]) * complex_phase_[out_idx]; + out_idx++; + } + + if (ms_counter_[sat] == 0 && data_flag_) + { + // New random data bit + current_data_bits_[sat] = gr_complex((uniform_dist(e1) % 2) == 0 ? 1 : -1, 0); + } + + for (k = delay_samples; k < samples_per_code_[sat]; k++) + { + out[out_idx] += (sampled_code_data_[sat][out_idx] * current_data_bits_[sat] - sampled_code_pilot_[sat][out_idx]) * complex_phase_[out_idx]; + out_idx++; + } + ms_counter_[sat] = (ms_counter_[sat] + 1) % data_bit_duration_ms_[sat]; + } + } else { auto delay_samples = static_cast((delay_chips_[sat] % static_cast(GALILEO_E1_B_CODE_LENGTH_CHIPS)) * samples_per_code_[sat] / GALILEO_E1_B_CODE_LENGTH_CHIPS); diff --git a/src/algorithms/telemetry_decoder/adapters/CMakeLists.txt b/src/algorithms/telemetry_decoder/adapters/CMakeLists.txt index a471dbd5f..0223bf737 100644 --- a/src/algorithms/telemetry_decoder/adapters/CMakeLists.txt +++ b/src/algorithms/telemetry_decoder/adapters/CMakeLists.txt @@ -16,6 +16,7 @@ set(TELEMETRY_DECODER_ADAPTER_SOURCES sbas_l1_telemetry_decoder.cc galileo_e5a_telemetry_decoder.cc galileo_e5b_telemetry_decoder.cc + galileo_e6_telemetry_decoder.cc glonass_l1_ca_telemetry_decoder.cc glonass_l2_ca_telemetry_decoder.cc beidou_b1i_telemetry_decoder.cc @@ -30,6 +31,7 @@ set(TELEMETRY_DECODER_ADAPTER_HEADERS sbas_l1_telemetry_decoder.h galileo_e5a_telemetry_decoder.h galileo_e5b_telemetry_decoder.h + galileo_e6_telemetry_decoder.h glonass_l1_ca_telemetry_decoder.h glonass_l2_ca_telemetry_decoder.h beidou_b1i_telemetry_decoder.h diff --git a/src/algorithms/telemetry_decoder/adapters/galileo_e6_telemetry_decoder.cc b/src/algorithms/telemetry_decoder/adapters/galileo_e6_telemetry_decoder.cc new file mode 100644 index 000000000..26fa7d2b5 --- /dev/null +++ b/src/algorithms/telemetry_decoder/adapters/galileo_e6_telemetry_decoder.cc @@ -0,0 +1,93 @@ +/*! + * \file galileo_e6_telemetry_decoder.cc + * \brief Interface of an adapter of a GALILEO E6 CNAV data decoder block + * to a TelemetryDecoderInterface + * \author Carles Fernandez-Prades, 2020 cfernandez@cttc.es + * + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2020 (see AUTHORS file for a list of contributors) + * + * GNSS-SDR is a software defined Global Navigation + * Satellite Systems receiver + * + * This file is part of GNSS-SDR. + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + * ----------------------------------------------------------------------------- + */ + + +#include "galileo_e6_telemetry_decoder.h" +#include "configuration_interface.h" +#include + + +GalileoE6TelemetryDecoder::GalileoE6TelemetryDecoder( + const ConfigurationInterface* configuration, + const std::string& role, + unsigned int in_streams, + unsigned int out_streams) : role_(role), + in_streams_(in_streams), + out_streams_(out_streams) +{ + const std::string default_dump_filename("./navigation.dat"); + DLOG(INFO) << "role " << role; + dump_ = configuration->property(role + ".dump", false); + dump_filename_ = configuration->property(role + ".dump_filename", default_dump_filename); + // make telemetry decoder object + telemetry_decoder_ = galileo_make_telemetry_decoder_gs(satellite_, 3, dump_); // unified galileo decoder set to CNAV (frame_type=3) + DLOG(INFO) << "telemetry_decoder(" << telemetry_decoder_->unique_id() << ")"; + channel_ = 0; + if (in_streams_ > 1) + { + LOG(ERROR) << "This implementation only supports one input stream"; + } + if (out_streams_ > 1) + { + LOG(ERROR) << "This implementation only supports one output stream"; + } +} + + +void GalileoE6TelemetryDecoder::set_satellite(const Gnss_Satellite& satellite) +{ + satellite_ = Gnss_Satellite(satellite.get_system(), satellite.get_PRN()); + telemetry_decoder_->set_satellite(satellite_); + DLOG(INFO) << "GALILEO TELEMETRY DECODER: satellite set to " << satellite_; +} + + +void GalileoE6TelemetryDecoder::connect(gr::top_block_sptr top_block) +{ + if (top_block) + { + /* top_block is not null */ + }; + // Nothing to connect internally + DLOG(INFO) << "nothing to connect internally"; +} + + +void GalileoE6TelemetryDecoder::disconnect(gr::top_block_sptr top_block) +{ + if (top_block) + { + /* top_block is not null */ + }; + // Nothing to disconnect +} + + +gr::basic_block_sptr GalileoE6TelemetryDecoder::get_left_block() +{ + return telemetry_decoder_; +} + + +gr::basic_block_sptr GalileoE6TelemetryDecoder::get_right_block() +{ + return telemetry_decoder_; +} diff --git a/src/algorithms/telemetry_decoder/adapters/galileo_e6_telemetry_decoder.h b/src/algorithms/telemetry_decoder/adapters/galileo_e6_telemetry_decoder.h new file mode 100644 index 000000000..d254c86e6 --- /dev/null +++ b/src/algorithms/telemetry_decoder/adapters/galileo_e6_telemetry_decoder.h @@ -0,0 +1,117 @@ +/*! + * \file galileo_e6_telemetry_decoder.h + * \brief Interface of an adapter of a GALILEO E6 CNAV data decoder block + * to a TelemetryDecoderInterface + * \author Carles Fernandez-Prades, 2020 cfernandez@cttc.es + * + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2020 (see AUTHORS file for a list of contributors) + * + * GNSS-SDR is a software defined Global Navigation + * Satellite Systems receiver + * + * This file is part of GNSS-SDR. + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + * ----------------------------------------------------------------------------- + */ + + +#ifndef GNSS_SDR_GALILEO_E6_TELEMETRY_DECODER_H +#define GNSS_SDR_GALILEO_E6_TELEMETRY_DECODER_H + +#include "galileo_telemetry_decoder_gs.h" +#include "gnss_satellite.h" +#include "gnss_synchro.h" +#include "telemetry_decoder_interface.h" +#include // for basic_block_sptr, top_block_sptr +#include // for size_t +#include + +/** \addtogroup Telemetry_Decoder + * \{ */ +/** \addtogroup Telemetry_Decoder_adapters + * \{ */ + + +class ConfigurationInterface; + +/*! + * \brief This class implements a NAV data decoder for Galileo CNAV frames in E6 radio link + */ +class GalileoE6TelemetryDecoder : public TelemetryDecoderInterface +{ +public: + GalileoE6TelemetryDecoder( + const ConfigurationInterface* configuration, + const std::string& role, + unsigned int in_streams, + unsigned int out_streams); + + ~GalileoE6TelemetryDecoder() = default; + + /*! + * \brief Returns "Galileo_E6_Telemetry_Decoder" + */ + inline std::string implementation() override + { + return "Galileo_E6_Telemetry_Decoder"; + } + + /*! + * \brief Connect + */ + void connect(gr::top_block_sptr top_block) override; + + /*! + * \brief Disconnect + */ + void disconnect(gr::top_block_sptr top_block) override; + + /*! + * \brief Get left block + */ + gr::basic_block_sptr get_left_block() override; + + /*! + * \brief Get right block + */ + gr::basic_block_sptr get_right_block() override; + + void set_satellite(const Gnss_Satellite& satellite) override; + + inline std::string role() override + { + return role_; + } + + inline void set_channel(int channel) override { telemetry_decoder_->set_channel(channel); } + + inline void reset() override + { + telemetry_decoder_->reset(); + } + + inline size_t item_size() override + { + return sizeof(Gnss_Synchro); + } + +private: + galileo_telemetry_decoder_gs_sptr telemetry_decoder_; + Gnss_Satellite satellite_; + std::string dump_filename_; + std::string role_; + int channel_; + unsigned int in_streams_; + unsigned int out_streams_; + bool dump_; +}; + + +/** \} */ +/** \} */ +#endif // GNSS_SDR_GALILEO_E6_TELEMETRY_DECODER_H diff --git a/src/algorithms/telemetry_decoder/gnuradio_blocks/galileo_telemetry_decoder_gs.cc b/src/algorithms/telemetry_decoder/gnuradio_blocks/galileo_telemetry_decoder_gs.cc index 63b0f5aa2..be882623e 100644 --- a/src/algorithms/telemetry_decoder/gnuradio_blocks/galileo_telemetry_decoder_gs.cc +++ b/src/algorithms/telemetry_decoder/gnuradio_blocks/galileo_telemetry_decoder_gs.cc @@ -24,6 +24,7 @@ #include "Galileo_E1.h" // for GALILEO_E1_CODE_PERIOD_MS #include "Galileo_E5a.h" // for GALILEO_E5A_CODE_PERIO... #include "Galileo_E5b.h" // for GALILEO_E5B_CODE_PERIOD_MS +#include "Galileo_E6.h" // for GALILEO_E6_CODE_PERIOD_MS #include "convolutional.h" #include "display.h" #include "galileo_almanac_helper.h" // for Galileo_Almanac_Helper @@ -108,6 +109,20 @@ galileo_telemetry_decoder_gs::galileo_telemetry_decoder_gs( d_max_symbols_without_valid_frame = GALILEO_FNAV_SYMBOLS_PER_PAGE * 5; // rise alarm 100 seconds without valid tlm break; } + case 3: // CNAV + { + d_PRN_code_period_ms = static_cast(GALILEO_E6_CODE_PERIOD_MS); + d_bits_per_preamble = 16; // Not available + d_samples_per_preamble = 16; // Not available + d_preamble_period_symbols = 1000; // Not available + d_required_symbols = 1000 + d_samples_per_preamble; // Not available + d_preamble_samples.reserve(d_samples_per_preamble); // Not available + d_frame_length_symbols = d_preamble_period_symbols - d_samples_per_preamble; // Not available + d_codelength = d_preamble_period_symbols - d_samples_per_preamble; // Not available + d_datalength = (d_codelength / d_nn) - d_mm; // Not available + d_max_symbols_without_valid_frame = d_preamble_period_symbols * 10; // Not available + break; + } default: d_bits_per_preamble = 0; d_samples_per_preamble = 0; @@ -150,6 +165,12 @@ galileo_telemetry_decoder_gs::galileo_telemetry_decoder_gs( } break; } + case 3: // CNAV for E6 + { + // TODO + d_preamble_samples[i] = 1; + break; + } } } d_sample_counter = 0ULL; @@ -421,6 +442,12 @@ void galileo_telemetry_decoder_gs::decode_FNAV_word(float *page_symbols, int32_t } +void galileo_telemetry_decoder_gs::decode_CNAV_word(float *page_symbols __attribute__((unused)), int32_t frame_length __attribute__((unused))) +{ + // TODO +} + + void galileo_telemetry_decoder_gs::set_satellite(const Gnss_Satellite &satellite) { gr::thread::scoped_lock lock(d_setlock); @@ -493,6 +520,11 @@ int galileo_telemetry_decoder_gs::general_work(int noutput_items __attribute__(( d_symbol_history.push_back(current_symbol.Prompt_Q); break; } + case 3: // CNAV + { + d_symbol_history.push_back(current_symbol.Prompt_I); + break; + } default: { d_symbol_history.push_back(current_symbol.Prompt_I); @@ -600,46 +632,33 @@ int galileo_telemetry_decoder_gs::general_work(int noutput_items __attribute__(( if (d_sample_counter == d_preamble_index + static_cast(d_preamble_period_symbols)) { // call the decoder + // NEW Galileo page part is received + // 0. fetch the symbols into an array + if (d_flag_PLL_180_deg_phase_locked == false) // normal PLL lock + { + for (uint32_t i = 0; i < d_frame_length_symbols; i++) + { + d_page_part_symbols[i] = d_symbol_history[i + d_samples_per_preamble]; // because last symbol of the preamble is just received now! + } + } + else // 180 deg. inverted carrier phase PLL lock + { + for (uint32_t i = 0; i < d_frame_length_symbols; i++) + { + d_page_part_symbols[i] = -d_symbol_history[i + d_samples_per_preamble]; // because last symbol of the preamble is just received now! + } + } switch (d_frame_type) { case 1: // INAV - // NEW Galileo page part is received - // 0. fetch the symbols into an array - if (d_flag_PLL_180_deg_phase_locked == false) // normal PLL lock - { - for (uint32_t i = 0; i < d_frame_length_symbols; i++) - { - d_page_part_symbols[i] = d_symbol_history[i + d_samples_per_preamble]; // because last symbol of the preamble is just received now! - } - } - else // 180 deg. inverted carrier phase PLL lock - { - for (uint32_t i = 0; i < d_frame_length_symbols; i++) - { - d_page_part_symbols[i] = -d_symbol_history[i + d_samples_per_preamble]; // because last symbol of the preamble is just received now! - } - } decode_INAV_word(d_page_part_symbols.data(), d_frame_length_symbols); break; case 2: // FNAV - // NEW Galileo page part is received - // 0. fetch the symbols into an array - if (d_flag_PLL_180_deg_phase_locked == false) // normal PLL lock - { - for (uint32_t i = 0; i < d_frame_length_symbols; i++) - { - d_page_part_symbols[i] = d_symbol_history[i + d_samples_per_preamble]; // because last symbol of the preamble is just received now! - } - } - else // 180 deg. inverted carrier phase PLL lock - { - for (uint32_t i = 0; i < d_frame_length_symbols; i++) - { - d_page_part_symbols[i] = -d_symbol_history[i + d_samples_per_preamble]; // because last symbol of the preamble is just received now! - } - } decode_FNAV_word(d_page_part_symbols.data(), d_frame_length_symbols); break; + case 3: // CNAV + decode_CNAV_word(d_page_part_symbols.data(), d_frame_length_symbols); + break; default: return -1; break; @@ -749,6 +768,10 @@ int galileo_telemetry_decoder_gs::general_work(int noutput_items __attribute__(( break; } } + case 3: // CNAV + { + // TODO + } } } else // if there is not a new preamble, we define the TOW of the current symbol @@ -771,6 +794,11 @@ int galileo_telemetry_decoder_gs::general_work(int noutput_items __attribute__(( } break; } + case 3: // CNAV + { + // TODO + break; + } } } @@ -798,6 +826,11 @@ int galileo_telemetry_decoder_gs::general_work(int noutput_items __attribute__(( } break; } + case 3: // CNAV + { + // TODO + break; + } } if (d_inav_nav.is_TOW_set() or d_fnav_nav.is_TOW_set()) diff --git a/src/algorithms/telemetry_decoder/gnuradio_blocks/galileo_telemetry_decoder_gs.h b/src/algorithms/telemetry_decoder/gnuradio_blocks/galileo_telemetry_decoder_gs.h index c3464b0ed..899fb6019 100644 --- a/src/algorithms/telemetry_decoder/gnuradio_blocks/galileo_telemetry_decoder_gs.h +++ b/src/algorithms/telemetry_decoder/gnuradio_blocks/galileo_telemetry_decoder_gs.h @@ -86,6 +86,7 @@ private: void deinterleaver(int32_t rows, int32_t cols, const float *in, float *out); void decode_INAV_word(float *page_part_symbols, int32_t frame_length); void decode_FNAV_word(float *page_symbols, int32_t frame_length); + void decode_CNAV_word(float *page_symbols, int32_t frame_length); // vars for Viterbi decoder std::vector d_preamble_samples; diff --git a/src/algorithms/tracking/adapters/CMakeLists.txt b/src/algorithms/tracking/adapters/CMakeLists.txt index 67a1ff042..c54a979aa 100644 --- a/src/algorithms/tracking/adapters/CMakeLists.txt +++ b/src/algorithms/tracking/adapters/CMakeLists.txt @@ -44,6 +44,7 @@ set(TRACKING_ADAPTER_SOURCES gps_l1_ca_tcp_connector_tracking.cc galileo_e5a_dll_pll_tracking.cc galileo_e5b_dll_pll_tracking.cc + galileo_e6_dll_pll_tracking.cc gps_l2_m_dll_pll_tracking.cc glonass_l1_ca_dll_pll_tracking.cc glonass_l1_ca_dll_pll_c_aid_tracking.cc @@ -63,6 +64,7 @@ set(TRACKING_ADAPTER_HEADERS gps_l1_ca_tcp_connector_tracking.h galileo_e5a_dll_pll_tracking.h galileo_e5b_dll_pll_tracking.h + galileo_e6_dll_pll_tracking.h gps_l2_m_dll_pll_tracking.h glonass_l1_ca_dll_pll_tracking.h glonass_l1_ca_dll_pll_c_aid_tracking.h diff --git a/src/algorithms/tracking/adapters/galileo_e6_dll_pll_tracking.cc b/src/algorithms/tracking/adapters/galileo_e6_dll_pll_tracking.cc new file mode 100644 index 000000000..3f12b97c4 --- /dev/null +++ b/src/algorithms/tracking/adapters/galileo_e6_dll_pll_tracking.cc @@ -0,0 +1,140 @@ +/*! + * \file galileo_e6_dll_pll_tracking.cc + * \brief Adapts a code DLL + carrier PLL + * tracking block to a TrackingInterface for Galileo E6 signals + * \author Carles Fernandez-Prades, 2020. cfernandez(at)cttc.es + * + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2020 (see AUTHORS file for a list of contributors) + * + * GNSS-SDR is a software defined Global Navigation + * Satellite Systems receiver + * + * This file is part of GNSS-SDR. + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + * ----------------------------------------------------------------------------- + */ + +#include "galileo_e6_dll_pll_tracking.h" +#include "Galileo_E6.h" +#include "configuration_interface.h" +#include "display.h" +#include "dll_pll_conf.h" +#include "gnss_sdr_flags.h" +#include +#include + +GalileoE6DllPllTracking::GalileoE6DllPllTracking( + const ConfigurationInterface* configuration, const std::string& role, + unsigned int in_streams, unsigned int out_streams) : role_(role), in_streams_(in_streams), out_streams_(out_streams) +{ + Dll_Pll_Conf trk_params = Dll_Pll_Conf(); + DLOG(INFO) << "role " << role; + trk_params.SetFromConfiguration(configuration, role); + + const auto vector_length = static_cast(std::round(trk_params.fs_in / (GALILEO_E6_B_CODE_CHIP_RATE_CPS / GALILEO_E6_B_CODE_LENGTH_CHIPS))); + trk_params.vector_length = vector_length; + if (trk_params.extend_correlation_symbols < 1) + { + trk_params.extend_correlation_symbols = 1; + std::cout << TEXT_RED << "WARNING: Galileo E6. extend_correlation_symbols must be bigger than 0. Coherent integration has been set to 1 symbol (1 ms)" << TEXT_RESET << '\n'; + } + else if (!trk_params.track_pilot and trk_params.extend_correlation_symbols > 1) + { + trk_params.extend_correlation_symbols = 1; + std::cout << TEXT_RED << "WARNING: Galileo E6. Extended coherent integration is not allowed when tracking the data component. Coherent integration has been set to 1 ms (1 symbol)" << TEXT_RESET << '\n'; + } + if ((trk_params.extend_correlation_symbols > 1) and (trk_params.pll_bw_narrow_hz > trk_params.pll_bw_hz or trk_params.dll_bw_narrow_hz > trk_params.dll_bw_hz)) + { + std::cout << TEXT_RED << "WARNING: Galileo E5b. PLL or DLL narrow tracking bandwidth is higher than wide tracking one" << TEXT_RESET << '\n'; + } + trk_params.system = 'E'; + const std::array sig_{'E', '6', '\0'}; + std::memcpy(trk_params.signal, sig_.data(), 3); + + // ################# Make a GNU Radio Tracking block object ################ + if (trk_params.item_type == "gr_complex") + { + item_size_ = sizeof(gr_complex); + tracking_ = dll_pll_veml_make_tracking(trk_params); + } + else + { + item_size_ = sizeof(gr_complex); + LOG(WARNING) << trk_params.item_type << " unknown tracking item type."; + } + channel_ = 0; + DLOG(INFO) << "tracking(" << tracking_->unique_id() << ")"; + if (in_streams_ > 1) + { + LOG(ERROR) << "This implementation only supports one input stream"; + } + if (out_streams_ > 1) + { + LOG(ERROR) << "This implementation only supports one output stream"; + } +} + + +void GalileoE6DllPllTracking::stop_tracking() +{ + tracking_->stop_tracking(); +} + + +void GalileoE6DllPllTracking::start_tracking() +{ + tracking_->start_tracking(); +} + + +/* + * Set tracking channel unique ID + */ +void GalileoE6DllPllTracking::set_channel(unsigned int channel) +{ + channel_ = channel; + tracking_->set_channel(channel); +} + + +void GalileoE6DllPllTracking::set_gnss_synchro(Gnss_Synchro* p_gnss_synchro) +{ + tracking_->set_gnss_synchro(p_gnss_synchro); +} + + +void GalileoE6DllPllTracking::connect(gr::top_block_sptr top_block) +{ + if (top_block) + { + /* top_block is not null */ + }; + // nothing to connect, now the tracking uses gr_sync_decimator +} + + +void GalileoE6DllPllTracking::disconnect(gr::top_block_sptr top_block) +{ + if (top_block) + { + /* top_block is not null */ + }; + // nothing to disconnect, now the tracking uses gr_sync_decimator +} + + +gr::basic_block_sptr GalileoE6DllPllTracking::get_left_block() +{ + return tracking_; +} + + +gr::basic_block_sptr GalileoE6DllPllTracking::get_right_block() +{ + return tracking_; +} diff --git a/src/algorithms/tracking/adapters/galileo_e6_dll_pll_tracking.h b/src/algorithms/tracking/adapters/galileo_e6_dll_pll_tracking.h new file mode 100644 index 000000000..3f2947409 --- /dev/null +++ b/src/algorithms/tracking/adapters/galileo_e6_dll_pll_tracking.h @@ -0,0 +1,117 @@ +/*! + * \file galileo_e6_dll_pll_tracking.h + * \brief Adapts a code DLL + carrier PLL + * tracking block to a TrackingInterface for Galileo E6 signals + * \author Carles Fernandez-Prades, 2020. cfernandez(at)cttc.es + * + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2020 (see AUTHORS file for a list of contributors) + * + * GNSS-SDR is a software defined Global Navigation + * Satellite Systems receiver + * + * This file is part of GNSS-SDR. + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + * ----------------------------------------------------------------------------- + */ + +#ifndef GNSS_SDR_GALILEO_E6_DLL_PLL_TRACKING_H +#define GNSS_SDR_GALILEO_E6_DLL_PLL_TRACKING_H + +#include "dll_pll_veml_tracking.h" +#include "tracking_interface.h" +#include + +/** \addtogroup Tracking + * \{ */ +/** \addtogroup Tracking_adapters + * \{ */ + + +class ConfigurationInterface; + +/*! + * \brief This class implements a code DLL + carrier PLL tracking loop + */ +class GalileoE6DllPllTracking : public TrackingInterface +{ +public: + GalileoE6DllPllTracking( + const ConfigurationInterface* configuration, + const std::string& role, + unsigned int in_streams, + unsigned int out_streams); + + ~GalileoE6DllPllTracking() = default; + + inline std::string role() override + { + return role_; + } + + //! Returns "Galileo_E6_DLL_PLL_Tracking" + inline std::string implementation() override + { + return "Galileo_E6_DLL_PLL_Tracking"; + } + + inline size_t item_size() override + { + return item_size_; + } + + /*! + * \brief Connect + */ + void connect(gr::top_block_sptr top_block) override; + + /*! + * \brief Disconnect + */ + void disconnect(gr::top_block_sptr top_block) override; + + /*! + * \brief Get left block + */ + gr::basic_block_sptr get_left_block() override; + + /*! + * \brief Get right block + */ + gr::basic_block_sptr get_right_block() override; + + /*! + * \brief Set tracking channel unique ID + */ + void set_channel(unsigned int channel) override; + + /*! + * \brief Set acquisition/tracking common Gnss_Synchro object pointer + * to efficiently exchange synchronization data between acquisition and tracking blocks + */ + void set_gnss_synchro(Gnss_Synchro* p_gnss_synchro) override; + + void start_tracking() override; + + /*! + * \brief Stop running tracking + */ + void stop_tracking() override; + +private: + dll_pll_veml_tracking_sptr tracking_; + size_t item_size_; + unsigned int channel_; + std::string role_; + unsigned int in_streams_; + unsigned int out_streams_; +}; + + +/** \} */ +/** \} */ +#endif // GNSS_SDR_GALILEO_E6_DLL_PLL_TRACKING_H diff --git a/src/algorithms/tracking/gnuradio_blocks/dll_pll_veml_tracking.cc b/src/algorithms/tracking/gnuradio_blocks/dll_pll_veml_tracking.cc index 58a8f9f0b..80a395855 100644 --- a/src/algorithms/tracking/gnuradio_blocks/dll_pll_veml_tracking.cc +++ b/src/algorithms/tracking/gnuradio_blocks/dll_pll_veml_tracking.cc @@ -32,11 +32,13 @@ #include "Galileo_E1.h" #include "Galileo_E5a.h" #include "Galileo_E5b.h" +#include "Galileo_E6.h" #include "MATH_CONSTANTS.h" #include "beidou_b1i_signal_processing.h" #include "beidou_b3i_signal_processing.h" #include "galileo_e1_signal_processing.h" #include "galileo_e5_signal_processing.h" +#include "galileo_e6_signal_processing.h" #include "gnss_satellite.h" #include "gnss_sdr_create_directory.h" #include "gnss_synchro.h" @@ -132,6 +134,7 @@ dll_pll_veml_tracking::dll_pll_veml_tracking(const Dll_Pll_Conf &conf_) : gr::bl map_signal_pretty_name["L5"] = "L5"; map_signal_pretty_name["B1"] = "B1I"; map_signal_pretty_name["B3"] = "B3I"; + map_signal_pretty_name["E6"] = "E6"; d_signal_pretty_name = map_signal_pretty_name[d_signal_type]; @@ -316,6 +319,30 @@ dll_pll_veml_tracking::dll_pll_veml_tracking(const Dll_Pll_Conf &conf_) : gr::bl d_interchange_iq = true; } } + else if (d_signal_type == "E6") + { + d_signal_carrier_freq = GALILEO_E6_FREQ_HZ; + d_code_period = GALILEO_E6_CODE_PERIOD_S; + d_code_chip_rate = GALILEO_E6_B_CODE_CHIP_RATE_CPS; + d_symbols_per_bit = 1; + d_correlation_length_ms = 1; + d_code_samples_per_chip = 1; + d_code_length_chips = static_cast(GALILEO_E6_B_CODE_LENGTH_CHIPS); + d_trk_parameters.slope = 1.0; + d_trk_parameters.spc = d_trk_parameters.early_late_space_chips; + d_trk_parameters.y_intercept = 1.0; + if (d_trk_parameters.track_pilot) + { + d_secondary = true; + d_signal_pretty_name = d_signal_pretty_name + "C"; + d_secondary_code_length = static_cast(GALILEO_E6_C_SECONDARY_CODE_LENGTH_CHIPS); + } + else + { + d_secondary = false; + d_signal_pretty_name = d_signal_pretty_name + "B"; + } + } else { LOG(WARNING) << "Invalid Signal argument when instantiating tracking blocks"; @@ -714,6 +741,21 @@ void dll_pll_veml_tracking::start_tracking() } } } + else if (d_systemName == "Galileo" and d_signal_type == "E6") + { + if (d_trk_parameters.track_pilot) + { + d_secondary_code_string = galileo_e6_c_secondary_code(d_acquisition_gnss_synchro->PRN - 1); + galileo_e6_b_code_gen_float_primary(d_data_code, d_acquisition_gnss_synchro->PRN); + galileo_e6_c_code_gen_float_primary(d_tracking_code, d_acquisition_gnss_synchro->PRN); + d_Prompt_Data[0] = gr_complex(0.0, 0.0); + d_correlator_data_cpu.set_local_code_and_taps(d_code_samples_per_chip * d_code_length_chips, d_data_code.data(), d_prompt_data_shift); + } + else + { + galileo_e6_b_code_gen_float_primary(d_tracking_code, d_acquisition_gnss_synchro->PRN); + } + } else if (d_systemName == "Beidou" and d_signal_type == "B1") { beidou_b1i_code_gen_float(d_tracking_code, d_acquisition_gnss_synchro->PRN, 0); @@ -1745,8 +1787,8 @@ int dll_pll_veml_tracking::general_work(int noutput_items __attribute__((unused) if (!cn0_and_tracking_lock_status(d_code_period)) { clear_tracking_vars(); - d_state = 0; // loss-of-lock detected - loss_of_lock = true; // Set the flag so that the negative indication can be generated + d_state = 0; // loss-of-lock detected + loss_of_lock = true; // Set the flag so that the negative indication can be generated current_synchro_data = *d_acquisition_gnss_synchro; // Fill in the Gnss_Synchro object with basic info } else @@ -1906,8 +1948,8 @@ int dll_pll_veml_tracking::general_work(int noutput_items __attribute__((unused) if (!cn0_and_tracking_lock_status(d_code_period * static_cast(d_trk_parameters.extend_correlation_symbols))) { clear_tracking_vars(); - d_state = 0; // loss-of-lock detected - loss_of_lock = true; // Set the flag so that the negative indication can be generated + d_state = 0; // loss-of-lock detected + loss_of_lock = true; // Set the flag so that the negative indication can be generated current_synchro_data = *d_acquisition_gnss_synchro; // Fill in the Gnss_Synchro object with basic info } else diff --git a/src/core/receiver/gnss_block_factory.cc b/src/core/receiver/gnss_block_factory.cc index e2bb66f73..830aee051 100644 --- a/src/core/receiver/gnss_block_factory.cc +++ b/src/core/receiver/gnss_block_factory.cc @@ -58,6 +58,9 @@ #include "galileo_e5b_dll_pll_tracking.h" #include "galileo_e5b_pcps_acquisition.h" #include "galileo_e5b_telemetry_decoder.h" +#include "galileo_e6_dll_pll_tracking.h" +#include "galileo_e6_pcps_acquisition.h" +#include "galileo_e6_telemetry_decoder.h" #include "glonass_l1_ca_dll_pll_c_aid_tracking.h" #include "glonass_l1_ca_dll_pll_tracking.h" #include "glonass_l1_ca_pcps_acquisition.h" @@ -290,6 +293,7 @@ std::unique_ptr GNSSBlockFactory::GetObservables(const Confi unsigned int Galileo_channels = configuration->property("Channels_1B.count", 0); Galileo_channels += configuration->property("Channels_5X.count", 0); Galileo_channels += configuration->property("Channels_7X.count", 0); + Galileo_channels += configuration->property("Channels_E6.count", 0); unsigned int GPS_channels = configuration->property("Channels_1C.count", 0); GPS_channels += configuration->property("Channels_2S.count", 0); GPS_channels += configuration->property("Channels_L5.count", 0); @@ -323,6 +327,7 @@ std::unique_ptr GNSSBlockFactory::GetPVT(const Configuration unsigned int Galileo_channels = configuration->property("Channels_1B.count", 0); Galileo_channels += configuration->property("Channels_5X.count", 0); Galileo_channels += configuration->property("Channels_7X.count", 0); + Galileo_channels += configuration->property("Channels_E6.count", 0); unsigned int GPS_channels = configuration->property("Channels_1C.count", 0); GPS_channels += configuration->property("Channels_2S.count", 0); GPS_channels += configuration->property("Channels_L5.count", 0); @@ -412,6 +417,7 @@ std::unique_ptr>> GNSSBlockFacto const unsigned int Channels_B1_count = configuration->property("Channels_B1.count", 0); const unsigned int Channels_B3_count = configuration->property("Channels_B3.count", 0); const unsigned int Channels_7X_count = configuration->property("Channels_7X.count", 0); + const unsigned int Channels_E6_count = configuration->property("Channels_E6.count", 0); const unsigned int total_channels = Channels_1C_count + Channels_1B_count + @@ -422,7 +428,8 @@ std::unique_ptr>> GNSSBlockFacto Channels_L5_count + Channels_B1_count + Channels_B3_count + - Channels_7X_count; + Channels_7X_count + + Channels_E6_count; auto channels = std::make_unique>>(total_channels); try @@ -492,6 +499,18 @@ std::unique_ptr>> GNSSBlockFacto channel_absolute_id++; } + // **************** GALILEO E6 (B/C HAS) CHANNELS ************** + LOG(INFO) << "Getting " << Channels_E6_count << " GALILEO E6 (B/C HAS) channels"; + + for (unsigned int i = 0; i < Channels_E6_count; i++) + { + // Store the channel into the vector of channels + channels->at(channel_absolute_id) = GetChannel(configuration, + std::string("E6"), + channel_absolute_id, + queue); + channel_absolute_id++; + } // **************** GLONASS L1 C/A CHANNELS ************************ LOG(INFO) << "Getting " << Channels_1G_count << " GLONASS L1 C/A channels"; @@ -972,6 +991,12 @@ std::unique_ptr GNSSBlockFactory::GetBlock( out_streams); block = std::move(block_); } + else if (implementation == "Galileo_E1_PCPS_QuickSync_Ambiguous_Acquisition") + { + std::unique_ptr block_ = std::make_unique(configuration, role, in_streams, + out_streams); + block = std::move(block_); + } else if (implementation == "Galileo_E5a_Noncoherent_IQ_Acquisition_CAF") { std::unique_ptr block_ = std::make_unique(configuration, role, in_streams, @@ -990,9 +1015,9 @@ std::unique_ptr GNSSBlockFactory::GetBlock( out_streams); block = std::move(block_); } - else if (implementation == "Galileo_E1_PCPS_QuickSync_Ambiguous_Acquisition") + else if (implementation == "Galileo_E6_PCPS_Acquisition") { - std::unique_ptr block_ = std::make_unique(configuration, role, in_streams, + std::unique_ptr block_ = std::make_unique(configuration, role, in_streams, out_streams); block = std::move(block_); } @@ -1122,6 +1147,12 @@ std::unique_ptr GNSSBlockFactory::GetBlock( out_streams); block = std::move(block_); } + else if (implementation == "Galileo_E6_DLL_PLL_Tracking") + { + std::unique_ptr block_ = std::make_unique(configuration, role, in_streams, + out_streams); + block = std::move(block_); + } else if (implementation == "GLONASS_L1_CA_DLL_PLL_Tracking") { std::unique_ptr block_ = std::make_unique(configuration, role, in_streams, @@ -1218,18 +1249,18 @@ std::unique_ptr GNSSBlockFactory::GetBlock( out_streams); block = std::move(block_); } - else if (implementation == "Galileo_E1B_Telemetry_Decoder") - { - std::unique_ptr block_ = std::make_unique(configuration, role, in_streams, - out_streams); - block = std::move(block_); - } else if (implementation == "SBAS_L1_Telemetry_Decoder") { std::unique_ptr block_ = std::make_unique(configuration, role, in_streams, out_streams); block = std::move(block_); } + else if (implementation == "Galileo_E1B_Telemetry_Decoder") + { + std::unique_ptr block_ = std::make_unique(configuration, role, in_streams, + out_streams); + block = std::move(block_); + } else if (implementation == "Galileo_E5a_Telemetry_Decoder") { std::unique_ptr block_ = std::make_unique(configuration, role, in_streams, @@ -1242,6 +1273,12 @@ std::unique_ptr GNSSBlockFactory::GetBlock( out_streams); block = std::move(block_); } + else if (implementation == "Galileo_E6_Telemetry_Decoder") + { + std::unique_ptr block_ = std::make_unique(configuration, role, in_streams, + out_streams); + block = std::move(block_); + } else if (implementation == "GLONASS_L1_CA_Telemetry_Decoder") { std::unique_ptr block_ = std::make_unique(configuration, role, in_streams, @@ -1402,6 +1439,12 @@ std::unique_ptr GNSSBlockFactory::GetAcqBlock( out_streams); block = std::move(block_); } + else if (implementation == "Galileo_E6_PCPS_Acquisition") + { + std::unique_ptr block_ = std::make_unique(configuration, role, in_streams, + out_streams); + block = std::move(block_); + } else if (implementation == "GLONASS_L1_CA_PCPS_Acquisition") { std::unique_ptr block_ = std::make_unique(configuration, role, in_streams, @@ -1535,6 +1578,12 @@ std::unique_ptr GNSSBlockFactory::GetTrkBlock( out_streams); block = std::move(block_); } + else if (implementation == "Galileo_E6_DLL_PLL_Tracking") + { + std::unique_ptr block_ = std::make_unique(configuration, role, in_streams, + out_streams); + block = std::move(block_); + } else if (implementation == "GPS_L2_M_DLL_PLL_Tracking") { std::unique_ptr block_ = std::make_unique(configuration, role, in_streams, @@ -1673,6 +1722,12 @@ std::unique_ptr GNSSBlockFactory::GetTlmBlock( out_streams); block = std::move(block_); } + else if (implementation == "Galileo_E6_Telemetry_Decoder") + { + std::unique_ptr block_ = std::make_unique(configuration, role, in_streams, + out_streams); + block = std::move(block_); + } else if (implementation == "GPS_L2C_Telemetry_Decoder") { std::unique_ptr block_ = std::make_unique(configuration, role, in_streams, diff --git a/src/core/receiver/gnss_flowgraph.cc b/src/core/receiver/gnss_flowgraph.cc index ac4876dd1..d331ed7c9 100644 --- a/src/core/receiver/gnss_flowgraph.cc +++ b/src/core/receiver/gnss_flowgraph.cc @@ -29,6 +29,7 @@ #include "Galileo_E1.h" #include "Galileo_E5a.h" #include "Galileo_E5b.h" +#include "Galileo_E6.h" #include "channel.h" #include "channel_fsm.h" #include "channel_interface.h" @@ -432,6 +433,9 @@ void GNSSFlowgraph::connect() case evGAL_7X: acq_fs = GALILEO_E5B_OPT_ACQ_FS_SPS; break; + case evGAL_E6: + acq_fs = GALILEO_E6_OPT_ACQ_FS_SPS; + break; case evGLO_1G: case evGLO_2G: case evBDS_B1: @@ -639,6 +643,12 @@ void GNSSFlowgraph::connect() available_GAL_7X_signals_.remove(signal_value); break; + case evGAL_E6: + gnss_system = "Galileo"; + signal_value = Gnss_Signal(Gnss_Satellite(gnss_system, sat), gnss_signal); + available_GAL_E6_signals_.remove(signal_value); + break; + case evGLO_1G: gnss_system = "Glonass"; signal_value = Gnss_Signal(Gnss_Satellite(gnss_system, sat), gnss_signal); @@ -1141,6 +1151,11 @@ void GNSSFlowgraph::push_back_signal(const Gnss_Signal& gs) available_GAL_7X_signals_.push_back(gs); break; + case evGAL_E6: + available_GAL_E6_signals_.remove(gs); + available_GAL_E6_signals_.push_back(gs); + break; + case evGLO_1G: available_GLO_1G_signals_.remove(gs); available_GLO_1G_signals_.push_back(gs); @@ -1196,6 +1211,10 @@ void GNSSFlowgraph::remove_signal(const Gnss_Signal& gs) available_GAL_7X_signals_.remove(gs); break; + case evGAL_E6: + available_GAL_E6_signals_.remove(gs); + break; + case evGLO_1G: available_GLO_1G_signals_.remove(gs); break; @@ -1234,6 +1253,9 @@ double GNSSFlowgraph::project_doppler(const std::string& searched_signal, double case evGPS_2S: return (primary_freq_doppler_hz / FREQ1) * FREQ2; break; + case evGAL_E6: + return (primary_freq_doppler_hz / FREQ1) * FREQ6; + break; default: return primary_freq_doppler_hz; } @@ -1498,6 +1520,14 @@ void GNSSFlowgraph::priorize_satellites(const std::vector available_GAL_E6_signals_.size()) + { + available_GAL_E6_signals_.push_front(gs); + } } } } @@ -1627,6 +1657,7 @@ void GNSSFlowgraph::init() mapStringValues_["1B"] = evGAL_1B; mapStringValues_["5X"] = evGAL_5X; mapStringValues_["7X"] = evGAL_7X; + mapStringValues_["E6"] = evGAL_E6; mapStringValues_["1G"] = evGLO_1G; mapStringValues_["2G"] = evGLO_2G; mapStringValues_["B1"] = evBDS_B1; @@ -2039,6 +2070,19 @@ void GNSSFlowgraph::set_signals_list() } } + if (configuration_->property("Channels_E6.count", 0) > 0) + { + // Loop to create the list of Galileo E6 signals + for (available_gnss_prn_iter = available_galileo_prn.cbegin(); + available_gnss_prn_iter != available_galileo_prn.cend(); + available_gnss_prn_iter++) + { + available_GAL_E6_signals_.emplace_back( + Gnss_Satellite(std::string("Galileo"), *available_gnss_prn_iter), + std::string("E6")); + } + } + if (configuration_->property("Channels_1G.count", 0) > 0) { // Loop to create the list of GLONASS L1 C/A signals @@ -2144,6 +2188,10 @@ bool GNSSFlowgraph::is_multiband() const { multiband = true; } + if (configuration_->property("Channels_E6.count", 0) > 0) + { + multiband = true; + } } if (configuration_->property("Channels_1G.count", 0) > 0) { @@ -2381,6 +2429,50 @@ Gnss_Signal GNSSFlowgraph::search_next_signal(const std::string& searched_signal } break; + case evGAL_E6: + if (configuration_->property("Channels_1B.count", 0) > 0) + { + // 1. Get the current channel status map + std::map> current_channels_status = channels_status_->get_current_status_map(); + // 2. search the currently tracked Galileo E1 satellites and assist the Galileo E5 acquisition if the satellite is not tracked on E5 + for (auto& current_status : current_channels_status) + { + if (std::string(current_status.second->Signal) == "1B") + { + std::list::iterator it2; + it2 = std::find_if(std::begin(available_GAL_E6_signals_), std::end(available_GAL_E6_signals_), + [&](Gnss_Signal const& sig) { return sig.get_satellite().get_PRN() == current_status.second->PRN; }); + + if (it2 != available_GAL_E6_signals_.end()) + { + estimated_doppler = static_cast(current_status.second->Carrier_Doppler_hz); + RX_time = current_status.second->RX_time; + // std::cout << " Channel: " << it->first << " => Doppler: " << estimated_doppler << "[Hz] \n"; + // 3. return the Gal E6 satellite and remove it from list + result = *it2; + if (pop) + { + available_GAL_E6_signals_.erase(it2); + } + found_signal = true; + assistance_available = true; + break; + } + } + } + } + // fallback: pick the front satellite because there is no tracked satellites in E1 to assist E6 + if (found_signal == false) + { + result = available_GAL_E6_signals_.front(); + available_GAL_E6_signals_.pop_front(); + if (!pop) + { + available_GAL_E6_signals_.push_back(result); + } + } + break; + case evGLO_1G: result = available_GLO_1G_signals_.front(); available_GLO_1G_signals_.pop_front(); diff --git a/src/core/receiver/gnss_flowgraph.h b/src/core/receiver/gnss_flowgraph.h index cc0f7be46..4c3b9d329 100644 --- a/src/core/receiver/gnss_flowgraph.h +++ b/src/core/receiver/gnss_flowgraph.h @@ -214,6 +214,7 @@ private: std::list available_GAL_1B_signals_; std::list available_GAL_5X_signals_; std::list available_GAL_7X_signals_; + std::list available_GAL_E6_signals_; std::list available_GLO_1G_signals_; std::list available_GLO_2G_signals_; std::list available_BDS_B1_signals_; @@ -228,6 +229,7 @@ private: evGAL_1B, evGAL_5X, evGAL_7X, + evGAL_E6, evGLO_1G, evGLO_2G, evBDS_B1, diff --git a/src/core/system_parameters/CMakeLists.txt b/src/core/system_parameters/CMakeLists.txt index 1f770e78e..beaac4b95 100644 --- a/src/core/system_parameters/CMakeLists.txt +++ b/src/core/system_parameters/CMakeLists.txt @@ -66,6 +66,7 @@ set(SYSTEM_PARAMETERS_HEADERS Galileo_E1.h Galileo_E5a.h Galileo_E5b.h + Galileo_E6.h GLONASS_L1_L2_CA.h gnss_frequencies.h gnss_obs_codes.h diff --git a/src/core/system_parameters/Galileo_E6.h b/src/core/system_parameters/Galileo_E6.h new file mode 100644 index 000000000..98d7c59c7 --- /dev/null +++ b/src/core/system_parameters/Galileo_E6.h @@ -0,0 +1,214 @@ +/*! + * \file Galileo_E6.h + * \brief Defines system parameters for Galileo E6 B/C signal, as published at: + * European Union, E6-B/C Codes Technical Note, Issue 1, January 2019. + * \author Carles Fernandez-Prades, 2020. cfernandez@cttc.es + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2020 (see AUTHORS file for a list of contributors) + * + * GNSS-SDR is a software defined Global Navigation + * Satellite Systems receiver + * + * This file is part of GNSS-SDR. + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + * ----------------------------------------------------------------------------- + */ + +#ifndef GNSS_SDR_GALILEO_E6_H +#define GNSS_SDR_GALILEO_E6_H + +#include "gnss_frequencies.h" +#include // for size_t +#include + +/** \addtogroup Core + * \{ */ +/** \addtogroup System_Parameters + * \{ */ + +constexpr double GALILEO_E6_FREQ_HZ = FREQ6; //!< Galileo E6 carrier frequency [Hz] +constexpr double GALILEO_E6_B_CODE_CHIP_RATE_CPS = 5.115e6; //!< Galileo E6 B code rate [chips/s] +constexpr double GALILEO_E6_C_CODE_CHIP_RATE_CPS = 5.115e6; //!< Galileo E6 C code rate [chips/s] +constexpr double GALILEO_E6_CODE_PERIOD_S = 0.001; //!< Galileo E6 code period [s] + +constexpr double GALILEO_E6_B_CODE_LENGTH_CHIPS = 5115.0; //!< Galileo E6 B code length [chips] +constexpr double GALILEO_E6_C_CODE_LENGTH_CHIPS = 5115.0; //!< Galileo E6 C code length [chips] +constexpr double GALILEO_E6_C_SECONDARY_CODE_LENGTH_CHIPS = 100.0; //!< Galileo E6 C secondary code length [chips] +constexpr int32_t GALILEO_E6_CODE_PERIOD_MS = 1; //!< Galileo E& B/C code period [ms] + +constexpr int32_t GALILEO_E6_NUMBER_OF_CODES = 50; + +constexpr uint32_t GALILEO_E6_OPT_ACQ_FS_SPS = 10000000; + +constexpr size_t GALILEO_E6_B_PRIMARY_CODE_STR_LENGTH = 1279; +constexpr char GALILEO_E6_B_PRIMARY_CODE[GALILEO_E6_NUMBER_OF_CODES][1280] = { + "E6648AA5EFF0907A170377FB20CEDEE1E8D253DAC2496831010336B444276BAAB17E599548B1A79C67379F98DF0CB81AE8D914EE4947093ADCB94FF4B3916EE562A4CAFD4A5A049721606E55FFFEB26C949D7C8B0AB7AD2F7DDBBF88A9B091510D209AAA3F6C83ECFB8DE21B77E793A9E759E73A7ED6330395FD9380E8E86F0A511E0FEC8E0B6501983FA7C693FFFFFF719EFE4249EF295B578E514817D9E86EBC10917B3B7656BEF6C9AAB8200B20DEE54BFDB18728A3393450F20B08A7AC84A9F945398E13434BD3076087F32CA165AFB94FEA9686924E831BA4BD76ACF62D7B216BAC53F71FAF1A13580BDFBB56DD555855A9E2541FFDED2ADFA6125D73F116006A715596001D1538B0D91C8D445D0342B910219EA64B77A7A245E4E70AD547590C3D59DF32CF8ACECB6D2F17ABDA7F89DB09C96A2E811599A39191CEE8F8D42A0BC2A5D00D8D7C318E8996470280F551D25DFAF153766F6D237236B23FBE1E74484CA9485848CD5C1966705EA03A0220E84B1AFC9F08F8BDC110D9597973C0F347F5259591EEEF00C399D9A6EF140B759465E07F487ACE9B8CE34EB219AC63C01127520C8E2D2FA7090AC754976B8DAE5C51F18001ED095518DC659B5746B390226013A6DEF1C54DF4BE1E60CBAAFD10D163E937BC753184791966B930990F9DF0A98D2F9060233858CD699613088876272B23C6FC27288BE7D258CE3E83A0F2D5C3A9BCFC0E7C9F02AA0A97AFC31A286E9CF85BE5885FFA22E51827EFB1066452B27335C01FCAE645B56819B9D7C7014B4B8C050E647B1B215A45F8B939624433F0319C840DEA265D8631A1EE9A66439DA99563DB168E9C1574E2730F6AAF709AA145B75DB885B14FB78E38A05948ABC6206C793C33CE9189BFF19338C", // 01 + "A2AB1DB576F2061D6993AC3832FF041C388E44240AA6EAB61ABB450D961ED6C8088C05313E8A31B322CC757C389FF8B033792BFDACA5D3D4926321497DDBBD7C40F833AA87EC0D27B11E215E491528C0919B2E0D3106DBBA384EF22B1133538C78C76C4ABAC8493E18A30E842CD00501EE9A737F8AD7E7D78EF22EA826E8973BD19945A5A30A4101295D4D9010CFB232E2187A98AB9876DE1C9B33B23A8328028E9111A3AD612D7583B0E6A502AA3AAD7E90680013A3C2E83C475C687307B9B4B71B668EE7B2573756F1AB9FC7DF4DBB0577C87FEA7031A1A3B55D054731C1FD7849296D03F66BECD04DC25C99BFEBD810622970C160248356A15A18C3F6907E39C4BE82EAC8AD3874D3EC67E77FBAF6AB25DE3FE764BD22B3EC24C944E4BC060675369DE7642972DCA19A687F6680CF598EEF1B29BA7A4C15D43A6FE33AA867B929F158E7CDECC08E11B6E35252AD148ADE7DAAED8B17648FF1E98094E560B89B75F72084BCDA4B93D237555F3FA22722537F4A7AEA9BEC228D7BA9DD7189B1165C35D9BCF7791E4823FD81A05131D9DDF0F696458C93FF255DEDAB2C4B55B64F5FF621B8A4773C1A95C0B47D84E2F410A79D8F959C76AEA247846A1666845653BA47CFEE762C6846EDFB5DFAE144FA31501FC18D8CB77160B8B3FA5500C30D5FB1C8B0B8F5060182F278BD748E3B64D8DA7FA85A48E0179CA3A2441B2205FBC5E335BC49D0B1FE60B2D7F94484C2921780B31B9968747EE4D19F1AEBD314F8018F47649B8EFE6D8F72304E810FA22301B620674202651D73A3C3AF84CC259F26AB775F46FD947EA5818B055224667A94C29D0BE44E925053CB0A35FC670A1FEBF39EB00D7D64AD23AF036915DB36FAB654AD9D5F39B3F97F16DB0B9522718", // 02 + "D7C50CAAB17471A735C51D4759644BE4A788FEA8ECD5F17E00B373DBD24917481DA741820804EA5CB366885ADFFC988EE21A0875EE7A36981726B7106EE3C7E4C1435890E415A2F74667633E1DC7D259FF2C3A1D408FEC4D1792326E7691253F6DC3E7B9B43EB546AE28E6152984FF677E4585B1637B8A38C7042138050888F88EA0479E974833EA5DA1B445350D4322DB625F567F6DCC3F1A8F4096BE5333BF0B9C7F5545C5D7430A9935A2001FD1742AA34E572F886892AC061B4A1D306B91928F2A8C159E237D23A4BF9D08F6780F9936FA6A63684500CDC51ED6AD2B23574C459B83BA0504CEB21980F324F91A37A48E8402155CB97798F2618DC292FFABD9DB8000500C30D4B85F4D6249B4033D098C08316E570B10E5C939DE08C1DDE918B749332B5E6AA488A5AF937F609414E885C77A953D8CA93FD382CA84752F54BD2779F460F0EDEE48C99AEA837877EF01AFA9687F334218F8EDB23EBA3FC0AC8CF8BB5794562E6D5B0A356CDB6BCFC0CAC2ACCE6CEEAF102CA980B23A36F89C35470D0BB41C8BEB2BE0E6CD0C9689DF2D1907AFE689B49C19D4539AA1A48412B6F15227839629D2A976249FF057261940C6E88719EE8672E8A772E7CD10DF669B1E966A8D93C4B3E9B9D4D0148BB53F272208F192B370B17291D33573D8BA76984B2980343928DE4F7AD5E335FBCCFDBBD31C8E9FA63015327D0517EA2F161EF5A08BCC0856B6D5BD210FA3DFAE918EDD7C4E444956FFF2455A4C3C71EAB715A12FB9DF145C7FB4F46A17C877797EE158737A730F8937BC6DF8325815464EED4F48ADE718C01CC7BFE5979D71D1FC87994524F89CA789788BA057F9D499EDC7E1DC6BFF41920FC1B5C3DE263D5768B31237BBE750617E5802094537DC8E62C", // 03 + "AA696973C3A5E818309F73264D9EE219FC69692F106F7FCB8ADCC3FCE9816C691C07B996C0E5DEDF63501B63A6F9E5F219FCF8ACB811E980346BA432CCC6E2A0FA95B9E30ED03468AC7D1C0A59035359666F1A04C7673F7FB7C5576F6B2C6548979F480754D4C7C31A4A4E125514DBFB61BD199830855A928BA58531257E01F5D69D2222C56C0472C3354DEA19D563EFF9E25150256F2D7E76A0F719B63AD6917BA13C69D49B228FDE7D4181B843AE23F4D8740D942F27BB703199D70A0EB5D3C0DF04F0BABA9AE21408C6070345FA82A69792BB88F1194C6C205008A7293C077169BBB58281CF57DF1846C976AFBE5BAAFBF05C4948AEC474CE85B0DDA450441DAC1E778BC63D1B75D867532FA3534CEEC0B796EADA76B8EFCC1FBB70F4902086E7C7926FF19EDB75FA9848525068D591D1F0E83995BC10C0A25DD8E22245AB72558D27F620E785E5132062F3AF642EB9C83944714CBF5A98EA18402500380FAA6EAA7823E00ADD6B70DDD1C879E486C6C39FCDA5DD734798AA70ABC07AD7867F03EC3C1D478227DCC91701AEA7E509085AB319858790D692AC9B1A85537E7A16D4682D69E0EF5FAE63B342D86C6B610DB41022FE46A8013A756A3131C0731A213C3BB07981ABD53C22103C5094636CD630BAAEC9400E574DFED45FAC5E1AF83206E62D1486A80ADCEAC8A89D3B8F9BC8C88ED5F1EC4A31CB16487510A054CACA7725E3FFC4AF4C65A7DDBE4D0861F4593F84CE36C5275C543B573B3ADC1659257DE53396F667E8A3F4375DA4AB23EB622546547C1E51EB436DB27BC3F321B15C373DE2755F3F860A8A2CE62C90E728717E5E8DF09AEC67FDAC2CD84FF8CE9180B4DA29D32F52FF849F64BC65278EEAD25BB54F1B33A2735EE9AC5766F2E30", // 04 + "AA48B0CC726F7B85B9ACE4D423964A1E548D6CB78F886DE941E49360E53EAA7AD303BCF4DDF3E8E640DB733727082AED108C3C497644800E14AE78DF95D3214D948BEDCB3D52FEAA68FEF9F0A98662B390CA0E98E663EBF8B7459A8526E06F639630B3790B5195F9330FFA0BCA9C14120136CF11560956B598A3A656A045E254BFAB4F4C9907F82C1D2BDFF35958980D49CD3E9D1DE1CD86D72A35385371CDA6C3BDF42256B13EE6CB959F1CBBFE6CA7493835123141089509B9D8741BE692FD6980C1F72F328C670FA91882AF369D8D75F640A4AEB20923738DE0DE2FF8C29CA505575C59B779D32F790D7AF8DB5C5E13810EB316844D1CFAD297A041D3B9BE524836180E5039AEE79444CA1ED352AD4DD61F7101AE2D556CECAA7E7DF1F7BF2E88BAE57F60906C27C12AF03780E42C1F0F4A2222988313326898728E5CE8E09267363575F9B2EA976495E73952C05E69F955C5F66937A4BE157D5BA63F529A28846FC25D800C8858F8BE4988681FDB6294DAD76E501C40D8A49B816AE8990D4EEE2A029DC9BA1717EB7C013ED8D6F57DEDA4784960FC505B91A1FE720F11B95DBF7900FDF182D0469B6764E159DCC1703CD4CE9AA2A2734D07E86B376A2D415C2B11FCBFFB0C249DAB0B48D27685D3CC2074E0F77AD08206DAD6BE6127A45F970EBC1FED5CA772323C0D70369E539065188FEDFB2E9968425C3D5F38565380D958075B7EAB6FCC7464BC4A77CC23EB07E81248A5ED68AF800C5A19074DB8B0AD6403B8875CD9C9E95CA09DB303DA879AB468BC213F248DC27FAF92E32F6B07E366F43A86EF30F8F3CB09CF94069E68546C8600964FC0DF7F413D2DFEB5CEAC12C176E49023AD74209838CF0992DD0D2679F7FED3BC236707A1342576A7258", // 05 + "C529A0B4203614FD1D8451D10DC39ADAE617BB03A918BD89FB81D4028113724B4C7E84ABB1977E35F632BC3FF3316A0D6CF465DCD39062FA3FEDE7F0CCD0764F1B1E2F27445282CEC6608F16A0F8FF994319C9C89301FF685294971382A2736062649EC280791B7724C35D14A9400A437B95161875143C86B51D4E0C319B822E8D91C09BD965AA7F83C0657A7DE5E346A27642D7D638A00CE0F0B77BB84D45C68121CA4B7F9307B267851AC06AC9F4D4CAD54FE8A6280A0C2529AB277A981433597F668C44961A813D6255E2BA29AEB051BF9432C4E75272F7C36A204BD20BEC5111A288E8559D68F3242900FC227D83BBD201396E3710FB2A3CB3EEC4A7810A074A920908A137577B3599C2B737B23D97D8015F1FFE6396C2360D46067767053C7A71BCC58264C3FD54BED7B5773FD727AEEA2A19F864284F1E0254A837E11C1B1FBD2CA747C4DFC2E60DD75AF044A971454C1227595370DEEC4A1F4DA39DF4A9D6416C3E4CC8B334F44EC3F5AE23063BB50025A5A5B3C1252A979E754C40FD2E4A6B779C7B0D5CF2C0A37A569A126E085CCC007D6AB91D8EDA3DDEDB1EA7D4B5B7DBFDA78F38552E663B49D4167B0F6A92203ED63CB7AD511473F1F6E976034C4F31AF3C07E728E5B991FA3F4FAD379A679EEEE2A73AF723623D22ACF8E0727589E36DECD8F8FFED7A048B911FAF616E48B7FA1829FBBF677868D901C0DA8B5D1433B3A7F9CCCB450812C04D280C8C2A13113B6149F54905636CDD191454FB05613D8BC8A36536E337E8807E0BAB2EDBB64DF986477CB4F93C91EBB9C2D85B0AEC22457CB07FC1DB98D52BB73E896FF59B1D5BA15ACA5CB9696E3410A6BA6E2E1D94B0DCC01D876F7442EA76DCAD9FB262EF1153CDD2B015F4783916C555C", // 06 + "F04412976580A3D5106BA358575C5549A7054E0DFB114BDED09ED2F8665801DE8F7D74E174D8393187850ED269E1055C9060C3441DFB91CA3CAD84A5C11FED126714C8CB9BB934D03F24A0350D3287A64D186FA5C945634C71F0D7A4FF5024BBDD2D79E8EB75DE7F31985585D056EEF1EBA9E4C100435776A7D3465C6DAE21B1D89A0FDF0264A5AB9F9C7A4418F6B5A317ED828E63D6AA566C511942203E62EBA4DFBCB818ED622CF03302E1B3A85C0B0F05F1CB78F9A90C77EF8565BF703DCF5D45C8D956765F994C8FB3243CE089E68E705F310BF9E9B81B044E307C7233CC1D4E3237F6AF5B1A699ABC11FC4C1DB7A998B9CB19454F5AB9ED7D3FFE5EE55A6184ACFA1D3A481BB74AB2E70E28809F92FA15CB99EA0AC7CBBA1385BBDA3D856E5C2C3AA379EC088EAD486D782FD28C5433EC11F01CC996A2F15BF3B5B36752A188FAF37BFDC170A54411D5D0B36D52064D88FF4C79C1F0E2825CD6BD9C6811C0B136F9EFA727591E98E7D673AD2B81B8F7033235689F9EB78A4DA85D3C26B52F0B3D8227048A191B3C2805311072B556E8B8C3DD961E72CFB6235DC6ACA2D16441AC2151C37D9AAB1B13BDB6E4E82E8FDD47FD99E8BA085131C4CC5D801C6B906080326F65A5C5FCD0FC22862AF6AACB79152AEECE0DDF3D432E0D002539014CA8C7C782F5E402B12C34F440EC7439F8CE1682FA6C2B489C0FB8650E7FB20AF59341655229C450AA29B24B279F9B8B38B62FCFC166E00125D6AEE53C3CDF4CE41206D829E00BAEFA79B8DB51CECE4E2CB66A1E30A1C39A2C39598B8675A935BBAA4E929DF27CAA3D410BAE9B74DAC1435B3E86C9C27FD5112A9B9D9E7F7A65B14F08962F34013DEC906A487FC233DB8BD24DE8616A8EAAEA6F4BD18AAFB3C", // 07 + "E77B8CA081B04234CC2E148ECDDC38912DE5D2239953512C2253D3624D52BE57DD3F3813FCCDB94E83E074CC6023993704D862A4238F377C5EE88DF60F41D9F3952FC85CFEDC500A75B7116E85C30D978B5396FF09A922F9F065ED050BA7EA3FAF59590EA167A10270595C08F5F60D34F232DAAEFF2E4D9E0BF50568E619A29175BEEB97A17CFA4D733AC70269F76219E480162EE76304E0D07FCF1033829C13D29322619ABD732CA49B9ADD983725E2B104C15DC495E01F9AF7A398DB00497A9ABF8E2698D4360C37364D752403B0B625AF7B5988D7BBC11ED37BFEF087D9BED79958FB2699B5382D318AE8AEDB97476AA69E56FCAE2676F0F3182FC2CD8959EE972A99D54BD3336DAC904C828EA8C421C6557B4504D655CE7543112C43554C472B50263B519D59F5515766CFA480444D6C1DCB15A68386D4D518154B639F2D5E01E086EDF1A73A0005AD91AE618B7B05ED0C3760FDFF522593F169CEBCFFA228BB960350E3BF67201FBD21C8A2B82328DCE6C71912BFF568097730709C54F271D3201AC563443361D55A2A3E989725EB7C510823A2D4F72FFEFEC81C30012FE8EEE479CE1F53E1A12D15D2BC51AEC23D5C86F0AF59186691022FA674E5BF405AA96A7E00398BDF02E4F9E66852C44E5E7D583A7D445CBDC54A6470C083C44340A65F6F7407A4C7BB784EB4D09A68FD41E27568294B2D13DE5C2AB7D4C7811FCFF67A97842B71219D9F3AD9899E8E334960DB56596A41C3645FAB842FC1CF41E8997CEF1277D961620C8A883DB761F1A126ADCA93A417230A419C00E4F7BF7FB3175AEB1EF614BDDF11AD3463715F39A61B1B001D5B9D4D5FD356F2E94E769D0D23DCB46B17E5DCD091AFA2CE2C2E819626F01A14CF5473A1389AB840F9EE4", // 08 + "BB25FD99C731209E762D4328C053ECDBD00118201FF7A8836F8E7651EF75F52CD8FD48662D53F8CDD9AA770C4CFD0B2FBFA07E59AE1BA177684026A24E4D96BBA3B856E56103752A11C37DACB4426213D5EE47FC82C0DE70BC4973FA024DF51DF567659E6947CF51D39CD21CF7B89119172653543FE54F1C57232D3ADCB347691B425D566996F5F8DB09C31E309923AC3D5422D3760F1F22B9EF7403A7FA6F30A611CEE87B7C46DC839AFE30BEF927AC6F40478AB4084C3A5E0864BFC3625EB494B2FEABD0A0837A3394DC9D21D03512FF94E260665540C6AEAE2C535295FD4BA4ADDF9F34C1EFC4807E65D599498468F539FD0C0008AF77B926244C3AAB5857B9F0E2510856FC371210BE0D054D96A87B57480EC43BE4AD421FE0E00A01AAD06422990312B86DCAF2E26F2327CD33DCB057FD4CDF94F83B578510C4D7D5660A864C6CB1102B7CCD3C9F43F5862443B9034B58F1DA347749369C97DBE6FEE8EF009328254D5D66FB9F45522354ABC7A42C2228EE53C41C7368C7FB798000D79F0C593F1CF1E6389F1F3F973354DCC74ECDC9D4299D2CCD3EAE1320AFE5BB8027641B13245D7149B6DD1EE8E10B828B6F2C792C597CBCF2985BCD63048EF90154A509D352B9AD20259EC7551572CA9665F7B3A96F19AF496BCCDF42FEB6340DCE0A27A4CEFD4755E71751DC9C5D6C95428EA2B7883E9937851962E3C6D817CAA8EC566F93EDF398CBC117B9372A01B2519225356A8DA72CD6B515D8DB5DEC3EAEC7365650114E82FBD84686EB11DA3F5E9671471C844988375E4105C37E00CBA220A3B1E27A049B9753D064E21E79513F892FB5620AC48E3A0070FEF89BE9333C52E6C151FC3CE36C21155007D1B67CDBCCB4E60CFD3C2C59A81330B720C3D58", // 09 + "946B6A0BECB29F278563193DCDDABADDEF407C8789437694F61A4178F85002A7D0A5E67B3EEE8B911F3DAE56EF335D953FBB02C56BEF30D21C00594968C5EB59C09D778C637D1283171B39026884B8412E2AC160D222576490B5E901B7DFCE1FB46D6166968079E9695A8B9EDDBF71ACEA00BF74CD6B0F3BB0854B9BC014BAE5D6B5F51960D4768FAEE75FBE96CEDB25F2E3D50211393659553850CFEC40D40BF5C2FFB8110796D9C8B1E73DDFD086D289B4028D926462D58501194D092E0C6A4100B46320158B7462615EE9CE6430FC9D412BC3659C04DFF276FEF903EC7C7F847CF7F23CEEE3760018FCC0E27517F80EE5A1565F7CA3CF54B18BE0222EFE2438F306B6DE2EF6C2771364083338CD76C74DC0C98FFAB349B1C9E7DA35751676A390445131AE564C9A7EE77EFAC0772931463EF926DA482ABBD23E0172343D595F7299B17633A344F652E09BBAA2378EC84B849DCBC21D64CDECE910779E1D4763CA753D347AC02D270983962F17BF8EE4BE62AE3F92C9831CFF23D44A83DA6BDD85955D1101D93EBB481593450F23C8DE87A2B1201616E86E05C835B4926DB6B6A90EDE6178071CFABA3C7CF19B4188F0C679CE4EB16B26DCB1E665998691A079B57772BD72E9E44A70F28C79D9ABB42250AA4116C75861B7F8AB411B9DB6AF52DF24955170874250450DF1C31782F002AD88747AFFD2FF542139E081A300DBD69B2B7757986D6DCE8D349892B1528922BA05F25AB4CDDA0EA8CC3600BB3704363B5A20AA02AF3A3F703B2284D5548F0019CA0C7C4E7180D0A5E7BFEEB3390EE63692BC66BA8A7BCCC502C06D9533016273543991B2B37B1ADBAF15DACD696F427F47AF62E3F72791951DB035A4D0B7BD3C8F1753B85E3B45C177440ED15A0", // 10 + "F28C3E75420656B86725900CE0B35CA3246A13D45516426E9FBCBC7451AA52574E14177063D74AC2E20CC7C1F4FB895D5AD29118DD3748E106A9C7D7CB859A32887E80D842F92CE8B3C4E285521358848EEDFC478872A42150DA67328949408855488BC9CDFD3B50A89DA0243E6C015321AC375063B36BC648C8CFA3D1BC3E01BF50F6FD3BA35D725E5B05A308C577296ACB7571DAD976027BF602FE4C3446DFED2E82A086BD132FF872E269F6770F0FA7E36ACC56F7DA2E8A5044319C2B8CE0999510F1C467EAE4BA80E86EFE289D929953E561BBC052CA5B8AD2F5B8CFE1669466BFE2FF823A66ADCB01819FE7977AC2F0DC7F2262411E3A790B5318FEF83A8BCF5929063928C1AEDE28877A6374C668A0A2E54DEA5AF1730277AE3FF4BAC240E0CA97AF9362328D45AE4BD78866D3A2756170FE9DF751F4827684A43030ED6E59587D242463CABF5F9404AA6565F305B6CEE1CF4C6F979602801530E1760E39BE3102184C98E633EB4EFC86E479F6D2BD6E16EDF573A7CDCCCDF005C993C5B35D6517A5E703C57037656D1CE0B66835ADDA24684FBC9DA33002DF1FF7A46BE81CAB3ED4EE9C5C9EE0C61CDF473D500AB3F00CB2B1BC37DBEC7550E5D604A8A58BF8770643FE8E165268B0F3B7CC015D2CA76B3230D14D48545F419B5AAB0C9D0E230FD44618D5A49C5749C7BED668C7336806758B629D823F16894FAF8316085A88DB6BF519AFF6E19412A767FF1B31ACC3CE1327E97B47AAE48E368EE95E1D6EF341968883CD2E4885DFBEBB7D1E8FB51CD20E5A62B321F0110AB1738F31CE6797114340AE65CC0EDE548D0220A281F4B2BDB733DEF7D792F401E3393E2BAD9C279ACC051AC1ECD1819DFDB55603E6AD7146A4D233DF28D3A4D85C16DE4", // 11 + "83E25EB35FEA9489F9460F1074A44727711FDF5BB180D3FFD3EEC59A3B6956D8F262BC9471D48479FD7DF6119A0B3DAEF645168C4AD4A70BAD6A31CE0AECCD882EC41E52CAB2EBD3C3E124BA6DAA703172AA6BCDB685D365E4337F07594B1988F67D0B2B48C244D691415CB50FA2E4F32805CD12DC96358992BAAA34AAAF11F77CF3BC6F8EC079CE00027235A9518AF9FA1688094F94A3685DC578B5C4F291B86B915012F16E35E4CBA9941E6CEE2144BE736703A55704998E2B6850B4B6E426C4591884958CEEDE53C6C5E21A773D823AE2851C1841E2A93FD2A48E51ED26F00364FCB0570348219F5B528A546B00850E012E114B3663B00F5C211DF22BAA1F2EC9248874DA57C73F639E666DC8893FF362F89D776A1663425463FA2419583890D34387FA2B59EAB7FFC8365E7F6CAA7C614C283E53CDE60CE85672B66BABB95E2B74ED9EDAA8DBFF2ED928590A83519ED3E66FB0601ED0211D29C8E188EA63A9D914ED789812BDD3A51F7C97C3E01EC1E47D6EC642339E7EF2D4180F2E4B3DE1213FD03F7FBBE2CA7A0298195AAFE8C1E70D5548F13EDA2CC8788D6B1D121F577AD1CF3156E87C9D72FB97A6DD0BF3760A82883C74BBF01D856D3296ECB93C618CEF9304107430A24B70DFF3C6359230E9A59DA41FD62DA7A3BEFA6443F2DABB112540257B3FD7BFBA9AFA0B3173453FC4443D23185FE4AA8C99D1099C735F65893A75345BE2890A7DD39BF900BD90C634784D051CCEAC8C2383E06640EB1CD74AF88EB8FBB9494B8EB200D8A197A8DBF34B3D1F4823ED06843FF370883C5843CFB9838D4CF9409E55C89F951AE2A827238B2B9F72C141B659C2BD18D3CAEF1A8B7B3ECC28331A0968E749288E74F9E329D7DB7B6245E4600EEAE3F300A58", // 12 + "DBBEE74EE5F69871BD6AFDAB05095D22D9C722E36DF5651EE4DBEBA48F299D800BA9FCEA066814492C5CBE2EEC67E10422F250233863E517CE10A093A44015D7557EAD2FA184A6CA933EF405FB67547212901078BC627B5DEC3339FEFE0C8B724D88E8A45D29CCAA238396C123DDFCA9FF04B21B0A0DACDE515B74B33D80AD3990B6F55069981C4026151C497473F1004B60D54AE2EFDC3F8C949BEB0D180601DA8DE3795034AE8DB8627F1C88A4CA0CC19FB2D6B77649FA859FF190B626EB5571DD9200F58A14A56AB43A8E95ADDE4B54EAA11DBC0280067B1D3CFCC377B7AB21CF46C1A94C5AFEB03E1B1349CAEDDEC0616068311488A0691795324DBA11EFF29D747489241741D222534DE334F0206EDD7A3593FDBB1BEBB29B0A820A349742997A3AFB3B5F2663EB71F47A89FC30BB759A5679982DF3DDFA12F05BFB6C73F188817CD8D5D3B3A363AB91208D9A3F743CD71DBFE4618EE0D949FC4EC73E5E30C4FCE75C0938CAE89944048FFFB67C43F600EAA7F5D73B0C31C48B88A1CB724A0F5F6B4D258991368C0F1D9D8B237ADC87C72DA0D2B17B121559D30E3875679A812F82C386A57B53419062F2C9FA664306E36CEB512238E867C8F9CF9E9C2213A08B08E4A24619B0D92B5486DBEE8D2E50CD64FECB48D4025D294B59051E52F595E2457120F21203CE999F8ED7F75731BFCD3E356D629FC5C98FDAAF8BF7D80C312657104DB3889BAFA852F8B709768E619AE13FDFBFF186CAAE895DA7E3E20A280B6C3A6B97321FEC27AA83BDB572B177F12FACD70A0C920791E258D81730DD705C65715E46F842DBD2FABD35E386166F0EAD438054D3237491AB40A40B7C864AC4E69F13BBDD254E34360E439DF0960A54271BA93D706653691F0239FB4", // 13 + "CD41661B18894EF1C3CEB0CE7B301352D1385E2C50A568EC04856ED4D4434F964A4701372440602E3156A45416C3F75C21C0262242FD26C878B93952350FED33239BE8D11F4E6C8576A5761C8A34C36F8C0F8C81FE0663DB900B49A572C9C9172ECBDD004AC4FC44257C6B3BD8DA6A619390C6E26AF0A0932EFEF989D3737F77E2A40C4FE221E125B63492729B77FDC493D5A4FAA2BD564149AF90E07497B57096F65BFB2E3A32044933C42B87B64F7D8B0A3AF0EF6694B8C36EFD97C97B63175DF5110F85809E478FCC25D83D8EB303C387151E031E4FA4A4C926CE9D893A301948347B191BABD5282B4A64ED6C389D1BC22D324AF8852C7956005661708A77075FDA82EE3206763CFFF42F74A65DCC3BC33E408D561D65964F5F2599469550A1C420BF4B46F14F530715EEB669EA819F5F288BE16D69F07517A9523528B1F5DB4826B11297F1EC01D933FAEAFE177F6ECF5E4C52DFF4F027AF5D9F91E5822B1EA23254AD3C2AA779C6DA422B976FE0814435F7348BEEAB926FC75690EE13CFBC421C305F565DC5E73B73B58111F1A87348E6EF9CF00D16FB3592B7F52B88A1FBF0115A60899ED57F8D94843EB3FC87F7AE96EBFA5B381F6AB355CD0C301F2B6A5B84832EA0D4E047007ED0A8648DC36E16F21E73E4A0E4992C1C2194C325646BCA5EC465D5EC8FF9B1FCC676E34602C271B32C4E93740E13B84C4CB3A01451E0593104EBDD9387ADAA37755DA90A15331263F904BA907150B52A7DEC815CE5A8E22DB7732C06C83DFCE1D118F6904C37211E0E56F534108D539307D44A578A821567EE4652EA0EDCFAA09EC34DF584977EFB3225AF28CB1F42D72E2004EBDB57F90DEA9C9887CC6EDE6214F5F8F686CEC80C46FB9DFD71EE4DA766EBF6F2C", // 14 + "CFEEA7B366F2B3687C364AF56BC702885EC5B1D0FEBC78201FE6C914CBA004720730BCF1A588A73A8E31F18853DA6619DB947B00E9E4580B7925C9198F887788A986927FA2B911489016B959482950F61FA58844601EB766DAB5447AC006753247A4DCB3A541750AA4A6A0ADBFC7FE1F52EB6D2142C6EF0F65C6E99E1EC23064CA35A3DB1498C42707C0581EDED461B55FF499975F889B879E4733133854B1066DA534873678C4FF8953A43C02F57D1A692E3B153FC1C0F7FC28C8A0EE13FDAC23BE991511B8F15ED706CBB735599F55027E89CF58F0185EE2AE805CBE7548D927956F4F7F5D938BF5A17220888211F2E7D759028E32CD6DE895C1E408B4727541C4DA51156188B6DE0376F1A3BA91F5FC1E2C3470C074E393380755AFA3F5EF8E68228CBE07CC42B7536307D8AF414F599F901DF5D764DC72A7EEB976CCF29AA043C5E2B203D9128ED2550B8FD14425E0FF4BA05BB7652C4647B2DFC9A50C20E002B0A50F5B3C6112F9BC9BA3120F2792C1A84415D99D8B762353838B1176EDCCA57C82E9307FBDE66AACEE5C4A2599D9BD826B5B336222E8E9C9F2489356A96B0BE97F6568D383AE1B6A8C53252EE81070B86172BD2276EBABE7BB70ACC32D13D589821CB53A7D6EFE1437BF6CFEC2650B139F37516AE11CF67828937C0BA6C09354EC70335F256B5C3B6B1C0E5C9CE4F2C53D54FB53342F7832D8C71C9A06AC78792F9EF35431EE1150112979F4351FAE2F3A0CC33163571DDF57D3C8821734D779B0C5BCD7E7BCBBCDEAA726085271ACF8AD0B48A8BA8495A21C958C73134CDD44AC3A96F0A190BD8BC6E1444F0695FDDDDF34FAE8917C0101B1FEF10710DB19751FAF76E4E7DCC2A7FC69149F5D9F2C813A6E3D5858446149475C9709C", // 15 + "D1331B8F157416095A0ED294C78A4EC987D3F8596ED5B9A558DBDA2FDB262985C9380877ABEFECAAE078F7EA40D8682091CCE5DF5470562275B320D44CD754A08A1ECF7EED89C377D17DBF35D6D64B82466C93C3099B0F9C0D096B0ED064BCA7B6BD8C026728213D29C0A58CB48DD46063C990CB1931C0FDD2C94C03ECFFB7668799C3B4A24B0F05AC39E787D942C2E2FCA85FD595595130665439E1D2FD994ECE255349A86B8F5B3104D33EA40B236EDD75F80262A3C04A81F051BDBAC6C289A437BDB99287DD787E9A4282E75C82C62E9E249A8CD40217F7D036AA5AC152029A1159391AFDF4703EA63CFD0DFA47A425A5CAF2121EA85DD57498677782BE381F482608AD3B8D4790C3850511676007AC02AD1BB4FF01C7E87FC4F8A865A61831454D156ED442F63AEFFA71BE0756987ECDD9633B711E08E526AF93E2D2918C427C58D1CA751868ACC69DC2CE567DE9B634046DCE84B56638B3A867ECC2A0E5E44D209100EABF083ABF96DE603C5CEA390C27860947FA020E6C940DE756E708A8DA50F4EAFE3F9B07D7BE8D1402101937F616E9B26BE7AF408020BE781D7AD1AD096B3303C62443CC3057F7FAB70490E3BD8BA8567C744942536BA931D26D27EB1FC1A1DBF12AC583F01E9F721F4F77392514E63555A60371CB5B5B649C3F1282ECF6625B4CF566C14B5E66B57CBB5FB3DBF7984DBD34DE76FA2F5564DF43B88DC5CADC2978A7E29293AE2D9FA8DD53C8929BA91074D46A59EB2D88C5941DB092D1A1DDE5817C4B8CB7B98F7820EFE599410A6717D248AE2E1084EAF8C67DF3E51D143CD2CCC69377C2A74544056ADF03AA640C2DDDBF022C419658D9CB05B2BE27FB3570B2A6B3B87799BB2620F679D230ACF39D319D05BF0CB7DFCF4EF74", // 16 + "A6AC8E0C4BFACE1FA8AAD408EE1E32A7AC3D4CAF0FB55ED10C80168306E61CC5A25713D29C4E7B150EBA2EFB83402D6EF7DAAD35E7196EA409FBACDC0E472A45AB74CCE67D92558B2DF6D3A73AEE2569E099AD0EB27CC90209448FD516C59FC5D4F6973507BCA26BB68FC997DFF7A3094A4E8B5ABA414C20BDF30D228467ADAC6AF196B7E5E1864BB3FF80339815E8A23818CD5E5AC8D72F46DC63FD8D55E0DE4FA34A7B1BA75C0B2F5F3FBD128774C810E09D52E95747CA96EC3C72AA44A3BC2CF4C3DDCAF857719980034D217C53DE0884AD51AA6E9A7837038640B54977E2328158B8E310B3F99F5A63C4785A2B5E1C86CB174E81BC594AE50F53C55802B25A53016C2D96400FEBC865DE47A597A7CA01A3C13610EDB89DE57608C5824A5EE5949791ED1AFBBA4B92CF17B70E38C5F5C18110DA33BCDC6ED8FC23B0719013CF921EB457A3EA663598DF225634A3C17DC8C4CF0F219C1202BC973A22E416B41B82880BB20BC2BDAEE1DBCF3FE3AC31AC9BD5817E3F0465079C3A98BF11A466FE3B206D2BFA4CA84FEE08B21F1D44C63563E278143AE44D7E8FEF13F2B8CC1851E6D59BFFB38E3EE2E29D8B13DACFF3B7A86C0CE0064712D3A2770E3E4EBF2BCF885BB74E2DAF09A6F9A2ECA620109DF36AF492E9F1C301D3FB5AF474FFDE956D3717DB57852653DAC7D1A756B5908DC969143D56D315540ADF080A48816A1395E93A9E23920C3BDA3CCD2A275A848EB6E2677F90AD215A04C1097A1A03AD9E2109DF4EC1BA189135FC47E034CC875325440D10E9C82C85ACCE3130B98E50B047985B13903283790D40DB1CCCE969BF64151CE530B316DE2DAF6BE27B85B3B5AC022E6BD3548498828F58B13C771DC8F76EE600B2DFF7D65968BFB04E65D98", // 17 + "927E57AF56E62F2F8AF99E1310FEB6E609A1784D6AAC0117B3C693D423BDB7E10D7710257A13658CC6543FF4F8B7A4CE05D9FDDC1DDA5493B9B3B74DE75C184EC37D2BEC276FF5990EBC0FF710C72BEDE0BFB805E3B7DDFB0AFAD6E210C0062A62A9ABB7B69F94625BFA1096F5CD3E52DBCFBC3268096A738D85225D8C3B49AEDC968187D266FF9B452DF79F4D2CF77659306CAF3EB157005096A1E94F708F88276CB80056683F7A44C3197C519B559380411A163D3A21D9BDEB5325AE8E0E9B3F293509D667D25C03DB6474B29C4A67D43B7F627DA75BEE37AEB2C78EB0D768834A3A0E5552F72621D18C446DF3174C0E6F8CB9456DF866F455263B1D7790CA6F06818EC1418D675E1CD490F9E80A6FD855483F9453DFACF1ADFB77EA084E8E749678862E8E9D095F885ACE09EB8B9918137ECF53C96A7E25BD8CEF5FB6FBBE81A39A445C172703D9EDA9DAD92EABD3A822E16EE0C9DECDED407783B4B6B6E0130A9C67871163E73450253303E5E394906A86D11B70240B79A9A91EC381A4CFEC3AB81BDED8D9CD001D414B3155A7C497BF573A27E1F92A7FC5206AEA7E400CB806320E103C60A9A8AF58CCD0C0EB03A4EA24CA7D106875B2A79FA3A8A8D1FAFA248DB4F69EC28FA632BE82D439ADB1E06804FA985F542A9852CC1522589EBF4E93CADC3B64B3E0D62632BE05959EEDB8E45BCB65D8729E975C8988F8001482CEF2C7AED6C1007BFA9331099D8335056E1B98D51411033C9E85F4999A43362A89246E0AB05D399C5B614DCB162BC6AFEFE14EA5640F100B2A8AA248C9413F0672B75AE08B4172CAE79AD16E43AC9F03A34CD16E34ADBBD9B942191B2764C9F2EF3C0518DC58DA052CA4E18E537846E50F1E08880054F69C3D8E0A5392E8538", // 18 + "999334A54881FD1A03D6C8FCC0F974F27002DB6ED452FECE0585F7E67C29FA76EDB9179141CEA6DE2C9E7210B6F6926E7225BFDD2E1D7FF19800B4BABF8A8C973D3531C30C7415535915CC5C82980CBB956A1D7965B38D2E0E61A5DC4B71256509F08BD2471A3BE363E30B9E95D1CFD6FF87B1D67C5BAF0EC1D0B364BCD0FC6BC3630FF030EC72DC9586DEEFDFA399866DD4ACBA25B118EC33D15979B2D65315FA3E9B3209681E99D8D9A74E60FBBC2578441DF8963403325496EF39191573786976D2461888459BFA938C553C8AF97D124621DD30851788956C4330188784297DC079D748468AAA119FA35606A8FF961AE3D8DC0A83CEA6F4C525A1867191042DE02D381EA126C5A427AD4791335AA76AA903FA4C5FA8366A7AA07AEFE023360A1AF93EC576853A1E75C2F5FC6EB395C65DE74B746706AAA558FB278B2641F3E2EBB912B99458D20014F588F070E80A38F9A86A6202C17D7AFBB32DBAE8CCCBB6C7DA1F4ABEAA76ED0BF1A9182051A64DBE176D49D48ED13C0CA3D23653FEDB0F342458617C4CA7E1355AED9B173F03C503CB8AA7A96E215C528444357EC2BA885859A32DCBF2D6A0945D70E0C100AB2F1457AA0543757F7FEA800FBFF2FEF67B936CA81FC91E625D35993443E0458981D76B93D658EF83DC3AA01CF068E3AE4309FD7ACE741C4B3F1B3A04743B6EAD6A3DB9E7A0A231DA26844F4E1BC7A7C3D8D059AD5184D89625D00E9279F90A5F468AC7F76C0367D72AF1A9B3631A23C16AF444843B30612A27920F0AB07B6C744C86CF7ABF281B2FE35EACBD1973D3834F06CE3919E9A0605634E6CE3D2995CC951D550CCD38E1BF1FBC02A7D9116FDF6EEC56A4CE44C89B1EA460625596D2D13C5423362DBE36E6F8129E0018D6B38", // 19 + "81B17B29E24D537AC89F10FC1EA01C8A954A4295AFC1FF83540B172156B063675D4D67A24CC84993B423F481B38B227F4F054A2C0EFC519898C2EF0CC321711E5A87077586DC59120268A1434AEA3F4812157C0877F6928162BF9859B3E42990EEAC9CE81777379B67FD36A26EE8645F6BD00474F49A4E037DF6D15AA5DBA827E8E133D2C9E562FBA458AC5E08976D885AF9657039B9B8847ECB973B57B9CD756F290B8F82AED64BCCF4361ED525F766507DA9E874B8F80FAB5BEEBA885603C0162656FB691755F244BDC9D758E323101E9C6AA9A51ECB6430E64F8185D933783F1B324B313A15874E70B3AF6559388189C0FBC74EFA9FCCE23E72E1E99DEC398200C716937D4C4CA98C6F0E45081A5FBE3731FE5962315AD0AF432F0C8D6E8FC6B3B01E083AE1F40D2AD886E15F29755CFC4EC2B6E644FB0448B2AAF86BC05966A01AC191F1D47A29DA34A77E11CDC472E9BEF50B4964E4D4F37B8B218549DD6ECC1FB6EDE38B45D2ADBFE534B736AA246A838F86CC6851F63A9D10F3369D151ABCB63FB2B519254CEA1D8A547F77CE6933A6A00F709DD163326E0CA45E892C8C143362F23930FC78DA9FA00472986FC268E5BF5756B1E28F0FF4669DC9683EE150C76E71ABC4BAEC0EC67741EAE2B973463D5B847C38997D7E22F0BD78DAC4EA4D0CC230F6CD6ACA4FA9B92B2E340E33C852FF0FEB83C113874A2C7FECACA4E54E818D7342880093F171F062F3DE7000BD475B62850BF7E6B85174FF2B1E6737B258C6A2B735137FD3C1B2DF9BCD5CF524B594AAAB386420FF87245E8692D6BE52701CC6AB24CC47B0647D7629FCBCF02D620063C14F9E6017AC95815138D75C650E391062163BA3A213C00F55AD2D418C7CAE603A6B397FB20AC9830F6F0", // 20 + "ABFC64FF4A31070A7973A18D50A243C32E589526985495507EE173AA47D24B043F6310BA9F2F1CD9A39288CC61974379CB8DE105F84540713FC22F4E9A9888DDDF37A5BF3195E0DC5AAC393B61CCB343AE9E4BA94710E5498AAD096FBDB6E7CCC5571EF49499DF0CB4E5E5749A182E473CC085970EE388AD16A0ED63608FDB43B4742667FDF068A54EBA314FEAB2C994BBB06116DD3B2F0E9450085A2D14ED14DB3C6676FA8C49A8B18504279107B137B7F00D8F6DB63FB24B3CD97D8DCFB6A15A16F7DC58A40C9ABEA0A2A4A7F0A53A9FA116CBC1F72BBB625F58C1EF611CBD8603C2788351CB9CE94DEE3B0AF607EFFA84D6BAF86744F778E51244EE174829BB599887EF1F174B96AB7EAF58F2EAA99A67D4F8C219FC49972DCA210BD1173A4875F497B80B4B3093C801D4D0AFE4A89EE7A14618CC2F9AA743D10D0723A518DA46C42A9AD0B7C3EB2D72D2EBC40253860E6A08470F44680353C9AD938467C5B08687004DB4BD6FEB29BBD051747DF4B8E54798076871DE9667F3EB9C8D87FEC1DB9744B1685A7D5FAA06CEAA8CEE6934C96768801D00CC84596E1BCA7C1DA93D9329047F54EFDB99F55DF2CB34CC3638306E193C1E33D840E22DD2E93C54F553A816EC643CBCA23BBFC555A6FDBF4F8D6A935772D0DD891BD80A481A8A6F2A40228B94BBB85DA6FEDC5E77CDF0471C58A103043AEDA666A811800C0EC300FAAACBA690E4D65AA344715F667C6D607E3AFB12F7300E1B0621E1346A73F73AC0D58B5D5D8F9FE2B359224C0C3B0CE84F7876C38E589C285F93854C9D66D81B47BABC5FF03B4B8FFD24D71F3869753A576EC8BA8DF81797283786C450528FBAEA9E84478D4E66077E3B37800508738B80DD40E657A498EE423859A5182DBA698", // 21 + "B34C68E9590ACA5456510AE673EFC8AC2655D2BFA5EBC651AAC11A3547F47F61DF980294DC38FA1DCC62447EFE19A05E99B5E0CCA2C0191A554654239F665B401E3B3E7F2328B3687347A04CB1A5F67AD7F92FF83B381144016908E6AC2C5B9886EB30443B7C7ADA88A5F1B8F5B871C9D66D8447645C473403B0FF54861773875A52B05672CBBB396092EB4351E3F938E693C5B3A20FB88ECDAE480D8170C82F2279A8FAB2DAA15918A4ACA70C56B5124F12F310812F7B35A8AF7E9BA97523584F6EC9EE840DF2156A59E7EB42AF6CD6D2FA9C24DBFC843ACADBF061D0A74582813484B6A23970A4D0DFD330C9C6D3B85B1CFB60DBF8C2E5D5AACAC2DFBB359F014E2E6651587A7A0D6CAC4533F5A1962DBBD2FBF8364D81764134EFC65DCEB7C59C332766F1BEB831D8270A2A927F1201FED121082ADD3E4824C848C93BC1D1C8E79239C6F0C431C5526EC7D93C71DF8A537C95CF583AA8020D4CDF88EA14C3FA764ABFBA553C0377F48A1F61F6B3A865E1D45B9734E23983EFE4BA98D9422F572DCDA2A2EE1AC8DE3ABBFC56B7636D4E874F469AB2769039DE7ACC7C2D0FBC12CAC79B18E159D4EF632E35546FABFCC1039BCE062CBBCDAE923914C783CAC9E0371A3D201B8D0FA5EF18D50138A0FE759A20597A00EC33C85506AF6195A8D8E0B9554058A918CFC1CA732CA943B2EE5E9DC7CD003DCE77E791CE4DBB91458404A60213EED1A7AA7242824D77F54642CD660DA5B92BD49BA2277A4475A190379D33F1D79EF412279F4B35E810C23A8F311D3787065FFC17FFA2CDE8C174476B4BF5308F799164A2E6FA1572A6E385480B2E7BE152EF45AA4B6B9D8F8C30040B3FAAF202EFAA0DBA1461C11178B12806CC3FF787B6DFD4C426E259E48E830A0", // 22 + "83ED043E6D7B295FB8011BF3380B80F4DA4E93F1A605AB5461AF8642F2DA006E539582C0E945B61E9FF9BF93B0DC96C05FE06632B8E996D0B2A49920A7DDAFD0D1F01612D1ED8133D76F4DB5FFEF461FB72A784928F87431CDEDEB4F97F3E3776FE8346D79B064C42234662116F04BBFB66A02964886AF42E9270303E739D12817150DA4970DB7A3C996C1E8D6A456317F7421A727AC811880F1CBBAC39CF8B95B998E59BC35BBA62DB6FD860B16B1A4D716486F629736CB50FD124C88B5AF1149F2879F5F05F21012022F1E68ACAB1899BA6BBE41C9398A354BF21C6BC1EEFB0A8DC95A361EBCDAD5E878443FA259E4E8036AA7B8E46888602F4B5B72BEDA6305A8FEC7B6B285589E9B388116A79D84942C0CE1B4AB2EA18265DD3CC3FFD7B41C3CE9E79D9D702D497ECDCEEABA241FA144547469E5F8993BAD9390B57568A81ACD0F2CC4B7089F2D4C6B16DC590620E021DA341DBA59DC8DF95F49EE4431FD8820B591414C6A7C651832731B94DA4118C4198FF2AD88E7A2B6FB04F34A00197681825CE2461419B900F4E91481CC9DB65687239AD01429C9A3E643C156B95BADFE9BE6342AF86F5FE52F0A5835ED05097549D99C6848A925576A42CD54F9BE6E25D1F59DD967E989563A47E428410879EFEC2CA0EF3EFF0E45341C2B02E05C16760BAC6B25904D1E993509E24936BF2E5EFDC757258FC662AB3784FC24789EC5F792D8B4BAD2D16570FF25EB95498E56AE80C1B003CAAFC957E75E08B3BCFD39253546B186C779C41D78B05F25B4373A1C26F766D8C88DD7FF86BA0E2E7145C5C1341AD33816E02D37F2420FB559781F2D900DFAB1F2665826FB346B9EE34607CFDCEC22AC1CB8CB5F12A88C5BEA927F083CF86115DBD41AFAB4D7ADB5510", // 23 + "9458B1CEAF59FD9721A1D5326146B5E0D33295B5A62E4D00BBAB17F5C538888B085A502BF8F5323B2EB8E08787EF7525F92E0CD2BB1A471BD6A868408F61775EA0A51F7B7CE488B3AD8D34E127D6B7A46FA74340C9D31FD72E48D42B7999B9E71DC5605599E146F494FB3EBACBEF10558E3C65DDB071641F72D3A02FCE12BD3B67CE1FDE526B5716C59E9C8EC73C90CAB964923BA3F1FAA3CF2DD0C27F8688FC515B6DB28C26C456BFDADEB92F8A0098E085B124F2EEC8B5E3E05379327D61D9B37752281D20F9CD1681ACB3BCE48B51F32A5E81C2BF833988F69242288F5AEA01911276F54E432C72351F1092A3C5748F82E106AC97D05FEF53AF26F9A8FF28933974A7F2B99FE7719C01D616288484DD1459B1F9BB09B8C2F2688DEF3733CF2DF9C7807DBD4411164EF90E88835A098C6F4346450DF340B07E59629034B13F45FA4013A5D9B2675ED60C32FFE809B646AF1094071E9BD7686C1F0C461011B42963670DF1593B6FDEDD2081BDF6B575896883D0F1FF064F60664B14DECA1007341349790A8B47B6A8684D8F6900C15FA1A08AE80DA578663777A54E5ADECB315B76BA8DB4DF945176F4DBF1150D99C6CB2FB51A5D2AEA97D233C7FD5E80A87A767467E0012C603DA17B90CC406D8341C1D8859C2E4FDDF8BF09F76DBC389DD287DA04F8D3887DB917233817CE393923E9AB5BBDD72B1572E6F6CEB7E155A9F55397BA532C620C35D6843EE297DF8E0DDEF71644418D673F7E688D8A043D2758D0827B0F543D5BA98AE89240E71CA7717EC4A6CC76E550B2746C78E055B9D324D9875CB5F6A6FA8109A98CE68D025119FFD817BF442F24AAA08912E195DC203084C784FE514621E5B40E4D2A6BA8420623DA28922584F3C75E1B48B915A0770", // 24 + "A38DA6E20CEC2CC1AFA8488C231069AA2E7CD81C45FEA7633EA166397BAD527ED16ED5405A5372344A29BDA4F3366325AA0E50777B14342857800C8B797C0984DAECA66607C52FFBAFD079F2A00E71AC705AE27007F13AF8A4CF94A22A1B86902BD43117E6A397DAE0CD6C0D1C9F29A27A641B41ED54E65BFA43528A12BD2D6D95567EF23A8BDD26EED9E4A4C61CF5A655F1AF76A85993D9DA744EBBE3CDACB353F7E80C155197A4483D51076FDF207AEF957526CC0F82731E2AE6251875D6EC16B6A69185DF14800CDCC1B4EC9A31488C6857641955DCE7AF20112DD11BC4A49C37BB45C8C3AB05D09A5DF55DDDF2FAA76AE9D0F61A7A53265EB7264F37F266D5EB216CBD884DAE5C119B7FEDC103A18967CBF99F9884035D545735101B13842D3AAD7738B06FC2CDB344896043DEFCFF0BE0F2BA9FA1898B550CC7CAD948A7825D9E6C4845E019EB086C5F19F52FACCDC74A3BFFF9F4B9118B400194CA10CEFC91D466CF9219BF1C9B707B915C381A80BC7BE858DE2CED2D22D8B2912D03043A66C902241AF16ACF7EBB829840079FE505D39BCE736B9E148D33927EBE9308BABA5476E38926DC0F9065899D9403D137EEE209131F65940DF42DF079FD23A35C2A4BF72EACE7FB1F7A569C3C525986703410739B77487BE171AD20427634AEC395FF6547B488010DF6FB60E2C878F22E9AE11B745255EC0004BB59B27C358EE369704F4EEA9ABEB52E452DC11AC02E0A6045F8D7F43C25956B49B8CCE2238E30CCD934C0004B5E68139D436887A8BBD650257870F420FD5FBE4C2A8E284DB4F7A4C658FA9607673DB86DF4C9F33954C1F1DAB5016DCDB2EF30E467FCC9851A597EB7CAE543FB4AE08B0F96B563ECD42CEF362140E330BD15627BE4AC033E8", // 25 + "98E4C6C9A946C0D4CACF1CA4256701ED297270D2DB732FF0310069CA84A275A64ECFAC9A1A441133BBEF6D3F2803E1344008489915287BD8734B20FE4380229EF469206E61EC8B2766D10D910095EF0BD15F08B111A16F65EEE1252861E2C2C358BA18A8F93E6BF7774BD2AA61E4BBD9FA79E21A2AE8501449D2B9FB9F6033DFF636B1B09E1B8B9C4C9935C7C09D0325DF878ABA8672073E04A37A48A6ABE9E354429E60D3E4E022F40B78AF5EB204D32C051D6BEEAC52DA0512B311959F9275987CCBC81A3257C66A345D7862A0B20F6ED657F28A30CA3FFEB3BFB1545F6ACCBCD4AE2F89A177C4F6F60D2CB545EADF3964368AC2B9DE3B7664278363B4E06B01A9A694519B9C2C3E54A93D74DE31B7D706445B36BFFBCB50CE89123B9B3DC8D5B9B7F2CF1BB64B0D91393682C3804DE3F44C23F2D275C03BF0D3C61617EBB755B3E104E7A7261F58D95FB64E9F9D2C6847A8FEB46D88BD0C38FACB70FBAB609F437F7C43E64EB8D8971EA6024E05E550481504253D28E1A526C4610AAB5E480E5318A286504962404540F11AAF8F5973E8F5A57D94A8ED01ADCE79D9DC8CE9802C4486268A6ABFA49A69DDA19F30138DF8FF48C4A835A509E8A0B7A4AC8AC0B0E9181F187A55E203C3A150B623337CE0530ADDEC807961E352E1BACA978673877BDE210914FA399D6C25B366728417B9FA4D72DFAAFCBFD3D24683EFCB517D11E7E0EB415A68236D11BB54A719C21A823D8D5D83B7168B491BDFADE5F56587B970498B3FE2861FEAC6E1C2294EC5E6B73F574D9610E42772E69D0741EAE23113D301BCB1C33BEE2526E87B02AA87221A29F0EBE607D1DB76A18456FD0F93B6338366B7B1B36D7DE67ADE6DDF928EBFBEBB0041D7754C5C5D5237EB8DB8768", // 26 + "D50D3B57C2BBB2A77424C4879123CA284B8E9F91EC4D976021278756FD90DC8D6890D278AEBD2123FA72FF16D3A21C177C26C5702E166E12E6E70F2D310BB2961FD3FBA77F4073F4DF4ECA215CB3788BB645F3DF328F36A1A84FD9AA372EA282DE6EDFD22E51D74153112E440FC5AEDF230189DE5475D0F8E985972CBFF70959FA6A0F3F1E6DF9712A67FD0A77290467595BC382C7D40D62B32D2DBB4AE5FD1E76AC30D0C18AD15C4B18058A9961B7BCB9DFABB535D207FA8CC51623F40B8931A21F46F4CC4D70DE064BC77663FC603CA8FCC47332D01018CC5167B6749AC6332C1BC19FB34A27C77DF365D218AEDC3D2E7A95980F542289E3966F9269C76D344044A23CAAE9077973D71AA3348F8809F6C94A281EFA50D61B7FF4531A7FAD52268E7F788D6FD2AD0129E0FE8CEE9C39AC9284425ABED8396EA69C4DEB7C4EA12F24997C625465E122D2778DC7A8611D3794BA7A5E6BBF936A91EAFC2089C242E3866FB19E1BBF894402AC50B429E7D128A564E46C63B5097EF0A594DCBD215C2AB74FBDBD1E6F3E6007238A36840DB45E61E8EC8AD9C3B1573A878C0197CAA649490EE9C6BF3E3D8246FFC7D7A2291FC1E7592D066C40C74463D9FD4A441C4103A1AD10E2136E6A276331A067B1CB1861291FF4F54274F25E96316DEEAECB101E685BB21AB90CB613FD8D4C9DD69C16E8E501D19AF6D91E37C04080ED93E75F77A8BD89EB0E94F99CA83E1A3B63FCED36BB302E17AD569A6A74BCC7E73D2BA8E87BFE309E6E06538FF3403768DC9B4A6D545C0A7ABD48E72DC11B1D29247EA52144256ADA7864921071E81B2B94A90E5D0174F3AC5B3D093BE0E65510108383AE1C18BD74B431053541C7DB7E10512B00B94E983B81842AC63B11749450B34", // 27 + "8A9FA0D16F5292CCCCAC78FC615E47D177FEAE3B3C3593C3D9C873531142F3A8625A7D486537ECDEF623D0BF39AC9BA4A8C40D601EBC7252645C1260CB1C2794F583A7A1E32E50BDE43E488539F08CC64B96907CD4FC4DE220279DC064EEF6491DD4EC2DBD4B1DD444F0EE6B6B5AB1388B3F6389283B05E2C7FE940BC72AA458CD3BCD5BA2199E193931136973EA78D1E2A6646B6000D4C33273F65D5E3CC04A4AFF7AEF33D05C56B24EDAC65B10A3C29B68DFB4996BBC439B8E0E5CA7B0F2DA8D430F00C726530B582C4EA992A6C0167F19577686125E057DF86DF78BC70E2CDE25DA3AA7743B0AD608F714B1152634CBEDD1B22EBE9B4E88F6C9C1BE37CC969B5770DBA1AD72ED79429D5280A885D6FDDC5EA7F8BB7540D1D175173AD2468A4BDD56316D1584620AB85542811F9BD724608B8BA4028C82E91907DA5F0EA46620281FC105CCF3C0C676CF30C8A080ABE4F65B8C4CFE20F7898479B5C24929F83CC4DF3A01BE91F27C1F2C6C3A5DD2AFB3988B746333F358DC5B0D6E8E49876A19BBF0BCC01A1A57250F045D7C219D0481249B40F0AFAC38357D972B250942A3EFCFED09FF3DED7D57A9447D14A3FFF7CB69DE6B83B6034E0708677E9A07F7541E58D30EDA01BE99C5E6CF25528646184D49B3290D135924D251D216236EBE58DF07CF875052A055991DA4056D564BB96524CA4FA16A06916FA3C8FE1C6D576A8014D126FE5A103F76DEEDF20C4699361664208066FDE6B6B68F637B92EF08C99F6041C80FAB3C6513BE3FDACE09E06C498F07A4BDFD709A102C8A823121685441B7D0F68F8F42CEB1BFCF77D9ECE55B8E09D72565BE5D194B8BCB578EBFD17564EAB1693F25AF06F52FD24051E7C0D4B771B44017953AEB4B8F59BE155A008", // 28 + "E0BFF9020D121FB7A7B467748C46D13FB1CC76C6A6984D9D1AE83077626573D9BFBA0D52ADFEC3EE53A32A57617E3FE12176CEFB42685724338D31D69994AB84B6EC07C6E7F8139C77573EAC93F1867F57AE8C0AE8099D89F053209B446A07C1370D0F337CA407D7C0D551773F7B04F2D218D309A452551FB9AA15BF929BEDDDEFFE4A8211E5EE581A2A5A7CD49937A98E95BA805C5660945503A657C4CAC945F93A744E11ABDBA765BB57E439CEB8D99466B7F268DB2CA8D408E952941830403BDC556395B1B469E0EBADA20E6B95DC6CA542FABA68CFD473915A8A4994D54355BDF8DAAE162E574ACC2D28D01D25B68EA87B8BCC741D47185E44461BC04A0479B35D29DB3627142B4F1CEA11BF0EB857D2644D17F4AC7008DA74EB8B7373AF6872A80E508EF712382A5D3C07E6C11932602D896B2A6A806DCDD0BB3E595C7F636D48A8FBD549CE12B4680EBA0F34CE10D81908E71D18C5217296B639E5276E713DA8C0D1323B3C0F2610F86127C465B9572E1F33E960B28EA2F20B7140E672578618FC77F6A5959DA75C4EFC63D3A7DF0F019E171302DC3E2332CA9B508EDFA9FE1B44027DC6BCB9F0F8887981A29064B503879D56720DCDFE80F29D0A4A6B84FBB0712A7E30A2B0A1D707828AEAF3E74820BEBF794B00547E0F47DA978F01F8B7FEBF486E738A38807116B39025945D6A629639C73729CC6458CEA4432A1207EC792CBD5310E7481955945463BF049F568AC6C72008F3ABA9EDB11D655FAACE8479BAF73A3335C19D88299A7D0AB67670CC0F45CD60F7FD1EE7832037C60BAC3290B34ADDF2423CF1CB1B4442FB30DF081E56EBC137CDC4C58C6F9DE24104BF0439B7DAF9362FF6817B5243CD96A909953D7CE494B06CFB171BDE68BD744", // 29 + "E0092C347E292217BB60B9003170F90C6F4DFB40F1A5BDF35F9873C768A872ADF2C8C1CFA60B5F306AE4FEDCDA8FBAAAA5639452401C1E8E9DF02D5E73DFE731B98AC49956A94936FD32391CAF16ED9CE4D563605A9498EA7CA1E2DD9A18B928B0FD6CE0D923EB4392D0377118C160BD2E4DC2ECA380D40D259560B0AC23C4E6D5C658701FAAC39FFA7EECE177CC89D91FBD849F400F86B2AF9FFE62CDE4152E0BE8132F362A05BCF138FA4312CE98355EFB07AE50ADDE5EB4BC6053CB76FC12704B39719062A3DC1A236402887C80CA7328A37D4C3982215250AA505F586FAEEC18B99407C5A156CBA4C04EFE4A2C8C7FA6CE48690DF2D6A5666FF81A642797EC089332CD1C31ABFB25AA134B39F6262CB9ECA326E05A32C6F61771B2B1711462D4C359C4BAF8723643C9845AE5A4A6A8DD6EA2962482C4A7B3AD4F6605A7BBE7EC7088AF50F15BAAD86C76EAEC79DDB0F2EC6CD16D374F31DDCDD347E9817BF8204756754250A1C0D445C2EF4CE1ED48FAB988225F5365606DEB2C08FB651812459F3A48EB8A0F7C6987B0FE16D4430766D3030E8C515DAE8ACB2DD3FF0035D2C27E5E0DB97BF127916C5AC1B53B6656B99BDAB4E4461BF8319705CD7160434BC6DADE81AE87834B030B2A21BFE91B4F13EF77FC4342372FB91A024EF01783E99BE73CC30832D695022F374D41D7B758CA8C49F4116F466929BB7404279C5E42100B9470AFB757C1D05D877078D50FEF84CAAD49A8C5673C67438F2FF2D96978FBBD78141BF78EB968286CC6BC515682682C8A8D63EBB9EAEEFFF166DC4D778F74C929E9042B48A9AB975A074BAE2CF8C0C6F757255E74B03DCF8176E2F603BA71E3BDD266741ACF022325800F561A2B2E44295735CDC018C18D75436CA34", // 30 + "9C6807B92D255E04EC3FFAEB477D0B1FB8589BFC29DACAC0A771C7C2770CDA8E560CD7D760EC19F23568C442CF5A4A99DF979C7DC0A80E046DDFD91CDA6BE48F8D5BE408FC46266ED688E58E57A8E467BF2BE77095AF0F6381341BE16313661A4246D5A63D82D1F5F0CB1F3051A152B17C14324D49E2AA02582F96B4380A652ADAAFEED1667D12E3838E0C36FE2C5B73F5DDC92027176CA06CDA2AF209795D7C959EF2DDF3D1418C9E4E2750F3F10CB14F43AC243A53774AC011AC998A01706E304A4979C21D7966075300CDB08726130ACE32440FAED229AD95B5A0B7F4000318C768FDA5724459E29753FA7C359205271FD802551967FC89A303C3F5A9FA78DB000ECA4115BABA8D0276CCDED2BE4A5DA8D5C2A31E6EF3AF3FF3E685BA0E2745A03C4CD6397A6AFAA9F35D4A79C2F3FFB0CCB8E87B3557F6C2D7DEE600AFDA16C1DD5D02B26BF504540936ED1298D6A78E0E9C82B917371114069B176A03E3CD08C47819B08C8BB259CDE7707FF97BD47C17607CCF2B4DC53A9AA0E7376E89B78962A54C8EA1ADB8596853A9B35E77CF63C3603157836C87831EBD61B31435D8CA0DFE5F8FDD92C1D5F95DE51424D36ABF2C8F61910AE46E74116B8F83202B32E3496EC9E107210924EC35727A3AB6A483FE6A94435CCB0B6D5C49975A3494BF3AC0343910AF755F0EF81CA00FC24B6ADB09E55AD5B0C5077FB0BAF13DBCD869913DC2184D0D774FD4DF36A246F1145BA370BD4B93477B97A6BB0C48ACE4284CE02243449962EF25F26740B4873EBEDD21C94BDF8C1F01E314E511F46F6A31CC11F54B0A2B45D64ED7D32E61F524B4AB5D8F52732CCAB3DDDE1ED8C31F800C31CBB92F8A9365CCF985131F99F8CD1E8E37BF228A8D375384AAEB1A403E180", // 31 + "F99E9EB34B7FD437C0DCB40A389AF3D5B659FA3A0057A7101416656FB21BC92617AED9D8F9095DF1E562F1D10497906583E294C59B86AA92DFA1D4DC77A816C7301BFEEE49201C71CA4697C1642D4C7AF0453E7C3D1AA14E3F6C329A31D9830B22C2373D6EEF06B8F194C14838C6AE4E0D516DADBE7F26ED76E38B906D1B1428E11B1466048B83669214A7974683350AFD25405B4CE2225768644EF5DE9F38C602F1D2ACCFA1014F09F7C7F8DA374CC4DF72AABE0515DB7A0CEF22D7BD60C86B21FCB28603E4BCA9F7FC4BE57BE52DB892ED0A9465907E5AE45BCA4DEA87D2A6E73DCF2FAEFB2B77A163569727C898D868336C2F69A4F0635E30303000C63A3A9B0BA7E6ED712171FE608E34BACEB857E1D314937F9C6238830DCE856BC686BA9C3645DBB022DBCC8F86A4931191262CC192001FC1F5D45C23644A44F9A0BBE8EA95BE4DA4EB4EED4F1E8BF6A33515F8C20487D808CBFBE142755D3014C5E1CE35EE607A1DE9E849572A6FC93FF60C516CBC7FCF163DF7A54CDA0720B1B43A658C2A1C42C34B50AAEC27DC9710465DB9C101BF8E2CD43ABD17ED52E8EAECB03F3A7FFF22F3999F9D2D35912685FA023D6C200A6FD29156AFBB79432FA2FF3847DF7D1F64417B910B82F52AC1DACF91E78B4395EA5AB4D3716D98B2160C3A3064FA49A306F81863E881A96CCD65D5816B2B64A7B2C6A09D906CDDB5BC799EE5A433496BFF9A432D5429F539C74AEF39441049E651B59E45A11A751CDE8911030C9444DD4F0E1E60B9CD4488E405E31A641BD82D5CC8532C3BA01551F57BF4496DFBAA0BA413A2E2EC8B038875E8BFE7DD4E707C72D539A403A011EC5DCDC9760E3EEC518644D68E529AC88A599217CA5DAEEEE047142B21F6508749BCB6DD91C", // 32 + "8EF641023FD4C1660E83C65765C1E7C41D8C5EB1EB85A14F1AC9939D8E8C656450D07BFAB1FFBA181113DA059E2D53EDADA800C04BFD23F44C144B2BA68A53C66E3DEFA76EDBBA679085D0DCB2479A8DB56B76B880EFBFE7942ED642FBF88701F6E425E87D5CF36A269F746FA66ECF1CB1583E8BAEF36AA90DAB08E9DDD296529AC25DDC6BE31E7C9C0E0C3EB8D0C9EFF6ACD7AF9DF6158CE75B382D2CD35F155149DCABC4B1C03935F8105CBA0C0AC84CE83B276C29F1CBF1319588634F92F4128F8BAA7B7D179F8485034E6997E9DD8DF476FBE851A305C59A5082B48EBA6BAB334F0E7F6C64626870495E13E6EBDC01C4FF544B67CA08FA319AEC58E839CEC80EDA2CEA9CAC7925E16000AFAD4A469D89AFC6EDDFC0498CF0D2C9BDFB927C99EEB6BB4E22FE2751AA72856AAA5F4C13707427FD70ED68F2817D68A632006C1C08F3C637A2ABEA9775F3599130D1A500B4E4AB968E6A72B8B21061758A8F80267B55F64E7D79898A71C435B9FF2CE9E43DAAA6FF1A335B15838E7353DF6B73482D03D3A8C33C11B2A2061B7ADB60CB2F7135D73EB3EB67E0A34994CE3EF985EACAFAF6725D83649012E1817CBF06885DBC4B320F8A541215AC2A99312C16C45882914A33718B61A0F04B125D935AC59A59E76F760E55E6043FF9A7BFA2CA9B8A1E077C633402AD6D9A949C5B46DE2D82E4012D0520365DC05211698CBD3E7C3426BCD2AD4AA1BFA1ECE0130D05C734A129A3C9A5397788B49329922F3B3860AB2C3B8C62235F4CAFD7E90780C37A57073FE00D941819D6C1DD347AC32C906C2B7E5FA0A74B40FC7B797E7C041CC2B44868576422D3B613BB68921CD9D78390242C211EE459556E45D0369A7B76371C2B50F1602A80CCC833FEB49EBC969B8", // 33 + "EB1EF5FED3E30E84B5745A5DEBF56482294D82C717C7DD60BA44EADA63C0F4391D0D1E1EC2938133F5F637B36BDC6F9AA8E9E458D56B0BEC410C1C736DD5D81C98A91A11D3BC43598E1BE12061C96C9E5E696D9689DE1220BE22F4B6545ED59E8DBB547F9B7A20465F3CDA2F56DFC6D22E049659609B99364C74501EA3D247D223EA6B7C29D8412AA92FF6E638DF61C70B81FF0DA0DF4EFED3F4D84689E4785D60620224F8E53C251B28CAFC8EA978E304947850D220667948C880E769359628710410CBB7C65FD59BFF58E7CC020E11CB72BA184C10C6CC07CAD27A6234BE29F5614680C7EC9CF0E39411D65B228EF6A1FBA54E12576599D74B1C7DA858F7BA89C29CBDFD4FACAE05B564963364C66EB88C955A345739D74944628EA55A5A0E769DF1718F0899BBDEA90C56EE7D54C5E297605C347A88F1B01C21233EA56E42E623C47EAEE9B1C20C0CFFB015988945C65D8729D831D9EABFE70E494B7462B096B07779A72F59DD003E4BCD0F81E4C4BC410AD855A826EA58002DAFE56B4309E33CA6722CEA7C94E76FA814AC580342746BF446FBBD7E081AFFFB4825C9448D5371D25085EBCFFF0470B5EA7109E4A84BF64855FEA7EE84D65D7B80D327C09C1E2A22939A211B159A573B881E0B6BE5C28A89AB5C1A9B814FADEBDE7A9B9B1EEB2D32BF9EB27EED64C1DFB100DEC107D1C779FB7416F48E72202497CDB111D74039C58F2F1D39C619376DB32B9DE280EE95839A952DAA4D130BC6BAF3B61FC963F8451215194E820A69DA77E41F6530E9D6E801EC49990AC5D2D00A9FD24E663F8896C328F6389891C5197CA84EA3738499E1A99DB55EFCC974E263BA05393910F452324C7217B73DF5AB29060BCDDB7B5C1E9B6F207F7084BAEB56F483C24", // 34 + "A248DFC9DD738A7E5AC3D93E93387534AE2B707B5036BE6935C38A62E7605DE427332FAB29E251CD5543DB8A7938EE2967EEAB3CC7336FE0C64AEBD4E8E444A156FEBE6F5B6D97F08BFF1C37AE4A0F94A253A673D1638DD6010416BC0DA9E67F6E8CA6C1C0D5AAC8FA44798E72FBBD713DA01BB57200F17C641574E5C9A511DE9CB0E5825EE180F3B198B1D677F697AEB668C20B68B55580B18A14928C9841F86920A5E2625FEE4C5E16A794B354D889CC5EB8BA85419FE6A2B47AFBA35E1AF3D90F56176F330EDB45FA91F3328351DBEE08D79ECBCF0CC32D57CDEF9A2E823C58DD76AEDDBA3B73A05A3FE8011C68BB6C452BF290E3AB09788E5815F3AC0DF9BD29B64110DA7E215517642290B91983C0AB0FD8923D5E80A5CCA9F79F2A0BB1A268740ABA3C8410F3823B2838F31F25953F41B8EDCC93A71946B56EC0B08909D101C2E1D417ECEBF06E220114952B2704D6D2967280393679EF76381DB071E04D86674CD340FA2A798DA90ED12D6FDEBFBFC92C018F6B71B09E87C991D1BA7425DCB10E988FA14C76B1F4D30C605414A40C3EBAF1C38A1D95A30F26EEA24900880F3932772B0333F5432039E8AF2D0974EE30A733DBC9CA1A4F62AABCB23248E0EFAAA97CE93DC0A0018E3BF6972A7CEA1AC941D168AE6C071D1D4A7B532FB0743F9A5E2DBEBA6920B25E1C93C4828FFB4E6AFBE8ECFDA7D3D2078F46CBF3031723DD00A89141FA5B712CF5778704790526315F0018DCF8FAED79FD18C34F42D820745AE480F32B9E69A71451960276AE39C32CECEADA41F6B5033686FDA44CA1F571E9CAD1F5BE7335C16C66134F4C8A20BBFBE05BB26790E85C519141757C8125CF08AD6B24AF41CCCB5D73B4DD2050DBA2F884E1E857CBB36858754F638", // 35 + "B7C32F27716B8A3100AE52B060043B6369157D6DB277069C9E90F4DE18A25FD568595AF67CEBEDD29E898F8445B73BA0EC2FCE412C550BE56206F9F118FFE2187A5CF703F17610A38D62FE1B04632A156E65462751EAB89B6F6B98CE0A3386ECDF62C5B6DFC02BDA8245A127AC9AEFCF7C4588CD0704A07ECCE68B34F419B03B9107D4D8CE7E6D6EE45EB3480C08A5B8FE584E6CCED489DB46F99107888AB5F4E49492E2EEC223BF646BC4363F45447A06328DFAD05C68EEB2A4F4ADFF68083C7E7DB43FE8610DF167ADAA07BF14BDFF009268A87174A96B568EE0E3B15BC86850EBFAB3C08D3559A41BA195289F3AFBECA053B22AE37F3DB19D7C0459E009D63D920388D5218AEE6D11FE15E8CDF744807F8B1EF95E21B06338B628F32BA4174B0FA7589CF31A8474C9C51BD3DA8FBD92B831B9C849F4B75A0A6ADBA6C2C911C8FE5F7AED582F7D53F2BFF5D7063BD8A6509D2A4DE67EE3CD955DB4F2F8C268B25C4FA36ACD175BB8E718C17E4538BD82F8F6615FE24DB816D8C53958498613338484D85875287C141D74A698464DA0CF9AC4D00718D02B4F59D12BDD149E6747D750949C7D150FB6823BB26DC33C1B90CA43218E24EA802A701909766B6C088CD60794A43A9035142C698FCEEA9E91FB6BC6D678EBDC6293A9285553CF8D7B51075DEF99A172D2CF04444AC8B16875D5969DB07B7F1EF6EEEC9AE4DDD2B18908560FB49BBF1151D6105FB7A547722444DC0CB1034F03A89D634C1E932D0479A1A31147D6FC4EA301682A888F406336AF4518714D0AB746CC79DA94E2F33FC5EE8457A16A4175B0A3FCD07B897CA0E3E0077131359B98BBF62CA363EA01D4C2AD5DF84CDF06EEA2F67DA1E56E46027BCE48C907536474982F3F341E90333C8", // 36 + "8279B8CB078D12AA941FA064F72C2C727C545A653BAF5C0DB1F4BF1819AEDC4AE09A5B844A49692BF342D63F0D839C6283B66865E88D41858CDBB705F1552C5083D9433B98723FE914E0C23ACFC1A2E9121AB2CBFD47474268A7E0894C9DF8AE4DE368A4EBB7CE9A82AFDFE56F57022DEB8F9CEE4A1F7EF01865C7D02C9AC3125197D073666355C05247B7CFDBCFD37C232238E15DEDE0734FBD5FC95CB425FE474F05F53E0BB64BCE72A1BA583EBD70C2C45FC883B630C86016DC895CB5C64B134A5A93455493950FF529762068ADF5BF3086379583805AFF7145BEB9B09AEF802D63A96AAE4BC148702465D89368AE54F6517C44AE557B74A6A83B332924C43363A3E93E2035C1C4706011042653CC7A6F41DA2EDEABAC3AE3698EC18D7E081F2FF70F325B36E5C3C46B8D69D2A5AC5C8E6F6F325F9917BC4CCCB03D510C4CEF3BE7E0356BB168F57CDDA9401DB138E388D3073D2E12B1B300972F16B2A41DEA9A3BC0841C9CD7546062525958AF820C6A78DA9B1712BA0C0B379A821A45CE97B8DA6ED83C877E1A6A792370E5D6396CF1BD44D4B6977BED5FEA84C08A44422BA1DC92BA27368B634DEDD198313A06EEE3DDD9E1C90C4955B4CDA5999BE46D9E6B3ECA9C0227442D5AC2D178ED9CA97E286D58FB191080318139AE4332F4EE5FD077561BB2FC5A35F548544340FBE617601BB7F35855EDB147382BD22D6B8FC84995BF60D05D98D7D109F87A4DFEC1379307986508BB90DBDF29FBB19DA552C8F41C28871DD4007576B0157E5BFA13BC1C7F694763C3DB61FD02032D31A3BE7162341C7D0D4A96AE56125865090560D9F82611EFF34A0835A1365E3FAB2B802FDF358681D7FFF1B7E1A38670F1535AF0D1831CD4CA1ACA1CEEE8D8B158B30", // 37 + "DCF8B1B7E674522419627D5AD4AEBF6C6447F9D38A4BF29C12DBC07DC2185232B1D0BD71898F7B08AD8DA2517524A597EFAD7D331BE4661968E1F8C43FD2A094B42BE350128FC4C1C4A428CBF888AE39E129463A2177FC1DEF2AFA62C95F909AE17DCF4032329F38AAE29AB13FEBD38FD3DA208CB395BD476F33BD8C2FCAB7F1D2450B002A6154E110AA2FBBB2C379531690C1113C51CB5D4175FA6925530C0E88536060928670BFF8DDF90154F60D2615E7E87D6224C190153FEEE0434C6949D82EDF633780F82836993E4D839B400372EAE98EBE21349AD245728977A6C78B358467769AF9BEF0168A7126B000331D28ED1316AF7EE798278EBCAD7847A7D72C68679DB4813CF67A7AFBBA3CDDA285A9E284D27D263D22345DB22D0334AAA630FF88597474AC867B0DFD43A47C461270EFEA09C971C000EC353ED5A408C9F442CE32FB0181A18A5D5D72A5BF621FEC23BDAEB60649BE3309B8A208A61B29512D823C5A066E4B2DFCC6F53FA2FE4BBE14B63A9E0368A7E23FEA409A930A1FA3ED41DCE2AF953EE06E7BC5EB3EF4E479CC144F6F8EA1238EC7B0EE0216F6CE75110BDC4B8224B08466C5FD8B0FBD7CB52DD9F92A3ACF9AED1F265A690683374EDEDB1BDB8CE9F6FBB9C8B12D7C2E4ABA5C48EE6B1491BE66BD1A485CBF07EEB932A0CC24482E38DD58A9A20CE3B225B4D1E66695418FF20ED2F6098523CECE06B5615DB4ADA0DF999D717A3229895A9EC0288035D2CB5307D5B9B27652DD2EBDECE8531074B828C6BE15242B2C41674CF5BEF9B1B2E5E05DD93F398737DD1E6BAD21A6552C7C5C376A92A89EBD5A11F4ACE4DCA3925DB5DBDFB1CC0FF7D1207D0F5C0579B39A32899A05C4A7C5E6240434D017441CB703101DE3862122C9E44", // 38 + "DD5F5D29A86DDCBA09CC373F11C14747D12C01974E74CC7C06BDDCD5F2D45256FD6964D93287B8DCBE414D74C6EF98F1CD72111DC32815E03E13B2B6F85785E5871264BEAD6F8BAAFFC97E7650EC187BECBFF59925258A84A671791C33619FE983EC5868FE5C377D91C690BEFAC9F19AE1FD9B5C4E2E0D0C52A2AF3F631C5A80BFDDC16EAE7ED73F66A69791E8DD2815848D6BDB4E747D6DC08FA6B606876C3D1A7AC9E344DE02744081E0C0CB522B091439F6263D40E7DBAA159D31BF49BAAD94BB974C273E037E49EF6826C8E1142B768A2E606F3A76204523024B0D6BE9B3A19032A5B877590E2EDFEBD880BAE7E22E439855D2B0217974A6D7C6F80C3B0D232ED496F931E5E84A3FF6BAD1AB581EE9723DD70C0A794C25281CC55207EE31D34C91A1A4B74D736A0859E62B25A93DB39998D26A1EAB3E4143F01B664858183F23238A26EE2047FD1FBF526CD6E3CB6017820E8F83495B4DD93B8D70DFF1C06AFCAF11E81E904B37F717A98DE354D45465741BA1B4F8B72A612582A2A1F2E40A05727E147238152128F03FE0F13AD4B3CDB2699CCCCFD2FFC2D6509C01FD4B8E7AF42D3F2DC2F7F0FC6230836BC2661F11CE7838D636D7A931C62E891F23A37FC7642BC94438BEB34C28AE6F000C1C334C41D5C0E29F9DA1C0FCB5F8FAF77F78684170A26962015E86761F17AE5D621B35C8D92EAA6BAE8C1A30B356386B99693A725BABC3574E265E4B0819502B886620D4D10DE43ECD300CBBB5CEE96F5CAE0567BFA11E4AC8CC1288A1A90E6B191CB128402C6B09A8189917AE49D14AF54F7AA9314F0423649DC7A575E731C8099ADE2AB3EEAE1F8F4CC677F920B664AF49155824C08A544DA3179C436D52536FC0B186B5212FB36AB29765CCA6BFD5C", // 39 + "87F0F71E090A523A05795BB3946AAE7225D4123530FE4930BA0570C51495C6E8C674AD68A0B1F9DF470C752F897677332299D7875F4969820512161FCD3E6183AB96E296DD58DD06F3D9FC28D934545E5DE6E667E41D23383F68FAEFB13139D1822AB1A50708E4598E0B877D08E004A75C75268FCA08BAC51532F3332CCE45DE81F112CD07ED277D1407AABFFA852A21BC1B11FCF906C8A24CE101E048606279EF6CFDFF73C6E69CF6C26C4FCC3DECE232FA9900B5D16AF9E76F54F35718583F746CE3ACD0D9DCDC3495C63CFABB2D8FD15845D7FA1BE3E1E04B403F5572F745EE74B0E3D6E912EC7C9FA465B14FD1CDDCAC6D35DCAF09ABCBB78B7752B17382046D94EAD4EEFC56FA0B92DE2B690801C16117B8A1772D9634E13AB0314FFF209DBDAA17BF512FE6BBB745641BF8799C1EA745264A2E9AF3F5EA28F01449E2D65678513806A3CD198CAC9199712FB577DE5376BE375018547D4D143B4C7D87705604DF1335EF12029CB54D06692A8EEF5D519A96F6A448B32D663EABBC6EE600244137847F367C12C245E06F3D963D00CEE03A308EDA0AD072E45F787F4DF945B011D76801A5599FDE954E257CFB993E2E15A8323370E6424A82668E4EB080708FB239416EAE3D235355198498A8CA3C630A864503716DE942937B462B80CD2F6C6E0294CD708E85F39D25377FF63C43147629876C6192EA1C34ACE520D7FB71AD03716641CF901206491096125B0E33252E7172BE6EDFB583E6034F3A72FD01D9C8D091F78BE4975577A6006128AEAE8DBD7E5AD97A7DEF7F519422802E2E9A5CB80C7E871CAD8579A6691B446607848D48E9583FF341E9B6CD5D4A128CCDF534E9666E4EBB929EECDCC03408A5F95276B65C09962C2A1DC2585B617C527F0", // 40 + "82A6C8E99391CCEDA8359F2DC980A844B26BEC60C7312CB540C3196433C05DDBBD2B6D43CD89D91195B9AE50B5382269A50B63EC8D3C32354EDBF22C4988CE66E76CC63D97C9EEF15C1F0714035ACC70F809BB82AC0E33C47F672BD54B133E160EC5C0B3857A69FFCB00968650455D7D99A689C9071929DDBC1C552623187D6F5A7F1E7E7A4CAE0571DBEE0CA1120184E545DCE542162227E485344FC12FB10850DF040B5DE5B5D7FF45A414E1E4BC61AE8BE695CB805B0FB848D39B12A61428A53D5F610DF4D62101D22E4BDFD1ACA5B06B6A6725364812F963C81300A19414AC98C7043C3D96FED8577A069FCBB2CACCA0CA2B28537834FAB53887CAD2C9BC10761A132C12893012F48671FE28702497BC2A39A3A5BC0E866A73E53FE2FB90F1749A3050B9554ED499BCF279DC427CE6499FADD5AA64C5964E4C676F9749927145244E8CC1A71BCCF4BB62366BC9CD2DB76D2E3FB4A56AD4E74CDC48247A3014CFD376B2E650325CBD7C6ED016A4941EAF71FDB91C2809E9E01A728C9F48851BBC4EADABB23FB3094CF69D08146B2305116D68A591DF11362E22D537323D0BF90DDA00B6BAB7B0A200FFD7BD0AE2B12B9A99D581CBF43F9EB06A8BF59885A36CB112DA1F78D2513B313951088A6AFA2255B6FA69B40CD9C8BFE0BDB837CF955044FA561FB3030FA6E04FCDFE2B3DAADA5BC4F77F71C780352CC7AA7D051F663A13C45C64433832336F0A9AC78A62C00777754489B6253EFA75EC6DEAF2A9EAF89FCDFA28AA9D43E39E25FC0FD3DB942F756C5D3BB437EF7C9ABF30FB4DA227BFF021E1CF783FF1B8BC7BE2FF6553FC84FB9A93E15CB8E07D503D1CFD00B4D8154BCB075EC100CC635B6615DF5FA558F4D58E0EA98200B97C9CEB1C80CA850", // 41 + "EAE3C7B357368A15F87B0872EB86194169108BB59B4BFC6C8BE058C57FCE77C99502266EDD2DF7335DC5BB3DACB0C0A042BF0F46B3773487F811B27103B791F7099F58F43FC82E682AA909EDA6A9F8BE4669D3185A704FDB3FC9C3E312612C3A2DF427C120AE3BB5FED4634E98E7EC9EAC666E317773FDA171B52E139336C70675CD65FEFB6812726C075FC9D1F822351B4E2234A754ED28B3FB3541AC0C26306AEC93EDECEF769AD8BD106A484FAC54D0AE027711D7E50096CE24D2DD2015C50611AF4EADFF16BD8BCFC7B9989EC1235FB1F7E6A72AF7A8AFCBF2321306702D3DAE840127F31926D71764B771CA482C7ADCB4578C5A0919DC5E7F042CE2549B3EA3E7D68FA12716695F765AC2950BA62602F579412D515C24D7DCA9E66B6AF120B94863C9B701AAD75EB3AF04BD7D32F330946AADB23F505953F2B5370CDD277736CA17E0FD4457934A2CE132FADB6A603D0EB3706629B1EB1407F794C2018B0C70074BE4061C909D5D760AD42FDC10089CD94F94F7B4CADB2263636BC2964884702DFD17A750B5C1129A120312EDDE0FA430E2AD51345ACC5F6DF2467EDA1875868DE3FFC9988F1536D0124ECC339EA2FB3C6A4D8A81710CBD6263C6617928A21729B2B983BEF6E0904CE678902E41C2DE8810F2DD02021BABB046D4559566E182B029974573F5AA03E061D3A93220D626FB6A3C3EEB4F4C92680CBE244071F4A800958F0F30A01B02D0D1E78ED5FD88EF518FFC9903EF838133CC4A5687BFA9B3F4D74DA8B0BB74ADA05C276E66BA76EFF8D255F667C4F007F70A7627AB6A9A43F18472FBAE698681E9F2E2B99B8F500D6BB548F5C7E5A3E915A9A927AFB240B47DEE3375731611938905202CD19A4BB184B06893C5A1EC3C2DA490F0AC4", // 42 + "81D7FDADDA0B9157C00450C3DA7DAE5E0CBCBCEE93039EF567E81961E115067C80BE5483261DCF0FDE08A5C1EA39CDE8B88C04F34225B7E4AB0A2DC241A72B9A2717A7525320C08ED62CD6E9B8C1A4E287B12CBE6503376C9EFC9B2267663E7DEB56E55B54D2E91623EFDC32C81D853DE51E73260B3FE1AF8DE75D8A9D0D418496C3F8D7A73317964ADFE9A0D45A5EFFDB81D7D7D5DBF5E2CDAF815D4B7DE87A2C28FA6D8311F0909D6C6FE2D06AEA7F98E4DA3581F0F2F47F328608E6D84EA2F9F2A5A4FF121BF8D1522C90D2D5F3CEF9E3781EB5D4E385E9A8C58530A79FE70E1399EFD0BA4FE93758BA67422B037AB39F4DD20FA848D5B255511F2F820C2B5B118757B09DDD262E100D295E5B74113D5DA75FFB7049E5E5D32C5C3CDA6B2BA503F48C82B95A76D9184ED3E915722261D6A1F004DA84DD274B556B17CD1FFB158B22C393CC06A7D6720ABEE0861021B98247F4A06FC04A28CCCEFEB7FBCE802E083410A646B29A37B732B99E2F90C1CEBEBA828B2AC07E8AA9C32038D8C7C7CD824ECEBC834DAEC4C80D303329D5F5389ECB4599380EA68EE5D5601EB212D4ED327B75C407F3A0C0E7B57820419F377288D4F04F4E53458931A51BEAA87CD3725E6958F03CA9E6C7B8598E445F4CE28379A76EA006DF773ECA22C9597501A5B1CB509C040D9375A966B7BC35CF5C575A4449F1A307E4CE64BF01566A7D4CED5BA410687B43E3D35521D03054C37B1E7D14E664C5442E1100F8DB7477270E39FB30BE495FE254CFF3E2C3FF133848BD75846EAA412779AD1C8455A7A5F1C951230826DD80165C4EB4BA8826E09BAB446CAB69493974D7A88C3152A4D25E1540C716E669A5B0D70B1EE58836D8C0F431981F019B6B6724F4B7FD17086D81E48", // 43 + "B67814C98514A451ED8FF4C8FA70190D6087809301EB8D5D17BA587BDB954D63190777969728C625B111A216C561E796BE9886EDEB000C727709151169EA8EC67CE997F748DB608E2D613140A787B579E31A5F9E92994A3C9DC21CBA91AA6B6742E0332A9C538D366DF211A721986453439CF1BA8D49D940A2A04293927B1094CC9EE1E33CBCE7678A676E33F7CD5DE8D93D1C0D5AF9BB9D6F7A51FF9EC56B80D83208FE71B0C85F3BFA60ED013BA0191E07D2B15AEF426A7122D3E754AE749E17E6DB43C772A71D7ED7645FF24760F83F2AD6448A7A14C2A2472032BA36B3238C5AABEADEF6240CBE68606954B60051D393A3BB014B0D5386097A02399E5BE386667086B1541D939EF67620B6D83EB004C6C71FBC6EB3DA8EC6E50A6B05D7BD4CDA52CF441AE032E693C3C297E0C350CFE95951DAF2E1E56B04C232B4765A2D0B4E5264BA5B8B4A3563EFEC098B9F01A7ABB5C6F146B828E47BACBE4117CACDF2B2C882C01F787F52A30476881EF03A47DD63F98804CD8BF8A77549F4CF893B1E5CEE4F2A7843AD78145AF3F186DD7F0EDFCB5AD04D598095A357CB7FCF10FEF6E9B891D1AF98803A5DCB68AEC494B684054C13C8EE429458A0E90A126EC823C02B7E7509F5F344C679B670B69E7D290A7853EBDF9304A66E7AFA4A16FA412F3FF810BBA9A75DA1A3E3EF8BBAEA081B76D5C935132497112E36501554B0177957D5FF8865817154C11DEF9F42ED0401BE65E060183777796B29E163DB4B800A874052E2839AC97970CD38ADF8834C56FA78DE936A366B931DDE81992AC1A8ED5DD54CE7FCD8F7578CC725DDE4C8ADCCDB3DAED876946D166107F088C5B48AF08FCFD419A4A0E0B9DBBC3A4A3BD4C5719658BF28BDB6D036E82D02EF9822208", // 44 + "EABABFF30EFC6AE2E63A7F47BBC4728B986F66463D3781C97BCFA1C6308F1BF7A47860A41B0601E35C49CC72C7C1069DABF2D0FA2DE88F83FEFA369C447AC31483890EAAA9B3FE321CAD3421D8DB9AC305102A9393848098482BA907E816BA6BFBC386D99191DF8E4DE74919DAD8B27A303D4F344995254E036C3B7FDEDBF1719BB6D48A385B055867EF3D9B4C10D9F1A08558AFD7ADD124C31AE9387EF72B3AE22CCD91B8964DBDE71E142D399B9E0FEEBB86A219D6E042786A1573F495E5469401961E9AF48CAC31C55561B75BE83245F7CA56EDE68AD2A9CB3C04F9206526D886E2E45AB8806728309D13C23A2F8D15D291DCF7F0A84D11301999E0C545A06A562847BF179FC3FB40401C8D20D479BA89A595C6CCF591E0828A457BB3DC0EDE6648B516788582E55C22D8F8A84CE4CDE3C749CFF7F8F8540CA8A2A1B472FE149313DC5F4EF7647D5228CF13CC8414B6FBB5F5AF69173D30E4CF90310A7F2070E0A6AE8A425FA381B8333EEDC45B3B887A9AD687E3FD5A9413A68BDC144193D9AB0F1A10299AC0290C7532A5B61BB8EFA1E48F188AC883072AFDA5A4E0D494E7CCF900FC751151691A06D29486F2CB984AD078910C6D39BFF75C5F481C15733FA0EFED29278BE0517AA25240F8C736A04F11FF68A1077C36E54B0CA2617211DBB5A819290C2624D6CB778767654FF61C4B820CDA94F4C7F59F612D6BE3BFD1119560C8715FA50B0A5EB921AE8B9E15BE5F6903D7B1B607575DA92CA93B3388916D9843351F33E97B5EBADC4B30FFC8852DD6EAB0F63352EA7573335EBDCC31FAB5CD988F3F8D62B5E32B8EDA81BAB65646158BA57FA37237F575C101450C223A734CDC13262C4FD4E8914519FAEA599F5DD3A17C44727A8DE22320B2F91F4", // 45 + "998031EE03AF13E73A983242CF8102737EAA04F5C475FE94EDEE67570C1794910266F096F24B02A1C9DA33B7C07442A49FBD6BDCB58556655529CB1B4056C159AD1D8D2C0E9861DDF78ADFF68A8FC35A1BC798C1246C5B15B1E9820F1F960D24017250B0781480127733FF1B67FC30A6CEBA8C66A8DBF09ED2225AC25A790F1080791A93D73CE1793A0E4070E326E8071F1E5C3CA70FC475248DD1A4F623413E1DF6154128B9E9CEE4361FCC269C32A54BDFB1F2AD1E7BBEC70DB73CF27014825BB5A18BF924D1DBDD6C7C929DBCE3E46DA1FA260245BAD192A7B56A703EC22E51DC2F58D00EB249638571E2EEE1EF396C04E740D9F5D44AABB160EF2DAFA01B725A6B45478FB55CBBC24AF4E4AFFCB5671658DB913B43B134C68A291A4C876CBC811BC05D68B2C073DD510C26E490B9EAD5F050401D16BA7405E570D7F2DD874865492603959B578153AAB478AC4FE885E1136B62C947BB0C867C6E8B87A4D9C38C143A13C7E3B954DC1B66BF3714512F46B6E4EF9DBE4CFDF5A7927B33A9E56CCBD56591593905BCC7F4419901281B14F01D4CF56337FA90613893F12E7BA7731ED016C1FBDA14BB33E5DFB9DD2B1761733CF417320B1D568C32B7F7BA02EB59183AEBF5218C6BA3895133E7E9E80F8D25BB0B2AE72F0294CFA3181388633642E487BFD4631F2C81F2593D10127714D2493869E81AC9432AF5FE5F7F1B2C2AD813A55028238D2D6AD9A26D9FED2064214F294E0AF3514535F56F7F659B814B7ACC8D7D13EEEE5777C971D94C657F88888DD478A3565530F7DDD4232D21A4A25D5D36214D68417FBF893ECD442FF6578B0F702F1BC07349E873F4F552F48926BAC6282FCF3998161035762F38C93C4108C7BA43D85F76A78720A5D97865128", // 46 + "D58F19B469D3B8182F9EBABAB5E2B3A2E7BB74601BAA44FBB77AADD78C9366DDEA04F21AD28FB0E71DEE8A169D41116E63F56DD3ED0E07FA94DDA8ADED454980BB900E8DF6FFE21409B582F5AFD684B5DEF4A875C3ECCFD18C4B27A7A80F3FD6480BD09C738E0040C234FA1F5881C970E167000169345C587D2A7817FEF71C1CF9F173924F39F3D76107A526358FC2520A59DC753DA8AB9FE705F339888BD797B433A5070E8E33BA0C9259614B7B89D69F4F541A5BF9B2BCF4F58431FE2B0D6809804C005C43D3308A97308B3B9DF6FF1150AB170AA54261C088C2A6C6CBE26D3B7B6703E3A565B4DBA21A09813F0C19EC8F801A34ED9F91A6114D465BB7E9E04FC15A369654D15D78E125BCF43E2A16FDD875366806448F9B10C6F176F39A9B4ED27D2AF8FDCCA68DA95E96B2342902D9FA7A07F1D2C2EAEC7B91FF3BAF211FC69D8C72AEEF5BDC767E7D1DD31390110936BE0535EAA37B3B2E11EFEF3FDBDA3D90A9F264508F7B2A172D95C3C43EFBAE0A6E706361BE044A157352A719B25A26676C9161AAE58E8A452BA0561AA27DD5486702FE2031A55668CF2D92A9200D08192EAE64691268C3D50B5248A3F1714BCB3825A016F6888C2998C29F8129CA6BC2D5D5B3A9CE8425FCD39C84E5F311ABF8DFC84EF58543B3FDE633124B329F042AA398D9386631A41464E6B32527435C342004A8FD78C175031E43736F060DCF43DEC4E594F18599121BE6E817B518A93C803EAA1AA807530EB7E27492EEC49470FE4AC65833EB4EA3EE8CB64A13D1AB688FB81609F068A876669D618CA663638715B2F0ADB5D4254D797F19D87DBA270DEE72ABCD5C402C174E8F88C8D99EE327952D0CEF2B69C2D18DAAE031A42E2C7790F59A77C58879BF16A720FA334", // 47 + "E55B50808920F178FA3DDF8259E9DC69921D014969A7256D744B6B55D5D9C90027C2D748AE72962A945338C153701F3803050BE7791704A0FE0C3A2021F2B3381694C64A9344EFC9ECA98DE06233AEED3C0E5C210866B0CF6804D356122AD5B7F3348FB764850488DFA8E37F70D86DC87DE5150F408C6E8E6BE0DFC2DCB1E77F9B60548B4657A0A8B7FAB7736480FE88A72B89CF6EFA7B18DBF43E611CA7B61145179FC6293DA3B5373C82DE82EB80ED22775764259B8BF6CC5928B92A038C28D73B7C146EB4B5C2F4DB9159ED1C5F3C99D73EC91EA22DD78844AA60E8F122E6EC73D241CEAF9EB48F5937351B6BB074A43F727BB05578F37C3E962769AFAD89E237BDB06C74534AF5B3D5CCCE7B73040D589E9F9A0EC0D58A51706425A02B2637620ED29C5FB1A7A12B1BD79A0C42C60BC9FB46200C19619F57A7AE12248FED59BD529CBBAE3DBDC8DF43D8F842CA54824B5712C0698523C6F2FDD38F93026DE1E474DF762A41EF58245A70A91B6B22A6BF4EE6CF3DF6384FC2F6CC4A462D025864BEE4BA550DB1BD956E85B18954CC86075118A384BEEFDE6063CCF4BCACC57A890494A8251863FB1E464FD711DCB1C0A5CBE9456198AF4B0CD94276EEB6C96948B939439D2580F5E1F1A58304B515DF47EF808BCEBDA532C67C18D5BBF7CA071C7614681285889DDEEC096AF7C133083CA787D7F3D9A470BC81B151F3FA195680F0679EC688ACED82D41E3EAE622B5F3E11A3B892687722CBEA99A0A28C8FEC5E9FCD0723EB81A43F6F66047FD136EB294E705F25A85C95AD8470273DC3B3CFD2294B69099154236EE9588EBA15ADC0BA2059040C9C194821BC290FFDADF97F6F5DB9FAA9598A8FB378F3C442009D27A30FCBCEA4B19D19BCC17000EDEE4", // 48 + "C62BFBD744BD9DADBE977CFBD96E58F9545D823DB6888B0C6345FA6C21D5AC83FF9BEE11072679F00041517757C476A876AA62280F8976DEA8201A7372719C54511143DC6344EC05603D3E70CF0D5252D53826D84444F7C89B12A5BC439248BF7ABA05CE5575881292428C83A59787F81BCD2C95270A13AEAA518EDEBFDFE798B28FDA50AEEC08973FE3AC395074A8D33ED0B781975BBEE0DB7CB6D7EA6BECE34BAD17EB538A636C2D174E031DB6D26B4C0633010785953BD773266430B12B14D1FA3CA8D0FFE23CD135B9368A86C01ADB25D9A98317F1A1271B7EBACAB64097DD406798157623BE40DF8CF1F888E117CE8476C6A878EC5083B9821EF0A65FA51DBAFDACC3476CCB2FBF1F5B6012BC539CFAB06C5B90C7D5C3AE701A25CE859B0AEA2BD3150B530FAD35C506C3249152EABAD7772715528640A6DFA24C19C945A51C4D76B7114116A1D82FC9E06E1C72722CD2C92F11D78645737CB634C383148C3B085B6075BCEAD9E68E4636E0383C0A2DAEF63CBC8E1E93A70EBB8471D95D2663DA8BA5B4D061FCE0F144FD1BD9D94549986819ABB2FCA20AE20D6BA2D2F11F0C1CE8D2B35A9A5341E509B9276F59271907063D8056B1EF5793F2136C32D113F6B1860C9FCB6C6AAEBB7F0FF95519A0D6977F8DAFE137700C19B8006A7A3F653BD6DA45380A8A604D317CEBA9C2C265A9DBD6D18E9F8441BC03E4F9BFE04A9DB0CD818ADE8D831731A9CECF11F440886F09D3863062F4D92BE0142886765F2661E3051C5A67BD3502B28F362130E4FB6F0FBF76BADD04A4563187D926E36BC61D88AA5E68A1A127C6B21B6FDF436ACC6AB56BE5EDBC217287280696169BAF2810EEAFE785AD047DE2A6BF2C060F6FC044F5C337716953273BE8DE7FE3184", // 49 + "FE62E6E5534E3DB8EC780270A7C33FADC4A612A91B62293B2B89A78CE2196AF2D0C693EA70A2D61A81D295E87FE8A930659AF517CE6F311EE423B2F1076560CA0B4602B060114FA00C6E681DF6A9DF7DEEEB9314E1B022BD714D90BABF6A2E1262A6F01BA663CAA0CEFF977A15DD3E564A8EAB39F72F60EFAD18DEAF2B1702441F262155B3E63A19A9715A76B276A24347C0BDE72D74D0EDC29B8C1D8EE902AD3B5295DB074AAEF0995F30BAC3FE4BA8C737E5BC2B9D1FABA5E054354E47FAB7F960F3A939FAE76B4D525971D18A5DB7F50ECE91B9AB8364E5B6A3EF874B9616237724B8A473C7D50339D0BBA17D241AEA85DA941C52F79DCAFE52DE5A618A3ACBC2FDCE17C0E603F7DC10B59E05235C802D96B36C924FF54C7C0418A0E76EE77E70D75C1995C0DE224CB65833067784E872AC3340A2950DAD26F1FC373F71DA4D1DF6069C1C7F5F772393BFF6F3524530E69857E60AFFFD213DCA2614653C2064DF76234C8B5FA1303CB0CA9C35BD9EF5D88E3347720822B3C06DE44509871339133132602D4228AA960800A2F5883CD40327F9C1F823BA56ED8ECBFDF7CFE0D26A21BFE9E1C6A86298840B6E233CDA4DF652B7F0485946AF349CA0C6738DAB1DF32559D7F623D7CF74F48809B9731A918C21B1F3242F8A4812F1E558F8DEA7E68162ED00096163C8FF6317D6A19326F9606374453F61B7E7816F7DAB45A04C8C687A218425CACA9D94A185BDA4B6C9B7E30EBEAE6E992A6F5590587B06C2B73024E09C46104750B4B446A971B9568C4C29C036401CCC36A6580DDE2E0ACC4509D6D0D4E1E7E6012B01411487673DDE5A134BB226BBE1E508F0A7CE3A60BD85CD528CDC8D2D86E27A27477C83F6E1A9CAC0AB73ABD25D2CA84D26BAFE257E4" // 50 +}; + + +constexpr size_t GALILEO_E6_C_PRIMARY_CODE_STR_LENGTH = 1279; +constexpr char GALILEO_E6_C_PRIMARY_CODE[GALILEO_E6_NUMBER_OF_CODES][1280] = { + "F5A3D656F9DAC534C05240934AEDC5F175B4D4873FA63FDBB4DC03D20321BE8E78FD2B652551FE532214B660B493575402DC6F93F68B3A22B5099899A21DFC979C3A955A68B6C9EAD355009D6C30036FC1C0DA770D6C598CBC95CC2710DD627B5E10CF9764825427FE0387DF7EC6F82D04F3BA9FB420C3EE731BBB5E39D5B9A52D6E74E1B359D94F31A1A6583CDDE1D34CD5B18D2F66E21E6E969BA981631F0C9BE31C8BBB66BBBE411EE227BFE9142C851A4EFD9D8B4C42A2C2480B47B39FA395D29C329F0016CE57943AFC6E927B439441C4E2C0475AE9C09D566AED394DB059623D8C1288BE6DF72C69C6AED2AAA5F8BD4A978C4A3AB6D64FF8B0FC1AD4956E183C047041310390C0F39F6AC03E20DF72A88121182170310B7B72E742743FC49DFBC34FDFBC380375457DC4147E78BD655EB3BF5D98A4BF788A8E9AA8DA792359F2E01F6E368886D294958C8A9EF481BB6C7AB4DA9D4534881560900066DE707B81F873D6607DAA5EAC4C4EA9D1B1B81E83A69E7ECE07A3B525EFC8FC1D16B42A3BA6B215E87C1698D15E14A066045C8CE1E49E39A6F54F473F6ED2D8A9E777FC39B2321E71BA999B11BB17D5C62894C5125A687EAA0A4A30817EC1BF7D7A5EC983029218255D4558A6784619444B916238CDF511179464272309E5F1BE2AE94FE7FB2578415A1AAF7414EA7100F9D7E9C886D148D4DB3E40B8761857F30CBE06E0952EEBD9425671A34381EEF7CD9F43B35E3E8563885332CB1F0DD358EBD237D513E0EA6D878001ABCD3138270F9A84B6D223FAF18B959B1E725F85D4DA26A127217F1791E8CEA41D307F38ACC716068A1D0CB76F8337D32B9602297C0E23BA115679B219A4D5936F5A1E7A03F2AAA77464AFA89B278C63865F672784C", // 01 + "FAF0C60E496650198BFC9A6439703908202698930C129C4F44DFF72FB0BB4CD682B432B37259394EB345B0170CAA2B80CF697DD6A748F16A57443AD90414D29E2F08585261256D601F25C69A1DDB6E1E2A84CFEC805E231ABB2636806CBE410E91F77D39BE7D2E2561C14AA6255F70893888BFA75E678B64CF24A6E58D6995BCE88D52EA67D235D142A7087624D70487B09D5DECBE0394BFAB0AE4BA70C9FE89BFF26FCCD8D0967DB8F8DEE946E351D682EDE22858DC551A0D7DEA29488C68A8DC47681284A1EA38365D7DE5040453208BF0427722B8FBEF5C945DA0053F6E86AAEF688E36E839480D88119B98D724791825087E05A460DEF21C8C5606A12CA60DF8FE6CCED0894CC82200350A4EFD1CE37D9E0F638546CA829E1BB73B59B324CDF74395FC2AD2798CB6F4A3E672AF65F107BAC8F03600A6705F13C9E61E249859D1A80AF1C1DC65586FD5DE0AF497DD14530071EDEE0EC495C29160B62B536613CDE573AAEF518086C1FF759F6D140633C74431305DAB5C78ED02A62780BFF8D0DD8DBD3A8ABBA7476E0233EF37C483AF79BAA74670149E718B032E782F6A0A0A344CE0E9A74C6707218ABF4732912E1FAD286D8451DC6CB4BAB5DAF13F6BC88D784C2F768E77679C97EBAEDBB0FADC2FAFC767B6BF8EE2E75530DAB81752A318D1A20409A6701C7DECAC13E5403EF6B6BC7F08E0E71834AB6AF60F10756F75B12020681F3C5FED95DEC068CB94DDB76EE356A9755E68C98ABF565A043FB0D5D78DA229EBAB7B8A09334BC7EAA1CCE4B5B3795A0CA50DFDD8FC97E02514EF8B1116F96A0BBC99C6C746E63B229E9DD8F12B8F523E2334E63B497FD9E4559071F9FC5DAA1EDD430184902FDCCE250DC635B27CA59D6C25E97998670D26B8B5C", // 02 + "D201BEF00E5E7722A2C298AC291E48BAF9A23010418483F6E934B44AA77B2AC6A63E5431C4C511E6BC05CA757EB3B55EB419BC1CF0B9F36748B966BF628F6A93A25B5B82585919F076CB6A8CE44BDD5F45BFD61887A1D0C166E2E7E7072FCCF9153685F284864B172CD4412256CF3B3235935485D2D7AEA104F325EEEC7690E9128FA66FAE53F6F909391A1A214F59E8622D110A1B3B8F0F9353360F0D0089F004D3138EBE2242A2F1C3BDE35E11E0927835F64654C9C33EEED8EEEB95BB41E2984506F055240EF1E4BC37AA09957258269229438F1DC2AE9B8D69EDF81D363476077884F8459E0530B154E180D5649F5F0976BB57024630B3E79F75F8D771C6A228A1F54243ECAD3F6DEA7FF0E8B2C00C51A7C2C57C4E126D1DE562F78729BD48BCA7BEDB42D926655FEB9C9ED27494F116A401EEF84AC1CD5FE0A6307E31CE8A5645BE4976CD69E892E7AF47F879FB74A1C9059EC7DF6583147ECE387C98F1D113056DA88FFA69823F1EC0BA85C15D19265AE3B4E5FCC492D46FE95A41363EDF1FCE1B5587DCD85A5C38534FC7F6E8FC3792B07F18F5492A1EFE800E75ADB9FB2D95B7A0725A5DDA376D87195148958B09A8D68CD70C6A3DB6160001E9B905D9AE3F36702C31C599141271BCDCADB774161D89CDF69D357BFA138C0EF8A1DED347E94964E515AF4110FEE5FB856348981DECA930BDFE097048C1675EF70CD2AE95FFF80869465EE4368F659E7D866CFD48939E10AD23BA969BDCB06B61BFB01089C961D69656348ED5611001726AA8F60222A676C45E64985C6D7550AF9A42242E84223EA30E40833C750D6615D200BE90DB979AB964E87C28D4E20B1BD77B483CCFA8667EB25B7689F7F0D6FD360612BD9883C4B124F1614B0524EE8F4AC", // 03 + "D4FD152565117A08F0E6EFB5F7F7887F38907B4D5AE8B678EFB899CEB429AD7DF00FB703840BBEE662284FBA7C3F422D20BCF8699386DE3D1FCE841FF98E6D96729C5CAD69AF22A3D05364603D1599FF1FDC5541DB486E9B8616E4F0A6151F9DEB45CE1385822DE89422FE2E0E6045BD974469502FB22895A03162AAC82A59E8193D21CD4A56EF18593B61E26B4FA19DF965923784C11B28D58FA424A286E74E0CE74BC24B1DBB1494C5C0D0689EF695272FBCF1357E01A14751804238425390E2313CF280D93CBFEEEA9C9E4430F5201FDD172C46AA4F92F4E7CCFFD884B662D579130B5D292D4289CFA6647BFEF52850E3FBB2DCA3EC501B57012A1B1099C25BD1D751428D52EABF52E41E4BB2788AF7F588EBA91093C89B72F766709351F9BCED9A0A5C00A82CD5B8395A847828682D2AE5A99F5D4199C0DED495452BBBC53D50CEFCCB92F294A1848C16257A754DAC456F238E364B28A4C9C175FFE091E9FA4A0A48E7B6D82A4D1337A659F9AC752EA773D09D45332459B3FC322BF89E29E6D8E8ECF2F7A02671FBB69A747163799E475A048EA94359C2646B7A7FF8462BD296A4A534749977A73EABD2D9B92C19D085554E4E91875989943E7B650BD52F279DA6F16B7197FF9F9E973075E456D7BCF81C847C89A60C9F772236E0411FFC62160D283645CE3DD96D143B99F20F6F7BF5331D7167E5429E3B4DB62ECC7C301E6DA239DD400899B838A1BA42641FE3B5EAECB09C1D9190241C072FB1F7C6594CD8DA18053C8BF8179A621CF335477CE97C05A6A9F7D63D9D4D651B23318285B3B7341207686AB801BD89DDAED3BC07B0F942C2AE2970136F0A73BC69455B4D312015141D20ADA39FA0382C6783181DB4380584FBAB7C4F1ADC15E261A07C4", // 04 + "97010A672C963DBD3D3ACAC38BA53CB739375BF1E093B2E64296D0D07476A0105C9DF0204EA54D9143B17756150A377A4E1F4F2A741B4FAFDB369B44C37F917F94D19EDF8EC3E00B7BEE32517568751111AE7F435057D4B7EE6F3B4512F99938F6F13984BC23A086DD96BB4076C815AC407D693E6FB1685C6D4B7611F3010CDEF1B2CFEF6FA7744437B8D199545720B19CF7BAD6A7B12CDFF2CC45811CE4C7A35CBB5EFCD95C2CD0BF16CB69C95A46B4541E0F7FB4645075FE4BC1E64865954BFB96187C265196CAD43EC41035774460FAA570186683A33E019719B14329128C4463FD32E5FAD23CB21D6224ED9634CEB7B0A36F359D3023D1142C10BBD85885329618E92BDCB786799423F38F7C871C1FB356C5E4008F9535D9C7334DF2D3CC4BCA196451BC47EC7B6C11283156BE86E53CBC4DA262AC1D19EB20350C5541F9F76AFF6A8F8055FACB6E1DD43F53AD0895689AD69D2BF12D9A1D1FFB14C7AFFCB0B9F0529122F1D1EC0BBFC582773EB588547D68D4E494CC330065CEC477869F15985F8D60D4A5E8BE45F2D156C6353347270EF50EBD2448D2156250B6A489A8BED543561F099ADD1A205C1F32725DD2A6759C9CBA0215B06F93FF423F7C10FD684E7310BAA9BE72BFDE2C2AD9210257E1C16DF9B38D94084F31C8E3D2627C1506AC041DEA65ED6C4E04CD4533DE37E65577F54E517781669A637E6C4DC98E73EA90D70A6D78EE8807E629AB100B68F2EC8282F232E8C8905D82FCC6E592C91A3E105C3EAFA38EB73E02B37ACD8F6447CB22AC99987A8EA7CDFFB0EDA3B96C68DF4A4D3E18391C84C850161F5058802F2D32821AB7FC6B31C1013E4D1EFEB41234BDA4C84107B3FB5E7CA8D01816B8DE61C43CC1B0A9EA415DB41D4820D6890", // 05 + "9CDFD69656832C506F718657BA4A8EF7F0AEAEE78FBB829A1EF3295DC643FABCAB21344DB1DD8E1DF73CCAAF198619192AE27B42E470F1C7CF2907D1C8F6F0650F52BC4C0382530D154752BEE42D38A03D1CFAB68AE474C22C7A4F4E06F2A3FDAAE81758D66741FFF25B6AFD529CF3C3F7EF339EFFCD3A3499DF2C85BA742FA348D449C167F7BF00688B11C453EE783623CC84762AC049D9929D470037709C71BD6FE5019C9432C8A7CCB29904C1846586D7639B09DEC1F1949AE5C6D14CE92118750B0FDDCDCE61DBA6B1EBD92CA5B8DD456EB39936FFE010B741A22DFF11DC489FB7EA415FB265BD2A7361E7A428E47DE546103CC8E4E7F83A6936BCB1F9074E692D4C2E1BCE4C6065D407764469A5B8BFDFDC43EF9CB843952B5141208147C47ECFD4B5CC6953E77E0B3498A9E4930B9679AFA3227529BE29A8794AC7A23F6E8D5391B81FCB07F3E3082DAFB331B5A9A6C5C203BF1402458834D4B12F69F0A3EA8EEA9357A4D2A7FF90BD7ECEB441BAB3F5F6F150B542EC9DC17028E5C13BCEBAFCB5D2089D8F207CE493F65917022BF08E1D55A299F420D651610A0A7690A2511BC48ADA6F68077AB928A3017A01FDE99DE9ED02BDD06EB9C06002580554C378D701C68FC56F1C47362CF5113D4D728CE9B9E4C77B086258862CE67992B34B8810A4766B4F26BE34CE35A356E81A828735022E70F3ACE90CD175AEAE9479BCA317B8B629DDB6C7D29A13A0155E57332460466C6CEA034F79EA1A8345850CDEB29AA02701A5FA6ED331EB96A9DCFE3DABD9009A5DF5D4ADC8535675B073060FA15A424EE7375D039C923C9448DAEBAB6A881DEA6567FEDB235ECFA49406853F61F41D1906CFF5C343952F8E914124586260907914E4C5E4225026034E030", // 06 + "EE21FF4082E6C3345ACE456EC43F40B2A12AE15C5BE1BE2807FB93BF95FC0D5462806256A03C1ECCDEFB0686372D1E8CF83FD93BC5A4ABD65171A643C1E2EB2341BDBF5F1920659C23AEE9641712E4BD7AFDAD4E3E95CF019CE0F43E87B4838DE690D51B9A1CBEE45C8B8BCD5757EBB51A11A13652A4164269150A901F48232753F9C633D459ABA310880BC30008F5B8FEEC0924BD2677EA5C3073959497B2F9FA34F9A93D2EF528DA3B43E96DF37CE5C0D11C207699246CDC6D0F18DE1C643FD4FC60D80C6E7415920A75E5358B64B7D61327C6BB90918788876CED884C8706EE4D7DE11E582DAA279C29DC0E828A2ECE0FBD336B63A4DBC07F730500F6D061D76DE34796347839A13A286635F44A05C340B3B20D3FBBC48EE3B2E156BED9F93E0AD92C24205A72E16FB425AE3D8508D4D8EAD20080CB028B4000FCA39FCC13B0630BE3A4E2B2AFD3622CA1D98D85BC88EF298B4A5DEAE3992D0BB1630A10D77165E0FDD2E757E7573DE3A17F3AF85C7B45CADCFF9A6D1909737934294485D54A62E8C681E64093562EF0CECA523784E1A46345EDF26CF69C895F5E5D3A1195F00D1DEDE55105666BA9819B7A20559CF624D5A0B167A8D63AB21A6330F89266A43DC90F712719302BD5E32AEDC6F0A206948B9E37E4B2ACA3542EE36ABD483B0100921531F2F9A6CD3135B250187C49628D52FE26F13F2A1C4E5ACC734C097D8692DF4F89144C32E767AA5BBECFA3CD0E49A24F0470E482AEBAAAF5FB33B77B52C1579B07A9D22952989AB1CF9167CE3552F6506C657E93FBA9A9E9C30F9AB133A01C8CB57702B8DA67D64D6BE89ECB7D75C431AFE28C02FF57FAC04962C412718CF9B8740F42BBF580A7DCF37E72CF43C3958D5FF7756EE1930A9EFC7A91C", // 07 + "D57C59619A6F398CC2A89D6D54F9E1F846BA7A7DECEA83A16101DB4BB31DBAB22CA53DFBBD32AAD3FCCC964A4C441B6DCCE145BCB7A59DE9AA123E8D590AF9999C7414333ADC7BF0C2CC3E3461232AA7C4633B626BDB6EE99EADCCBAF3D2F7D51963A604C0E234B5B1F19F56301C2465974324FE52F22355B7E8229DEC199012AAEBDCDE7DA613C04C3D978D615C07214E2051A3D8BE099AEA258AA7765FBD3E9BAB6F572B81F0A6FAE0C2BAF0672052ED8B23AE3EC694689968E3E6F1055BD70BB78E82503A3CE3DB48EA5E47D229E976B0C48CA00C771E377F02CC08342EEFAB06F7F723B910B14C6793925C11938756A787AB0A05E123CEDDDA545D70FAB1E78CB4053AE3ECD76B2021F5F9105DB0B3CCFDF0A9C2F4E5486749126BD7C4DE65015F84702AF1D8A4213E5C7D487379C54FE1E1224C2D576217ACD1490C3E0FC84B24A2008C55F335137195A9AF9CBA46F695EE5F97C7C3B2165B561920C1C488D9B165D1FB726B63758B61B8E93F3E5BE097110612DC469FEAFE150E67F594F2B8D5BC1EB4AA67F733FE309D96A14C0716454DE41D2BD8668EA6EC1084A3C2A0C761D78DFFBE70884D7D24FE54AE8DAA71C857350395CF11AC3FA4BB6AB84F58F9856D0802633DFE2C67840AB432E00F42007EA46AFC0CB22AF6C97241F8B7D05081AA67CA16B1217D467CC0076C8EFE15B9EAC83E08D65E13A6686E8F72988343411432DEB668A82FDC4B73E2C1CC9086CE058EBEE5726C956B927D45FB646CE54ACAF821E993BD33132FC549C620346B234AD0D86422BC0D833F8C866279F12EE7DFD6C6F7E2B088EC6711C2CEF6EA64FD758345040D55ECB443BACBFD8AF4F23A1703A8B1B633DC5176602D1A842574AD30A64403C37C03BDD026C6354", // 08 + "829CC3C571A2BB19164855795A60833734868AFF6C47F933119B4192048D740AF94292F56B22B263CAC2047455A46790417D2F8170D05C57C62CB0B06209B6A12405F7FE9146EC142A4AB8F7C73768A4E98469B897FA1BADFE07763EB95675EA202BE998BF04B68BC6E3ABC749F60AA44DB45C609CEFAA011E3748FA1FEBD9A5894C2CD399C644D09ED9E25E8E574056EADC96708887685ABAA4CE56B27D7C7CCEA8B28C4D8A9EF758A502747717C7E14B8E4DEE68E2B545F9BB5BB94E53AE779638EC429BE9A9396666BA6961711DF385FB4E1E9106BEE3F80AEF8F5392FB31E74E16168FCE9E1969724576B522A4C91620A44F0EFFBE0567E87C7B681791D4E510068AB39D7F16D5E78E17EE00C14F722E4CD48AD67EE110401C70BC2F9F0F61DD1BC91EF6741B3A77745EDBD43BEC8036B6E4A2CFB84A5C191EC35B561044FDE81A7ECEFC4E646AEDEB9F10E5303E2A25AEDD8F10B938E8F580F766F73E2A53C04D7C82DAD64BEAF4D8EE3A47943A6CE5A2A3185D5ABF2C4B829D02F86FD976895056869B28B36851337F36412950D8CD6F957EF06F7BCA0633EAA13ECC7E600E2A1B3C42067F241D79B07D045A3E38990F6B6E0A44F2C10834ED3A2DE1D172534C31E8ACC6F66D9545906AACD083D8EA0CC30F535883D6B110B1CFEDBED33F77D84F698446C92124ADF0B633B62E4BEA52085AF0AC40CF80803A0459E345E6BE44D925F7CFBF10D7EEC73816C8D87380D93FBE77C300B2368C2AC45BFE5004AC6ABB4E68377661112EF717EF4F34B6BCC382D0F08F75799B2451038491A339A0C5675DCDFA5044B89EB4A423F1DA084AFCFC32D9DFA6B7808C6A1D108A33B8FBAB59A758E4E6CAF334971413066908B0B1D56A373515E8F1EC066E8AB30", // 09 + "B2413CBEEE7A6CFB5400DF607C16E694DBFF1375B9AB755724EEB0759221F2CD261F15C2BD0D3C8802B5B035B4E9F7976E3A31286A9789549297DA272693746E2C3841413F328103F0A0ACF1AB94F0E35626F11B5D8934BE54DB9524F9B5F9BD4EBB46BC8350E78E4C05885F7DF3CED09369D44D4025DA9A861274A10AA161D4A69701F03F1684716C6EA79FB19CAB0DA4368FCE3BD177D2B9EB4C9729F615AEFE232B7875F2D1125DB8838292CAD93F45303AE0C8BFEBD050F32A9F2BD11DAB69D84976441ACCB6311DF1AB09143926D138B336B839DDD4FA5FFA32F1EE65F6F7D2FFA363815C288FBF708F9D81BF4AEE2ADE4FFA5F2FA647EC8DF62D564C41A5EB04BFAB5803EFE44B041B7A2798AD9EF34204F573D855136442B5AB6A85A402399C1E4EBC0D111C9C3A374FEAE92B6ECD22BF21A47F7096C48846FE685236171B4B19C80778F4236B68078C7CC3CDFEF7CBA3A7681593F0FADA37D709598AB0A6DEE2DFB0ED8894D2457CC268D1C4D78E7FA50B99B19AD4FF7B97916A87B39C2694C24C360EEA1BB0920C2DE28AF4D76816E63690E38BE3E85BC6047F8AC762D85C0D641F15497E4615B295E314971EC646136D0418B42D0F326FB798407077627FAD436271917398FBE18FA095BFC7521E361683E464D2EABB64D5C3BA927B9A10143F16C528C91E50C625807717648B7396EC6BBD820FAC6C3C30A517CA3A730A4C7C38E9280A27C6496723DAC59D8F02D0519C306069FF2F1F6871892613C11A2C7001EB913341B6F0A9B2049CF8F156B695C10ABBCBC495277755FC3E824AD7B947F00BECC85AE53C5C51CA507046B78E674D7CBB206C09183EF03DA63D911484B790A5128724557EF769B76167C8126D4332F1C42EC16457EDD1108", // 10 + "F4C4B94772CE84695325CB3AEEF60BD564147ADEE80541C6FE5F5653DFF51AA05764D6FAC7B59B0F365EFAFA4CFA15EAE9AC57D932F9B73EB90328F617C9588A8CFBC92805A025EF87E92CB06A1DDAFD24097395C4D45189D229B793A241C80E4124F9890E018647BEEFA9534B5118DBEAF65432D66D2A0A9755F5EEDE57D79930B5112F4823632F375A9B085AFE938D862139EE3034DF716AAB84D9A233F82561426D33BF33D70C3181477B24BA1C3E70622817414C58BD09C0CFEE8244677F2B3F5F12BDD5CA3300CFCF23F854FE08BA140C22E17200D17C4C368C8CCDA00B14B289F91F75F7D44D3F556C26E8C5476340DF71B01A993038DB68AD589ABA593CEF78852B567AE047B1776386200978E45FB6E7380ACCE6575A12EDC02A6F4070B570B40CD05E3A16949A681771A349E66B6C8BAE36C24C4203662E1C1C0C4744800D4E0A4356CA79DD087839D35B3B1684B1FC6748526854CAD7EEE517D54B4D682C6951C74ACB98E8E63ACB1567585EDE5D056A1C375BC82BA4D9E3D1773326A54617B0790E1C687C15BFCCE14C8E7A84670C01FE0D7CC7DFEA604A644EC552D72AF3BB3FD3F4CFD4A94235E132165D14B1F412331FF47D6F7AE5B2A429237B0D9C2085EFC7C0EBE3B799D78621D8DA4CC7A67966DCEDD37AD84F9E44A775A1F1CF610077CA993E13117A2B0868A677E4E677F548658D9AA5CFE91221CCC623B08F65F0C7301A0877E97DC3A6928D8B8E69E702703ED7A760DD68AE184BAF4E9F50AFC38138E52FBE5B57BFDB56230C4F4D14FDF6A95353E26A1366FFA0B41AA3703770D46D73B3EB27603C35E36C008497C3CB01D8398E5823370D18F40B3BAC32ADED06819135A8BD06755963BC7793E6E0AE0AF9B60FB948E5103302C", // 11 + "D174C617556835B19D5960E23554CC72DB74D0EA1FAFDF8C0246CFAE6A8FFEE862CB9A5281EB8F140068D986D133AB150D40326E2FA87BB3450DAE0804A4C62C9492F5F96553D9D43A1FAE170114E9C445302E9D21CFB0C19A9EA0853A79C49379B40265A44E0817212CF5249016A27C4C2CCE7EBA1CA9FD688F46C7529ED9706420A3ECCD1B5057606BEB0470A3C763786BD7771CE7B9FDB5C87C4BA27A3A7E165036EFB7F0695CE4365591206E27EE874282043D513A920B9438A1F05DC14DF93C5D49D07CD44873807CC57D75382A2C5E6BE193743B317D5B491B9984EC215EAE7B9426FB4F64D1C6B880635EB5067479C9AA351E8D6CBC98A65A12238EE347173BFCC47E6D8ED753F4E6942DCF60A0A4C15C21DA17A13FF00A50E175F7935EB113A088E4EF86E87FA710DA93B99E99ACFD01FEFB68F4154AF85CDF7C66D20355F9ECD51969CFD33ABCE58C1B1807E04212C4D0209ED76AEF40116F861BB558585E144C9351451CAE605D0B765C87B5AD51A7A430A0B8D5D05E58976137D5102CF771544AF66201B253D4665F1FF69E775A18D4AB7F24A000A3E2375694B03CF933FECAF701949BAB66B51606569ECC2EC4F24D2720E5E1DEB68C37594A1256EE376EACE92A5B728B9A0D1EF50530FDE76D94DCF0F83D393E3318FEFE11E03523BBBFDFCF4560E43B583139DD17269E87EAF57ACA8E202C51FD39DA50E6095DC0F73332CED270DCC09F1A6D1613FCEBA8E94AEDA15C36C1C4EE6473C6688FFCDAB46F1E2657F310845288213398EEF25C72E6FB17E9A375471B96D5137E1246D035B6681371BE08FAACED63444E9E36BCFF48A69DA97FCAD19F4A575C8C8FC7FC4ACCA11DF09479BB8488CC91B9E1B0C1D458A6B30C8369823703C20E29C", // 12 + "C5DF1B2B634C4D2A78DF4B5B3E241E680D6C23AC76D6628F4F54640BF4A02635EA662C21CDA94970DBBCEC6FECB55991D712F0CE85F1A51211302CC584E53528B385CF87244ABCDF469B5904163D13053885C3FA5362CEC766EF045E712039DCEA042C05D4DE5F40D96F3A5692EA710E4BBF55E343F0F91DAA8B57089637F5660E0A7CF3A972FA5EC1F3CB709DC466C9C6947F625B176F7001C1F8023813593C71E8525538BE92985638E24F65A76CBA45EA7A4A91A649D0125BA25F1CD3298FFD529720206788AFEA0069BA99F2C99E5936870528E6050B3AA4EC63D152406EF5FA6B003E87FB513FBE49046ACFBEE51B976D19E4155979FC8444A215799774A2EA254931F21B2BA11226C589029898B057EE6AB6FE46D835A70382581AD5CBF10BE6BFFFD3E7422D92688D6B21A1A4F0E6EA33B75DC807ED6A81A1C680A0D404B336CDA9C25842B529D52CD7C47199CCE9025A3376E7A862920435CA63A9A15F2205BA89F1058E11967546585FFFB6D447E608DDA0714AB130066C6802AA85749F8F04D56266DFB1CA1C14861E789F1CD517C849F6FBABCC77775501C4E0E90EE3BF6DA51F6B601040CE72D9F5C5777B2A98F361E587373E3E6512D754084CFE93AC63350FBE72C7C0BDE2C6F172F9D37035F7547830362461FAA94EE864FF63AF608670F75D835F34713D23724C538514B5CBEE32F5FA38B5ECDBDC38AA3D76F98E8B198BFE357FBAA9AA4F4EE2B2A6A6359B433C561FA6085F31C68A597FF3F0264ECFC0F60DA727613EC063B7C3CA6E6B44DE29BF78101BCAB6678E1E3EC1F1644768EE11BAA498055AFE6506F371328375D7BC6499E680402E254FBF35F13DA867316A310A67A99CE1555BE880EDFB23F611697268C2A7FE22AA8C484", // 13 + "99553082DACAED3A9D8C2BC62B100A611FB9290BC70B80CCCB3848DB085EB673D54A3DA91D7AFF3F32104D82C048265976A4D434643895328DEB977B0B443C5B707874FEEC0F4F2FCBC9D11B1533D69F76BB0E0385B2D2C63B6353A7A0C3AFDCF53FE9E114376740BBADC000EE5D91AA0374537D89C52EC1509AE47872F21288FA9730A287C6DBECEDC910C04488789093830392E19BD364B128FFBD247246E12325C1205429E6742D0BBD7215CE420145F72CB29BD25A906F5C798FD4A575E775CB7D3404B7C210B0D52E035841031AA8DC341D8F51CF76B5A0BAB7A2AD485B2A35E024E257E438E7D9C778D3D6C4776ADE71500581D748F8678682A06A92AF0BF58EDB721827B4E53CDEFCA2CC17D891C5CF70423A2351D30D68249275A12704BE1198569C06A9A80942525F559AA1DA963F488EF43BC365F9851E4BBCAB743BDAD827B7D0651C832B1E46CB6A3F86E2776C28B91384519BDE28B718BD4C573214D1A5C612AF7766CF76C62B6614F31F86843019A87EBA5F1DFB1E9F75EBD93C70211C627030BE342C81D872AA48B819F722D401413FAD3249D21409E6C75CE3675AF1A58AFD6B6AE89E187E7F996AB04C8806FBD228A1BFAF3C968F25DC38F5D5A6BE70E964903CA9E26490493B2CDBCFA21EBC06EB4D091F68882D5921CF462B633DED2B22F6C0B5BA32BFDE473AAED4438EAB2702A96D9521F2E0F77E6A0572245FA7C1CE8D208C80631065B742CB54A960CF26FBC39BE71A3DD8F6917B5E42FE004B240DFF1FBD3BEFDE62D365CD90DE8C8E6EA10C0BB41B2F0E5E727298A51E6E17D7C1DB21463D7A7EE7D31F6D6FACF6EF6BE5DE2923196102A1E333E66650AC7560BA7BF414FE8DBB6BCBB7D9984FB2BBC67CFE3E429FB0D89B118", // 14 + "CB1C401CAF1A29C20B51EABD89D7D33CC1A87FBDC6585DBE08DB43E71263AB39FABDD384A4A3FC2123F946DD41DC71A001DA565B7461298E3F7563D8ECE03A88E32E0B160F7FEB1BE3478EC8A5678EDD796C4189653F60B54E1C6883E8C8AA39E25339C7ABBEF64F44F0A101F1C0B2B2C9C9D8AAA52D6138A97474C6846B748A00C79F0E6B3199606C96F27EF514D0DF0A7E19FAB0FB6F2BC285E29BACD339DBC0DB249D7793583CCD255438D6D6AB60F4391E5B00432DA0937DA4E0C5CB78F2B3260C9E1FD4EDFBCE8B58AD830317492CD55482600E960AB8A817E273D140B98AD2A65D2FA49CEA1999ECCED6C959D9F1E2AF88AA2DDD5CBF23F24E97EDEF4848AE69C8776EA1D26005D19A8D5775DEFAD8D05D7264290EE8801DA11CD339BDD19226920DFFFFDBCC9AEA51DD307C46727239EA1A75308DB6BD265C3385F86F8A508BFFF721EC83EE506410DF67A455C394F53CD878E2910DE68FDCCFDDFDD1BACA4C9B0F88F0B48069053449DBA65C88D19F8933071BB92BE7A8C90B5C3A273543B037D7376B2E62DFA057255696694E3BC4841D7F604D1AD887977EFD806C352A8521C87A170E8B6A34B51B1BD7A8D100AF8B28B0688F236F3E46CC938DF606F971B0220D1B2DC5732FD84B4653917DFBF880EB6FC4CB5DDD96783513EBF955D6532097DCE92A6B1EF5FFC14122C3487B250D4760160DDA65BA139AA6C6B4F153A563215C075A6FF3F69F91D20B19859810D090B80485F8A6AA7AEEA92A2821D28D30642E9AA09C77974EDED3C5ADE25381C17ECF1B083E282FEF30C861506B7570A4898A1BEE9FADB6BD8E5A1D764C240065B1E20E12C880A28F40C70360228F609F23752BEE6FA3131568A48EBE3D3A233D49E038FF9AB4D38F5F1C844", // 15 + "B84F109CD8389BCECD75039A199EC00D2EEE6B25AC377D745F53A5D1ED39FB1FCB4F6F4AFD777C4A25796488CF472B401D92CC72EA350ABD40F97299A4FF136DE0279E565B4A12EEDA837FF7B19CA1E93F5A99165882444D13C93CA89E6755379E20F625C6100E4F49EE42B3C0CB42DFF0DD0D48B1A493BBB70E63212CCFAA9AFE196E2439B464D8CBA5C2369DA2EF5CC516828ABA5D913D8073C642A30766A58EA9D2EAE0FDBDB7F53EB787BCDD5A2D39A0CEBE2CE48213784BB8AFA96649F0464449FEF244134754AF150891B2FA6A91F46B6AA89CCA338BF0A24C7D32C9E65249501FF157C3882B6901A92BF0F8404083D4E303BA691B8C459A20C28C20E3EF52E9B723E3E66C83C517B3546568C18348B0F6A6280FF96BBE767D26E885122B1969017FFDCCDCCBF9FEAB42704356A90D45E51F4B831BDBF9876D3DCD4E9ED85518DFE2770ABEE9052558399FC54CFA8F681468D7E6BC6C86B041A0A076D99C8EE9419E2FE6441B260D4D1ED17283B12B62A383876A4661713552D56FD894FC6E7DC47BF5186784DF1F94C777B5172767EE0BE98178334F3CBF35BBF356095BB1E0362D5CB4E98A90DBB9388C5799923F13BAB7463F8056E2721BD1FFF68C157383D2BFC00DA77BCDB0B551BAD8A7F775DA4318A27D64BD8B83F425D24187DD0175106997F55DBC63829CD1E735CEE21FB56F5017C305C359339C487B0FB7E58EF0824F685AB6690089EBF304927A4A18601DD705C44FA55A42BC89E12DED3EA505067AC0F8ACB14BA9607890915B4837B1F9FB41C14F2016E6049DE6F6245ECED1A3578689CF8EF22096042B489D7C5893C65C0D2AC101FA9057519B72562E724C2A3A30389E743D3F45272064B06C13D8B1316A5A660326F34156B8640", // 16 + "B42ADA523F329F9CCA7199214DF87AC1C50118F7E238FBE3CE9CE8095F79E78481602141FBC3B87CB1F7C68DE22D74F44F4FB30B2EE48F218117512185B3F1F4AD2EAFAC164E64B5DB1DD1D0425A6331638413ABAA7940CCA0E6C0C509B0379CC8A36DDE4AE0D9A66A2475C4E9013E62DDA0A923FD6427F119E9F82E3E75C580865D23F04D99D2BBBA095898BE9D923E68E80C44AA0BDF2539B2F55ADD4B7C9279501AB2AF650DB22494441C5C5CFF830E0AEC172971C5DF3920F49B20A73E4CB5427217DBF6165A09A038F22D958015586E4AF9A9ED33BFF2F9C72064BA8F9AFC92F4DB4480348C578D6D66127C3FA9C1BEBC210129F18997467E69F3FF46757B0820D29F714D5E58E67D0349F1E8936EBA52A9B1617AEA064B1CDACA91276C51858FB8789E9E33F8587A732B20A5FDA984A6BDB86EF7A8E26D56380555E8CDFEFC5264962B4E31DA645EDB62DED96EED3E495A723DE2F80DDEE78A8A751DC24CF05BDAB15A0E4FD50B311E45EACDA6D1DD4A2FE6367AFC07BB54D5C040A16EE0B892CAC0CB791EB9CF7CEAD2483B9EDC0E91E2F995730318B226D663A699BCBD9AEACE57B842D4890D9D681429A8D382A21617242E3871B39D15681BA8838BAFF11974DA1ACAACEA54549800B6CD1784BFD9445439D7F1A832D4278688E77DD7E3E2A51DA7BEE25BCC3DCDABE90A3E06D6EC0D57F0EBE3C98E751567FF5E1E0E9C3D74DA40E49855A0AA4D35D63F5BB1901924F887C563951D144B75B776E623EC065B77781AB02258751A06624C5C803C6BE60EF6F60D24103AA100CFBE7124CCFAC338421D614AD80BAE9CB2F80E110E4049085B681DCA3E44C8561E548916F0BE26B967E127F4CE9BCC23BB437BC85FD060BDAACB1E8F2EE47AD3AFF48", // 17 + "CA255F7D7F404B16F1FC7911712420551AB5B3F9F79BDB69C726E5BE2D99D6BCEDF3BF5319D2692BA47D5D043020EA4D53D9B0094D4B9B75EBF1534C19399B7F4B61482B63D8CE0BD3EB523917820D7655808F39D1D33853CCD1DA81E21757969D497A8047A2D7B1E90B969E13CBCFFFA528619B83698A1E9A61801E430C25B18D3972E103647872C252EF4595DAA9F9D2DF35B2491E3F8CDCE6E2EA7D0610D09E4D5474CBB17C12F0BFC424424B448CF30A14C80886E9797161C716B7AF3F72B02CCB7E90C46EE4BEE1F85B2E9C950B969DAB140F3728882D5A162932F9D41ECFDBA16F652AFFD0CF6CF6891A718E9FC2FE0B5AFE92106985B42CF866EDFFF26A151252BF5CE381DDF779FFFC75E7813898497DBDDA3555D16809086A0505F831ED47932120AFDA68D423FF4F62CDDF66FB84BCFF787658C103379DA2F2DBF2868D8C436E3D555D148B6C51BD4E361E8FEC932CE4C4D204FF8F10F936D1C780D7603C1824C222323E5D32ADACBE5510D6E2AED520CD11560AEB9367D22D9F29506D407078B9D8AF8BEF3087DA339C9BE1412F3C16B13181BDD8397D6C84791E9C3CE73AD5A2279515558B7FF69A0CEAD79D3890DE3F4D89ADEE6A646E01D0B5E45924E08F706642C6EE18A3DEB0044325BEC020B12EBA41EA540976962ACA2ED628E917E7F18596BE3CCC457DCA41A35A0499423C643BC3C95AB34C4AA2BF65E74079DEA2681169DC3DE6DED27E7596C26ED0614EB77ABF2219CD294C9C4E80E09914038381D2F8B6D472B770A0FC860A0CFA2B27663A41E6354C45AF8DC90E5D12F28AD91A3200A626CFD837C563FBFDC4C7B0A8319E8C4A4975CE54DF744412BDA3B1D54C44E59D24CF83EBBC9B6990A4B23A62729500250184E63E1E084", // 18 + "CC8AE040A281117C472F7C87E108B575563E252A5C96064F9242F3D7F2544FE06DBAA94F6847BFB89B3B6AA1C57F5A4E59FEDA18E365A24F4D1E3A8776768742BCA81BF7FA596A7BE09CB190CF9FC5D937FBA0CBB6721B3A8B6860AC3EB8B39A19365DEA72D22DB146563106544DD6E4EB47C7DC740F2AB1C5E44660D41ABB467237FDAA712BB7EBDEF277161178483A3E1D73C68E42113070E92F36C82BE02048B61735CA16AC302A0821BEB0EB4A4C84E425C61A549556744942CB0F6055ECCD3D4D56B1610BA7C6621C75AC449AC02DE04A2770E792B65D004AA9CD8DFA96F12C5BDA8782FB08EC0E3EA957CFBFFF321D7C57F8DD78E4D561279AC33E522E33F517FEB5BB78ECC3BDC325E8C5FD75BB4A7362D9D995AD4B86D3DE969D06A04FD9C99F3DC4DA88BA4F99D1C37C2E96A302CADC30A7DE63FC1F75853D2E8B15E2396CD6617EA77129D4D63EA1D103DB0604B0153F234B48EC4F74E312D1582CD88CF6CCB958730106667A490DA0530BB66D909FD1DFC725D104ECD8FE1E8C3F0ECD9553E3553AF1597CC690BFD9E83690EE7DEB4EDB606961A1E53716075564251A689549AB1CF26C14375AFC82510F8E5EC467EFDA137D77A3640C87099EEE6052C89356C1A7F3AA2274F6A7E6485C5EF1E91EFF12202DF45C6F3CE0E7C0424C141FC946B451A472A9139AE0E9EA500FF86F3CBC9EB021706038E708A32CCB11412B421045737958216135D63B7364181F4CAD70B7C15D4D0DD8DB5BF2E20A5F103855A7AB306F26055C658308036A72637696987E5D84B8664CF552FE3CDFB86421015170CE7F802DA7CCF8921096536C3DA9237ECAE43A16A736DE01C595259AA18B858ED081183C726B63FC2D1372FEDAD7F48C1950FE1CFC155E610E4", // 19 + "B7BEC67BE0FF68762DBA40B31D235BB9361A98F8D2D538FCEF76E5286EEB677CDAAFCDEA268F0DB6849E140A4B0FECAFD4D02927EB095437C2C6C2B2C065FCCC14CB580A647CF53CDDF09FA0DE8E5DB1CD030EBD0CEEBFA2FE7766490D0C61A690DF9AAE4961268E51B8E5E35F837EF080BB64B21DF3039B36A621432848408E3F7C066463352C21FFB88D06A2F87966FB35B65A8D91BF29E70B3FD78F67C0998BE8622ED3C48EE6112FE4D6D176B448D5D55C52B60A1FA6134CABA76E5FBC74CF4BCE85A1B6DE091899B7DDAC66B353DE01507A6B6F69E75A20071E94FD57F0C26D48EF70F23C735AD1C21CB122709E79BF875E150CD269459EB5B8D328EDF5C342615965ADEF6DDED2AC583B1F7834207949EAAFED3E1C2CA2BE46F572DD6A344531B90634299A09929C602A3012ECB0738D36B9BFE3625DEF26E11F3BF179A4ADD324AF1EA860993D4DE881A4D32B209432DDD7D083AE4A37E3FE25FB7CD5D47430311D37B93D7F3191C91ADC0534E670F5871163C09ABDCD72C0B8BB8697C62EE9BCD3169066A97FBEDAE909327A35400FA12F8915E546C1830898EC814D5BCC459B24DF4A0C4E40AF4058241DB9897A3E62142E92A1FB1FBF40B22ACE4A1F14B745411443B5A092AEE0539374F6DDF2FFF84DE33D76295EF6E2E8DDE639435821BCCFC21B39E034EB0152C2B5C5C3AF8F073A4A83556F6492B07C86534175BEDE22F414A4E77012A46A789822A096246AA08880E43839FF5D4274784FF3E6240261A201CFA69A4385944C4E513615BECB948703BA86E36570C45499043A457256B0973268BDF6747F584F7687056FD7C8060F2DA08440582B6325AA1DEC779381A157D267C163AEDC81194D415B2B041EB151D7EE95286D3F53FB86130", // 20 + "F4CF29F794CC9CB418F513F3E8D26E34073939CAC74CE0C82C99505DE0BEF392D3F95BF7176EADE5D31CFFD43977FB31FDC78884447A3B7344DA50CB56B5B47A0BA9791E27A45F41B474C99F801996FE467FF80CCA7AC3B1B0BE33265BF82C4E88D2F660BB92BD75E34786977CEF8E8DA9571FB56515ECC6D3240DB2A6FDFDACB2A273AD4BDFF88BE8509AFCEF967B9B57CFEB115B06B0455A6AECC65C588964261104103EB7A78A5CE531AF579F8CA534898348042B14A238C6FDDAF2E5F16FF9B204525CAD0C3DDAA4B8A890D3BB97B963A0C9F8134DFA0A21B6195857663748BA48C2DBD2AD7E4DC0841E875B36892BF76144A5C242E969C19421C18D8EC219AF206A1349AA822E49EADB1E97DCF852C3B3A99AE10A5F090B0976E9DAF4F72C1D43C72F39AD76715F80CB472B2A15B7FE555C74D6B808C1648D7377BE0E218A3DB00B405D6396088DD457F472209E387BB9F8C2A941E0C1C9A28D8B3FA18AE509ABC9F9F2F727C5698EE520AFBD786BF6668A5A8AEA9F359E27AD48CEE9FAE878F2C91C43CBB927B761BBFB1CA183DA0A2987086140C8241F5D37A7B304B3677E5D7287CD7C8AA4DBB0DF85AB25EBA59191E5CD22B06E389B20002B848BB6BC02CDF28EF6C985D048FE3BC8800269252380093385BAD2801701C9D4436F35A1627277A6ABDD995E85A4620A940E3F9E56612BF25F3A0E17B5AF7491F475133667643A71F72D75613C1546CA722209114545C8DBE2CC0A1A57098F66BA20B3E674521F417D0716EB46561561E5C3CBFFCBC94A7DCBD15B140ACE1DF47FAE637930FB665520A20323DF7C5812B9308F9C3106CE1F84B814433A54F50243CAC9A3D24B29CBD981191285E4ED9E238F91B04600B79B2DF3B6AE4188F7E60302C", // 21 + "D84B4EA8E6A13C1596B2F0266D32A11425C0BAEDF57E1DD9C081F51A3F5043C49FB2D4DD581C8F76DCE746C2D4264371D459C60A0129805C7011CC57B05ABBB743F2EC90F7278A101C4479D176EBA5D711E32189B11F6CC2419A2BF659FAD2CCD7D67B51566C623979A2EB8075F5E8EFC6E3A18CD9E705E73B948B1551AFC0AE902353A1510E7F3B0A4A446EE19F61DE93AAFC0553027BE4EBA7C1D9C76575F4BCE1B3C8620FB781CCE777E587E587F26CB62E386EDAE3B0AB6CAE5BC4F7149F183AB73AEEE44ADB412AF2849AF8C9D4AD6E9851D99FC206DBC2989FB554072810705B57D3C7529BB38D5006D24014234D285C44F8246262C4FE9AC4FCB3FAFEC40DC751BCC9AC3CE82E3C3A69346D08B7AA3902317582E9626402AB299D9DA83D3D43EF13FDB6D5343C36F04B9250E98481FBDB766B81D0B669CBADD4CED277D69E15A6B1ADC24EFE4F47633FAC44F80D53B85BFFBA2301A647C753480AE5150ADF6854E79ED71CEA29A87D58E39B220AC5840F1BC0187635673980989FD08F3EED4538B6ECBBAD4768EF211C9F5176D013545F5013DC9CC786FCF32D4DAE3C0D299940A9A3109708277E7F0A93A796D3FD907811DB4E824605600BB21BB1A53A395F7732947BB0C68506E830819F84245A162151461DD9E2D6C37278413FDD70B0442179B9CF44A7C360AF5A905E6B911AC253B0A3F9CE36794CCFCD4A3306CB0AC11A8B0BE9320C1F5ABF1F8A9FA7221ED0FE3FC69CEBA0D93EBBD7B3BC663C3EDD30A0290F26A369E8ECD629EEAE51F93F43595B833294427FA0F5D14A4D8E7B116C6B66C0A76612B09A4868A82866E8FD4C760EEF4146DFB2A235DEA57B39877E84186A0EC98A095E977BD2D2C444BDDA867208924B8EDC5D7D7A65A54", // 22 + "8642BB604191809623A899925E0E67D0521EFC720D4C80211E7B7BB4ACF2CE3644922F59A26CB2584CB354BFC2AB83790C172FC1F2E8A01470DF0E0542422EBE3512D1A6526A439514AD7B865168795C99D75D535DFBCE2CB4E83AC2BE79D15844C4514A505CA52324FBAD0A547C1640683B50EBADC87B051E6A888899945BC15311F80DB36F4E3FDDABA13112F1DB5DF6D5BD66C8C7C4384B86579DB85940EC9BD4037B3BD4D40D536489A6107A22E8D22F9883B423FD842D209FAB39616EBACA54F0056F00FCDBB2CB0906E593EF65BFE954CCABC460ED34F77B9A8D22F09BD3859F9439575CC08866FB87A6196C506B97FCBD1EFFB5085EA4BC0C5C540E331CA7B498206604E3D1497BFC2996DC8A759A87FC6EC739B783827F8B5DC0FAA7968317B5C8D176541D64C99A3274E45F80ECBEA25CFB79F0BE881D8A1705406765202F13C6E7A2F2D7CA991D3C85DC599F8CE853AFD50CCCB5C1A4649C29CC31A9EBE57B2C346161733C23598277151E91EEF57B51438E96B827B44F1CEE1AC2EA1AFAF2A011DAF21D76A125EEB85A5DE6DD6FCDF2D93527CA8E0B53EFE5F73855140321A4E4B23A2C7FE6B2305CA6E33F84A47DFCB13F9945BA6A82668217EDBC45F3958799573F7BC208799C3E0AB9684694E264BCD36CBB86EF53AA35C0FDA7115875AD39BF546830CF62D496968079A7EC5D618344A82D487F43E5C1485985F1C8C6A9528E8ECC85ED2EE473733C2253510E86A781D9EA85C7FC9B39764016334EFCBE57A599F908602482E73AE0914D3362739C384EFE618C05F2899AF101CC695E57B8F55C88C0F8D3F84184239EE9AF4E14758BFE5AF7B7C3FC467A47B88BB03A2E2E4B51EA7158703BE3F4E7584B67F48C9D0A41B5F9AE4D2BCDAC0", // 23 + "B9C6C5105394B0CBDC150C6A6961AE449F625D838891B683D87F05499063D51F9839480B559F9998E8286A4D756738B0DB9F31B9DD2ACEE5A5DB5C67A617466FE38DA55912793390B69817D94E2F53BBE1254CEA7B87C49984384FD69EB341A5BE772EAF5E49929FF9E8096B190D1B91F9D94EC3D28AA49B4A8C78F5614BB1EB4F42E21238C23A3F574CC5F805D2438AB5DAB4A1ADC2DEAB9A86299E8B1D8C51478AFD1EA7ED89A450A88D1AB04D792AC98B36C62D2C803AAC319E0D2438DFB6495A75689D233C8A4044AA9D90AA5BCDB36A385ED8F79843C25F2855BC46E7F5D42CD243ECB17EE8DFB388CE95F353FE64E53E6D206C0BC40D40A179CEE7C42F586B92CF9E19EBEFAA8FCFBE5200FF5452BA22EEB894490BE371E200FF09E9608FE5F131BD6F1D75B5F9CD91884E780788B3A213827CF1C68D31C642A706C018B00B62F1877E4AB97FE53FCD36473528982460200A33B392FBB67C2C2A036E1F855130A17E20B2506733451A0FA65FF344BE4ABDE757A7DA2EEC2FEEB00D849462A3AA88BD2F2995C475E428EFB2F0F216239425247A2359BCB41DB8C9AA6E9A7B3870ACBF8FCC21CEF23AA12268023F3FC83AF658E08804A7DCA8BC3873D0CBAD54436FA3F69AA993BB176363E08A55A346B8F7B33115BC834F4DD6CCD3E81A6F140B0D007CD2093585EA67670180AA76DED02CE9C55780E19411F2EB59E1B51F569CFE6009EFC9347BDB81BDE287DD1650EFBE39FDDBA0AB6270A4D31B3BFBFDCA6A64195798E63EB5F413837996C25FB9381EA5B87928B8EED6778487FDBB84CFE82896F139FCE0705E1120A725948A5841287C9E9010733142369ECF5877A41ED13989AA4E1732C64D587F2A114A7F3746BC1696D5EAB4CEEBAC0B022B0", // 24 + "A1F6FA8D82B89F75B980A5ED804DBF43B0065A591EA773674A1A76D8CA272812F10FEE1DC0B762250696E22D43BA240D170C1578DC85D367DA956358FE2EF23C21D526822F607078975141F078D14E8C146F033FB8CDB7E6D3F8EDB59F3B6198DEFE0F32E092AAF2A30E06D90A4114ACD7B52F951ECE05C143D27E10BF13A446E53F8B73669FCCD7D6808261718A7DCACF96BE92037B8560D9E6C2F93B4F573E5697FC3C8B3FCCCB6715277D29A78265AFD2BBB3F944166849C925145456850E44BB206C43422DB510BCB62663E0A4D75858E095364F6AC8A7F853CCD9F3C26D96DB2AB17F3BE4113B3496F2C13EEFAD5F319927E245545D4773D6EFC61989F2399C9E66C01EF32466DEB522BAA45AE1FB784C95E2F6D96EB7A74A87619D64698538AF5485282FB922D4542E563F9E8D66CDFD0F65E575F069E88202AE53945AE5AAE23A06AD33BB23F4278F8392BC4664AA5C11695F6F51FD0101D3163DC2E895C8CDB479750AB91416D1A3F273E7E16F02C712A2418EB5FD26CC9AC9FBF07FEEE065EF0D44408010CE24DC2982E7F6027067F47D256527F216F18A4E93C5846948B8991282F617E18EFB8157870278C16E3528EB565F814BD79CF3912602FFC279CB0C7A1EDE1E9DAD9E461A1942FFA23F939D4B3764C9C3AB44E5732C9FC079A09CC3C0B89538D4064093384F99491115FF494F1D2DE458B2700F267FFD9FED9FDADA0979A20383A49945556DEC54F3AEC5413830EC09019615AA7D6BB175CA6C8AAC1D0E31C3E3D7E2A32D33D9FD734300768DE19F41285886C808AEAA633FE4E7B082F889AE6B46E59D6204B41CA0B810BF8589053D6A44E8A8139329530EC9A3963333AE7536BF6311493AD6AB1F530C4B8AD97D3A36E2CEFAFCC5430", // 25 + "DCBE11877CA625BFF2A41723A110FAD9B29444FE3641DD4E95271C2DFF6E40AC05255B74461B8080C83D21BEC301F942F845677531434A814898D391646C2037E701D50B019863CEE6B4295AF42E574B2B78EA12824DF8CBC46805ED14DA4C741DA25653B901946FB3D95B097DC4268B094683B7ABF6AC6C3EF247E87146FFBCF47AD4436BD7C56EBEA788EFE96EB63B47D3BCD3AC9729CC3592B94FB6DCA42F35BB92C5D51BDB976D5667D6FE10F143D4C5305449D81C43E84AA65654EE787B2A03F5D699D1A523A2B0BAB0D724674B8CB5B365A369A23A53F7346357A4077FE422B202538FC0CC0311CDCD34E104F6738FE80F962EF89CEFF0E08E1EDF89E8F6C093EA4049436645CE8614EED08E9D1A92B321F785C01103DAC7EC8DBC43FCC47AA9096742CDEEC44606429D8912CDAB0A834457FCC32759DF7954C299F2F51EA0B5F3855515C1E9776728DCDFE901A4F831BB45C8C66FBA13B4EC45015AB45E8370013331F26A71165FCD9E9D8C62D43AFEFB7669519E78510039A2A8CFF2EB9B3B53A3CAFA9818948D9A9EA205BC49638D105B993B23A8A544B4BAF6C5C7C72F19DE15F24E4806B4FF275C36C1AD18DD20A38E15DDC2A905642E26FA92C5848A0866EABDA77D6397C2BC7E7B6C416E85383C7F0102711E9599F4B0E7C06D8EEBE9115E450262D387930725817179D87CBD563967E3E48F7FEE39ADB8B0DA6928A4040627F2E96E71C726C830F0E7800671BA2C0511F757EDEDA7DDB69BB5C68817EBE9D7D4180FC172D816F3C889A9986B739360C99F9FB8DFCFFA6D464298E918A89CB09428E46C6EF1E9CBE61915AA2B0B29508C8B3004658D09DB17E6345EA20501E4FDCE27DD8F6B9025B776DB586E5C5A6B5D5CD64C7539535DF54", // 26 + "9381AD3789E337802B6E8458CC2D725126B80D82366CD6B24DB7C4C561DFF33E612721F4481ED6619C7A7514CA1647495B41F4E7130BF1DF6F87E0CDC1A0280B63D9791D2AE5208638E61FDCF74DC916903C6E7DC3AB2F41AB27ECBE4309DBCFCF128BE89B37048D93882537A3AFEF92F14F22132706FC1787EE4F36AB60D14DE2071FA11817BA9B22AE462593A5CED4C0512C5E651E9080EC8EB439C8E48BE557C4875C9A9055FD72AF727EEAAD3774B6CB379A283EC7C0BDF5ED0CA5982739A3A3471015A606578718B0DE21BD323980F65F40B0A4D27DD9FE696E036CBDCD5350FCFB6B0D8F966AF9EE72C71329387BE2B6CEA4E9ED432F3F53DEEF22DD3E5C55BDC6B0CB7862CA35C2DAFB52CDF1CE7E63C502A05822E2578BDEC4CA9BE8E542CCA527120CC6163088F981AD109C84C7C9F190A083DBDD14CB7D4ACF8FAA5F2261780EBD89AF20870A7A401CD02907708AE12D2931D457975C073D5FB241E58B4BCB6601E908A3417421046942145D5E1A18D33A92114D24F405F6AAADE9ECE8C238B5F45AD7DB223C8CD1113DD3151701C99A86F99BEF93B11C061F6398FFFFBB90C4385052E9ECB102AE42519E90EA7392A7FC504B6E26AC0EA7DEA0E8BE2EF7B85049CB6FFD7D05BF16504846D42F2C33254FD3BA377E4DF223200B5F2F7B01476BA96778F7F16299CF88D1ACA4F7CEACE14F533AD50EAEBA63D847662082F445CD041F7D563EF2157486D3F5BBDB6F2B45620A9631C2355DBBF0535BC8FD2E9CC1E7F1E2632E3139585B6BB27F51C37A57731ACBCD01E87EC284E0EC1FB18ADABADA1275C4A2E45BCD3251977DFA9901FB524692B80AE7A0DD9B6CA604577620C71A2D0B0B110355C941111554E251658D460C194D5C3FD982A7FA0", // 27 + "E5487EFD24AD377B33607300FCD602F9FCB49730AC30D58F69A915B7912B7622E6C69A518110787CEA4D3B48AB2E17AA09CB7B3F21389F977F0E0D6787AA4EA8501154B6878FAD595B7EA7EF437FE17901A017E3725B989A81FAC1B52736AB672D6C5BF3BBE86193F62919A033CA0EA4A23C51791E4AE35F96435D0DA571908BD4D63D0665B34A138E319CF65C6A65E1506178F977E3E62872EAB30544152F48A4C314B374064BEFF9744EE0A6EA9FE0AAD46C5AE6ACA975B9DD28C66BA09F70FBE4BAA3C626CA5BE7791447E63BCA07DFD24864D0C36ECC3922761D18E0574F01B3EADAB4DADCF1CBA8902E405279CF31B23F21503BF58AE67781AB39D7B9C4AF1BC92BD5AD7097A43DC7C0C6D7E9FD0ED15F7395B3C44E4AC78F776256A8168836C881DD0328EAE29DF99890551818063B9037652C5847638E111EC0E97219442964B49453F57929061E14A9C7FFB0DA28706C29339B65DD907CCB39D7E3966FEA29C4E82E827019201ACDB48C1C38D5FF510F289BB3F2C36880509D8D25D1D3BF956610AFAF82EA89558289E5FA6B13F7A6D8479E1EBB0A73C0C1045FDF81C8FD9DCCC4D2D7769A20B939D5643B6BD7B5039BADC90A6DC96B097723CC0B3FECFFF448CBCAF438D1385A2EB903F4CB6365898D67D4ECE9DCAC0DA810A772ADB357C75357BDA4081D5AACC41CC77D9BDF16FC80B55AE9C60416218C6F2C01A0474482ACC916E3BF11B72C73B8E8EF6801B6375DD1FD3D5861E4E71BCC0DBAD002A488E43E7A83180AE69A7F7D24B1285EDFB325E819FA644C49671B8026CAC3D75255B9B068C15A83E55D0C09C2104C72BA1EC84DF143F5E10B9B40470FB4E57E124ED67BD3B673D6DFA785D493752010339434821DADAA22CE633C472185C", // 28 + "AE631B48FC62F9B90929480C17F48E9E5EF285B204909B25F8933876395075BE0A02A788629CF657D7117C5C8F778E93A38CD7D419C6CEE706410A33920BDBC8B3F0C0C21B27028B10F5A8FDB4C35F1F5795451CF52D8928658A0E692BD799A1BC06D0A231A886642483E5D3CBFD4598C3D4F64A1FF623B159E8EC1BA3B66549347B5DE15CB9BEF1695BD58D20B832D49145EB58F0141CE22477820807078EE41BBA6803428C60AB1E2B3E1368FF0630610D4604EDE0B023D6EFE2B4311287CB5292F4A212EF01DD0987884D12F4317F0506A87B8CFC8B6F27F452BFEBE4A51DD69FD0679C10633FA7F19A5558D417893BD6B39D29E19A2302A6EC4A9BE6935D7E8118721AA090C69C990A1C086A1E33D57833C9B561902B676641632B1325A388B76439EA0EE5F01FA53D93CA61670CEFBD5CDD5D58367BD6DB99C88465FD9DD1CF5A94265EED2C6524BAE03676B44127CB7E4908697BF1CE93F53B2561978A3ADCAE7AB80CFBEBEB0F6E88513A9885CD832AC0C3B57F9B5798F672BB60601A871EA105640C09A581C68D5525D4977F8123A275F602CA40D5CFD50644F09B2207D6887E74C93696CB5531435A0D364FA0649DBD0B54CB730BC31AF475C28792CBF67491BD84D63754E37B1EACCDFB8EDD095E753266A5824E1AC77C02A441904993405FEA4A177D3A00453456782E8ABFBC5CF47EA237BBD6DC7C5E75F64C3CEB478F25006436FCB0BD768674257740CC383FA717B77A3B384CD1273724356433A8BFEC603A38BD7979A8FBF5C6614772580C4775A4879BFBEAA0F93FE37E77BD9AFE9351B15FA37EB5AAC87356EF4D3A9992E3E525D9F454B919FF892E22C13B2D93D9BF197F8C6A93375DA7F6E5630A48CEEC74AA783CAC2270582AA2A98", // 29 + "940AB0FBF5D34D53D00095DECDE97492D6C806AAD743760AF951F9DFBF68B9AC4809CD7D0EF7998944E5C16D60A54A12093FC3C802A5F67016430635BFC039403E244D59E6C02D5C10E3ABB97024D451037AA52180A23065AE2E74DF1EB04ABF4548215EFE95397B86D3C8756DD9E830F96FD26B5501DC3A53CC1EBF1EB1DFB90D6D41B292A8D62AA5C8608949CF90330986136B9A5C0C0B2EF661DAFD21104B412C8590621C4C65D6099EE25F6E2156290EE1C679F68ADCF55E37C0C0D19B785C5E63E0D6469D3A9274A7058304A05B3EF796C81E248ECD7AC85434F02F8E26CC46AF67832A9A715E75EEF4CCA528A07BD57B6CD51DBD47D357843915C8090273011B8DD8ACC9D51123E4CFD8291634E4990CA6DE35BBDD0051612DBB61C5A24C97146B61911FD47E4D54D30BE406D5A89E29BA2B8772DC4EE9770ADFFE8B1526193881D5F3104355D34A9E3D36D322FB79032F2171F4EF2CDE53F58F76135F78BE0C2C76BC9B8E7863A4482A6F37FEEF27E2A61F3686909F171FFB8629F86C5EE353C9F48C6067AE66798B0C50058C8ADDE3A0E5C736C8AFDDDFFB67078E526E1BB31C28A5EADDC674E65963A1B57E361B60B964E3A0B07267461AC738E4A0FD14F07D5C5757532BC2F4699F1F9CA4541B41CC8372A6336D4AFE7052359C750CE0CB866CF15C01E969F8635E9C73DBD3C77778C424631882B11267663F4DD24CDBA30942C657C894A1FA973E7C4091ABF858214E36FBA12CA159BC40A87630D5AE51510FF75E99E712C4DEAB587EA8D1D95A5767205E78923F0A6E08B2CB9C9352A773184FF2653C716A12E6BFA4404E4533FAEA2F40BFEB6A5FD3D34A7BDADDA5DCB008F081C45A90411F64DFDCC682FAAFFEC242B90C75E1332EECF6BF8", // 30 + "AF07710836A5AA122AF190399AEBB0ED49CD908A821EDA0C4ED5D6615A431C42C6A9FACC9B33891A1C58FC4FD786CF7238B95F3E0512858F9BAF2A30C9EE0A13219A693EDC046050DF638D41499E626DEDD085D9CAFD687290F19D628C29EAC839C65EE008F584780E2A2F99DA26243009D599438B05874592DD1BD44E48496BF6ED2C795DD35F4E5DBB1251D63736D5528A86A332E1FC959F10F99234627B62FD5F773635D44858040173703E6C22768F16CA7B2BE2FEFACBF7B4853D71F8A3BC420CDD0DB67A3354DC747CDD4BD0E5BBA53E72E6EBEBEA3AF00583EAB1ECE8BC49876DDB3C1B51E20E954FDD7988FBBF54850B098CFD54A2730F46B80148D18EF2BBBFF87CE8A2CA0C61AE7DBE15917F506F58D81CB2289448324E5C85FF9FB2EC6DCDC0DD27A9FE8147792D9403562F171E9C400E253E10D0C004235D3926836DB7C44B57C3F9FC322E6FD8F22741A9FEC04F8BDE90132099346A65C72034497EC1BA5E4DB0A5E6E92DDC4AE8110A6DBF7D53DB059609B18D590E83DEEBB1EF8450B5FFA70D2B19A78BA53603AC17C89D39AC8DCAEA1DED7BDC381F5E928F425CEAB22EB54E1A388550B9B5A663F273A1592347695E2AD4E00CD4FF240BFEC137AB050C39F59E2359953D185863D5D9E2C9A299317797F95B77772D8CCE3B09E9508E6806535753B842A4FF30C6B8403F594BD5AE77FD85C6CCF25C82F453E8E5A3FC337A54FE01632E4E348C07A6D7C9019746A1005E3BFEAC3E4DA6D5598AAEE0F468E8DB4919CBB82895779BCEA4134F13F974719614D00A17E945CAA70E64720E96A9C98A0C37531E36388796D27A694E8A430BBC546CE164B180BF8A8F84B675EA1B85840694479DC7B1760D89D18BE957E643F2D72AE6700A660C8", // 31 + "AE35D9E0DF692D03A1E7A1EEEDA307513BD85EBFEB24D47342C8E5DDF399DC1E9D52D36DBC70B22F39D20E4BADD58045171F3A9F372429B7F5162F76823FA7E04FA22CD4054B43B77DF34AAA4853D49F942ABDE11CA36F9400B0ECFF00B5CF86F05C9657275A28407946D02A7576D88C5273B2AF09D573866D9A1050922A66D0D8D5D9B37EFD8EAED208410F805E1C31FB76510F6775D9F2F4C0DB4DB9FADF52342E9D98E61E7762772E6D76B6AB2E729A4F487E1ABB49BD675409BBF176C1F399B086EF42F817D99354F68CF4985A8C0ACB7B3FB70F1DE8301F46F702AC5A58016C61BA34DA5D02CD681327827C18D98F2C80E8BAC04399EDBCAEBE3ED7171391BF5316E62B972DF64F9BBFC1B58AE0B45C6ED2A59823229FA858EEA2ACC42A6F0D5029176EEA0C206757DBA13A723411DC2E210AF343875E36CB18211F4BD3E22F2A03F31201C2D9D3255AFF04DD3D6D46638EE7E772D033978E7D5EC2DE7EA1F6E0B1E15C65A7BC31B51D265E9E08F527E4A5C89B249200C00A1E8A84D0A0A2847DCD531FD52E44402394E2331808DC2E4F6B70A7DA0DA13B062833F6DD8EF3086F2B6743A41DEA63DE43D94595F7270F670FBADEE74E6036A44413000CF0E9CC2742964270C9F42BAFBA890D287EC5FFBC69F9271022F7E3F251B4568BE3584AEE97C77014E623232435F4FA98F4407535D984CCFC1F73ACAB60E06BF8870065AB22591B38E6B146A535B93055BB97C94C90B07E2ABA263C41A90F14B9B7DDE090DD04946886235B0A4DDA152EBE76444C3DCD80A9607872FDEEC65112DE6B13AA2B86230B31DB50566570115CF935C5E47E5872A8E97A0873273F6090A29E30E94B239BAC1FDA20BA5BEAEC54496B0FDA2CEB6D99E901F169EDA8AB920", // 32 + "A93C92EE0E6313BB969005B1B7B56D1CB117EFA4357F8C5A0DCD7416ADC54DF5C0565DC44FCDDCF53BE98A2B212177BCF3A0CD287147A2C5A9A6D517A0A3C8E1373A3F5C2B1C677F01412DB618F421B51EA54122673ACCDC72F8C3BC3CE4D50FFA1BE0A551F93F03AA2F46DEE9406884E571213A245CC4025F77214CA3149B192E1AFB80D99411590B1FF065C264C338A399B5A5973CBFB33EC1A8DC0223E2A3A34B02C7FF74E82708AD513862662755999139BB860500AB3EB4BDBD5F34103B23E654429E0E778E43CB2825F8FB7EAC8AF50EB6B0EA25C8EB576F658CFF0638F9929E02FDE8CF2CF51EB574698EF58C78B979D8F2B0F6DA53EDC935860156DB22035B47F307F12D39382534007E6233766872D3B61FA8748E2ECF21A207ABA8D3DC874C24609D28A876EBF929D459422B1F2702E1DA477AF7B1CC325546C268FD4B7DD683EA2E80728BB40E7D27CC1F8EC9992E3FBC51FA5740C8EE993A3F209EAD5D076DAD8575E25AA2247C886FCEB12C7DBEA25CE2208A23EC2EFFB4412FFB9E97530E9EDD04F52E9258DF64B2CC474B41F209D9F685B79D63A78A9EFD06F1116C79C9B0D8003588BC0846C59D11BC1524DD1E2B23C17BA1D3A42E673DE1D0E514CDE95536F99A067E0E00C1D15458798669A604A5900B7BC43AFF6D7361B9ADDB8B5E6185776148AF75A57E3946D1C0376E072C910F4466F42F8D67BB1C5F27530E24234B22F8E1A96A0ECE1F2995A6741C15C91600E86F461BC64F2CCF860E524F1E271EB6D9F29B9C1DD54F5AC97706B72F2DDF903895E5127C581D648341F86185BE980E6D14ABAFB7AF8674949428553B31BE2FCC33DE92229EB21B24CD0A97D2576B58C9C80D732B56ADD6507F555E1007CAE0A537F82742AA898", // 33 + "D32684039605FF18C6881E9A5578FB91DEF05935660F2B8921F45459826EEDC6BE3CB55CB1489920DEE66DB5EEE7D952914C46C45FAF3BD033012D9B7F4EAB1C752FCC20A9C9872122DD725682145714122A5CD2AE6DAB9EE24CBB6FD495A7444E9D89A90721730215ECE633E627E8FFB23901B44DF580E08DD1912B6A1F0DFA05802E144296248A14009E92ED71091CD167E9C1BE1E67BAADE6D95780026F491BE86FAE681282B6895F5605559A1520FC5ADDAF49F0EFFEAF66BA20F0F06224FF1F8F1912BBC4A2A425DA6DB0B1DD76A7E34905FD25C3639A6465746D737877B7C97433F37664B7BBB36BCEB4CA552ECF10D2F0E096454E201C88370A19A372BC7B91B402FE12F9EF79B479A1178263E90AFC48AFCAE32D604DBFBA15E703D8C0C62C7D0A563392075EA8179F62DBAC26C045E7509DB7DB70DB5CF924AF5A0A2762ABDF27A6303E312D01275D9CAAE9CD4963C8E06AC56C7137C1A49178C2A69E269040C23880DEB737CE0F63548994B2FC48C64788533C040FC1C31971703893ED62C393CA4BA6DD8C0D5F99DD8B010D2FB6EAFCAC80FE292E090EDA2B440BCFCECDE414890D284AFE9D3233D7E4B50F4F6E86891F90F54A6BA768068F91BD5F543EDBEC049B8D825338B7B72B66054361E68FE6BF23CF3844831A9F8C855D25115C3D6B740B17E63703E133177F825B6703C436CD9AB2B8172A8EB50037E3B839EDD9EE5BDDD483377EE2933651B0A83D2759634CEE2BFEB86DD31206832F166C61458EE956C7395421D8B7BC646586C0767365D55E023ED453F2B38D714AB85F92256BD92FF2BF934CF375F3982E82A79C87F5B04B4C04E1B57E1BBA070F14722EC880FEB5A3AA4E3FC0158DE96D916F9C97633BD2742DE8B60E9D8C264", // 34 + "8C3627FAE2FA9E2B3B7E97B0B3C62BFFEF95365AD049BCE5AE8AD9D9E57EBC427E488766325D91EE1A3AF9ACBBEBD3875AAADC7561097EFE37D6DFD8B617D2A5876FCF968DBB1FFFAC4AF0113FE801FF722CE1093B8A4178035344E77564835D4C8C48AE00CC88FD9B9241E272EDBD72037C45A4A77EA7670C82B85AA2A8CB7A0B7EDB4F3B39ED7915D89E543486D75C01D8A659CEB6459F3E3E152656FC730D40B604A9A6C8B02D069C156CEA43224B4F9772A53AAA44B4EBE3E49AE222EB42151C3588178B0FAC39C9C9EBCE541D74500CDC08C8262EBF3A59DF09346E44ADEC62641E404C0E264C4DAA5B917AD687FE91B932F92193E5FC87649CC1D5C7C8FF3A2926B9255AC9167FB6BEF8DBA7795FFFCE010277B68D55554D36FA842CB2F2F1C5760934558D4031611EA0B5327F2345667D2B3FDD8EE06EE64E9025536D074FC134E2A5251A15F0F7485C767628531A02C62778BF2AF48EE3DC5092237E46430ABC9FC89F015A249427A5B935C996B263E15CFE8438161DEB41B043D3D57C63B176211D3072086CCF27050A6494CB6C3FC2E72EDF8958B5EE5132DDCC33333C6FFDA08985E378450FECFD2442042B55E1F2AD8DE97680B2C6C17FBE5261D9310CD9283AB1076BA982B563FCDCB5E922F13659DF1AB2E736D75F01C1A88B1184C78706473BA1CF18829E5A93E5C04FAEE6D953B1FE1434672A452921B92062F440E31E19441E83838CE0D3A01AF7254C831145C07D686237574B2CA0B9CB2AFF30AE70AF2E97F10B058402C23B523772703C81ACFB8D698F875F1E63B1C7D8F6DA7BD96313AC8EA9DC004D01892FA683D16FD9735B3E627167DD2DA2F74628555F222626A825DF2B898767968EF4C46074202E365153035291591EFE810", // 35 + "8AF5247C4E1296ED850CE9D12E5C1AE78D3CDBFF66439B5D9281FED33F3D505DFC1718AD1170033DFE72FA0B0FA0C54163A58E721118E058C330E76485AD282FB57E7D0BAD6C5F9E4D2DF4CF27835007230E3D49501C1277FE9BB9F321AD4C292E0C9E3B345E0ABB1FBBF0A81CB40653AF6E3CB0AD6960A10B5C30EC5B1B48B9B0D5164E1245355F7B1C54B8CBC2B333D2C6FA597FAB6AA5006167781FC8B1054E284FC8ACDAEAE92D3CA520753BC6F88F50A0A516518B6182345E1E7DE9AA215CF4FF299AA44E6A76DCE4B4A2562B961DD118151EBFEF12E3231CF29CC2EFC0502FA4D70CE6626309941070BD0B91C9D947D794A304027B85AEA9E1A31542B3673289B112EEA96220422045733CC5E0EB951B14FCB431B13D08C8AB3AE1D500E298D4163F2EFD3470131A949BD2F28EEB21572EE2CF11B54595383C25518BA48CCE91651CA41AE5C75AF4C3589F331EA16B7BCDDCB0A036C581741F773B9E94FF4FEE160A26CAF7107EB0C07B236B11828E8A509C5207CF58B34ABBB6B77D80827FB4CEEEDDC09649A740F316F9206A28C3A0A8E97112EEEE7C70A9A9EF04563750A68DFF06DE3D953C56677D03648612FF7BBE3BB46B8D08EDAFEDE427AF9E8E087399E410D2F8DB33772F2371C4A9E57049F18CC2564E66E2158EA9D77B29391363A86B0A2A476A6603CDC0E5D2C8E589353C893487FFC1A6C1DC22AC5560C65DF4ECC2C97EADF3E87BA668BA7208253FB170D5F1B570FD027859DA05A17B739EBA433E17A3A2312C71529DC518856D687EEC2C245E7CBCE19F697253C9797029FB7E12079DB4715BD6B934DFE10DCFC083D1BE53F894570E052C1681F44B77FFC4EDB0E66CB970AB141F35D3CFA0484EE2CDF95702CB2D9F58FF6325E30", // 36 + "FFF67C13E26F4604239AF32CA58D4EF9007ED0FAFBEF5C5653E8943FB34338096E78C87854F5217019C43B5B066DFEF9EA6BE0F57EC9E7CAE8CF2CEB9BE04F62506F08A54402274BFD90ECF1A2E66694E18CA9A1652BD72D224043B50F60D31C762852168ECF0643F817BFFC819BF7A9ACF8F0DE24E2F00B81E4BF75B284AC9A08836AB656BBC8E75BF2B1B30A3B278B4270DA1404CA821E83F3035313AC9BD8488E776C70BAC06B1089C80BCD3FB9967BC471E312CE7CA40B91C72EF8439F63C5A8D17EBFE9355412BE059F8C97E81D2D82B46AB626740C2E30F3DC32CA561118C4CACD57441514882F28E580D36FD8827BE96633868BF85AD1DC374FC2720B6AD70ECB0622F95A8212D2D8D5494F2509F7C152DF33466B241FD9F1934C020C4BDF29908B59801F8D95EDC2E87693917B042E4E8374AFD33E2472CBABEA672175112CEF5BDEBD151DD6C688AB6004B804C19443E0DBAEEA8AFD4AC26BB51B57F9FD08C6A38801CDA3C5E9387DC3AAD4D7DA7367159675733460AE1B22C73346DD412473152D08C90B7561DF94ACB36D5FC67129CA8B4699F4E175D88763D82D93DE0D7DCC13757C925663921A7FEAC8B6AE4B13A6CFA050CE7FBB2CA7E741B8C0F3E218CA356DB8E74019246526E4C7B6C819F8845528C3DCB71CE156A98E9861DC28E9B39AF89B0BB346B6F60A91B2867A8BF9297135101EC536E54BF05BD379EF8A3E5A8A523ECA0A1525FFBFE5514BBC3998B166ECB52F2E7D45B6F65D7CCF95D012E011DB513D5DE61545B4E904B148EE82800D6126DD7113CC1C8657026677583A7F041FDA18A8BFF9E4D4C0CD3E082F4A0925DDAC6278E533972392FADF66C25C19992DED31CFD4D216EAF001BFFEBAC5252C88BFB905D1284BF2314", // 37 + "B0643FB1F06E1D5741F2F9CD1FAAAF3C3C010014E17B6DEBA1B868357E090C186F1A7C34F42AF8134D9FD4FD6BD301DD6174CC59DAC54C98F93ABB7C1B74DA21F28DFFB5715692AABDD8995266C3F72F52250BA6F48B0D9DD474982D7A6A3215D45E7CFC11CA95DD083F8E8FF6D26BB6D9D328E491CD6D73EB4E4FC98287BE31122BBD97C90AF9CAC6349A4AEB19890D5E2B09C0214785F523DE48CC7897B3C1878EFBD9073DDB5F06AE8ED67CD8C0702DC362C73D3634A81FBE7D458A8C6E71F0260BF4BA6D8E2CD8E265582BA270623212081D284C7BD6985713D80756FAA8B98FD3D2BA6A8F362013EC49538C45221E001EDE5E67D31F9A0B0775EC4B003CC08409532E405FA41BCD1B76947BFD99CDF6C7FDB07D34BCC23CD67B464A870584458C86C2C4D998AE7F14BB2B3764CA6228F6F3C26BF17E378A876D41EAAB0F46939BE3001B9A6DFAD5B8CE54DC4C986B5B1B1704BFA06843D7F7367DCB5E53C7FFE2105703F32FFF014F6975A2DD4ED2099F606AF1BAB0C796ECA0223F42DB8FA5C38D9FC25A8FD3027613E14272632C757A2A255847D1C39DE2E4236ED1A78C3144DB5DFF324537E696BF0904094CBD6C8D6A6D7E111715947429FF836F10D1F6E8CA45898C4AD590E13DC1B5B7BE3C419B6C418F6BECD3B130A3884DBAAA48B53AB54112E8E4E9F17F3448D3ACDF2C2829EC14CF68BC184884AE408743A90C92FEB9F4D0877931AD2BB848971C061334A1E402809E3ACB289D16C68CEF364A1A75B76E58A8E32F3F44480345D426A2D809D9771A6FBB5A666E5963806A91564DD62514E444861F0B81FCD65A8A59A8A63F98E4DFF242838CAF22A6015FCB8AD8164C1C53E7D4F39B2E8D4BED0635FA7514A2D773A168D4EFA2895151608", // 38 + "BE8B93ED7C85573DB5EDFEAAAB80312E3C3225FF6B65457F9833D7666DA0B818021845926922A42D3DBC0E19E6BF4FD81111DA2D159D6EA1CE2DEDAAD2A3DAABFFD91698AE157CD0CC553ECAF1F4956E667B3D96EB9404D3590EECD2B2D600C1C2FB3E9E918EC2C90271C5B7842395D22DEE66B6BE16A49F698DABBDA851210DE9F22EFB2B2CCA93C6592A8D88DA6A42A4A4AB062674538A1EF48EFF39749BA22DC8D40F62B670828117FC09E5537303214249199EE117FD69B94D462A6778B44AD2A35A03A1137D0C0023863BB92F4A587078B232C99EE4278F812A8B69A41F58FD13175D09C34FFF79D08FC9153DC3C896B61899F37E31F4A9C0B0EB9DABD25EA89EA6BB91389BAC5274300B8E27E7354CB8C3C3872CB7C714735F41F3C2FDDA43739A5C1E255059E6A1E44BD6DE31B63F4BE586C3B2BC0A66F93470B159857A370A5BA305584803C0EE8E02C6A8C237A3A14344EEE4953AFB13DCA779C1893048E8415138C7E64921C4B8DF266019EB6FB3987731AF75B2F07012E91F666B982B3870F60D963A901C2872CBD029B8F0420663F65CEC7C573A86FADAD25BDE5BC89A21032B7F164A228B2793096ADEFD1A76209E4556D7AD8C2B41A06E2E1FB89D841E149D311D8AE0ABAD26927A170A785E25DE9EA3DE6FC51A21201ACB15F481BB8F01DB5EC7E6F9AC61D1F58C828ED672FA2810CB3B551DF5DB3846DD1DEDA975A308B7BB07FD9F8F3F01D95FFBDCE2B45909F61685BF2E0C8C1D73B90ED6415300C23AFEE172DF3C355ABFAB0CE384929600BAF569E3C9AB65EC12C07A79A22FDB8FFDB2009E3D51C7C676A6527909E0AF7D9343324C94D361BF6A862C0FC2B4A4E8C7C5C7F36B2F5301C00DCC9635081186C2BE643ABEE415B90D1C8", // 39 + "EC3B51481481486D8823AB29BA36FAC0BA96AD14982DA36695859DE864C11478A630433FD76A4505A2FE3DEFF8027EC93A9CCB3FBAF4A5B0EAEDB867280E7482386030068A9EA8CFA4D522DF9818C782EBDFD0C5321452E4B6E883A153CFB276CB0327F141500ABC3CFF5C83C19A67B66701714B4C48A562F296FEC437E2431D98098CEE25F636E399B870A59D83229F4753F67DBB895A3E150551D0578EB4795C4AD5E027317E63AFE431B0CBF3BA94E9835CF072BF91369DD7414976712F226D9A005F25BF427F8EC67C8E0C82A2060137DB850722A565FC2BF73922C28C687E4B914E2A30CEC484CCE9360A495FE352C21DDDB503E7DA11AA96AFC3722FB83054DEDDC11959AC20B7905318FA95D080E99D14CD7A6462DE93856E598FE2FB30679A1BF42F173E08DDF79541B1932B5AD345C974BD025EE5AE9AA62F77C0C2D6DB056FFCB5B5BB91028DB44B653C8EF3D684E42C81E2B2D84A406A3ECEC4665737244D390937AD551AACBAB4FA3EADF47F7E39E30F0656430B5219B6078B095468323187C4A9176708A240278869EDBD54BECAFA13BBFD86367B7248B567A27A78112E13566FB4CFAC539F364F9EE30C130E7547BF48E939DC61ECD4900AEE4D86F191368501D77D06BC327F5AA76109E42D18A6447D2CEAB57A065417E2FD20D5DFC6A6F84937F2514CBC37EEF4FD5E679E451F8862363E40AF7620184CCBD55916E26CB73B71E674A1E6502F6D8CA74894933C444EEEC1476526AD2CD7CA9AA5D918D7F2EF23E4BBA1AFECF9959C801C3C3F3793140F69CE250FA9B954350AEC14EC47D47E7EEDE377223FEF7052F66EB205598BBF8163673DE410DBC817459930328D63C35FAF57C1010BEE90CB6D0FD80C1360D48D20D6143C705EC34", // 40 + "BA8137844CEB25E531F33EEC1F8DA7589D85F2EA0B99F51040EA36A9C6FA16FF993E676A2A994EE553CDE801E138DFDA6A797D4E592F8647606341ECF17D8746174DF4668FD108317D89DCDEEBC7A86B0FEAA48AFF0DB1E2A4D02C4C8145FB259D8411EEEBC6F3A96B85869FDAFA0B5C2197E1DAFE3CF16EF6A56B057CD5463011B03BB810E26AF4355DB962F510BC71F8602D8A9DDD4244772DE59C638FC3658C64C600CB69EF3BCA2904C242427DF431D9F47488FF324F38E85BEFA5B66A977FD9809E32A9EEF99A944612F1D65DE6794782E278CC8AE9EAC377C5A114B0150678D40A7DC7ADE021F289A657210AC0C84B5970D56A3629B6BA7B3EA98CC9EB575FD6FA210DEC335E82B1963FCF0DE32DE0AE12884FEFFADF33F7AF874324A712824A37E147F7BEC3C2238C0C45C562A6D48AC3756FC21D62BFDE47AF915F51A34202A994E7222954CBB1EC3EE7F6D691A53BB2E692CD80BEF07BEDF47B02C2826F1373AC50E317BB57183A935121B913D6A8DA1AA66A749FB25BCE08CEA10932F49077A258EF3C9B24DD8B265BF14E4B1BF072CCB833FECDB250B400F1D1B6F4BDBE460CAC0BD0009585BDF11609EC8AD8C75A341B5BEE2A55776D18DDA2AED5CE666AD03386BC86F0369E529203BAD8940F5A96E98198D94BF6B9E6A48F5B7D3ADA260A19336590BF98861722098A4CDF4CBF35E8C55DC0D8D0465066AF954654E76AEB69FE0E634DAE60F972841F79AA68604167C178FA11127F30994D667B1169A4B1A0649BD0E13B9541E3E1F530ADB22DF21C92DC17F7CD289F5CC24EAD69503425AAD76CC14DF302993AA842D40121491EC8BC040427D7DB30644881CBE1F3BDB93444B46F42C001C1144D265379B6085FC1C73ACF04A81A1302F20", // 41 + "8C296D08BA204B2B2C7E11D7E6959FCAFD027202B6D92C4092940D3FB268653AD99406B5FD1C92DCC3BB11D44F38F23F2B88052C1E48E34FDEBADF8C18D851791EF78EFDC2A34BDE68109410C00E0146804C2C5571261C9483B6887B7769C895B76FDD162A4A99FDEBB9DCD6A04CD7A6328E7694E3C2080A611665FA2508F64332E5D8AB3357E36753CABF60725FF30C6F6E0E82E80DF32F6DF9BC11EA9CE6F0851D1B0CBAF0732AE05A307BBB39A56A28B164D6459EC3AEF052D517BFE62E9798FAB52BCE087E42D0D8DC45625A6F7A32A7E67305C8000E5738D2C8B53E82F10459665AAF2592CDA2D00792532FB24A77802482B4C70B38072E738457A80CE8D7E2BFD049E4353F929FCE6E6AF3091695080CE93C11C8B3C177C51BB9640519A81EA7A4A1CEDC26D128575A281DFA3499E5146E0D47CDFA9E8885E168AF35C64501B9AE45DE4A28973098FE8B6E809D357503CAA5BD373F767B41763F6864F42E8B7B7EE40D88A137AEF2D22B5F95FD973EEE370FB3CD9338E15547B5A1934D50110CBF3E4B8DC070B323CCF905CFCAA6DAA359739AFEB7C2B0CC98996D202FB51ACDC8379709423BB7683294A2D7AF0DF6B44B8243DA21DC8296305F89ED52F0EABBCD1DDF19044FC033B5EAA5BCA6FB93EB7ECE4B8934C3A35D0F5380C5B4FE526CDBA2F473F233A4F32C4C2B4A3F9A47F96FBF0023CB8FF2FA1CCB07717CA449AAB5BD989B6CAA8D52E046AED4BFB1B2600D3F3BD037F096F006389C3BD875B6064CFBF44C2DE09962D1F30995D32CA0F5474F41BE202AD67AFC1049870FF330C6887E3F765CFA3A85ECF11182C867598AA61C6997158A367D94E202709D839960E6311AD687ADAA05A5ADE0088E6B492B0F7FE053A67CB4DA4F3CEDA88", // 42 + "E40E6EF4455C67D6EF9D6A72858B9E6E08AE3B48EB5F0339A02BACBF979E8266AD92F3D1002DDE744C69B7C92656B3239925CB7910A50EB4D03DCD28908ED3D7F560AEA47AC4B0EC9D7B91AD12DC15920032C25CA1247FC7F69B148166B6E61629C3B84687B9045AD65FE45CFBED4134B68A2CEF9BE972482C0A9442ED0A2D48A55F2DCE46412BE557D793C26C98A7F68CBE30F070435F552811CAF34F2C94EA0A46AC4FA41252D1AB745B5647AEC23A70EECFCFC0B88F93138C45140CF23AC030C6571BE79520684B0785CACA1D918ABFAF2FA925D0E194C85BC7D750A1EC4E85E51111ECE870361DBE27791A3A2E26C80A7C0B1AAFB0C013C29824E1708D7DEED9EB4C55BB5DF732F361BB6F3322166854F8ED0910E29FCC51B86549D47CFE2D4B7EB110B306EFFC076F37FBE276329C70DEB6308D9B959447D8604ABE6F18F653E141142E0C77F08744CD490C284D2C41410786F892A662821BFA5CB5F0FF00ED7584B576E8D3E0D94C0D1A8B1B532A64F78BFC8AD8E676799E8970349828A780B76A76DE7625E6C76EABE4AF12D7F80E3E43AF3DC2D7EC10A7AF4EC6C3C158AE63D82FB47EE6D98F9043EA025448254A34F4FDAC70C139B73088026AD9607A3B04FEC00BC9E6B86E30C4A606D470FB0E022200A786133CDB870BADEBBF881281C8C02FD8DAEBC229DAC57C9F5B49E75551488968821DA88FB7778F796030CA04E78E53BAEE835D2D51AC62564E579291F205AE7337774677394A326D1D7C1E1E692B7738ABABD3BBFDB1F0E999098FDF74B3817A19E6BA5CC4A41F6760F2A57CEB0C3F668BA49E0E52065B3C307234D5735EAE7FE7EF5ABA539C2178A321ED5A59E7BE313757C51969AF57486FBEF7757C2558A1599C996C8CB823A580C", // 43 + "8B831E082C933E273C83032428539C331C131145AF171D3CB5C4E46C44E2CA0E185EA893B069CABC0DBE995C6A123ACF7D546CF4F0FD15FB0F862B4983845F5CCE43F01EA0B2D6523A8D01BBEE1956EEDA8102AEFFD2EDAEC51644548744D6B51D76F03EEF031C4B5486CED1C94B069F1F73E53B2027D028CCF042FA100CDB6BBCD82A4D9DF60A4E7C70664AD2069E57365B0F28C793D5B8D3313AD3E582404918F0258FD7D70FE878CE8B4DD4DBD092BC17622C46EDFE8852B638AD2B60AA4784DCAAA7CA2F40069D675AACD131FEB0A2E967769670E66A3825ABC8BA6F5ACC7EBC505CD2BDBE148F21B0398237076CC593D9BCC457BE7A522E3C5E1B0B6A6EB5A4D4CF788DA7C640FEEA1B28E555B21FB0DAC6503F70D0137387BAD7AA915C9BC7EA692573FA012E4A5FF41215F4244AADC04342917B6AE6BA63B4A46C0B715FAC82CEEDB6228BDA51B4548DC477EECC3FCE6EECED8B15A458420CE6BA48798D7FAB3295C9AE161D7FA1DED73858A29D698C1D4005A75D914659603824C5BA2FB807EB63E60DA4C47D3A43D59FAB931E9BC50FEFFBCCAE3930778018849C388B405EE902183A69A8FE2645378C0144B05CB45BF88FFCB9FD1D19C0283EDC6116C66D5A54F6542921D4B423A3CBF2530EF534CE01B735F49D5BCB0634066B0617EC916E7076CF084B30BA248730645D299C37B8B6B181C67AE492AFAFC33241D22336432DEC1B6F74194CA5821417AE9D4AEE87FD30E77800DE6697FD1040F9BCCA86C2B838F3973591C63F843D875B3A2023EAF38DF399DE1DFB696C8D06EE8D75FEF76956AFB612D5F4CF06DB484EE4E31D7B3C4B5D371EE92EAD3DC6C1D0067D57B822C9F70E3CA17C065546D678D04828BEB076572B5582FC6939EC5B0", // 44 + "96FF996AF5BF7DBF540BC4737B5F5C0C6CABAFDD3A821A2DA1F597066FC4DB438D8918310B3184EAEE80F2F383C0B0619A1F1F2F77C7F4DD055363C3795EDB3036F582E813D7F44518E8E959839ABFFFFF2DE9C657C63B9DEF44D18474BE37AA6FCE42A0D2C8F1BD4DE0A7B674A44B4AF2E67AD12411B0F6113AC6084B5B1F5260E973CF98C0E96B3FA80A224C0BB3295446D5384FD94A3BEEAE7CF8AA7A71313550554AB13E1179D6BAF847D0E3D898E66683973871F989A40D3353E1DB83EAECA00345DDC6D5D2987D666624B38B9EB0921253B5CFFC2DC9FA5E226F2659EC8CD131BF7E4093C81F99EEBFFE27B40319E2D4052502F56E79042EFB042709FD035AD58203DE6180757C5B7B0C471AF3612E93EA2442B577039AD52C68E08DD9B377D96ED0A453DCCB5650392DC2710EF24B2BE9E3F3D0CEE96F7EFB18ACC226F022C136FC0FDA126A0E1CF7EB17C1A8AD9E9110B24A4A188A51C19FB67A514F80A967F847C7A4E4BC1BB0505D9865FEC26FD91813D10A263421503856AEE4DC1C6CF50544EFF6C685174CF644B729C28A4AAC12B44876B09C10FC6766313A226AD1CB2881903AC4043DD36E8BC1B302653B0035837AC6D1D349A92E577F4E84ACDB2532BC954CB08ACFFBBDB1D28AE65951BC79F78CC755925A565268BE2441B1D40DA1F450B1EFDD2BCF8F7815D8DD1F3B05D8F98A838944B60DE76DCE52C36FE5DDB4F83714985357831A536A95EC0BAAA9A94C96F86B342DA911FEFBA959DE0E58999DC13C16EB9792B5B92288FF16148F910A5B06D8D05457B12E15619C025B13E4D93472E66F80AB49A23A2201FB2817A61273291500D84147BAFC8A3B96BEF241A7B0064F13AD2E0C84C82BC1F3CFCEB8A93B927D1B58939AA6EC018", // 45 + "87FC908811EE777B2C509E835CDA0260B65E061E9E508F09B49DBF046446AA33C336FDF490573B177B6C8C791B3B1CC26A874FD9BF1AC54E0D3841A04AE293B5D9E079EBD219082C402F56ABDBA94930E91F99273811E8198E31EBE31E466AAF25F002BA7A3E09C7FC4A5188FE5616BC1E1A5E54B663B502AD59B67579072DC93F49CBAB351B9E563BD67F56179D8370579A8FC14133C4E9437434A5E1D0E53E398EADD2C0BF0EF6542EF6101944F25B96EADD0B3853D1D27AC64108701E3DCBB0BF7F45B9C173CD4207E267090CF7870427BE248713E064111959D2D27DB5510376AAE140C5FB79D4AC873E55A3863A0FD183E4FC2D1B5D6A5B942F74A79BB64981F7189FCDAD06793891B7B97BBA3ABB8BC88976D8902B56CA9D548182C65F33D3C3121A80817CD1DF6B4EBB07F2F40D03443A7EFDA50812ED4E4AC241EB86596146D3228FE464D849C51CF9CEDCB851AFFB2C92DB16CEF971B6A352BD1589289C0BD619645A0140879923A9588AFA38DC097DB0C006D82C0D4E1B6B273E184EC81BD688E832B61D39AADD13C2E27E0D57CA72C1FABA81CA38936473BC3C4C79C6635D265172A26F794A2AA8175DCB91792871E14258717AFEF66EDCC1AF2E2D3A232DEE44A3CFB24696324E466DCF10575C0F14562DA1AF866ADF26A4D54EAA888F231327F006716BF1F4F801E09FF7FD4528D2B4FC9247358EA7B9AA2EA2B9E80F5660CFD19B77A9D83379493A0BBA1FEF3B998AA45DBAA7D5E6F4BDABD00C86097576E199A9C0E8D9E91794E3D545AFDA0AB92A62B66429F3668762B10DBF683F77DD2FF99F92B94B3060D8747C34C581102E21C6596248AA139283377A3ED85DA3C999A6FB029780A9CA1DAED1C9B889EF1A1441219E3AFED76F1BEA0", // 46 + "ACE6BD607FADA0C25C3F74EF23BAF6E29C70214318035E80B160E5485BD57C691605DE7783B9EFCF498CE5B4D8EE6619191DA2A15820015DF7F0B8AB05B22562DD437FBE3AD141AE0E6AE45313FF915A39A49466F11B651CF210143D6FB06BA6E5BF4083671FB3DC9454E4EA38ED980C5C2EB65F24FAD970316D5BBF490B2403BFDCA348A7DB1B4AFD9A91B6898381CEEBAA5F6EA1419C19D21E1A64DA2063D824AADF7C0E546D608F7FF19B89BDD9E8808A7A236E98DF0728BA39498DC6BCFE82C534DDB6FE02B303B2DB401DF41C53F4F5321CC28861D22010DF2D1457F9C594F3A3DE00753CAF0CB6EE6B34DA2364EDDCC2619930C7DCE4C976F50BE69444BB275944D17D07061A87D1CFE0DED63400B514C6457F4119845A19072678017FD6A498CE3D3932CAC6B63335D87426F1C16FCE0F5861FF0953CA13E9C911A947A2C82231E61012DB7E782BB839764EE42A94FCC7BCE4BED84D891EE959D9430B963CDD38EC2F63884AA493BE7FEAF47B6252F2F042531A5FF5347AE9C322C374AEF6A7F4529BCBD085696178D1D2C12C9AB91A2B66E3F05A45A1950BBFA797134B4430DC09289B09EB1BD88D05B03D2EC269500532FCEBF7EC7A652EA9F96AFB622E6D71E568756C2D1700D55238AD36D86A6D92C62C4522F4EB2F2AA0F9FAB8E56D4052B13890FD0A9BC17376E248EC86AE21EA8430B110A8A2AA835D7317095CCF2FC00ABD132A1D2CB7CCB105871E4FCEF7F5D8B7CB3E9FE24303DAF57EE80581725B9ADCD9CE09F7CA50DB4B2B7CF8AFCF7C39958F4A963E92E3E29F4349275C772F68D135AE05CA99019C14166ED34E0C3598A67C9CD8CA68832D7D7EEECA0BE86B4355B8439002D2F7CC7933F07E6A8F0908CF0F3903996C390142728", // 47 + "9F4DE194AFEB87B4D4934D86F08C2F3506BFC8F0F0AE05E9AB45A17B36AA9C7D5534127B3C98E60885AC2D00006A8CBA4EAF711CE0C9497B04B539553ED80B24EAC9496BE53725F8EAEC5DB5BDB71664C45D01CD17BE31D039CE8CA16FD9D9D60E9294C8ECFF4975392793C879BB2E375AC7EFA47B32D6D895E6A3F929E78BE82EFC896E89304FC3F3F0751E22D523D3117AF239B897B12841429AFDBE8F8D303D50D2A56275CEADAC04CC32A95E9DDB45F4D4C83EA7CA29EF5E8F7D172D18134E5FE9BC9E442F6D2DD3B01807B994C80697A28CAC0927FE31E62985F629A965FD14083E72D65835BF6230A660F10D91A04EA9809FF8817129E6503F28596FE9A486D486A3B789580E36C0F9C6B75BB6548742676F50FDA64602EE6986EF10824C90E66585A118F5C3BD1FF67BBCAD408CF0762D59216884525FCB32863B6A1502302C4F09B3BCB27A9EBA72EABE68D86A15CA6FDCCE89CD4F20055C3221611F1B1F9CFAD8B93A68C42AB19565DABC486B0CA93215DDC8E128806B4ED06FAA0987A49EFCFC1C23E1BACCB6ACB332D720311B8FED658024F0CA12EAC658494DA79F9CB56D59F17C927650575562095A99F5A11D13CD1267BE89FBE761DF5CF0E9D8C67A9DE21E8BEA108BC3E2E3AA68C7742103B6F975DC232443C34F17D9EAA1939C23514BFF908757849AC3EC6E954FB4991373DF8E7DC6011D16F9C19E4C7021A788C0834D6BC09A560908AF50F71D0496A3DE1EA1DF8F485A3C496B9F4B65A279083F8390966C0348296D05BCFA81D2EF8A2714C4A886D35DF870BA4DCF73BFD8E3EA1DFD4D9E37A0ED8E9032E350947D61B0CA2FE10FC20B8A5AD0E3FD70AE3C8BB91F2E04619ADB3C684E10B9127E5B76B8CD0E9BAD2FD100131E6FCB8", // 48 + "953E31E5F9C45E57DB6C54DBEBE650C07F8C14D832BC39D5D860912A7B4EE9F726C0D3FF842DA45E3E9286321AEB6A45FAB260E1FB3AFB7A4DCBFF3D65491BC61AE83207E1CC56858D13046191A5CBAB51C59F3BB37E281847EE624A8E54DA17273262E790246A54C8873BE252E9FBD7A7ECCD476D62124FD1F28AFE207545525B325DC6F73F25D09E3589BC848E0A803EB10FF50E81732E5F317CD3AADAFA6C123807677B273B6B56955065869078BBE27AA04CEB7ECCA493DC2E9499188C51B16309F014624554EB96E3A69E3E067FA946977379B4BD23A9DD57D5E175FAA891F5FCD72431A3C857A582B233403B0D13C48C502F9E5324DB127DA83F019272A29ACDF325E6AB2908ABA54CD56D1D91188B320A1941720BDCC2CF00991C932FC5D7025B69F242F775A06DD6BD8FFB7615246AC4B7495A350C4BBE9F65F7449969EDCE013BF3F7367647316C2AA55F882BB42C7BD7C43618F78A6409A8659919E3FA20DAA9BC384CC6BEA776A438080DA6DF11AB8A853F06DAAE429883F3C153FEDFDFF9D968FF806D9B7CA193F3A72D316E0297A01CF1AB6350D2E807F93EC27E7BD91CEB6CDC44001E092EADF5523BF0D67EFDCD89D8E5A2F7ECAF0B126F4D2AA32A484B5272AD7CE9E9F26CF418E6642927124483043E3C15B7833655C6388A26A10152857221718FB9AFE36331E071C04E498DA3181B5F5BC701CC5C30BB963183F6AC5F26A5CACB32ECB7C8A88FDFCE73823FA6FBD965B2772067CC2B81BE5A35660ACD3F5658ED28A0D225B54392F0D03983148ED738FF3A80E5DEB5D2DF3092C1AAF81272E75190397F9FA66AD3C101F259496F9EA011082D7BE7E1E10F80ECC4C42AF40E0AB498081404E5593A711470383D8149ACE622AA769A900", // 49 + "CE11D9F5C85027678446809BB43074F041568F30717E5B784BD0B64FAAE0610A144496CF4AAC1BEE53CF98AAB2BA75239D228BF946D99CD0899C250CF1850D17ADD418E389F4ACE5A9426A1251074E28C13A818477875BC0FF732517A6CFB18A23F633E9D0F876582907AC8C3403A57BBAC9706D7BFAABEDF132A3990E6C4089BAD9939F1F8CE910779BE5CF96EA87716BDC45CB5C7BCB5D14123D03BE2E50909C523D10096335770D849D509811EC426D80E1115B3AF367D941C74D8025D7C7F5B2AEC5E5E7AD5F38538CFE33387B5D19AB846A7CFA6C6CC4E0AFC054DFEDCBDB0A8B489A082B3B2A064EBE0902AF6BDA5B64310641F2D717D06E1064D2C2C0ABB19C66DFBA807E36ACE0F6BB487C9868C2F354909960BDF7A8B4C42CC935EDFB10C1975F051D4029A76D255A1CBAD199393A8A7BBCF80C25649C22B475244323D5323675D74886B30A90B9B91939521DE115DD5AD0509363C6057B9DB321C0871D45B9269B048A98148BD91E3976DDB7405F0AA990A8C7987767F76AD1CBAA8B442DF151E28FFE3D4A985BFF6C263108D8873CE28F62DEDF610ECB1A6C35FE5D66F437B227F184D856EF994099763510425B16234A31B14FC8BD6BC37264F9BAEBF838AC2D03BEA98EA7F9FA0160A21BC211EBFB82A5CC2A70F72FFBED8170AEC8209A6B7ED8CFA7A6FFB9E956239C9A50249D14E365EA7166033BC83D1FB6F336A8BC85E0EB6EE26EFD4B8A5028CA26F9CE872C7A3CFD21CACF29E42DE0309211CCC8143C7CB336A450A2B786D526F53265B60339ABC9ECA67FAAFBD3CE1DE3C3867A9D11D758AE3162BC3D464EDD1C77EA1A4B4A3A0DBD80D05E566F97DF2555E427C8466AE006876402B89FBF37CA38ADDBC2083D30D7F27DAEADEF334" // 50 +}; + + +constexpr size_t GALILEO_E6_C_SECONDARY_CODE_STR_LENGTH = 25; +constexpr char GALILEO_E6_C_SECONDARY_CODE[GALILEO_E6_NUMBER_OF_CODES][26] = { + "83F6F69D8F6E15411FB8C9B1C", // 01 + "66558BD3CE0C7792E83350525", // 02 + "59A025A9C1AF0651B779A8381", // 03 + "D3A32640782F7B18E4DF754B7", // 04 + "B91FCAD7760C218FA59348A93", // 05 + "BAC77E933A779140F094FBF98", // 06 + "537785DE280927C6B58BA6776", // 07 + "EFCAB4B65F38531ECA22257E2", // 08 + "79F8CAE838475EA5584BEFC9B", // 09 + "CA5170FEA3A810EC606B66494", // 10 + "1FC32410652A2C49BD845E567", // 11 + "FE0A9A7AFDAC44E42CB95D261", // 12 + "B03062DC2B71995D5AD8B7DBE", // 13 + "F6C398993F598E2DF4235D3D5", // 14 + "1BB2FB8B5BF24395C2EF3C5A1", // 15 + "2F920687D238CC7046EF6AFC9", // 16 + "34163886FC4ED7F2A92EFDBB8", // 17 + "66A872CE47833FB2DFD5625AD", // 18 + "99D5A70162C920A4BB9DE1CA8", // 19 + "81D71BD6E069A7ACCBEDC66CA", // 20 + "A654524074A9E6780DB9D3EC6", // 21 + "C3396A101BEDAF623CFC5BB37", // 22 + "C3D4AB211DF36F2111F2141CD", // 23 + "3DFF25EAE761739265AF145C1", // 24 + "994909E0757D70CDE389102B5", // 25 + "B938535522D119F40C25FDAEC", // 26 + "C71AB549C0491537026B390B7", // 27 + "0CDB8C9E7B53F55F5B0A0597B", // 28 + "61C5FA252F1AF81144766494F", // 29 + "626027778FD3C6BB4BAA7A59D", // 30 + "E745412FF53DEBD03F1C9A633", // 31 + "3592AC083F3175FA724639098", // 32 + "52284D941C3DCAF2721DDB1FD", // 33 + "73B3D8F0AD55DF4FE814ED890", // 34 + "94BF16C83BD7462F6498E0282", // 35 + "A8C3DE1AC668089B0B45B3579", // 36 + "E23FFC2DD2C14388AD8D6BEC8", // 37 + "F2AC871CDF89DDC06B5960D2B", // 38 + "06191EC1F622A77A526868BA1", // 39 + "22D6E2A768E5F35FFC8E01796", // 40 + "25310A06675EB271F2A09EA1D", // 41 + "9F7993C621D4BEC81A0535703", // 42 + "D62999EACF1C99083C0B4A417", // 43 + "F665A7EA441BAA4EA0D01078C", // 44 + "46F3D3043F24CDEABD6F79543", // 45 + "E2E3E8254616BD96CEFCA651A", // 46 + "E548231A82F9A01A19DB5E1B2", // 47 + "265C7F90A16F49EDE2AA706C8", // 48 + "364A3A9EB0F0481DA0199D7EA", // 49 + "9810A7A898961263A0F749F56" // 50 +}; + + +/** \} */ +/** \} */ +#endif // GNSS_SDR_GALILEO_E6_H diff --git a/src/tests/test_main.cc b/src/tests/test_main.cc index 02ecf73fb..8fc5b2036 100644 --- a/src/tests/test_main.cc +++ b/src/tests/test_main.cc @@ -73,6 +73,7 @@ DECLARE_string(log_dir); #include "unit-tests/signal-processing-blocks/acquisition/galileo_e1_pcps_tong_ambiguous_acquisition_gsoc2013_test.cc" #include "unit-tests/signal-processing-blocks/acquisition/galileo_e5a_pcps_acquisition_gsoc2014_gensource_test.cc" #include "unit-tests/signal-processing-blocks/acquisition/galileo_e5b_pcps_acquisition_test.cc" +#include "unit-tests/signal-processing-blocks/acquisition/galileo_e6_pcps_acquisition_test.cc" #include "unit-tests/signal-processing-blocks/acquisition/glonass_l1_ca_pcps_acquisition_gsoc2017_test.cc" #include "unit-tests/signal-processing-blocks/acquisition/gps_l1_ca_pcps_acquisition_gsoc2013_test.cc" #include "unit-tests/signal-processing-blocks/acquisition/gps_l1_ca_pcps_acquisition_test.cc" diff --git a/src/tests/unit-tests/arithmetic/code_generation_test.cc b/src/tests/unit-tests/arithmetic/code_generation_test.cc index 72d4ffe08..6527559b6 100644 --- a/src/tests/unit-tests/arithmetic/code_generation_test.cc +++ b/src/tests/unit-tests/arithmetic/code_generation_test.cc @@ -18,6 +18,8 @@ * ----------------------------------------------------------------------------- */ +#include "Galileo_E6.h" +#include "galileo_e6_signal_processing.h" #include "gnss_signal_processing.h" #include "gps_sdr_signal_processing.h" #include @@ -102,3 +104,45 @@ TEST(CodeGenerationTest, ComplexConjugateTest) ASSERT_LE(0, elapsed_seconds.count()); std::cout << "Generation completed in " << elapsed_seconds.count() * 1e6 << " microseconds\n"; } + + +TEST(CodeGenerationTest, GalileoE6CSecondaryTest) +{ + const auto len = static_cast(GALILEO_E6_C_SECONDARY_CODE_LENGTH_CHIPS); + std::array secondary_code{}; + int prn = 1; + galileo_e6_c_secondary_code_gen_float(secondary_code, prn); + std::array expected_output = {-1, 1, 1, 1, 1, 1, -1, -1, -1, -1, -1, -1, 1, -1, -1, 1, -1, -1, -1, -1, 1, -1, -1, 1, -1, 1, 1, -1, -1, -1, 1, -1, -1, 1, 1, 1, -1, -1, -1, -1, 1, -1, -1, 1, -1, -1, -1, 1, 1, 1, 1, -1, 1, -1, 1, -1, 1, -1, 1, 1, 1, 1, 1, -1, 1, 1, 1, -1, -1, -1, -1, -1, -1, 1, -1, -1, -1, 1, 1, 1, -1, -1, 1, 1, -1, 1, 1, -1, -1, 1, -1, -1, 1, 1, 1, -1, -1, -1, 1, 1}; + for (int i = 0; i < len; i++) + { + ASSERT_FLOAT_EQ(secondary_code[i], expected_output[i]); + } + prn = 2; + std::string generated_string = galileo_e6_c_secondary_code(prn); + std::string expected_string("0110011001010101100010111101001111001110000011000111011110010010111010000011001101010000010100100101"); + for (int i = 0; i < len; i++) + { + ASSERT_FLOAT_EQ(generated_string[i], expected_string[i]); + } +} + + +TEST(CodeGenerationTest, GalileoE6BTest) +{ + const auto len = static_cast(GALILEO_E6_B_CODE_LENGTH_CHIPS); + std::array, len> gale6b_code{}; + int prn = 1; + galileo_e6_b_code_gen_complex_primary(gale6b_code, prn); + std::array, len> expected_output = {}; + + std::array expected_bin = {-1, -1, -1, 1, 1, -1, -1, 1, 1, -1, -1, 1, 1, -1, 1, 1, -1, 1, 1, 1, -1, 1, -1, 1, -1, 1, -1, 1, 1, -1, 1, -1, -1, -1, -1, 1, -1, -1, -1, -1, -1, -1, -1, -1, 1, 1, 1, 1, -1, 1, 1, -1, 1, 1, 1, 1, 1, -1, -1, -1, -1, 1, -1, 1, 1, 1, 1, -1, 1, -1, -1, -1, 1, 1, 1, 1, 1, 1, -1, -1, 1, -1, -1, -1, 1, -1, -1, -1, -1, -1, -1, -1, -1, 1, -1, -1, 1, 1, -1, 1, 1, 1, 1, 1, -1, -1, 1, 1, -1, -1, -1, 1, -1, -1, 1, -1, -1, -1, -1, 1, -1, -1, -1, 1, 1, 1, 1, -1, -1, -1, -1, 1, -1, 1, 1, 1, -1, -1, 1, -1, 1, 1, -1, 1, 1, -1, 1, -1, 1, 1, -1, -1, -1, -1, 1, -1, -1, 1, -1, 1, -1, -1, 1, 1, 1, 1, -1, 1, 1, -1, 1, 1, -1, 1, 1, -1, 1, -1, -1, 1, -1, 1, 1, 1, 1, 1, -1, -1, 1, 1, 1, -1, 1, 1, 1, 1, 1, 1, 1, -1, 1, 1, 1, 1, 1, 1, -1, -1, 1, 1, -1, -1, 1, -1, -1, 1, -1, 1, -1, -1, 1, -1, 1, 1, 1, -1, 1, 1, 1, -1, 1, 1, 1, 1, -1, 1, 1, -1, -1, -1, 1, -1, -1, 1, -1, 1, -1, -1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, -1, 1, 1, 1, -1, 1, -1, -1, -1, -1, -1, -1, 1, 1, -1, 1, -1, -1, 1, 1, -1, -1, 1, 1, -1, 1, -1, 1, -1, 1, -1, 1, 1, -1, 1, 1, 1, -1, 1, -1, -1, 1, 1, 1, -1, -1, 1, -1, 1, 1, -1, -1, -1, -1, 1, 1, -1, -1, -1, 1, 1, 1, -1, -1, 1, 1, -1, -1, -1, 1, 1, -1, -1, 1, -1, -1, -1, -1, 1, 1, -1, -1, -1, -1, -1, -1, 1, 1, -1, -1, 1, 1, 1, -1, -1, 1, -1, -1, -1, -1, -1, 1, 1, 1, 1, -1, -1, 1, 1, -1, 1, -1, -1, -1, 1, 1, 1, 1, 1, 1, -1, -1, 1, -1, 1, -1, -1, -1, 1, -1, 1, 1, 1, -1, -1, 1, -1, -1, 1, 1, -1, 1, 1, 1, -1, 1, -1, 1, 1, -1, -1, -1, 1, -1, -1, -1, 1, 1, -1, 1, 1, -1, 1, 1, -1, 1, -1, 1, 1, 1, -1, -1, -1, 1, 1, 1, 1, -1, 1, 1, -1, 1, 1, -1, -1, -1, 1, -1, 1, -1, -1, 1, -1, -1, -1, 1, 1, -1, 1, -1, -1, -1, 1, 1, -1, 1, -1, 1, 1, -1, -1, -1, -1, -1, -1, -1, -1, 1, -1, 1, 1, -1, 1, -1, -1, 1, 1, -1, -1, -1, 1, 1, -1, 1, 1, 1, -1, 1, -1, -1, 1, -1, -1, -1, 1, -1, -1, -1, 1, 1, -1, 1, -1, 1, -1, -1, 1, 1, 1, -1, 1, -1, 1, -1, 1, 1, -1, 1, 1, -1, -1, 1, 1, -1, 1, -1, 1, -1, -1, -1, -1, -1, -1, 1, -1, 1, -1, 1, 1, -1, 1, -1, 1, 1, -1, 1, -1, -1, 1, -1, 1, 1, 1, 1, 1, 1, -1, 1, 1, -1, 1, 1, -1, 1, -1, -1, -1, 1, 1, -1, 1, 1, 1, 1, -1, 1, -1, -1, 1, 1, 1, 1, 1, 1, -1, -1, 1, -1, -1, -1, 1, 1, -1, 1, -1, 1, -1, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 1, -1, 1, -1, -1, 1, 1, -1, 1, 1, -1, -1, 1, -1, -1, 1, 1, -1, 1, 1, -1, 1, -1, 1, 1, -1, 1, 1, -1, -1, -1, 1, -1, 1, -1, -1, -1, -1, -1, 1, 1, -1, 1, 1, 1, -1, 1, -1, -1, 1, 1, 1, 1, -1, 1, -1, 1, -1, 1, -1, -1, 1, -1, -1, -1, -1, 1, -1, 1, -1, -1, 1, -1, 1, 1, -1, 1, -1, -1, -1, -1, 1, -1, -1, -1, -1, -1, 1, -1, -1, -1, 1, -1, -1, 1, -1, -1, -1, 1, -1, -1, -1, -1, -1, -1, -1, 1, 1, 1, -1, 1, 1, 1, -1, 1, -1, 1, -1, 1, 1, -1, -1, 1, -1, -1, 1, 1, 1, 1, -1, 1, 1, -1, 1, 1, 1, -1, 1, -1, 1, -1, 1, 1, 1, -1, 1, 1, 1, 1, -1, -1, 1, -1, 1, 1, -1, 1, 1, 1, 1, 1, -1, 1, 1, -1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, 1, 1, -1, -1, -1, -1, -1, -1, 1, -1, -1, 1, -1, -1, 1, 1, -1, 1, 1, 1, 1, 1, -1, -1, -1, -1, -1, 1, -1, -1, 1, 1, -1, -1, -1, -1, -1, 1, -1, -1, -1, 1, 1, 1, -1, -1, 1, -1, -1, -1, -1, 1, 1, 1, -1, 1, 1, 1, 1, -1, -1, 1, -1, -1, 1, -1, -1, -1, 1, -1, -1, -1, -1, -1, -1, 1, 1, -1, -1, -1, -1, 1, 1, -1, 1, 1, -1, -1, -1, 1, -1, 1, -1, 1, 1, -1, -1, -1, -1, 1, 1, -1, -1, -1, 1, -1, 1, -1, -1, 1, 1, -1, -1, -1, -1, 1, 1, -1, -1, -1, 1, 1, -1, -1, -1, 1, -1, 1, 1, -1, -1, -1, -1, -1, -1, 1, -1, -1, 1, -1, 1, -1, -1, 1, 1, 1, -1, -1, 1, 1, -1, -1, 1, 1, 1, 1, 1, 1, -1, -1, -1, 1, 1, -1, 1, -1, 1, -1, -1, -1, -1, -1, -1, -1, 1, -1, -1, 1, 1, -1, 1, 1, -1, -1, -1, 1, 1, 1, 1, 1, 1, 1, -1, -1, -1, 1, -1, 1, 1, 1, -1, -1, -1, 1, -1, 1, 1, 1, 1, -1, -1, 1, -1, -1, -1, -1, 1, 1, 1, 1, -1, 1, -1, 1, 1, -1, 1, -1, 1, 1, 1, -1, 1, 1, 1, -1, -1, -1, -1, 1, 1, 1, 1, 1, -1, -1, -1, -1, -1, -1, -1, 1, -1, -1, 1, 1, -1, 1, 1, 1, -1, -1, -1, 1, 1, 1, 1, 1, -1, 1, -1, -1, 1, -1, -1, 1, 1, -1, 1, -1, 1, 1, 1, 1, 1, 1, 1, -1, -1, 1, 1, -1, -1, 1, 1, 1, 1, 1, -1, -1, -1, -1, -1, -1, -1, 1, -1, 1, 1, -1, -1, -1, -1, -1, 1, 1, 1, -1, -1, 1, -1, 1, 1, -1, 1, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 1, -1, -1, -1, 1, 1, 1, -1, -1, 1, 1, -1, -1, -1, -1, 1, -1, -1, -1, -1, -1, -1, -1, 1, 1, -1, 1, 1, 1, 1, -1, 1, 1, -1, 1, 1, -1, 1, 1, -1, -1, -1, -1, 1, -1, -1, -1, -1, 1, 1, -1, 1, -1, 1, 1, -1, 1, -1, 1, -1, -1, 1, -1, -1, 1, -1, 1, -1, 1, -1, -1, -1, -1, 1, 1, 1, -1, -1, -1, 1, 1, -1, 1, -1, 1, 1, 1, -1, 1, -1, 1, 1, -1, 1, 1, 1, 1, 1, 1, -1, 1, -1, -1, -1, -1, -1, 1, -1, -1, 1, 1, -1, -1, -1, -1, 1, -1, 1, 1, 1, 1, -1, -1, 1, -1, -1, -1, 1, -1, 1, -1, -1, -1, -1, 1, 1, 1, 1, 1, -1, 1, 1, 1, 1, -1, 1, 1, -1, 1, 1, 1, -1, 1, -1, -1, -1, -1, 1, -1, -1, 1, 1, -1, -1, -1, 1, -1, -1, 1, -1, -1, -1, 1, -1, -1, 1, 1, -1, 1, -1, 1, -1, -1, 1, -1, 1, -1, -1, -1, -1, -1, 1, -1, -1, -1, -1, 1, -1, -1, 1, -1, -1, 1, 1, -1, 1, 1, -1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, -1, -1, 1, 1, 1, 1, 1, -1, 1, 1, 1, 1, 1, 1, 1, 1, 1, -1, 1, -1, -1, 1, 1, -1, 1, 1, 1, 1, 1, -1, -1, 1, -1, -1, -1, -1, 1, -1, -1, -1, 1, 1, -1, 1, -1, 1, -1, 1, 1, -1, 1, -1, -1, -1, -1, -1, -1, -1, -1, 1, -1, -1, 1, -1, -1, 1, 1, 1, -1, -1, 1, 1, 1, 1, -1, -1, -1, 1, 1, -1, 1, -1, 1, 1, 1, -1, 1, -1, 1, 1, 1, -1, -1, 1, 1, -1, -1, -1, 1, 1, -1, 1, 1, -1, -1, 1, -1, 1, 1, 1, -1, 1, -1, 1, 1, 1, 1, -1, -1, -1, -1, 1, 1, -1, 1, 1, 1, 1, 1, -1, 1, -1, -1, 1, 1, 1, 1, -1, 1, 1, 1, -1, 1, -1, 1, 1, -1, -1, -1, -1, 1, -1, 1, -1, -1, 1, 1, -1, 1, 1, 1, 1, -1, 1, 1, -1, 1, -1, 1, -1, 1, 1, -1, -1, -1, -1, -1, -1, 1, 1, -1, 1, -1, 1, 1, 1, -1, 1, -1, 1, 1, -1, -1, -1, 1, 1, -1, -1, 1, 1, 1, -1, -1, -1, 1, 1, 1, 1, -1, 1, 1, -1, -1, 1, -1, 1, 1, 1, 1, -1, -1, 1, -1, 1, 1, -1, 1, -1, -1, -1, -1, 1, -1, 1, 1, -1, -1, 1, 1, 1, 1, 1, -1, -1, -1, 1, -1, -1, 1, 1, 1, 1, 1, -1, 1, 1, 1, 1, -1, -1, -1, -1, -1, -1, -1, 1, 1, -1, -1, 1, 1, -1, 1, -1, -1, 1, 1, -1, 1, -1, 1, 1, 1, 1, -1, 1, -1, -1, 1, 1, -1, 1, -1, -1, 1, -1, 1, -1, -1, -1, -1, -1, 1, -1, -1, -1, 1, 1, -1, 1, -1, 1, 1, -1, -1, -1, -1, -1, -1, -1, 1, -1, 1, -1, 1, -1, 1, 1, -1, 1, -1, -1, 1, -1, 1, 1, 1, 1, -1, -1, 1, -1, 1, 1, -1, 1, 1, -1, 1, 1, -1, 1, 1, -1, -1, -1, 1, -1, 1, 1, 1, 1, 1, -1, -1, 1, 1, 1, -1, -1, 1, -1, -1, -1, 1, -1, 1, 1, -1, 1, 1, -1, 1, -1, -1, -1, -1, 1, -1, 1, -1, -1, -1, 1, -1, -1, 1, -1, 1, -1, 1, -1, -1, 1, 1, -1, -1, -1, -1, 1, -1, -1, 1, 1, 1, -1, 1, -1, -1, 1, -1, 1, -1, -1, -1, -1, 1, -1, -1, 1, 1, -1, 1, 1, 1, 1, -1, 1, -1, -1, 1, -1, 1, -1, -1, -1, 1, -1, 1, -1, -1, 1, 1, 1, -1, 1, -1, 1, 1, -1, -1, -1, -1, -1, -1, 1, -1, -1, -1, 1, 1, 1, -1, -1, -1, -1, -1, -1, 1, -1, 1, -1, -1, -1, -1, 1, 1, 1, -1, -1, 1, -1, 1, 1, 1, 1, -1, 1, 1, -1, -1, 1, -1, 1, -1, -1, 1, 1, 1, 1, 1, 1, 1, -1, 1, -1, -1, -1, -1, 1, -1, -1, -1, -1, -1, -1, 1, -1, -1, -1, 1, -1, -1, 1, -1, 1, -1, 1, -1, -1, 1, -1, -1, 1, -1, -1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, -1, 1, 1, 1, 1, -1, 1, -1, 1, -1, 1, -1, -1, 1, -1, 1, -1, 1, 1, -1, -1, -1, -1, 1, 1, 1, -1, 1, 1, -1, 1, -1, 1, -1, 1, 1, 1, 1, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 1, -1, -1, -1, -1, 1, -1, -1, 1, -1, 1, 1, -1, 1, -1, 1, -1, 1, -1, -1, 1, -1, -1, -1, -1, -1, -1, 1, -1, 1, 1, -1, -1, 1, 1, 1, 1, -1, 1, 1, -1, 1, 1, -1, 1, -1, -1, -1, 1, -1, 1, -1, -1, -1, 1, 1, -1, -1, -1, -1, -1, -1, 1, 1, 1, -1, 1, 1, 1, -1, 1, -1, -1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, -1, -1, 1, -1, 1, -1, 1, 1, -1, -1, -1, 1, 1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, -1, 1, 1, -1, 1, -1, -1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, -1, -1, -1, 1, -1, 1, 1, 1, -1, 1, -1, 1, -1, 1, 1, -1, -1, -1, 1, 1, 1, -1, 1, -1, -1, 1, 1, 1, 1, -1, -1, 1, -1, -1, 1, 1, -1, 1, 1, 1, -1, -1, -1, 1, 1, -1, 1, 1, 1, -1, -1, 1, -1, 1, -1, 1, 1, 1, -1, 1, 1, 1, -1, 1, -1, -1, -1, 1, -1, 1, 1, 1, 1, 1, 1, -1, -1, 1, -1, 1, 1, 1, 1, -1, 1, -1, 1, -1, -1, -1, 1, 1, -1, 1, 1, 1, -1, 1, 1, 1, 1, 1, 1, -1, 1, 1, 1, 1, -1, -1, 1, 1, -1, -1, -1, -1, 1, -1, 1, -1, 1, 1, -1, -1, 1, 1, -1, 1, 1, -1, 1, -1, -1, 1, -1, -1, -1, 1, -1, -1, -1, -1, 1, -1, 1, 1, -1, -1, -1, -1, 1, -1, 1, 1, 1, -1, 1, 1, -1, 1, 1, 1, -1, 1, -1, -1, -1, -1, 1, 1, -1, 1, 1, -1, -1, -1, 1, 1, -1, -1, -1, 1, 1, 1, 1, -1, 1, -1, 1, -1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, 1, 1, -1, -1, -1, 1, -1, 1, -1, -1, 1, 1, -1, 1, 1, 1, 1, -1, -1, 1, 1, 1, 1, -1, -1, -1, -1, 1, -1, 1, -1, 1, -1, -1, 1, 1, -1, -1, -1, 1, -1, -1, -1, -1, -1, 1, 1, -1, -1, 1, 1, -1, 1, -1, -1, 1, 1, -1, -1, -1, -1, -1, 1, 1, 1, -1, 1, -1, 1, -1, -1, 1, 1, -1, -1, -1, 1, -1, -1, 1, 1, -1, 1, -1, -1, 1, -1, -1, 1, -1, -1, 1, -1, 1, 1, -1, 1, -1, -1, -1, -1, 1, 1, 1, -1, 1, -1, -1, -1, -1, 1, -1, 1, -1, 1, -1, -1, -1, -1, 1, -1, -1, 1, -1, 1, 1, -1, -1, -1, -1, -1, -1, -1, -1, 1, 1, 1, -1, 1, 1, -1, -1, -1, 1, -1, -1, 1, -1, -1, 1, 1, 1, 1, -1, 1, 1, -1, -1, -1, 1, 1, -1, 1, 1, -1, 1, -1, -1, 1, -1, 1, -1, 1, 1, 1, -1, 1, -1, -1, -1, 1, -1, 1, 1, 1, 1, 1, 1, -1, 1, 1, 1, -1, 1, -1, 1, -1, -1, 1, 1, -1, -1, 1, 1, -1, -1, 1, -1, 1, 1, 1, -1, -1, -1, 1, 1, -1, 1, 1, 1, -1, -1, 1, 1, -1, 1, 1, 1, -1, -1, -1, 1, 1, -1, -1, -1, 1, -1, -1, -1, 1, -1, 1, 1, 1, -1, -1, -1, -1, -1, 1, 1, 1, -1, -1, 1, -1, 1, -1, 1, 1, 1, 1, -1, 1, -1, 1, -1, 1, 1, 1, 1, 1, -1, 1, -1, -1, -1, -1, 1, 1, 1, 1, -1, 1, -1, 1, -1, 1, 1, -1, 1, -1, -1, -1, 1, -1, 1, 1, 1, 1, 1, 1, 1, 1, -1, -1, 1, -1, -1, 1, 1, 1, -1, -1, 1, -1, 1, -1, -1, -1, -1, -1, 1, 1, 1, 1, -1, -1, 1, 1, 1, -1, -1, 1, 1, 1, -1, -1, -1, 1, -1, 1, 1, 1, -1, 1, 1, -1, -1, 1, 1, -1, 1, -1, -1, 1, 1, -1, 1, 1, 1, -1, -1, -1, 1, 1, 1, 1, 1, 1, -1, 1, -1, 1, 1, 1, 1, 1, 1, 1, -1, -1, -1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, 1, 1, -1, -1, -1, 1, -1, 1, 1, -1, 1, 1, -1, 1, -1, -1, -1, 1, -1, -1, -1, -1, -1, -1, 1, -1, 1, -1, -1, -1, -1, 1, 1, 1, -1, 1, -1, 1, -1, 1, 1, -1, -1, 1, -1, -1, -1, 1, -1, -1, 1, 1, -1, -1, 1, -1, -1, -1, -1, 1, -1, -1, 1, -1, -1, 1, -1, 1, 1, -1, 1, 1, 1, -1, -1, 1, -1, -1, -1, 1, 1, -1, 1, 1, 1, -1, -1, 1, -1, -1, 1, -1, 1, -1, -1, 1, 1, -1, 1, 1, 1, -1, -1, -1, -1, -1, -1, -1, 1, -1, -1, -1, -1, -1, 1, 1, 1, 1, -1, -1, -1, -1, 1, 1, -1, -1, -1, 1, -1, 1, 1, 1, -1, 1, 1, -1, 1, 1, 1, 1, -1, 1, 1, -1, -1, 1, 1, -1, 1, -1, 1, -1, 1, 1, -1, 1, -1, 1, 1, -1, 1, 1, 1, 1, -1, 1, -1, -1, 1, 1, 1, 1, -1, 1, 1, -1, 1, 1, 1, -1, -1, 1, 1, -1, -1, 1, -1, 1, -1, 1, -1, -1, -1, 1, 1, 1, 1, 1, -1, -1, 1, 1, -1, 1, -1, -1, 1, 1, -1, -1, 1, 1, -1, -1, -1, 1, 1, 1, 1, 1, -1, 1, -1, -1, -1, -1, 1, -1, 1, -1, 1, 1, 1, 1, 1, 1, 1, -1, -1, -1, 1, -1, 1, 1, 1, 1, 1, 1, 1, -1, 1, 1, 1, -1, 1, 1, 1, 1, 1, -1, -1, -1, 1, -1, 1, 1, 1, 1, -1, 1, 1, -1, 1, -1, -1, 1, 1, 1, -1, -1, 1, -1, 1, -1, -1, -1, -1, -1, -1, 1, 1, -1, 1, 1, -1, -1, -1, -1, -1, 1, 1, 1, 1, -1, 1, 1, 1, -1, -1, -1, -1, -1, 1, 1, 1, -1, 1, -1, -1, -1, -1, 1, -1, -1, -1, 1, 1, 1, 1, 1, -1, 1, 1, 1, -1, 1, 1, 1, 1, -1, -1, 1, -1, -1, 1, 1, -1, 1, -1, 1, -1, -1, 1, 1, -1, 1, -1, -1, -1, -1, 1, 1, -1, 1, -1, -1, -1, 1, 1, -1, -1, -1, -1, 1, 1, 1, 1, 1, 1, -1, -1, -1, -1, 1, 1, -1, -1, 1, -1, 1, 1, 1, -1, -1, -1, -1, -1, -1, -1, 1, -1, 1, -1, 1, 1, -1, 1, 1, -1, 1, -1, -1, 1, 1, -1, 1, -1, 1, -1, -1, 1, 1, -1, 1, 1, 1, -1, -1, -1, -1, 1, -1, -1, -1, 1, -1, -1, -1, 1, -1, -1, -1, -1, 1, 1, 1, 1, 1, 1, 1, 1, -1, -1, 1, 1, 1, 1, -1, -1, -1, 1, 1, -1, -1, 1, 1, -1, -1, -1, 1, -1, -1, 1, 1, -1, -1, 1, -1, 1, 1, -1, -1, 1, -1, -1, -1, 1, -1, -1, -1, -1, 1, 1, 1, -1, 1, -1, 1, 1, 1, 1, 1, 1, -1, 1, -1, -1, 1, -1, -1, -1, 1, -1, 1, -1, -1, 1, 1, -1, 1, -1, 1, 1, 1, -1, -1, 1, 1, -1, 1, -1, -1, -1, -1, 1, 1, 1, 1, 1, 1, -1, -1, -1, -1, -1, -1, -1, 1, -1, 1, 1, -1, 1, 1, 1, 1, -1, -1, -1, -1, 1, -1, 1, -1, -1, 1, 1, -1, -1, -1, 1, -1, 1, 1, -1, -1, 1, -1, -1, -1, 1, 1, 1, -1, -1, 1, 1, -1, -1, -1, 1, 1, 1, -1, -1, 1, -1, 1, 1, -1, -1, -1, 1, -1, 1, -1, -1, 1, 1, -1, 1, 1, 1, 1, -1, -1, 1, 1, -1, -1, 1, -1, 1, -1, -1, 1, 1, 1, -1, -1, 1, 1, 1, -1, -1, -1, -1, 1, 1, 1, 1, 1, 1, 1, 1, 1, -1, 1, 1, 1, -1, 1, 1, -1, 1, 1, -1, -1, -1, 1, -1, 1, -1, 1, 1, -1, 1, 1, 1, 1, 1, -1, -1, 1, 1, -1, 1, 1, 1, -1, -1, -1, 1, 1, 1, -1, 1, -1, -1, 1, -1, 1, 1, -1, 1, -1, -1, -1, -1, -1, 1, -1, 1, 1, -1, -1, -1, 1, 1, 1, 1, -1, 1, 1, -1, 1, 1, 1, 1, -1, 1, -1, 1, -1, -1, 1, 1, 1, -1, -1, -1, 1, -1, 1, -1, 1, -1, 1, 1, -1, 1, 1, -1, 1, -1, -1, -1, 1, -1, -1, 1, -1, 1, -1, -1, -1, 1, 1, 1, -1, -1, 1, -1, -1, 1, -1, 1, -1, -1, -1, 1, 1, -1, 1, -1, -1, -1, 1, 1, 1, -1, 1, -1, 1, 1, 1, -1, -1, -1, -1, -1, 1, 1, 1, -1, -1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, -1, -1, -1, -1, 1, -1, -1, 1, -1, 1, 1, 1, 1, -1, 1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, 1, 1, -1, -1, 1, 1, 1, -1, -1, 1, -1, -1, -1, 1, 1, 1, -1, -1, 1, 1, -1, 1, -1, -1, 1, 1, -1, -1, 1, -1, -1, 1, -1, 1, -1, 1, -1, -1, -1, 1, -1, 1, 1, 1, -1, -1, 1, -1, 1, -1, -1, 1, 1, -1, -1, -1, 1, 1, -1, 1, 1, 1, 1, 1, 1, -1, 1, 1, 1, -1, 1, 1, -1, -1, 1, 1, 1, 1, 1, 1, 1, 1, -1, 1, 1, -1, -1, -1, 1, -1, 1, 1, -1, -1, 1, -1, -1, 1, -1, -1, -1, -1, 1, -1, -1, -1, -1, 1, 1, 1, -1, -1, -1, 1, 1, 1, -1, 1, -1, 1, -1, 1, 1, -1, -1, 1, -1, -1, -1, -1, -1, 1, -1, 1, 1, -1, 1, -1, -1, -1, -1, -1, 1, 1, 1, 1, -1, -1, -1, -1, 1, 1, -1, -1, 1, 1, 1, 1, 1, -1, -1, 1, 1, -1, 1, -1, -1, -1, 1, -1, 1, -1, 1, -1, 1, -1, -1, -1, -1, -1, -1, 1, -1, 1, 1, 1, -1, 1, 1, 1, 1, -1, -1, 1, -1, 1, 1, 1, -1, 1, -1, -1, 1, 1, 1, -1, -1, -1, -1, -1, 1, -1, 1, 1, -1, 1, 1, -1, -1, 1, -1, -1, -1, -1, 1, -1, -1, -1, -1, 1, 1, 1, -1, -1, -1, 1, -1, 1, -1, 1, 1, -1, -1, 1, 1, 1, -1, -1, 1, 1, 1, 1, -1, 1, 1, 1, -1, -1, -1, -1, 1, 1, -1, 1, 1, 1, -1, -1, 1, 1, -1, 1, -1, -1, 1, 1, -1, -1, 1, -1, 1, -1, -1, -1, 1, 1, -1, 1, 1, -1, -1, 1, 1, 1, 1, -1, 1, 1, -1, -1, 1, 1, -1, 1, 1, 1, 1, -1, -1, -1, -1, -1, 1, 1, -1, -1, -1, 1, -1, -1, -1, -1, -1, 1, 1, 1, 1, -1, 1, -1, 1, -1, 1, 1, -1, -1, 1, 1, 1, -1, -1, 1, -1, 1, 1, -1, 1, -1, -1, -1, -1, -1, 1, 1, -1, 1, 1, 1, 1, 1, -1, -1, 1, 1, 1, 1, 1, 1, 1, -1, 1, 1, 1, -1, -1, 1, 1, -1, -1, -1, 1, 1, 1, 1, -1, 1, -1, -1, 1, 1, 1, -1, -1, 1, 1, -1, -1, 1, -1, 1, -1, -1, 1, -1, 1, 1, -1, -1, 1, 1, -1, 1, -1, -1, 1, 1, 1, 1, -1, 1, 1, -1, -1, 1, 1, 1, 1, -1, 1, 1, 1, -1, 1, 1, 1, -1, 1, 1, 1, 1, -1, -1, -1, 1, -1, -1, 1, 1, 1, -1, 1, 1, -1, -1, -1, 1, 1, -1, 1, -1, 1, -1, -1, 1, 1, -1, 1, 1, 1, -1, -1, -1, -1, 1, 1, 1, -1, -1, 1, -1, -1, -1, -1, -1, -1, 1, 1, 1, 1, -1, 1, 1, -1, -1, -1, 1, 1, -1, 1, -1, 1, 1, 1, -1, 1, 1, 1, -1, 1, -1, -1, -1, -1, -1, 1, 1, -1, -1, -1, -1, -1, 1, -1, 1, 1, -1, 1, 1, -1, 1, -1, -1, 1, 1, 1, -1, -1, 1, 1, -1, -1, -1, 1, 1, 1, -1, -1, -1, -1, -1, 1, -1, 1, 1, 1, 1, 1, -1, -1, -1, 1, -1, 1, 1, 1, 1, 1, -1, -1, -1, -1, 1, 1, -1, 1, -1, -1, 1, -1, 1, -1, 1, -1, -1, -1, 1, 1, 1, 1, -1, -1, -1, 1, -1, 1, -1, 1, 1, -1, -1, 1, -1, -1, -1, -1, 1, 1, -1, -1, -1, -1, -1, -1, 1, 1, 1, 1, 1, 1, -1, -1, -1, 1, 1, -1, -1, -1, -1, -1, 1, 1, -1, 1, 1, -1, -1, -1, -1, -1, 1, 1, 1, 1, 1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, 1, 1, 1, 1, -1, 1, -1, 1, -1, 1, 1, -1, 1, -1, -1, -1, -1, 1, -1, 1, -1, -1, -1, -1, -1, -1, 1, 1, 1, 1, -1, -1, 1, 1, 1, -1, -1, 1, -1, 1, 1, 1, -1, 1, -1, 1, 1, 1, 1, -1, -1, 1, -1, -1, -1, 1, -1, 1, 1, -1, -1, -1, 1, 1, -1, -1, -1, -1, -1, 1, 1, 1, 1, -1, 1, -1, -1, 1, -1, -1, -1, -1, -1, 1, 1, -1, 1, -1, -1, 1, 1, 1, -1, 1, 1, 1, 1, -1, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 1, -1, 1, 1, 1, -1, 1, 1, 1, -1, 1, -1, -1, -1, 1, 1, -1, 1, -1, 1, 1, 1, -1, -1, 1, 1, 1, 1, 1, -1, 1, 1, -1, -1, -1, -1, -1, -1, 1, -1, -1, -1, -1, -1, 1, -1, -1, 1, 1, 1, -1, 1, 1, 1, 1, 1, -1, -1, 1, 1, -1, -1, 1, 1, -1, 1, 1, 1, -1, 1, -1, 1, 1, -1, 1, -1, 1, -1, -1, 1, 1, -1, 1, 1, -1, -1, -1, 1, 1, -1, -1, 1, 1, -1, -1, 1, -1, 1, -1, -1, -1, 1, 1, 1, 1, 1, 1, 1, 1, 1, -1, -1, -1, -1, -1, -1, -1, 1, 1, -1, 1, -1, 1, -1, -1, -1, 1, 1, -1, -1, 1, 1, -1, 1, 1, 1, -1, 1, -1, -1, 1, -1, -1, 1, -1, 1, -1, 1, -1, -1, 1, -1, 1, 1, 1, 1, 1, 1, -1, -1, 1, 1, -1, -1, 1, -1, -1, -1, 1, 1, -1, -1, -1, 1, -1, 1, -1, -1, -1, -1, -1, 1, 1, 1, -1, -1, -1, 1, 1, 1, 1, 1, 1, 1, -1, 1, -1, 1, 1, -1, 1, -1, -1, 1, -1, 1, 1, -1, 1, -1, -1, -1, 1, 1, 1, -1, -1, 1, 1, 1, 1, 1, 1, 1, -1, 1, -1, 1, 1, 1, 1, -1, -1, -1, 1, 1, -1, -1, 1, 1, -1, 1, 1, 1, -1, -1, -1, -1, 1, -1, -1, 1, 1, 1, -1, -1, 1, -1, -1, 1, 1, -1, 1, 1, 1, 1, -1, 1, -1, 1, -1, -1, 1, -1, 1, 1, -1, 1, 1, 1, -1, 1, -1, -1, -1, -1, -1, -1, 1, 1, 1, -1, 1, -1, -1, -1, 1, 1, -1, 1, 1, -1, -1, -1, 1, 1, -1, 1, -1, -1, 1, 1, 1, -1, 1, 1, -1, 1, 1, 1, -1, 1, 1, 1, 1, -1, -1, 1, 1, -1, -1, -1, -1, -1, -1, 1, 1, 1, 1, 1, 1, -1, -1, 1, 1, 1, -1, -1, 1, 1, -1, -1, -1, 1, 1, -1, 1, 1, 1, 1, -1, 1, 1, 1, 1, 1, 1, -1, -1, 1, -1, -1, -1, -1, 1, -1, 1, -1, 1, 1, 1, -1, 1, 1, -1, -1, 1, 1, -1, 1, -1, -1, -1, 1, -1, -1, 1, 1, 1, 1, -1, -1, 1, 1, 1, -1, -1, 1, 1, 1, -1, -1, 1, -1, 1, 1, 1, 1, -1, -1, -1, -1, 1, -1, -1, -1, 1, -1, 1, 1, -1, -1, 1, -1, 1, 1, -1, -1, 1, 1, -1, -1, 1, 1, -1, 1, 1, 1, 1, -1, -1, -1, 1, 1, -1, -1, -1, 1, -1, -1, 1, -1, 1, -1, 1, 1, -1, -1, 1, 1, -1, 1, -1, 1, -1, 1, -1, -1, 1, 1, 1, -1, -1, -1, -1, 1, -1, -1, 1, -1, -1, 1, 1, 1, -1, 1, -1, -1, 1, -1, 1, 1, 1, -1, -1, -1, 1, -1, 1, 1, -1, -1, -1, 1, 1, 1, 1, 1, -1, 1, -1, 1, -1, 1, -1, -1, -1, 1, -1, 1, 1, -1, -1, -1, 1, 1, 1, -1, 1, 1, -1, -1, -1, 1, 1, -1, -1, 1, 1, 1, 1, -1, -1, -1, -1, 1, -1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, -1, -1, -1, 1, -1, -1, -1, 1, 1, 1, 1, -1, 1, 1, -1, -1, 1, -1, 1, -1, 1, -1, 1, 1, 1, 1, -1, 1, -1, 1, 1, 1, -1, 1, -1, -1, 1, -1, -1, 1, -1, -1, -1, 1, -1, 1, -1, -1, -1, 1, -1, -1, 1, -1, -1, -1, 1, 1, 1, -1, 1, 1, 1, 1, -1, 1, -1, -1, 1, -1, -1, 1, 1, 1, -1, 1, -1, 1, 1, -1, -1, -1, -1, -1, 1, -1, -1, 1, -1, -1, -1, -1, 1, 1, 1, -1, -1, -1, 1, 1, 1, -1, -1, -1, 1, 1, 1, -1, 1, -1, 1, 1, 1, 1, 1, 1, -1, 1, -1, -1, 1, 1, -1, 1, -1, 1, 1, -1, 1, 1, 1, -1, 1, -1, 1, -1, 1, -1, -1, -1, -1, 1, 1, 1, -1, -1, 1, 1, 1, -1, 1, 1, 1, 1, 1, 1, -1, -1, 1, -1, -1, 1, 1, 1, -1, -1, -1, -1, 1, 1, -1, 1, 1, -1, -1, -1, -1, 1, 1, 1, 1, -1, -1, 1, 1, -1, -1, -1, -1, 1, 1, -1, -1, -1, 1, -1, 1, 1, -1, 1, 1, 1, -1, -1, 1, 1, 1, -1, 1, 1, -1, -1, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 1, 1, 1, -1, -1, 1, 1, -1, 1, 1, -1, -1, 1, 1, -1, -1, -1, 1, 1, 1, -1, -1, 1}; + + for (int i = 0; i < len; i++) + { + expected_output[i] = std::complex(expected_bin[i], 0.0); + } + for (int i = 0; i < len; i++) + { + ASSERT_FLOAT_EQ(gale6b_code[i].real(), expected_output[i].real()); + } +} diff --git a/src/tests/unit-tests/signal-processing-blocks/acquisition/galileo_e5b_pcps_acquisition_test.cc b/src/tests/unit-tests/signal-processing-blocks/acquisition/galileo_e5b_pcps_acquisition_test.cc index 9615e6c55..231ce360d 100644 --- a/src/tests/unit-tests/signal-processing-blocks/acquisition/galileo_e5b_pcps_acquisition_test.cc +++ b/src/tests/unit-tests/signal-processing-blocks/acquisition/galileo_e5b_pcps_acquisition_test.cc @@ -1,5 +1,5 @@ /*! - * \file Galileo_E5b_pcps_acquisition_test.cc + * \file galileo_e5b_pcps_acquisition_test.cc * \brief This class implements an acquisition test for * GalileoE5bPcpsAcquisition class based on some input parameters. * \author Piyush Gupta, 2020. piyush04111999@gmail.com diff --git a/src/tests/unit-tests/signal-processing-blocks/acquisition/galileo_e6_pcps_acquisition_test.cc b/src/tests/unit-tests/signal-processing-blocks/acquisition/galileo_e6_pcps_acquisition_test.cc new file mode 100644 index 000000000..af6c900fe --- /dev/null +++ b/src/tests/unit-tests/signal-processing-blocks/acquisition/galileo_e6_pcps_acquisition_test.cc @@ -0,0 +1,445 @@ +/*! + * \file galileo_e6_pcps_acquisition_test.cc + * \brief This class implements an acquisition test for + * GalileoE6PcpsAcquisition class based on some input parameters. + * \author Carles Fernandez-Prades, 2020 cfernandez(at)cttc.es + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2020 (see AUTHORS file for a list of contributors) + * + * GNSS-SDR is a software defined Global Navigation + * Satellite Systems receiver + * + * This file is part of GNSS-SDR. + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + * ----------------------------------------------------------------------------- + */ + + +#include "Galileo_E6.h" +#include "concurrent_queue.h" +#include "fir_filter.h" +#include "galileo_e6_pcps_acquisition.h" +#include "gnss_block_interface.h" +#include "gnss_sdr_valve.h" +#include "gnss_synchro.h" +#include "in_memory_configuration.h" +#include "pass_through.h" +#include "signal_generator.h" +#include "signal_generator_c.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if HAS_GENERIC_LAMBDA +#else +#include +#endif + +#ifdef GR_GREATER_38 +#include +#else +#include +#endif + +// ######## GNURADIO BLOCK MESSAGE RECEVER ######### +class GalileoE6PcpsAcquisitionTest_msg_rx; + +using GalileoE6PcpsAcquisitionTest_msg_rx_sptr = gnss_shared_ptr; + +GalileoE6PcpsAcquisitionTest_msg_rx_sptr GalileoE6PcpsAcquisitionTest_msg_rx_make(Concurrent_Queue& queue); + +class GalileoE6PcpsAcquisitionTest_msg_rx : public gr::block +{ +private: + friend GalileoE6PcpsAcquisitionTest_msg_rx_sptr GalileoE6PcpsAcquisitionTest_msg_rx_make(Concurrent_Queue& queue); + void msg_handler_channel_events(const pmt::pmt_t msg); + explicit GalileoE6PcpsAcquisitionTest_msg_rx(Concurrent_Queue& queue); + Concurrent_Queue& channel_internal_queue; + +public: + int rx_message; + ~GalileoE6PcpsAcquisitionTest_msg_rx(); //!< Default destructor +}; + + +GalileoE6PcpsAcquisitionTest_msg_rx_sptr GalileoE6PcpsAcquisitionTest_msg_rx_make(Concurrent_Queue& queue) +{ + return GalileoE6PcpsAcquisitionTest_msg_rx_sptr(new GalileoE6PcpsAcquisitionTest_msg_rx(queue)); +} + + +void GalileoE6PcpsAcquisitionTest_msg_rx::msg_handler_channel_events(const pmt::pmt_t msg) +{ + try + { + int64_t message = pmt::to_long(std::move(msg)); + rx_message = message; + channel_internal_queue.push(rx_message); + } + catch (const boost::bad_any_cast& e) + { + std::cout << "msg_handler_telemetry Bad any cast!" << std::endl; + rx_message = 0; + } +} + + +GalileoE6PcpsAcquisitionTest_msg_rx::GalileoE6PcpsAcquisitionTest_msg_rx(Concurrent_Queue& queue) : gr::block("GalileoE6PcpsAcquisitionTest_msg_rx", gr::io_signature::make(0, 0, 0), gr::io_signature::make(0, 0, 0)), channel_internal_queue(queue) +{ + this->message_port_register_in(pmt::mp("events")); + this->set_msg_handler(pmt::mp("events"), +#if HAS_GENERIC_LAMBDA + [this](pmt::pmt_t&& PH1) { msg_handler_channel_events(PH1); }); +#else +#if USE_BOOST_BIND_PLACEHOLDERS + boost::bind(&GalileoE6PcpsAcquisitionTest_msg_rx::msg_handler_channel_events, this, boost::placeholders::_1)); +#else + boost::bind(&GalileoE6PcpsAcquisitionTest_msg_rx::msg_handler_channel_events, this, _1)); +#endif +#endif + rx_message = 0; +} + + +GalileoE6PcpsAcquisitionTest_msg_rx::~GalileoE6PcpsAcquisitionTest_msg_rx() = default; + + +// ########################################################### + +class GalileoE6PcpsAcquisitionTest : public ::testing::Test +{ +protected: + GalileoE6PcpsAcquisitionTest() + { + config = std::make_shared(); + item_size = sizeof(gr_complex); + gnss_synchro = Gnss_Synchro(); + stop = false; + message = 0; + } + + ~GalileoE6PcpsAcquisitionTest() = default; + + void init(); + void start_queue(); + void wait_message(); + void process_message(); + void stop_queue(); + + Concurrent_Queue channel_internal_queue; + std::shared_ptr> queue; + gnss_shared_ptr acquisition; + gr::top_block_sptr top_block; + std::shared_ptr config; + std::thread ch_thread; + + Gnss_Synchro gnss_synchro; + + size_t item_size; + + double fs_in = 32e6; + double expected_doppler_hz = -632.0; + double expected_delay_chips = 1234; + double expected_delay_sec = 51; + + double mse_doppler = 0.0; + double mse_delay = 0.0; + double Pd = 0.0; + double Pfa_p = 0.0; + double Pfa_a = 0.0; + + float integration_time_ms = 1; + float max_doppler_error_hz = 2 / (3 * integration_time_ms * 1e-3); + float max_delay_error_chips = 0.5; + + unsigned int num_of_realizations = 1; + unsigned int realization_counter = 0; + unsigned int detection_counter = 0; + unsigned int correct_estimation_counter = 0; + unsigned int acquired_samples = 0; + unsigned int mean_acq_time_us = 0; + + bool stop; + int message; +}; + + +void GalileoE6PcpsAcquisitionTest::init() +{ + gnss_synchro.Channel_ID = 0; + gnss_synchro.System = 'E'; + std::string signal = "E6"; + signal.copy(gnss_synchro.Signal, 2, 0); + gnss_synchro.PRN = 1; + config->set_property("GNSS-SDR.internal_fs_sps", std::to_string(fs_in)); + config->set_property("SignalSource.fs_hz", std::to_string(fs_in)); + config->set_property("SignalSource.item_type", "gr_complex"); + config->set_property("SignalSource.num_satellites", "1"); + config->set_property("SignalSource.system_0", "E"); + config->set_property("SignalSource.signal_0", "E6"); + config->set_property("SignalSource.PRN_0", "11"); + config->set_property("SignalSource.CN0_dB_0", "50"); + config->set_property("SignalSource.doppler_Hz_0", std::to_string(expected_doppler_hz)); + config->set_property("SignalSource.delay_chips_0", std::to_string(expected_delay_chips)); + config->set_property("SignalSource.delay_sec_0", std::to_string(expected_delay_sec)); + config->set_property("SignalSource.noise_flag", "false"); + config->set_property("SignalSource.data_flag", "false"); + config->set_property("SignalSource.BW_BB", "0.97"); + config->set_property("SignalSource.dump", "false"); + config->set_property("SignalSource.dump_filename", "../data/signal_source.dat"); + config->set_property("InputFilter.implementation", "Fir_Filter"); + config->set_property("InputFilter.input_item_type", "gr_complex"); + config->set_property("InputFilter.output_item_type", "gr_complex"); + config->set_property("InputFilter.taps_item_type", "float"); + config->set_property("InputFilter.number_of_taps", "11"); + config->set_property("InputFilter.number_of_bands", "2"); + config->set_property("InputFilter.band1_begin", "0.0"); + config->set_property("InputFilter.band1_end", "0.97"); + config->set_property("InputFilter.band2_begin", "0.98"); + config->set_property("InputFilter.band2_end", "1.0"); + config->set_property("InputFilter.ampl1_begin", "1.0"); + config->set_property("InputFilter.ampl1_end", "1.0"); + config->set_property("InputFilter.ampl2_begin", "0.0"); + config->set_property("InputFilter.ampl2_end", "0.0"); + config->set_property("InputFilter.band1_error", "1.0"); + config->set_property("InputFilter.band2_error", "1.0"); + config->set_property("InputFilter.filter_type", "bandpass"); + config->set_property("InputFilter.grid_density", "16"); + config->set_property("Acquisition_E6.implementation", "Galileo_E6_PCPS_Acquisition"); + config->set_property("Acquisition_E6.item_type", "gr_complex"); + config->set_property("Acquisition_E6.coherent_integration_time_ms", std::to_string(integration_time_ms)); + config->set_property("Acquisition_E6.dump", "true"); + config->set_property("Acquisition_E6.dump_filename", "./acquisition"); + config->set_property("Acquisition_E6.pfa", "0.01"); + config->set_property("Acquisition_E6.doppler_max", "10000"); + config->set_property("Acquisition_E6.doppler_step", "250"); + config->set_property("Acquisition_E6.repeat_satellite", "false"); +} + +void GalileoE6PcpsAcquisitionTest::start_queue() +{ + stop = false; + ch_thread = std::thread(&GalileoE6PcpsAcquisitionTest::wait_message, this); +} + + +void GalileoE6PcpsAcquisitionTest::wait_message() +{ + std::chrono::time_point start, end; + std::chrono::duration elapsed_seconds(0); + + while (!stop) + { + acquisition->reset(); + + start = std::chrono::system_clock::now(); + + channel_internal_queue.wait_and_pop(message); + + end = std::chrono::system_clock::now(); + elapsed_seconds = end - start; + + mean_acq_time_us += elapsed_seconds.count() * 1e6; + + process_message(); + } +} + + +void GalileoE6PcpsAcquisitionTest::process_message() +{ + if (message == 1) + { + double delay_error_chips = std::abs(static_cast(expected_delay_chips) - static_cast(gnss_synchro.Acq_delay_samples - 5) * GALILEO_E6_B_CODE_LENGTH_CHIPS / (static_cast(fs_in) * 1e-3)); + double doppler_error_hz = std::abs(expected_doppler_hz - gnss_synchro.Acq_doppler_hz); + // The term -5 is here to correct the additional delay introduced by the FIR filter + /* + double delay_error_chips = abs((double)expected_delay_chips - (double)(gnss_synchro.Acq_delay_samples-5)*10230.0/((double)fs_in*1e-3)); + double doppler_error_hz = abs(expected_doppler_hz - gnss_synchro.Acq_doppler_hz); + */ + detection_counter++; + + mse_delay += std::pow(delay_error_chips, 2); + mse_doppler += std::pow(doppler_error_hz, 2); + + if ((delay_error_chips < max_delay_error_chips) && (doppler_error_hz < max_doppler_error_hz)) + { + correct_estimation_counter++; + } + } + + realization_counter++; + + // std::cout << correct_estimation_counter << "correct estimation counter\n"; + std::cout << "Progress: " << round(static_cast(realization_counter / num_of_realizations * 100)) << "% \r" << std::flush; + // std::cout << message << "message'\n'"; + if (realization_counter == num_of_realizations) + { + mse_delay /= num_of_realizations; + mse_doppler /= num_of_realizations; + + Pd = static_cast(correct_estimation_counter) / static_cast(num_of_realizations); + Pfa_a = static_cast(detection_counter) / static_cast(num_of_realizations); + Pfa_p = static_cast(detection_counter - correct_estimation_counter) / static_cast(num_of_realizations); + + mean_acq_time_us /= num_of_realizations; + + stop_queue(); + top_block->stop(); + } +} + + +void GalileoE6PcpsAcquisitionTest::stop_queue() +{ + stop = true; +} + + +TEST_F(GalileoE6PcpsAcquisitionTest, Instantiate) +{ + init(); + acquisition = gnss_make_shared(config.get(), "Acquisition_E6", 1, 0); +} + + +TEST_F(GalileoE6PcpsAcquisitionTest, ConnectAndRun) +{ + int nsamples = 21000 * 3; + std::chrono::time_point begin, end; + std::chrono::duration elapsed_seconds(0); + + queue = std::make_shared>(); + top_block = gr::make_top_block("Acquisition test"); + + init(); + + acquisition = gnss_make_shared(config.get(), "Acquisition_E6", 1, 0); + + auto msg_rx = GalileoE6PcpsAcquisitionTest_msg_rx_make(channel_internal_queue); + + ASSERT_NO_THROW({ + acquisition->connect(top_block); + auto source = gr::analog::sig_source_c::make(fs_in, gr::analog::GR_SIN_WAVE, 1000, 1, gr_complex(0)); + auto valve = gnss_sdr_make_valve(sizeof(gr_complex), nsamples, queue.get()); + top_block->connect(source, 0, valve, 0); + top_block->connect(valve, 0, acquisition->get_left_block(), 0); + top_block->msg_connect(acquisition->get_right_block(), pmt::mp("events"), msg_rx, pmt::mp("events")); + }) << "Failure connecting the blocks of acquisition test."; + + EXPECT_NO_THROW({ + begin = std::chrono::system_clock::now(); + top_block->run(); // Start threads and wait + end = std::chrono::system_clock::now(); + elapsed_seconds = end - begin; + }) << "Failure running the top_block."; + + std::cout << "Processed " << nsamples << " samples in " << elapsed_seconds.count() * 1e6 << " microseconds" << std::endl; +} + + +TEST_F(GalileoE6PcpsAcquisitionTest, ValidationOfResults) +{ + top_block = gr::make_top_block("Acquisition test"); + + init(); + + acquisition = gnss_make_shared(config.get(), "Acquisition_E6", 1, 0); + + std::shared_ptr input_filter = std::make_shared(config.get(), "InputFilter", 1, 1); + auto msg_rx = GalileoE6PcpsAcquisitionTest_msg_rx_make(channel_internal_queue); + queue = std::make_shared>(); + + ASSERT_NO_THROW({ + acquisition->set_channel(0); + }) << "Failure setting channel."; + + ASSERT_NO_THROW({ + acquisition->set_gnss_synchro(&gnss_synchro); + }) << "Failure setting gnss_synchro."; + + ASSERT_NO_THROW({ + acquisition->set_doppler_max(5000); + }) << "Failure setting doppler_max."; + + ASSERT_NO_THROW({ + acquisition->set_doppler_step(100); + }) << "Failure setting doppler_step."; + + ASSERT_NO_THROW({ + acquisition->connect(top_block); + }) << "Failure connecting acquisition to the top_block."; + + ASSERT_NO_THROW({ + std::shared_ptr signal_generator = std::make_shared(config.get(), "SignalSource", 0, 1, queue.get()); + std::shared_ptr filter = std::make_shared(config.get(), "InputFilter", 1, 1); + std::shared_ptr signal_source = std::make_shared(signal_generator, filter, "SignalSource", queue.get()); + filter->connect(top_block); + signal_source->connect(top_block); + top_block->connect(signal_source->get_right_block(), 0, acquisition->get_left_block(), 0); + top_block->msg_connect(acquisition->get_right_block(), pmt::mp("events"), msg_rx, pmt::mp("events")); + }) << "Failure connecting the blocks of acquisition test."; + + acquisition->reset(); + acquisition->init(); + + // i = 0 --> satellite in acquisition is visible + // i = 1 --> satellite in acquisition is not visible + for (unsigned int i = 0; i < 1; i++) + { + init(); + + switch (i) + { + case 0: + { + gnss_synchro.PRN = 11; // present + break; + } + case 1: + { + gnss_synchro.PRN = 19; // not present + break; + } + } + + acquisition->set_gnss_synchro(&gnss_synchro); + acquisition->set_local_code(); + acquisition->set_state(1); + start_queue(); + + EXPECT_NO_THROW({ + top_block->run(); // Start threads and wait + }) << "Failure running the top_block."; + + stop_queue(); + + ch_thread.join(); + + if (i == 0) + { + EXPECT_EQ(1, message) << "Acquisition failure. Expected message: 1=ACQ SUCCESS."; + if (message == 1) + { + // std::cout << gnss_synchro.Acq_delay_samples << "acq delay'\n'"; + // std::cout << gnss_synchro.Acq_doppler_hz << "acq doppler'\n'"; + EXPECT_EQ(static_cast(1), correct_estimation_counter) << "Acquisition failure. Incorrect parameters estimation."; + } + } + else if (i == 1) + { + EXPECT_EQ(2, message) << "Acquisition failure. Expected message: 2=ACQ FAIL."; + } + } +}