diff --git a/include/iridium/CMakeLists.txt b/include/iridium/CMakeLists.txt index 0a5463b..fcd1dc6 100644 --- a/include/iridium/CMakeLists.txt +++ b/include/iridium/CMakeLists.txt @@ -15,6 +15,7 @@ install(FILES iuchar_to_complex.h tagged_burst_to_pdu.h burst_downmix.h + burst_downmix_next.h pdu_null_sink.h iridium_qpsk_demod.h pdu_round_robin.h diff --git a/include/iridium/burst_downmix_next.h b/include/iridium/burst_downmix_next.h new file mode 100644 index 0000000..3934101 --- /dev/null +++ b/include/iridium/burst_downmix_next.h @@ -0,0 +1,63 @@ +/* -*- c++ -*- */ +/* + * Copyright 2020 Free Software Foundation, Inc. + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3, or (at your option) + * any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#ifndef INCLUDED_IRIDIUM_BURST_DOWNMIX_H +#define INCLUDED_IRIDIUM_BURST_DOWNMIX_H + +#include +#include + +namespace gr { +namespace iridium { + +/*! + * \brief <+description of block+> + * \ingroup iridium + * + */ +class IRIDIUM_API burst_downmix_next : virtual public gr::block +{ +public: + typedef std::shared_ptr sptr; + + /*! + * \brief Return a shared_ptr to a new instance of iridium::burst_downmix_next. + * + * To avoid accidental use of raw pointers, iridium::burst_downmix_next's + * constructor is in a private implementation + * class. iridium::burst_downmix_next::make is the public interface for + * creating new instances. + */ + static sptr make(int output_sample_rate, + int search_depth, + size_t hard_max_queue_len, + const std::vector& input_taps, + const std::vector& start_finder_taps, + bool handle_multiple_frames_per_burst); + + virtual uint64_t get_n_dropped_bursts() = 0; + virtual size_t get_input_queue_size() = 0; + virtual void debug_id(uint64_t id) = 0; +}; + +} // namespace iridium +} // namespace gr + +#endif /* INCLUDED_IRIDIUM_BURST_DOWNMIX_H */ diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 3f11b4f..54e1a97 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -16,6 +16,7 @@ list(APPEND iridium_sources iuchar_to_complex_impl.cc tagged_burst_to_pdu_impl.cc burst_downmix_impl.cc + burst_downmix_next_impl.cc pdu_null_sink_impl.cc iridium_qpsk_demod_impl.cc pdu_round_robin_impl.cc diff --git a/lib/burst_downmix_impl.cc b/lib/burst_downmix_impl.cc index 7d655cf..0d30a95 100644 --- a/lib/burst_downmix_impl.cc +++ b/lib/burst_downmix_impl.cc @@ -733,6 +733,7 @@ int burst_downmix_impl::process_next_frame(float sample_rate, pdu_meta = pmt::dict_add(pdu_meta, pmt::mp("id"), pmt::mp(sub_id)); pdu_meta = pmt::dict_add(pdu_meta, pmt::mp("noise"), pmt::mp(noise)); pdu_meta = pmt::dict_add(pdu_meta, pmt::mp("magnitude"), pmt::mp(magnitude)); + pdu_meta = pmt::dict_add(pdu_meta, pmt::mp("next"), pmt::mp(false)); if (d_debug) { printf("center_frequency=%f, uw_start=%u\n", center_frequency, uw_start); diff --git a/lib/burst_downmix_next_impl.cc b/lib/burst_downmix_next_impl.cc new file mode 100644 index 0000000..4076323 --- /dev/null +++ b/lib/burst_downmix_next_impl.cc @@ -0,0 +1,940 @@ +/* -*- c++ -*- */ +/* + * Copyright 2020 Free Software Foundation, Inc. + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3, or (at your option) + * any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "iridium.h" + +#include +#include +#include +#include + +#include "burst_downmix_next_impl.h" + +#include +#include +#include + +namespace gr { +namespace iridium { + + +void write_data_c2(const gr_complex* data, size_t len, char* name, int num) +{ + char filename[256]; + sprintf(filename, "/tmp/signals/%s-%d.cfile", name, num); + FILE* fp = fopen(filename, "wb"); + fwrite(data, sizeof(gr_complex), len, fp); + fclose(fp); +} + +void write_data_f2(const float* data, size_t len, char* name, int num) +{ + char filename[256]; + sprintf(filename, "/tmp/signals/%s-%d.f32", name, num); + FILE* fp = fopen(filename, "wb"); + fwrite(data, sizeof(float), len, fp); + fclose(fp); +} + +float sinc2(float x) +{ + if (x == 0) { + return 1; + } + return sin(M_PI * x) / (M_PI * x); +} + +float raised_cosine_h2(float t, float Ts, float alpha) +{ + if (fabs(t) == Ts / (2 * alpha)) { + return M_PI / (4 * Ts) * sinc2(1 / (2 * alpha)); + } + + return 1 / Ts * sinc2(t / Ts) * cos(M_PI * alpha * t / Ts) / + (1 - powf((2 * alpha * t / Ts), 2)); +} + +std::vector rcosfilter2(int ntaps, float alpha, float Ts, float Fs) +{ + std::vector taps(ntaps); + + for (int i = -ntaps / 2 + 1; i < ntaps / 2 + 1; i++) { + taps[i + (ntaps + 1) / 2 - 1] = raised_cosine_h2(i / Fs, Ts, alpha) * Ts; + } + + return taps; +} + +burst_downmix_next::sptr burst_downmix_next::make(int sample_rate, + int search_depth, + size_t hard_max_queue_len, + const std::vector& input_taps, + const std::vector& start_finder_taps, + bool handle_multiple_frames_per_burst) +{ + return gnuradio::get_initial_sptr( + new burst_downmix_next_impl(sample_rate, + search_depth, + hard_max_queue_len, + input_taps, + start_finder_taps, + handle_multiple_frames_per_burst)); +} + + +/* + * The private constructor + */ +burst_downmix_next_impl::burst_downmix_next_impl(int output_sample_rate, + int search_depth, + size_t hard_max_queue_len, + const std::vector& input_taps, + const std::vector& start_finder_taps, + bool handle_multiple_frames_per_burst) + : gr::block("burst_downmix_next", + gr::io_signature::make(0, 0, 0), + gr::io_signature::make(0, 0, 0)), + d_output_sample_rate(output_sample_rate), + d_output_samples_per_symbol(d_output_sample_rate / ::iridium::SYMBOLS_PER_SECOND_NEXT), + d_max_burst_size(0), + d_search_depth(search_depth), + d_pre_start_samples(int(0.1e-3 * d_output_sample_rate)), + d_n_dropped_bursts(0), + d_debug_id(-1), + + // Take the FFT over the (short) preamble + 10 symbols from the unique word (UW) + // (Frames with a 64 symbol preamble will use 26 symbols of the preamble) + d_cfo_est_fft_size(pow( + 2, + int(log(d_output_samples_per_symbol*(::iridium::PREAMBLE_LENGTH_SHORT + 10)) / + log(2)))), + + d_fft_over_size_facor(16), + d_sync_search_len((::iridium::PREAMBLE_LENGTH_LONG + ::iridium::UW_LENGTH + 8) * + d_output_samples_per_symbol), + d_hard_max_queue_len(hard_max_queue_len), + d_handle_multiple_frames_per_burst(handle_multiple_frames_per_burst), + d_debug(false), + //d_debug(true), + + d_frame(NULL), + d_tmp_a(NULL), + d_tmp_b(NULL), + d_dl_preamble_reversed_conj_fft(NULL), + d_ul_preamble_reversed_conj_fft(NULL), + + d_magnitude_f(NULL), + d_magnitude_filtered_f(NULL), + d_cfo_est_window_f(NULL), + + d_corr_fft(NULL), + d_corr_dl_ifft(NULL), + d_corr_ul_ifft(NULL), + + d_input_fir(input_taps), + d_start_finder_fir(start_finder_taps), + d_rrc_fir(gr::filter::firdes::root_raised_cosine( + 1.0, d_output_sample_rate, ::iridium::SYMBOLS_PER_SECOND_NEXT, .2, 51)), + d_rc_fir( + rcosfilter2(51, 0.4, 1. / ::iridium::SYMBOLS_PER_SECOND_NEXT, d_output_sample_rate)), + d_cfo_est_fft(d_cfo_est_fft_size * d_fft_over_size_facor) +{ + d_dl_preamble_reversed_conj = generate_sync_word(::iridium::direction::DOWNLINK); + d_ul_preamble_reversed_conj = generate_sync_word(::iridium::direction::UPLINK); + + initialize_cfo_est_fft(); + + initialize_correlation_filter(); + + message_port_register_in(pmt::mp("cpdus")); + message_port_register_out(pmt::mp("cpdus")); + message_port_register_out(pmt::mp("burst_handled")); + + set_msg_handler(pmt::mp("cpdus"), [this](pmt::pmt_t msg) { this->handler(msg); }); + + if (d_debug) { + std::cout << "Start filter size:" << d_start_finder_fir.ntaps() + << " Search depth:" << d_search_depth << "\n"; + } +} + +/* + * Our virtual destructor. + */ +burst_downmix_next_impl::~burst_downmix_next_impl() +{ + if (d_frame) { + volk_free(d_frame); + } + if (d_tmp_a) { + volk_free(d_tmp_a); + } + if (d_tmp_b) { + volk_free(d_tmp_b); + } + if (d_magnitude_f) { + volk_free(d_magnitude_f); + } + if (d_magnitude_filtered_f) { + volk_free(d_magnitude_filtered_f); + } + if (d_cfo_est_window_f) { + free(d_cfo_est_window_f); + } + if (d_dl_preamble_reversed_conj_fft) { + volk_free(d_dl_preamble_reversed_conj_fft); + } + if (d_ul_preamble_reversed_conj_fft) { + volk_free(d_ul_preamble_reversed_conj_fft); + } + if (d_corr_fft) { + delete d_corr_fft; + } + if (d_corr_dl_ifft) { + delete d_corr_dl_ifft; + } + if (d_corr_ul_ifft) { + delete d_corr_ul_ifft; + } +} + +volk::vector +burst_downmix_next_impl::generate_sync_word(::iridium::direction direction) +{ + gr_complex s1 = gr_complex(-1, -1); + gr_complex s0 = -s1; + std::vector sync_word; + //std::vector uw_dl = { s0, s1, s1, s1, s1, s0, s0, s0, s1, s0, s0, s1 }; + //std::vector uw_dl = { s0, s0, s0, s0, s0, s1, s1, s0, s1, s1, s0, s1 s0, s1, s0, s1, s1, s0, s0, s1, s0, s1, s1, s1, s1, s0, s1, s1, s1, s0, s1, s0, s0}; + std::vector uw_dl = { s1, s1, s0, s1, s1, s0, s1, s0, s1, s0, s1, s1 };//, s0, s0, s1, s0, s1, s1, s1, s1, s0, s1, s1, s1, s0, s1, s0, s0}; + std::vector uw_ul = { s1, s1, s0, s0, s0, s1, s0, s0, s1, s0, s1, s1 }; + int i; + + if (direction == ::iridium::direction::DOWNLINK) { + sync_word.insert(std::end(sync_word), std::begin(uw_dl), std::end(uw_dl)); + } else if (direction == ::iridium::direction::UPLINK) { + sync_word.insert(std::end(sync_word), std::begin(uw_ul), std::end(uw_ul)); + } + +#if 1 + volk::vector sync_word_padded; + std::vector padding; + for (i = 0; i < d_output_samples_per_symbol - 1; i++) { + padding.push_back(0); + } + + for (gr_complex s : sync_word) { + sync_word_padded.push_back(s); + sync_word_padded.insert( + std::end(sync_word_padded), std::begin(padding), std::end(padding)); + } + + // Remove the padding after the last symbol + sync_word_padded.erase(std::end(sync_word_padded) - d_output_samples_per_symbol + 1, + std::end(sync_word_padded)); +#endif +#if 0 + fft::fft_complex_fwd fft_engine(sync_word.size()); + memcpy(fft_engine.get_inbuf(), &sync_word[0], sizeof(gr_complex) * sync_word.size()); + fft_engine.execute(); + + fft::fft_complex_rev ifft_engine(sync_word.size() * d_output_samples_per_symbol); + memset(ifft_engine.get_inbuf(), 0, sizeof(gr_complex) * sync_word.size() * d_output_samples_per_symbol); + memcpy(ifft_engine.get_inbuf(), fft_engine.get_outbuf(), sizeof(gr_complex) * sync_word.size() / 2); + memcpy(ifft_engine.get_inbuf() + sync_word.size() * d_output_samples_per_symbol - sync_word.size()/2 ,fft_engine.get_outbuf() + sync_word.size()/2, sizeof(gr_complex) * sync_word.size() / 2); + ifft_engine.execute(); + std::vector sync_word_padded(ifft_engine.get_outbuf(), ifft_engine.get_outbuf() + + sync_word.size() * d_output_samples_per_symbol); +#endif + +#if 0 + int half_rrc_size = (d_rrc_fir.ntaps() - 1) / 2; + std::vector tmp(sync_word_padded); + + for(i = 0; i < half_rrc_size; i++) { + tmp.push_back(0); + tmp.insert(tmp.begin(), 0); + } + + // TODO: Maybe do a 'full' convolution including the borders + d_rrc_fir.filterN(&sync_word_padded[0], &tmp[0], sync_word_padded.size()); +#endif + +#if 1 + int half_rc_size = (d_rc_fir.ntaps() - 1) / 2; + volk::vector tmp(sync_word_padded); + + for (i = 0; i < half_rc_size; i++) { + tmp.push_back(0); + tmp.insert(tmp.begin(), 0); + } + + // TODO: Maybe do a 'full' convolution including the borders + d_rc_fir.filterN(&sync_word_padded[0], &tmp[0], sync_word_padded.size()); +#endif + + if (d_debug) { + std::cout << "Sync Word Unpadded: "; + for (gr_complex s : sync_word) { + std::cout << s << ", "; + } + std::cout << std::endl; + + std::cout << "Sync Word Padded: "; + for (gr_complex s : sync_word_padded) { + std::cout << s << ", "; + } + std::cout << std::endl; + } + + std::reverse(sync_word_padded.begin(), sync_word_padded.end()); + volk_32fc_conjugate_32fc( + &sync_word_padded[0], &sync_word_padded[0], sync_word_padded.size()); + return sync_word_padded; +} + +void burst_downmix_next_impl::initialize_cfo_est_fft(void) +{ + // Only the first d_cfo_est_fft_size samples will be filled with data. + // Zero out everyting first. + memset(d_cfo_est_fft.get_inbuf(), + 0, + d_cfo_est_fft_size * d_fft_over_size_facor * sizeof(gr_complex)); + + // Compute window and move it into aligned buffer + std::vector window = + fft::window::build(fft::window::WIN_BLACKMAN, d_cfo_est_fft_size, 0); + d_cfo_est_window_f = + (float*)volk_malloc(sizeof(float) * d_cfo_est_fft_size, volk_get_alignment()); + memcpy(d_cfo_est_window_f, &window[0], sizeof(float) * d_cfo_est_fft_size); + + if (d_debug) { + printf("fft_length=%d (%d)\n", + d_cfo_est_fft_size, + d_output_samples_per_symbol * (::iridium::PREAMBLE_LENGTH_SHORT + 10)); + } +} + +void burst_downmix_next_impl::initialize_correlation_filter(void) +{ + // Based on code from synchronizer_v4_impl.cc in gr-burst + + // Make the FFT size a power of two + int corr_fft_size_target = d_sync_search_len + d_dl_preamble_reversed_conj.size() - 1; + d_corr_fft_size = pow(2, (int)(std::ceil(log(corr_fft_size_target) / log(2)))); + + // TODO: We could increase the search size for free + // d_sync_search_len = d_corr_fft_size - d_dl_preamble_reversed_conj.size() + 1; + + if (d_debug) { + std::cout << "Conv FFT size:" << d_corr_fft_size << std::endl; + } + + // Allocate space for the pre transformed filters + d_dl_preamble_reversed_conj_fft = (gr_complex*)volk_malloc( + d_corr_fft_size * sizeof(gr_complex), volk_get_alignment()); + d_ul_preamble_reversed_conj_fft = (gr_complex*)volk_malloc( + d_corr_fft_size * sizeof(gr_complex), volk_get_alignment()); + + // Temporary FFT to pre transform the filters + fft::fft_complex_fwd fft_engine(d_corr_fft_size); + memset(fft_engine.get_inbuf(), 0, sizeof(gr_complex) * d_corr_fft_size); + + int sync_word_len = d_dl_preamble_reversed_conj.size(); + + // Transform the filters + memcpy(fft_engine.get_inbuf(), + &d_dl_preamble_reversed_conj[0], + sizeof(gr_complex) * sync_word_len); + fft_engine.execute(); + memcpy(d_dl_preamble_reversed_conj_fft, + fft_engine.get_outbuf(), + sizeof(gr_complex) * d_corr_fft_size); + + memcpy(fft_engine.get_inbuf(), + &d_ul_preamble_reversed_conj[0], + sizeof(gr_complex) * sync_word_len); + fft_engine.execute(); + memcpy(d_ul_preamble_reversed_conj_fft, + fft_engine.get_outbuf(), + sizeof(gr_complex) * d_corr_fft_size); + + // Update the size of the work FFTs + // TODO: This could be moved to the initialization list + d_corr_fft = new fft::fft_complex_fwd(d_corr_fft_size); + d_corr_dl_ifft = new fft::fft_complex_rev(d_corr_fft_size); + d_corr_ul_ifft = new fft::fft_complex_rev(d_corr_fft_size); + + // The inputs need to zero, as we might not use it completely + memset(d_corr_fft->get_inbuf(), 0, sizeof(gr_complex) * d_corr_fft_size); +} + +void burst_downmix_next_impl::update_buffer_sizes(size_t burst_size) +{ + if (burst_size > d_max_burst_size) { + d_max_burst_size = burst_size; + if (d_frame) { + volk_free(d_frame); + } + d_frame = (gr_complex*)volk_malloc(d_max_burst_size * sizeof(gr_complex), + volk_get_alignment()); + + if (d_tmp_a) { + volk_free(d_tmp_a); + } + d_tmp_a = (gr_complex*)volk_malloc(d_max_burst_size * sizeof(gr_complex), + volk_get_alignment()); + + if (d_tmp_b) { + volk_free(d_tmp_b); + } + d_tmp_b = (gr_complex*)volk_malloc(d_max_burst_size * sizeof(gr_complex), + volk_get_alignment()); + + if (d_magnitude_f) { + volk_free(d_magnitude_f); + } + d_magnitude_f = + (float*)volk_malloc(d_max_burst_size * sizeof(float), volk_get_alignment()); + + if (d_magnitude_filtered_f) { + volk_free(d_magnitude_filtered_f); + } + d_magnitude_filtered_f = + (float*)volk_malloc(d_max_burst_size * sizeof(float), volk_get_alignment()); + } +} + +size_t burst_downmix_next_impl::get_input_queue_size() { return nmsgs(pmt::mp("cpdus")); } + +uint64_t burst_downmix_next_impl::get_n_dropped_bursts() { return d_n_dropped_bursts; } + +void burst_downmix_next_impl::debug_id(uint64_t id) { d_debug_id = id; } + +// Maps an index in [-N/2 .. (N/2)-1] notation to [0 .. N-1] notation +int burst_downmix_next_impl::fft_shift_index(int index, int fft_size) +{ + // Clamp the input to [-N/2 .. (N/2)-1] + index = std::max(index, -fft_size / 2); + index = std::min(index, fft_size / 2 - 1); + + if (index < 0) { + index += fft_size; + } + return index; +} + +// Maps an index in [0 .. N-1] notation to [-N/2 .. (N/2)-1] notation +int burst_downmix_next_impl::fft_unshift_index(int index, int fft_size) +{ + // Clamp the input to [0 .. N-1] + index = std::max(index, 0); + index = std::min(index, fft_size - 1); + + if (index >= fft_size / 2) { + index -= fft_size; + } + return index; +} + +float burst_downmix_next_impl::interpolate(float alpha, float beta, float gamma) +{ + const float correction = 0.5 * (alpha - gamma) / (alpha - 2 * beta + gamma); + return correction; +} + +int burst_downmix_next_impl::process_next_frame(float sample_rate, + double center_frequency, + uint64_t timestamp, + uint64_t sub_id, + size_t burst_size, + int start, + float noise, + float magnitude) +{ + /* + * Use the center frequency to make some assumptions about the burst. + */ + int max_frame_length = 0; + int min_frame_length = 0; + + // Simplex transmissions and broadcast frames might have a 64 symbol preamble. + // We ignore that and cut away the extra 48 symbols. + if (center_frequency > ::iridium::SIMPLEX_FREQUENCY_MIN) { + // Frames above this frequency must be downlink and simplex frames. + // XXX: If the SDR is not configured well, there might be aliasing from low + // frequencies in this region. + max_frame_length = + ::iridium::MAX_FRAME_LENGTH_SIMPLEX * d_output_samples_per_symbol; + min_frame_length = + (::iridium::MIN_FRAME_LENGTH_SIMPLEX)*d_output_samples_per_symbol; + } else { + //max_frame_length = + // ::iridium::MAX_FRAME_LENGTH_NORMAL * d_output_samples_per_symbol; + + max_frame_length = + 245 * d_output_samples_per_symbol; // 248 symbols in 8.267 ms. We remove the first 4 or 3 (preamble?) + min_frame_length = + (::iridium::MIN_FRAME_LENGTH_NORMAL)*d_output_samples_per_symbol; + } + + if (burst_size - start < min_frame_length) { + return 0; + } + + /* + * Find the fine CFO estimate using an FFT over the preamble and the first symbols + * of the unique word. + * The signal gets squared to remove the BPSK modulation from the unique word. + */ + + if (burst_size - start < d_cfo_est_fft_size) { + // There are not enough samples available to run the FFT. + return 0; + } + + // TODO: Not sure which way to square is faster. + // volk_32fc_x2_multiply_32fc(d_tmp_a, d_frame + start, d_frame + start, + // d_cfo_est_fft_size); + volk_32fc_s32f_power_32fc(d_tmp_a, d_frame + start, 2, d_cfo_est_fft_size); + volk_32fc_32f_multiply_32fc( + d_cfo_est_fft.get_inbuf(), d_tmp_a, d_cfo_est_window_f, d_cfo_est_fft_size); + d_cfo_est_fft.execute(); + volk_32fc_magnitude_squared_32f(d_magnitude_f, + d_cfo_est_fft.get_outbuf(), + d_cfo_est_fft_size * d_fft_over_size_facor); + float* x = std::max_element( + d_magnitude_f, d_magnitude_f + d_cfo_est_fft_size * d_fft_over_size_facor); + const int max_index_shifted = x - d_magnitude_f; + + const int max_index = + fft_unshift_index(max_index_shifted, d_cfo_est_fft_size * d_fft_over_size_facor); + if (d_debug) { + printf("max_index=%d\n", max_index); + } + + // Interpolate the result of the FFT to get a finer resolution. + // see + // http://www.dsprelated.com/dspbooks/sasp/Quadratic_Interpolation_Spectral_Peaks.html + // TODO: The window should be Gaussian and the output should be put on a log scale + // https://ccrma.stanford.edu/~jos/sasp/Quadratic_Interpolation_Spectral_Peaks.html + + // To access d_magnitude_f we have to shift the index back to how the FFT output works + const int alpha_index = + fft_shift_index(max_index - 1, d_cfo_est_fft_size * d_fft_over_size_facor); + const int beta_index = + fft_shift_index(max_index, d_cfo_est_fft_size * d_fft_over_size_facor); + const int gamma_index = + fft_shift_index(max_index + 1, d_cfo_est_fft_size * d_fft_over_size_facor); + const float alpha = d_magnitude_f[alpha_index]; + const float beta = d_magnitude_f[beta_index]; + const float gamma = d_magnitude_f[gamma_index]; + const float interpolated_index = max_index + interpolate(alpha, beta, gamma); + + // Normalize the result. + // Divide by two to remove the effect of the squaring operation before. + float center_offset = + interpolated_index / (d_cfo_est_fft_size * d_fft_over_size_facor) / 2; + + if (d_debug) { + printf("interpolated_index=%f center_offset=%f (%f)\n", + interpolated_index, + center_offset, + center_offset * d_output_sample_rate); + } + + + /* + * Shift the burst again using the result of the FFT. + */ + float phase_inc = 2 * M_PI * -center_offset; + d_r.set_phase_incr(exp(gr_complex(0, phase_inc))); + d_r.set_phase(gr_complex(1, 0)); + d_r.rotateN(d_tmp_a, d_frame + start, burst_size - start); + center_frequency += center_offset * sample_rate; + + if (d_debug) { + write_data_c2(d_tmp_a, + burst_size - start, + (char*)"signal-filtered-deci-cut-start-shift", + sub_id); + } + + // Make some room at the start and the end, so the RRC can run + int half_rrc_size = (d_rrc_fir.ntaps() - 1) / 2; + memcpy(d_tmp_b + half_rrc_size, d_tmp_a, (burst_size - start) * sizeof(gr_complex)); + memset(d_tmp_b, 0, half_rrc_size * sizeof(gr_complex)); + memset(d_tmp_b + half_rrc_size + burst_size - start, + 0, + half_rrc_size * sizeof(gr_complex)); + /* + * Apply the RRC filter. + */ + d_rrc_fir.filterN(d_tmp_a, d_tmp_b, burst_size - start); + + if (d_debug) { + write_data_c2(d_tmp_a, + burst_size - start, + (char*)"signal-filtered-deci-cut-start-shift-rrc", + sub_id); + } + + /* + * Use a correlation to find the start of the sync word. + * Uses an FFT to perform the correlation. + */ + + memcpy(d_corr_fft->get_inbuf(), d_tmp_a, sizeof(gr_complex) * d_sync_search_len); + d_corr_fft->execute(); + + // We use the initial FFT for both correlations (DL and UL) + volk_32fc_x2_multiply_32fc(d_corr_dl_ifft->get_inbuf(), + d_corr_fft->get_outbuf(), + &d_dl_preamble_reversed_conj_fft[0], + d_corr_fft_size); + volk_32fc_x2_multiply_32fc(d_corr_ul_ifft->get_inbuf(), + d_corr_fft->get_outbuf(), + &d_ul_preamble_reversed_conj_fft[0], + d_corr_fft_size); + d_corr_dl_ifft->execute(); + d_corr_ul_ifft->execute(); + + int corr_offset_dl; + int corr_offset_ul; + + float correction_dl = 0; + float correction_ul = 0; + + // Find the peaks of the correlations + volk_32fc_magnitude_squared_32f( + d_magnitude_f, d_corr_dl_ifft->get_outbuf(), d_corr_fft_size); + float* max_dl_p = std::max_element(d_magnitude_f, d_magnitude_f + d_corr_fft_size); + corr_offset_dl = max_dl_p - d_magnitude_f; + if (corr_offset_dl > 0) { + correction_dl = interpolate(d_magnitude_f[corr_offset_dl - 1], + d_magnitude_f[corr_offset_dl], + d_magnitude_f[corr_offset_dl + 1]); + } + float max_dl = *max_dl_p; + + volk_32fc_magnitude_squared_32f( + d_magnitude_f, d_corr_ul_ifft->get_outbuf(), d_corr_fft_size); + float* max_ul_p = std::max_element(d_magnitude_f, d_magnitude_f + d_corr_fft_size); + corr_offset_ul = max_ul_p - d_magnitude_f; + if (corr_offset_ul > 0) { + correction_ul = interpolate(d_magnitude_f[corr_offset_ul - 1], + d_magnitude_f[corr_offset_ul], + d_magnitude_f[corr_offset_ul + 1]); + } + float max_ul = *max_ul_p; + + gr_complex corr_result; + int corr_offset; + float correction; + ::iridium::direction direction; + + if (max_dl > max_ul || 1) { + direction = ::iridium::direction::DOWNLINK; + corr_offset = corr_offset_dl; + correction = correction_dl; + corr_result = d_corr_dl_ifft->get_outbuf()[corr_offset]; + } else { + direction = ::iridium::direction::UPLINK; + corr_offset = corr_offset_ul; + correction = correction_ul; + corr_result = d_corr_ul_ifft->get_outbuf()[corr_offset]; + } + + + if (d_debug) { + printf("Conv max index = %d\n", corr_offset); + } + + // Careful: The correlation might have found the start of the sync word + // before the first sample => preamble_offset might be negative + int preamble_offset = corr_offset - d_dl_preamble_reversed_conj.size() + 1; + int uw_start = + preamble_offset; + + // If the UW starts at an offset < 0, we will not be able to demodulate the signal + if (uw_start < 0) { + // TODO: Log a warning. + return 0; + } + + size_t frame_size = std::min((int)burst_size - start, uw_start + max_frame_length); + int consumed_samples = frame_size; + + /* + * Rotate the phase so the demodulation has a starting point. + */ + d_r.set_phase_incr(exp(gr_complex(0, 0))); + d_r.set_phase(std::conj(corr_result / abs(corr_result))); + //d_r.set_phase(exp(gr_complex(0, 2 * M_PI * 0.25 * 1))); + //d_r.set_phase(exp(gr_complex(0, 0.91))); + d_r.rotateN(d_tmp_b, d_tmp_a, frame_size); + + //if(max_dl > 10000) + // d_debug = true; + if (d_debug) { + write_data_c2(d_tmp_b, + frame_size, + (char*)"signal-filtered-deci-cut-start-shift-rrc-rotate", + sub_id); + } + + /* + * Align the burst so the first sample of the burst is the first symbol + * of the 16 symbol preamble after the RRC filter. + * + */ + + // Update the amount of available samples after filtering + frame_size = std::max((int)frame_size - uw_start, 0); + start += uw_start; + + if (d_debug) { + write_data_c2(d_tmp_b + uw_start, + frame_size, + (char*)"signal-filtered-deci-cut-start-shift-rrc-rotate-cut", + sub_id); + } + + timestamp += start * 1000000000ULL / (int)sample_rate; + /* + * Done :) + */ + pmt::pmt_t pdu_meta = pmt::make_dict(); + pmt::pmt_t pdu_vector = pmt::init_c32vector(frame_size, d_tmp_b + uw_start); + + pdu_meta = pmt::dict_add(pdu_meta, pmt::mp("sample_rate"), pmt::mp(sample_rate)); + pdu_meta = + pmt::dict_add(pdu_meta, pmt::mp("center_frequency"), pmt::mp(center_frequency)); + pdu_meta = pmt::dict_add(pdu_meta, pmt::mp("direction"), pmt::mp((int)direction)); + pdu_meta = pmt::dict_add(pdu_meta, pmt::mp("uw_start"), pmt::mp(correction)); + pdu_meta = pmt::dict_add(pdu_meta, pmt::mp("timestamp"), pmt::mp(timestamp)); + pdu_meta = pmt::dict_add(pdu_meta, pmt::mp("id"), pmt::mp(sub_id)); + pdu_meta = pmt::dict_add(pdu_meta, pmt::mp("noise"), pmt::mp(noise)); + //pdu_meta = pmt::dict_add(pdu_meta, pmt::mp("noise"), pmt::mp(max_dl)); + pdu_meta = pmt::dict_add(pdu_meta, pmt::mp("magnitude"), pmt::mp(magnitude)); + pdu_meta = pmt::dict_add(pdu_meta, pmt::mp("next"), pmt::mp(true)); + + if (d_debug) { + printf("center_frequency=%f, uw_start=%u\n", center_frequency, uw_start); + } + + //if(max_dl > 10000) + // d_debug = false; + pmt::pmt_t out_msg = pmt::cons(pdu_meta, pdu_vector); + message_port_pub(pmt::mp("cpdus"), out_msg); + + return consumed_samples; +} + +void burst_downmix_next_impl::handler(pmt::pmt_t msg) +{ + /* + * Extract the burst and meta data from the cpdu + */ + pmt::pmt_t samples = pmt::cdr(msg); + size_t burst_size = pmt::length(samples); + const gr_complex* burst = + (const gr_complex*)pmt::c32vector_elements(samples, burst_size); + + pmt::pmt_t meta = pmt::car(msg); + float relative_frequency = + pmt::to_float(pmt::dict_ref(meta, pmt::mp("relative_frequency"), pmt::PMT_NIL)); + double center_frequency = + pmt::to_double(pmt::dict_ref(meta, pmt::mp("center_frequency"), pmt::PMT_NIL)); + float sample_rate = + pmt::to_float(pmt::dict_ref(meta, pmt::mp("sample_rate"), pmt::PMT_NIL)); + uint64_t id = pmt::to_uint64(pmt::dict_ref(meta, pmt::mp("id"), pmt::PMT_NIL)); + uint64_t timestamp = + pmt::to_uint64(pmt::dict_ref(meta, pmt::mp("timestamp"), pmt::PMT_NIL)); + float noise = pmt::to_float(pmt::dict_ref(meta, pmt::mp("noise"), pmt::PMT_NIL)); + float magnitude = + pmt::to_float(pmt::dict_ref(meta, pmt::mp("magnitude"), pmt::PMT_NIL)); + + if (id == d_debug_id || id == d_debug_id + 1920 || id == d_debug_id - 1920) { + d_debug = true; + } + + if (d_debug) { + printf("---------------> id:%" PRIu64 " len:%zu\n", id, burst_size); + float absolute_frequency = center_frequency + relative_frequency * sample_rate; + printf("relative_frequency=%f, absolute_frequency=%f\n", + relative_frequency, + absolute_frequency); + printf("sample_rate=%f\n", sample_rate); + } + + if (d_hard_max_queue_len && get_input_queue_size() >= d_hard_max_queue_len) { + std::cerr << "Warning: Dropping burst as hard queue length is reached!" + << std::endl; + d_n_dropped_bursts++; + message_port_pub(pmt::mp("burst_handled"), pmt::mp(id)); + return; + } + + // This burst might be larger than the one before. + // Update he buffer sizes if needed. + update_buffer_sizes(burst_size + 10000); + + if (d_debug) { + write_data_c2(burst, burst_size, (char*)"signal", id); + } + //return; + + /* + * Shift the center frequency of the burst to the provided rough CFO estimate. + */ + float phase_inc = 2 * M_PI * -relative_frequency; + d_r.set_phase_incr(exp(gr_complex(0, phase_inc))); + d_r.set_phase(gr_complex(1, 0)); + d_r.rotateN(d_tmp_a, burst, burst_size); + center_frequency += relative_frequency * sample_rate; + + + /* + * Apply the initial low pass filter and decimate the burst. + */ + int decimation = std::lround(sample_rate) / d_output_sample_rate; + +#if 0 + // Option for additional padding. Probably not needed. + int input_fir_pad_size = (d_input_fir.ntaps() - 1) / 2; + memmove(d_tmp_a + input_fir_pad_size, d_tmp_a, sizeof(gr_complex) * burst_size); + memset(d_tmp_a, 0, sizeof(gr_complex) * input_fir_pad_size); + memset(d_tmp_a + input_fir_pad_size + burst_size, 0, sizeof(gr_complex) * input_fir_pad_size); + + burst_size = burst_size / decimation; +#else + burst_size = (burst_size - d_input_fir.ntaps() + 1) / decimation; + + timestamp += d_input_fir.ntaps() / 2 * 1000000000ULL / (int)sample_rate; +#endif + + d_input_fir.filterNdec(d_frame, d_tmp_a, burst_size, decimation); + + sample_rate /= decimation; + + if (d_debug) { + printf("---------------> id:%" PRIu64 " len:%lu\n", + id, + burst_size / d_output_sample_rate); + write_data_c2(d_frame, burst_size, (char*)"signal-filtered-deci", id); + } + + /* + * Search for the start of the burst by looking at the magnitude. + * Look at most d_search_depth far. + */ + + int half_fir_size = (d_start_finder_fir.ntaps() - 1) / 2; + int fir_size = d_start_finder_fir.ntaps(); + + // The burst might be shorter than d_search_depth. + int N = std::min(d_search_depth, (int)burst_size - (fir_size - 1)); + + volk_32fc_magnitude_squared_32f(d_magnitude_f, d_frame, N + fir_size - 1); + + if (d_debug) { + write_data_f2(d_magnitude_f, N + fir_size - 1, (char*)"signal-mag", id); + } + + d_start_finder_fir.filterN(d_magnitude_filtered_f, d_magnitude_f, N); + + if (d_debug) { + write_data_f2(d_magnitude_filtered_f, N, (char*)"signal-mag-filter", id); + } + + + float* max = std::max_element(d_magnitude_filtered_f, d_magnitude_filtered_f + N); + float threshold = *max * 0.28; + if (d_debug) { + std::cout << "Threshold:" << threshold << " Max:" << *max << "(" + << (max - d_magnitude_filtered_f) << ")\n"; + } + + int start; + for (start = 0; start < N; start++) { + if (d_magnitude_filtered_f[start] >= threshold) { + break; + } + } + + if (start > 0) { + start = std::max(start + half_fir_size - d_pre_start_samples, 0); + } + + if (d_debug) { + std::cout << "Start:" << start << "\n"; + write_data_c2(d_frame + start, + burst_size - start, + (char*)"signal-filtered-deci-cut-start", + id); + } + + if (d_handle_multiple_frames_per_burst) { + int handled_samples; + int sub_id = id; + do { + handled_samples = process_next_frame(sample_rate, + center_frequency, + timestamp, + sub_id, + burst_size, + start, + noise, + magnitude); + start += handled_samples; + // This is OK as ids are incremented by 10 by the burst tagger + sub_id++; + } while (d_handle_multiple_frames_per_burst && handled_samples > 0); + } else { + process_next_frame(sample_rate, + center_frequency, + timestamp, + id, + burst_size, + start, + noise, + magnitude); + } + + message_port_pub(pmt::mp("burst_handled"), pmt::mp(id)); + + if (d_debug_id >= 0) { + d_debug = false; + } +} + +int burst_downmix_next_impl::work(int noutput_items, + gr_vector_const_void_star& input_items, + gr_vector_void_star& output_items) +{ + return 0; +} + +} /* namespace iridium */ +} /* namespace gr */ diff --git a/lib/burst_downmix_next_impl.h b/lib/burst_downmix_next_impl.h new file mode 100644 index 0000000..14129db --- /dev/null +++ b/lib/burst_downmix_next_impl.h @@ -0,0 +1,114 @@ +/* -*- c++ -*- */ +/* + * Copyright 2020 Free Software Foundation, Inc. + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3, or (at your option) + * any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#ifndef INCLUDED_IRIDIUM_BURST_DOWNMIX_IMPL_H +#define INCLUDED_IRIDIUM_BURST_DOWNMIX_IMPL_H + +#include +#include + +#include + +namespace gr { +namespace iridium { + +class burst_downmix_next_impl : public burst_downmix_next +{ +private: + int d_output_sample_rate; + int d_output_samples_per_symbol; + size_t d_max_burst_size; + int d_search_depth; + int d_pre_start_samples; + int d_cfo_est_fft_size; + int d_fft_over_size_facor; + int d_corr_fft_size; + int d_sync_search_len; + int d_hard_max_queue_len; + uint64_t d_n_dropped_bursts; + bool d_handle_multiple_frames_per_burst; + bool d_debug; + int64_t d_debug_id; + + gr_complex* d_frame; + gr_complex* d_tmp_a; + gr_complex* d_tmp_b; + gr_complex* d_dl_preamble_reversed_conj_fft; + gr_complex* d_ul_preamble_reversed_conj_fft; + + float* d_magnitude_f; + float* d_magnitude_filtered_f; + float* d_cfo_est_window_f; + + gr::fft::fft_complex_fwd* d_corr_fft; + gr::fft::fft_complex_rev* d_corr_dl_ifft; + gr::fft::fft_complex_rev* d_corr_ul_ifft; + + filter::kernel::fir_filter_ccf d_input_fir; + filter::kernel::fir_filter_fff d_start_finder_fir; + filter::kernel::fir_filter_ccf d_rrc_fir; + filter::kernel::fir_filter_ccf d_rc_fir; + + volk::vector d_dl_preamble_reversed_conj; + volk::vector d_ul_preamble_reversed_conj; + + blocks::rotator d_r; + gr::fft::fft_complex_fwd d_cfo_est_fft; + + void handler(pmt::pmt_t msg); + int process_next_frame(float sample_rate, + double center_frequency, + uint64_t timestamp, + uint64_t sub_id, + size_t burst_size, + int start, + float noise, + float magnitude); + + void update_buffer_sizes(size_t burst_size); + void initialize_cfo_est_fft(void); + void initialize_correlation_filter(void); + volk::vector generate_sync_word(::iridium::direction direction); + int fft_shift_index(int index, int fft_size); + int fft_unshift_index(int index, int fft_size); + float interpolate(float alpha, float beta, float gamma); + +public: + burst_downmix_next_impl(int sample_rate, + int search_depth, + size_t hard_max_queue_len, + const std::vector& input_taps, + const std::vector& start_finder_taps, + bool handle_multiple_frames_per_burst); + ~burst_downmix_next_impl(); + + size_t get_input_queue_size(); + uint64_t get_n_dropped_bursts(); + void debug_id(uint64_t id); + + int work(int noutput_items, + gr_vector_const_void_star& input_items, + gr_vector_void_star& output_items); +}; + +} // namespace iridium +} // namespace gr + +#endif /* INCLUDED_IRIDIUM_BURST_DOWNMIX_IMPL_H */ diff --git a/lib/iridium.h b/lib/iridium.h index 6089133..b9c3e9d 100644 --- a/lib/iridium.h +++ b/lib/iridium.h @@ -5,9 +5,11 @@ enum class direction { UNDEF = 0, DOWNLINK = 1, UPLINK = 2, + DOWNLINK_NEXT = 3, }; const int SYMBOLS_PER_SECOND = 25000; +const int SYMBOLS_PER_SECOND_NEXT = 30000; const int UW_LENGTH = 12; const int SIMPLEX_FREQUENCY_MIN = 1626000000; @@ -24,4 +26,6 @@ const int MAX_FRAME_LENGTH_SIMPLEX = 444; const int UW_DL[] = { 0, 2, 2, 2, 2, 0, 0, 0, 2, 0, 0, 2 }; const int UW_UL[] = { 2, 2, 0, 0, 0, 2, 0, 0, 2, 0, 2, 2 }; +// std::vector uw_dl = { s1, s1, s0, s1, s1, s0, s1, s0, s1, s0, s1, s1 };//, s0, s0, s1, s0, s1, s1, s1, s1, s0, s1, s1, s1, s0, s1, s0, s0}; +const int UW_DL_NEXT[] = { 2, 2, 0, 2, 2, 0, 2, 0, 2, 0, 2, 2 }; } // namespace iridium diff --git a/lib/iridium_qpsk_demod_impl.cc b/lib/iridium_qpsk_demod_impl.cc index 957e666..29d2a4c 100644 --- a/lib/iridium_qpsk_demod_impl.cc +++ b/lib/iridium_qpsk_demod_impl.cc @@ -188,11 +188,13 @@ size_t iridium_qpsk_demod_impl::demod_qpsk( float max = 0; int low_count = 0; int n_ok = 0; + int n = 0; volk_32fc_magnitude_32f(d_magnitude_f, burst, n_symbols); for (index = 0; index < n_symbols; index++) { sum += d_magnitude_f[index]; + //printf("%f ", d_magnitude_f[index]); if (max < d_magnitude_f[index]) { max = d_magnitude_f[index]; } @@ -217,16 +219,19 @@ size_t iridium_qpsk_demod_impl::demod_qpsk( } if (d_magnitude_f[index] < max / 8.) { + break; low_count++; if (low_count > 2) { break; } } + n++; } + //printf("\n"); - *level = sum / index; - *confidence = (int)(100. * n_ok / index); - return index; + *level = sum / n; + *confidence = (int)(100. * n_ok / n); + return n; } void iridium_qpsk_demod_impl::decode_deqpsk(int* demodulated_burst, size_t n_symbols) @@ -263,13 +268,13 @@ void iridium_qpsk_demod_impl::map_symbols_to_bits(const int* demodulated_burst, bits.clear(); for (i = 0; i < n_symbols; i++) { - if (demodulated_burst[i] & 2) { + if (demodulated_burst[i] & 1) { bits.push_back(1); } else { bits.push_back(0); } - if (demodulated_burst[i] & 1) { + if (demodulated_burst[i] & 2) { bits.push_back(1); } else { bits.push_back(0); @@ -292,6 +297,11 @@ bool iridium_qpsk_demod_impl::check_sync_word(int* demodulated_burst, uw_len = sizeof(::iridium::UW_DL) / sizeof(*::iridium::UW_DL); } + if (direction == ::iridium::direction::DOWNLINK_NEXT) { + uw = ::iridium::UW_DL_NEXT; + uw_len = sizeof(::iridium::UW_DL_NEXT) / sizeof(*::iridium::UW_DL_NEXT); + } + if (direction == ::iridium::direction::UPLINK) { uw = ::iridium::UW_UL; uw_len = sizeof(::iridium::UW_DL) / sizeof(*::iridium::UW_DL); @@ -341,8 +351,13 @@ void iridium_qpsk_demod_impl::handler(int channel, pmt::pmt_t msg) pmt::to_float(pmt::dict_ref(meta, pmt::mp("magnitude"), pmt::PMT_NIL)); uint64_t timestamp = pmt::to_uint64(pmt::dict_ref(meta, pmt::mp("timestamp"), pmt::PMT_NIL)); + uint64_t next = + pmt::to_uint64(pmt::dict_ref(meta, pmt::mp("next"), pmt::PMT_NIL)); int sps = sample_rate / 25000; + + if(next) sps = sample_rate / 30000; + timestamp += uw_start * 1000000000ULL / (int)sample_rate; update_buffer_sizes(burst_size); @@ -356,6 +371,9 @@ void iridium_qpsk_demod_impl::handler(int channel, pmt::pmt_t msg) // frequency or phase offset float total_phase = qpskFirstOrderPLL(d_decimated_burst, n_symbols, d_alpha, d_burst_after_pll); + if(next) + center_frequency += total_phase / (n_symbols / 30000.) / M_PI / 2.; + else center_frequency += total_phase / (n_symbols / 25000.) / M_PI / 2.; #else memcpy(d_burst_after_pll, d_decimated_burst, n_symbols * sizeof(gr_complex)); @@ -363,6 +381,7 @@ void iridium_qpsk_demod_impl::handler(int channel, pmt::pmt_t msg) int confidence; float level; + //printf("id:%d ", sub_id); n_symbols = demod_qpsk( d_burst_after_pll, n_symbols, d_demodulated_burst, &level, &confidence); @@ -370,10 +389,11 @@ void iridium_qpsk_demod_impl::handler(int channel, pmt::pmt_t msg) check_sync_word(d_demodulated_burst, n_symbols, ::iridium::direction::DOWNLINK); bool ul_uw_ok = check_sync_word(d_demodulated_burst, n_symbols, ::iridium::direction::UPLINK); - + bool dl_next_uw_ok = + check_sync_word(d_demodulated_burst, n_symbols, ::iridium::direction::DOWNLINK_NEXT); d_n_handled_bursts++; - if (!dl_uw_ok && !ul_uw_ok) { + if (!dl_uw_ok && !ul_uw_ok && !dl_next_uw_ok) { // Drop frames which have no valid sync word return; } diff --git a/python/bindings/CMakeLists.txt b/python/bindings/CMakeLists.txt index b4c2a9c..e7a1e7d 100644 --- a/python/bindings/CMakeLists.txt +++ b/python/bindings/CMakeLists.txt @@ -30,6 +30,7 @@ include(GrPybind) list(APPEND iridium_python_files burst_downmix_python.cc + burst_downmix_next_python.cc fft_burst_tagger_python.cc iridium_qpsk_demod_python.cc iuchar_to_complex_python.cc diff --git a/python/bindings/burst_downmix_next_python.cc b/python/bindings/burst_downmix_next_python.cc new file mode 100644 index 0000000..11cb768 --- /dev/null +++ b/python/bindings/burst_downmix_next_python.cc @@ -0,0 +1,84 @@ +/* + * Copyright 2022 Free Software Foundation, Inc. + * + * This file is part of GNU Radio + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + */ + +/***********************************************************************************/ +/* This file is automatically generated using bindtool and can be manually edited */ +/* The following lines can be configured to regenerate this file during cmake */ +/* If manual edits are made, the following tags should be modified accordingly. */ +/* BINDTOOL_GEN_AUTOMATIC(0) */ +/* BINDTOOL_USE_PYGCCXML(0) */ +/* BINDTOOL_HEADER_FILE(burst_downmix_next.h) */ +/* BINDTOOL_HEADER_FILE_HASH(bd3e6509aa1d33caabdce6b13ea519fd) */ +/***********************************************************************************/ + +#include +#include +#include + +namespace py = pybind11; + +#include +// pydoc.h is automatically generated in the build directory +#include + +void bind_burst_downmix_next(py::module& m) +{ + + using burst_downmix_next = ::gr::iridium::burst_downmix_next; + + + py::class_>(m, "burst_downmix_next", D(burst_downmix_next)) + + .def(py::init(&burst_downmix_next::make), + py::arg("output_sample_rate"), + py::arg("search_depth"), + py::arg("hard_max_queue_len"), + py::arg("input_taps"), + py::arg("start_finder_taps"), + py::arg("handle_multiple_frames_per_burst"), + D(burst_downmix_next,make) + ) + + + + + + + .def("get_n_dropped_bursts",&burst_downmix_next::get_n_dropped_bursts, + D(burst_downmix_next,get_n_dropped_bursts) + ) + + + + .def("get_input_queue_size",&burst_downmix_next::get_input_queue_size, + D(burst_downmix_next,get_input_queue_size) + ) + + + + .def("debug_id",&burst_downmix_next::debug_id, + py::arg("id"), + D(burst_downmix_next,debug_id) + ) + + ; + + + + +} + + + + + + + + diff --git a/python/bindings/docstrings/burst_downmix_next_pydoc_template.h b/python/bindings/docstrings/burst_downmix_next_pydoc_template.h new file mode 100644 index 0000000..aaa3c16 --- /dev/null +++ b/python/bindings/docstrings/burst_downmix_next_pydoc_template.h @@ -0,0 +1,39 @@ +/* + * Copyright 2022 Free Software Foundation, Inc. + * + * This file is part of GNU Radio + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + */ +#include "pydoc_macros.h" +#define D(...) DOC(gr,iridium, __VA_ARGS__ ) +/* + This file contains placeholders for docstrings for the Python bindings. + Do not edit! These were automatically extracted during the binding process + and will be overwritten during the build process + */ + + + + static const char *__doc_gr_iridium_burst_downmix_next = R"doc()doc"; + + + static const char *__doc_gr_iridium_burst_downmix_next_burst_downmix_next_0 = R"doc()doc"; + + + static const char *__doc_gr_iridium_burst_downmix_next_burst_downmix_next_1 = R"doc()doc"; + + + static const char *__doc_gr_iridium_burst_downmix_next_make = R"doc()doc"; + + + static const char *__doc_gr_iridium_burst_downmix_next_get_n_dropped_bursts = R"doc()doc"; + + + static const char *__doc_gr_iridium_burst_downmix_next_get_input_queue_size = R"doc()doc"; + + + static const char *__doc_gr_iridium_burst_downmix_next_debug_id = R"doc()doc"; + + diff --git a/python/bindings/python_bindings.cc b/python/bindings/python_bindings.cc index 1f552b0..7f9ca01 100644 --- a/python/bindings/python_bindings.cc +++ b/python/bindings/python_bindings.cc @@ -22,6 +22,7 @@ namespace py = pybind11; /**************************************/ // BINDING_FUNCTION_PROTOTYPES( void bind_burst_downmix(py::module& m); + void bind_burst_downmix_next(py::module& m); void bind_fft_burst_tagger(py::module& m); void bind_iridium_qpsk_demod(py::module& m); void bind_iuchar_to_complex(py::module& m); @@ -60,6 +61,7 @@ PYBIND11_MODULE(iridium_python, m) /**************************************/ // BINDING_FUNCTION_CALLS( bind_burst_downmix(m); + bind_burst_downmix_next(m); bind_fft_burst_tagger(m); bind_iridium_qpsk_demod(m); bind_iuchar_to_complex(m); @@ -70,4 +72,4 @@ PYBIND11_MODULE(iridium_python, m) bind_iridium_frame_printer(m); bind_fft_channelizer(m); // ) END BINDING_FUNCTION_CALLS -} \ No newline at end of file +} diff --git a/python/iridium_extractor_flowgraph.py b/python/iridium_extractor_flowgraph.py index d885a41..53fd6d2 100644 --- a/python/iridium_extractor_flowgraph.py +++ b/python/iridium_extractor_flowgraph.py @@ -45,6 +45,7 @@ def __init__(self, center_frequency, sample_rate, decimation, filename, sample_f # Sample rate of the bursts exiting the burst downmix block self._burst_sample_rate = 25000 * samples_per_symbol + self._burst_sample_rate_next = 30000 * samples_per_symbol if (self._input_sample_rate / self._decimation) % self._burst_sample_rate != 0: raise RuntimeError("Selected sample rate and decimation can not be matched. Please try a different combination. Sample rate divided by decimation must be a multiple of %d." % self._burst_sample_rate) @@ -508,7 +509,7 @@ def match_gain(gain, gain_names): max_bursts=max_bursts, max_burst_len=int(self._input_sample_rate * 0.09), threshold=self._threshold, - history_size=512, + history_size=300, offline=self._offline, debug=self._verbose) self._fft_burst_tagger.set_min_output_buffer(1024 * 64) @@ -542,6 +543,7 @@ def match_gain(gain, gain_names): if self._use_channelizer: self._burst_to_pdu_converters = [] self._burst_downmixers = [] + self._burst_downmixers_next = [] sinks = [] for channel in range(self._channels): @@ -568,10 +570,18 @@ def match_gain(gain, gain_names): (start_finder_filter), self._handle_multiple_frames_per_burst) + burst_downmixer_next = iridium.burst_downmix_next(self._burst_sample_rate_next, + int(0.007 * self._burst_sample_rate_next), + 0, + (input_filter), + (start_finder_filter), + self._handle_multiple_frames_per_burst) if debug_id is not None: burst_downmixer.debug_id(debug_id) + burst_downmixer_next.debug_id(debug_id) self._burst_downmixers.append(burst_downmixer) + self._burst_downmixers_next.append(burst_downmixer_next) channelizer_debug_sinks = [] #channelizer_debug_sinks = [blocks.file_sink(itemsize=gr.sizeof_gr_complex, filename="/tmp/channel-%d.f32"%i) for i in range(self._channels)] @@ -598,16 +608,21 @@ def match_gain(gain, gain_names): if self._burst_to_pdu_converters: tb.connect((self._channelizer, i), self._burst_to_pdu_converters[i]) tb.msg_connect((self._burst_to_pdu_converters[i], 'cpdus'), (self._burst_downmixers[i], 'cpdus')) + tb.msg_connect((self._burst_to_pdu_converters[i], 'cpdus'), (self._burst_downmixers_next[i], 'cpdus')) tb.msg_connect((self._burst_downmixers[i], 'burst_handled'), (self._burst_to_pdu_converters[i], 'burst_handled')) else: tb.msg_connect((self._channelizer, 'cpdus%d' % i), (self._burst_downmixers[i], 'cpdus')) + tb.msg_connect((self._channelizer, 'cpdus%d' % i), (self._burst_downmixers_next[i], 'cpdus')) tb.msg_connect((self._burst_downmixers[i], 'burst_handled'), (self._channelizer, 'burst_handled')) tb.msg_connect((self._burst_downmixers[i], 'cpdus'), (self._iridium_qpsk_demod, 'cpdus%d' % i)) + tb.msg_connect((self._burst_downmixers_next[i], 'cpdus'), (self._iridium_qpsk_demod, 'cpdus%d' % i)) else: burst_downmix = iridium.burst_downmix(self._burst_sample_rate, int(0.007 * self._burst_sample_rate), 0, (input_filter), (start_finder_filter), self._handle_multiple_frames_per_burst) + burst_downmix_next = iridium.burst_downmix_next(self._burst_sample_rate_next, int(0.007 * self._burst_sample_rate_next), 0, (input_filter), (start_finder_filter), self._handle_multiple_frames_per_burst) if debug_id is not None: burst_downmix.debug_id(debug_id) + burst_downmix_next.debug_id(debug_id) burst_to_pdu = iridium.tagged_burst_to_pdu(self._max_burst_len, 0.0, 1.0, 1.0, @@ -617,12 +632,15 @@ def match_gain(gain, gain_names): tb.connect(self._fft_burst_tagger, burst_to_pdu) tb.msg_connect((burst_to_pdu, 'cpdus'), (burst_downmix, 'cpdus')) - tb.msg_connect((burst_downmix, 'burst_handled'), (burst_to_pdu, 'burst_handled')) + tb.msg_connect((burst_to_pdu, 'cpdus'), (burst_downmix_next, 'cpdus')) + tb.msg_connect((burst_downmix_next, 'burst_handled'), (burst_to_pdu, 'burst_handled')) # Final connection to the demodulator. It prints the output to stdout tb.msg_connect((burst_downmix, 'cpdus'), (self._iridium_qpsk_demod, 'cpdus')) + tb.msg_connect((burst_downmix_next, 'cpdus'), (self._iridium_qpsk_demod, 'cpdus')) self._burst_downmixers = [burst_downmix] + self._burst_downmixers_next = [burst_downmix_next] self._burst_to_pdu_converters = [burst_to_pdu] tb.msg_connect((self._iridium_qpsk_demod, 'pdus'), (self._frame_sorter, 'pdus'))