From 50098ee8790389e45a8cf0d8e09f6fc27748dd30 Mon Sep 17 00:00:00 2001 From: Manolis Surligas Date: Sun, 22 Jan 2017 19:33:36 +0200 Subject: [PATCH] Dev (#53) * UDP Message source can handle multiple data types * Add a waterfall sink block * Fix dependency issues with VOLK * Add mean and max hold mode to the waterfall sink * Add mean and max hold mode to the waterfall sink * Install satnogs_waterfall.gp gnuplot script at /share/satnogs/scripts * Automatically retrieve x and y axis ranges at the satnogs_waterfall.gp --- CMakeLists.txt | 1 + apps/CMakeLists.txt | 1 + apps/scripts/CMakeLists.txt | 4 + apps/scripts/satnogs_waterfall.gp | 60 +++++++ grc/CMakeLists.txt | 2 +- grc/satnogs_block_tree.xml | 1 + grc/satnogs_udp_msg_source.xml | 20 ++- grc/satnogs_waterfall_sink.xml | 67 ++++++++ include/satnogs/CMakeLists.txt | 2 +- include/satnogs/udp_msg_source.h | 5 +- include/satnogs/waterfall_sink.h | 77 +++++++++ lib/CMakeLists.txt | 15 +- lib/udp_msg_source_impl.cc | 102 +++++++----- lib/udp_msg_source_impl.h | 4 +- lib/waterfall_sink_impl.cc | 268 ++++++++++++++++++++++++++++++ lib/waterfall_sink_impl.h | 86 ++++++++++ python/__init__.py | 7 +- swig/satnogs_swig.i | 22 +++ 18 files changed, 695 insertions(+), 49 deletions(-) create mode 100644 apps/scripts/CMakeLists.txt create mode 100755 apps/scripts/satnogs_waterfall.gp create mode 100644 grc/satnogs_waterfall_sink.xml create mode 100644 include/satnogs/waterfall_sink.h create mode 100644 lib/waterfall_sink_impl.cc create mode 100644 lib/waterfall_sink_impl.h diff --git a/CMakeLists.txt b/CMakeLists.txt index d08bd10..578a050 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -115,6 +115,7 @@ find_package (Threads REQUIRED) ######################################################################## find_package(CppUnit) find_package(Doxygen) +find_package(Volk REQUIRED) ######################################################################## # Include or not into the module blocks for debugging diff --git a/apps/CMakeLists.txt b/apps/CMakeLists.txt index 65be159..f944868 100644 --- a/apps/CMakeLists.txt +++ b/apps/CMakeLists.txt @@ -20,6 +20,7 @@ include(GrPython) add_subdirectory(flowgraphs/satellites) +add_subdirectory(scripts) GR_PYTHON_INSTALL( PROGRAMS diff --git a/apps/scripts/CMakeLists.txt b/apps/scripts/CMakeLists.txt new file mode 100644 index 0000000..4355ed3 --- /dev/null +++ b/apps/scripts/CMakeLists.txt @@ -0,0 +1,4 @@ +INSTALL(FILES + satnogs_waterfall.gp + DESTINATION share/satnogs/scripts +) \ No newline at end of file diff --git a/apps/scripts/satnogs_waterfall.gp b/apps/scripts/satnogs_waterfall.gp new file mode 100755 index 0000000..60dfab0 --- /dev/null +++ b/apps/scripts/satnogs_waterfall.gp @@ -0,0 +1,60 @@ +#!/usr/bin/gnuplot +# +# gr-satnogs: SatNOGS GNU Radio Out-Of-Tree Module +# +# Copyright (C) 2017, Libre Space Foundation +# +# This program 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 of the License, or +# (at your option) any later version. +# +# This program 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 program. If not, see +# + +reset +if (!exists("height")) height=800 +if (!exists("width")) width=800 +if (!exists("outfile")) outfile='/tmp/waterfall.png' + +set terminal pngcairo size height,width enhanced font 'Verdana,14' +set output outfile + +unset key +set style line 11 lc rgb '#808080' lt 1 +set border 3 front ls 11 +set style line 12 lc rgb '#888888' lt 0 lw 1 +set grid front ls 12 +set tics nomirror out scale 0.75 + +set xlabel 'Frequency (kHz)' +set ylabel 'Time' +set cbtics scale 0 + +# palette +set palette defined (0 '#3288BD',\ + 1 '#66C2A5',\ + 2 '#ABDDA4',\ + 3 '#E6F598',\ + 4 '#FEE08B',\ + 5 '#FDAE61',\ + 6 '#F46D43',\ + 7 '#D53E4F') +set ylabel 'Time (seconds)' +set cbrange [-110:-20] +set cblabel 'Power (dB)' + +# Get automatically the axis ranges from the file +stats inputfile using 1 binary nooutput +set xrange [STATS_min*1e-3:STATS_max*1e-3 + 1] +stats inputfile using 2 binary nooutput +set yrange [0:STATS_max + 1] + +# Plot and scale the frequency axis to kHz for readability +plot inputfile using ($1*1e-3):2:3 binary matrix with image diff --git a/grc/CMakeLists.txt b/grc/CMakeLists.txt index 90cdd05..40a7195 100644 --- a/grc/CMakeLists.txt +++ b/grc/CMakeLists.txt @@ -40,12 +40,12 @@ list(APPEND enabled_blocks satnogs_coarse_doppler_correction_cc.xml satnogs_ax25_encoder_mb.xml satnogs_ax25_decoder_bm.xml + satnogs_waterfall_sink.xml ) if(${INCLUDE_DEBUG_BLOCKS}) list(APPEND enabled_blocks ${debug_blocks}) endif() - install(FILES ${enabled_blocks} DESTINATION share/gnuradio/grc/blocks diff --git a/grc/satnogs_block_tree.xml b/grc/satnogs_block_tree.xml index 345437f..ee61c74 100644 --- a/grc/satnogs_block_tree.xml +++ b/grc/satnogs_block_tree.xml @@ -22,4 +22,5 @@ satnogs_coarse_doppler_correction_cc satnogs_ax25_encoder_mb satnogs_ax25_decoder_bm + satnogs_waterfall_sink \ No newline at end of file diff --git a/grc/satnogs_udp_msg_source.xml b/grc/satnogs_udp_msg_source.xml index d39e03c..10a8eec 100644 --- a/grc/satnogs_udp_msg_source.xml +++ b/grc/satnogs_udp_msg_source.xml @@ -3,7 +3,7 @@ UDP Message Source satnogs_udp_msg_source import satnogs - satnogs.udp_msg_source($addr, $port, $mtu) + satnogs.udp_msg_source($addr, $port, $mtu, $msg_type) IP Address @@ -25,6 +25,24 @@ 1500 int + + + Message Type + msg_type + enum + + + + msg diff --git a/grc/satnogs_waterfall_sink.xml b/grc/satnogs_waterfall_sink.xml new file mode 100644 index 0000000..9bf1e90 --- /dev/null +++ b/grc/satnogs_waterfall_sink.xml @@ -0,0 +1,67 @@ + + + Waterfall Sink + satnogs_waterfall_sink + import satnogs + satnogs.waterfall_sink($samp_rate, $center_freq, $pps, $fft_size, $filename, $mode) + + + Sample Rate + samp_rate + samp_rate + real + + + + FFT Size + fft_size + 1024 + int + + + + + Pixel Rows per Second + pps + 10 + int + + + + Mode + mode + enum + + + + + + + Center Frequency + center_freq + 0.0 + real + + + + File + filename + + file_save + + + + in + complex + + + diff --git a/include/satnogs/CMakeLists.txt b/include/satnogs/CMakeLists.txt index c91e88f..847f492 100644 --- a/include/satnogs/CMakeLists.txt +++ b/include/satnogs/CMakeLists.txt @@ -54,12 +54,12 @@ list(APPEND HEADER_FILES ax25_encoder_mb.h ax25_decoder_bm.h qb50_deframer.h + waterfall_sink.h ) if(${INCLUDE_DEBUG_BLOCKS}) list(APPEND HEADER_FILES ${DEBUG_HEADER_FILES}) endif() - install(FILES ${HEADER_FILES} DESTINATION include/satnogs diff --git a/include/satnogs/udp_msg_source.h b/include/satnogs/udp_msg_source.h index 75003a0..ad6b45e 100644 --- a/include/satnogs/udp_msg_source.h +++ b/include/satnogs/udp_msg_source.h @@ -48,9 +48,12 @@ namespace gr * @param addr the address to bind the UDP socket * @param port the UDP port to wait for packets * @param mtu the maximum MTU. Used to pre-allocate a maximum packet size + * @param type code of the data type of each message. 0 corresponds to raw + * bytes, 1 to 32-bit signed integers and 2 to 3 bit unsigned integers. */ static sptr - make (const std::string& addr, uint16_t port, size_t mtu = 1500); + make (const std::string& addr, uint16_t port, size_t mtu = 1500, + size_t type = 0); }; } // namespace satnogs diff --git a/include/satnogs/waterfall_sink.h b/include/satnogs/waterfall_sink.h new file mode 100644 index 0000000..ee0d654 --- /dev/null +++ b/include/satnogs/waterfall_sink.h @@ -0,0 +1,77 @@ +/* -*- c++ -*- */ +/* + * gr-satnogs: SatNOGS GNU Radio Out-Of-Tree Module + * + * Copyright (C) 2017, Libre Space Foundation + * + * This program 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 of the License, or + * (at your option) any later version. + * + * This program 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 program. If not, see . + */ + +#ifndef INCLUDED_SATNOGS_WATERFALL_SINK_H +#define INCLUDED_SATNOGS_WATERFALL_SINK_H + +#include +#include + +namespace gr +{ + namespace satnogs + { + + /*! + * \brief This block computes the waterfall of the incoming signal + * and stores the result to a file. + * + * The file has a special header, so that the satnogs_waterfall Gnuplot + * script to be able to plot it properly. + * + * \ingroup satnogs + * + */ + class SATNOGS_API waterfall_sink : virtual public gr::sync_block + { + public: + typedef boost::shared_ptr sptr; + + /** + * This block computes the waterfall of the incoming signal + * and stores the result to a file. + * + * The file has a special header, so that the satnogs_waterfall Gnuplot + * script to be able to plot it properly. + * + * @param samp_rate the sampling rate + * @param center_freq the observation center frequency. Used only for + * plotting reasons. For a normalized frequency x-axis set it to 0. + * @param pps pixels per second + * @param fft_size FFT size + * @param filename the name of the output file + * @param mode the mode that the waterfall. + * - 0: Simple decimation + * - 1: Max hold + * - 2: Mean energy + * + * @return shared pointer to the object + */ + static sptr + make (double samp_rate, double center_freq, + double pps, size_t fft_size, + const std::string& filename, int mode = 0); + }; + + } // namespace satnogs +} // namespace gr + +#endif /* INCLUDED_SATNOGS_WATERFALL_SINK_H */ + diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 46201a5..aeb1960 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -22,7 +22,11 @@ ######################################################################## include(GrPlatform) #define LIB_SUFFIX -include_directories(${Boost_INCLUDE_DIR}) +include_directories( + ${Boost_INCLUDE_DIR} + ${VOLK_INCLUDE_DIRS} +) + link_directories(${Boost_LIBRARY_DIRS}) list(APPEND satnogs_debug_sources @@ -31,7 +35,6 @@ list(APPEND satnogs_debug_sources debug_msg_source_raw_impl.cc leo_channel_impl.cc ) - list(APPEND satnogs_sources cw_matched_filter_ff_impl.cc morse_tree.cc @@ -52,7 +55,8 @@ list(APPEND satnogs_sources coarse_doppler_correction_cc_impl.cc ax25_encoder_mb_impl.cc ax25_decoder_bm_impl.cc - qb50_deframer_impl.cc ) + qb50_deframer_impl.cc + waterfall_sink_impl.cc ) if(${INCLUDE_DEBUG_BLOCKS}) list(APPEND satnogs_sources ${satnogs_debug_sources}) @@ -68,8 +72,11 @@ add_library(gnuradio-satnogs SHARED ${satnogs_sources}) target_link_libraries(gnuradio-satnogs ${Boost_LIBRARIES} ${GNURADIO_ALL_LIBRARIES} + gnuradio-digital ${CMAKE_THREAD_LIBS_INIT} - ${NOVA_LIBRARIES}) + ${NOVA_LIBRARIES} + ${VOLK_LIBRARIES} +) set_target_properties(gnuradio-satnogs PROPERTIES DEFINE_SYMBOL "gnuradio_satnogs_EXPORTS") diff --git a/lib/udp_msg_source_impl.cc b/lib/udp_msg_source_impl.cc index ba2566e..7521537 100644 --- a/lib/udp_msg_source_impl.cc +++ b/lib/udp_msg_source_impl.cc @@ -33,37 +33,37 @@ #include #include - namespace gr { namespace satnogs { udp_msg_source::sptr - udp_msg_source::make (const std::string& addr, uint16_t port, size_t mtu) + udp_msg_source::make (const std::string& addr, uint16_t port, size_t mtu, + size_t type) { return gnuradio::get_initial_sptr ( - new udp_msg_source_impl (addr, port, mtu)); + new udp_msg_source_impl (addr, port, mtu, type)); } /* * The private constructor */ udp_msg_source_impl::udp_msg_source_impl (const std::string& addr, - uint16_t port, - size_t mtu) : - gr::block ("udp_msg_source", - gr::io_signature::make (0, 0, 0), - gr::io_signature::make (0, 0, 0)), - d_iface_addr (addr), - d_udp_port (port), - d_mtu(mtu), - d_running (true) + uint16_t port, size_t mtu, + size_t type) : + gr::block ("udp_msg_source", gr::io_signature::make (0, 0, 0), + gr::io_signature::make (0, 0, 0)), + d_iface_addr (addr), + d_udp_port (port), + d_mtu (mtu), + d_type (type), + d_running (true) { - message_port_register_out(pmt::mp("msg")); + message_port_register_out (pmt::mp ("msg")); boost::shared_ptr ( - new boost::thread ( - boost::bind (&udp_msg_source_impl::udp_msg_accepter, this))); + new boost::thread ( + boost::bind (&udp_msg_source_impl::udp_msg_accepter, this))); } void @@ -75,10 +75,13 @@ namespace gr socklen_t client_addr_len; ssize_t ret; uint8_t *buf; + uint32_t bytes_num; + uint32_t uint_val; + uint32_t int_val; if ((sock = socket (AF_INET, SOCK_DGRAM, IPPROTO_UDP)) == -1) { - perror ("opening UDP socket"); - exit (EXIT_FAILURE); + perror ("opening UDP socket"); + exit (EXIT_FAILURE); } memset (&client_addr, 0, sizeof(struct sockaddr)); @@ -86,35 +89,60 @@ namespace gr sin.sin_family = AF_INET; sin.sin_port = htons (d_udp_port); - if( inet_aton(d_iface_addr.c_str(), &(sin.sin_addr)) == 0){ - LOG_ERROR("Wrong IP address"); - close(sock); - exit (EXIT_FAILURE); + if (inet_aton (d_iface_addr.c_str (), &(sin.sin_addr)) == 0) { + LOG_ERROR("Wrong IP address"); + close (sock); + exit (EXIT_FAILURE); } if (bind (sock, (struct sockaddr *) &sin, sizeof(struct sockaddr_in)) - == -1) { - perror ("UDP bind"); - close(sock); - exit (EXIT_FAILURE); + == -1) { + perror ("UDP bind"); + close (sock); + exit (EXIT_FAILURE); } /* All good until now. Allocate buffer memory and proceed */ buf = new uint8_t[d_mtu]; - while(d_running){ - ret = recvfrom(sock, buf, d_mtu, 0, &client_addr, &client_addr_len); - if(ret > 0) { - message_port_pub(pmt::mp("msg"), pmt::make_blob(buf, ret)); - } - else{ - perror("UDP recvfrom"); - close(sock); - delete[] buf; - exit(EXIT_FAILURE); - } + while (d_running) { + ret = recvfrom (sock, buf, d_mtu, 0, &client_addr, &client_addr_len); + if (ret > 0) { + bytes_num = (uint32_t) ret; + switch(d_type){ + case 0: + message_port_pub (pmt::mp ("msg"), pmt::make_blob (buf, bytes_num)); + case 1: + if(bytes_num < sizeof(uint32_t)){ + continue; + } + memcpy(&uint_val, buf, sizeof(uint32_t)); + message_port_pub (pmt::mp ("msg"), + pmt::from_uint64 (ntohl (uint_val))); + break; + case 2: + if (bytes_num < sizeof(int32_t)) { + continue; + } + memcpy(&int_val, buf, sizeof(int32_t)); + message_port_pub (pmt::mp ("msg"), + pmt::from_long (ntohl (int_val))); + break; + default: + LOG_ERROR("Unsupported message type"); + close (sock); + delete[] buf; + exit (EXIT_FAILURE); + } + } + else { + perror ("UDP recvfrom"); + close (sock); + delete[] buf; + exit (EXIT_FAILURE); + } } - close(sock); + close (sock); delete[] buf; exit (EXIT_SUCCESS); } diff --git a/lib/udp_msg_source_impl.h b/lib/udp_msg_source_impl.h index 36736f5..4c4c810 100644 --- a/lib/udp_msg_source_impl.h +++ b/lib/udp_msg_source_impl.h @@ -35,6 +35,7 @@ namespace gr const std::string d_iface_addr; const uint16_t d_udp_port; const size_t d_mtu; + const size_t d_type; bool d_running; boost::shared_ptr d_thread; @@ -42,7 +43,8 @@ namespace gr udp_msg_accepter (); public: - udp_msg_source_impl (const std::string& addr, uint16_t port, size_t mtu); + udp_msg_source_impl (const std::string& addr, uint16_t port, + size_t mtu, size_t type); ~udp_msg_source_impl (); }; diff --git a/lib/waterfall_sink_impl.cc b/lib/waterfall_sink_impl.cc new file mode 100644 index 0000000..94c1f30 --- /dev/null +++ b/lib/waterfall_sink_impl.cc @@ -0,0 +1,268 @@ +/* -*- c++ -*- */ +/* + * gr-satnogs: SatNOGS GNU Radio Out-Of-Tree Module + * + * Copyright (C) 2017, Libre Space Foundation + * + * This program 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 of the License, or + * (at your option) any later version. + * + * This program 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 program. If not, see . + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include "waterfall_sink_impl.h" +#include +namespace gr +{ + namespace satnogs + { + + waterfall_sink::sptr + waterfall_sink::make (double samp_rate, double center_freq, + double fps, size_t fft_size, + const std::string& filename, int mode) + { + return gnuradio::get_initial_sptr ( + new waterfall_sink_impl (samp_rate, center_freq, + fps, fft_size, filename, mode)); + } + + /* + * The private constructor + */ + waterfall_sink_impl::waterfall_sink_impl (double samp_rate, + double center_freq, + double pps, + size_t fft_size, + const std::string& filename, + int mode) : + gr::sync_block ("waterfall_sink", + gr::io_signature::make (1, 1, sizeof(gr_complex)), + gr::io_signature::make (0, 0, 0)), + d_samp_rate (samp_rate), + d_pps (pps), + d_fft_size (fft_size), + d_mode ((wf_mode_t)mode), + d_refresh( (d_samp_rate / fft_size) / pps), + d_fft_cnt(0), + d_fft_shift((size_t)(ceil(fft_size/2.0))), + d_samples_cnt(0), + d_fft (fft_size) + { + float r = 0.0; + const int alignment_multiple = volk_get_alignment () + / (fft_size * sizeof(gr_complex)); + set_alignment (std::max (1, alignment_multiple)); + set_output_multiple (fft_size); + + d_shift_buffer = (gr_complex *) volk_malloc ( + fft_size * sizeof(gr_complex), volk_get_alignment()); + if(!d_shift_buffer){ + LOG_ERROR("Could not allocate aligned memory"); + throw std::runtime_error("Could not allocate aligned memory"); + } + + d_hold_buffer = (float *)volk_malloc(fft_size * sizeof(gr_complex), + volk_get_alignment()); + if(!d_hold_buffer){ + LOG_ERROR("Could not allocate aligned memory"); + throw std::runtime_error("Could not allocate aligned memory"); + } + memset(d_hold_buffer, 0, fft_size * sizeof(gr_complex)); + + d_tmp_buffer = (float *) volk_malloc (fft_size * sizeof(float), + volk_get_alignment ()); + if (!d_tmp_buffer) { + LOG_ERROR("Could not allocate aligned memory"); + throw std::runtime_error ("Could not allocate aligned memory"); + } + + d_fos.open(filename, std::ios::binary | std::ios::trunc); + + /* Append header for proper plotting */ + r = fft_size; + d_fos.write((char *)&r, sizeof(float)); + for(size_t i = 0; i < fft_size; i++) { + r = (samp_rate/fft_size * i ) - samp_rate/2.0 + center_freq; + d_fos.write((char *)&r, sizeof(float)); + } + } + + /* + * Our virtual destructor. + */ + waterfall_sink_impl::~waterfall_sink_impl () + { + d_fos.close(); + volk_free(d_shift_buffer); + volk_free(d_hold_buffer); + volk_free(d_tmp_buffer); + } + + int + waterfall_sink_impl::work (int noutput_items, + gr_vector_const_void_star &input_items, + gr_vector_void_star &output_items) + { + const gr_complex *in = (const gr_complex *) input_items[0]; + size_t n_fft = ((size_t) noutput_items / d_fft_size); + + switch (d_mode) + { + case WATERFALL_MODE_DECIMATION: + compute_decimation (in, n_fft); + break; + case WATERFALL_MODE_MAX_HOLD: + compute_max_hold (in, n_fft); + break; + case WATERFALL_MODE_MEAN: + compute_mean (in, n_fft); + break; + default: + LOG_ERROR("Wrong waterfall mode"); + throw std::runtime_error ("Wrong waterfall mode"); + return -1; + } + + return n_fft * d_fft_size; + } + + void + waterfall_sink_impl::compute_decimation (const gr_complex* in, size_t n_fft) + { + size_t i; + float t; + gr_complex *fft_in; + for(i = 0; i < n_fft; i++){ + d_fft_cnt++; + if(d_fft_cnt > d_refresh){ + fft_in = d_fft.get_inbuf(); + memcpy(fft_in, in + i*d_fft_size, d_fft_size*sizeof(gr_complex)); + d_fft.execute(); + /* Perform FFT shift */ + memcpy (d_shift_buffer, &d_fft.get_outbuf ()[d_fft_shift], + sizeof(gr_complex) * (d_fft_size - d_fft_shift)); + memcpy (&d_shift_buffer[d_fft_size - d_fft_shift], + &d_fft.get_outbuf ()[0], sizeof(gr_complex) * d_fft_shift); + + /* Compute the energy in dB */ + volk_32fc_s32f_x2_power_spectral_density_32f (d_hold_buffer, + d_shift_buffer, + (float) d_fft_size, 1.0, + d_fft_size); + /* Write the result to the file */ + t = (float)(d_samples_cnt / d_samp_rate); + d_fos.write((char *) &t, sizeof(float)); + d_fos.write((char *) d_hold_buffer, d_fft_size * sizeof(float)); + d_fft_cnt = 0; + } + d_samples_cnt += d_fft_size; + } + } + + void + waterfall_sink_impl::compute_max_hold (const gr_complex* in, size_t n_fft) + { + size_t i; + size_t j; + float t; + gr_complex *fft_in; + for(i = 0; i < n_fft; i++){ + fft_in = d_fft.get_inbuf (); + memcpy (fft_in, in + i * d_fft_size, d_fft_size * sizeof(gr_complex)); + d_fft.execute (); + /* Perform FFT shift */ + memcpy (d_shift_buffer, &d_fft.get_outbuf ()[d_fft_shift], + sizeof(gr_complex) * (d_fft_size - d_fft_shift)); + memcpy (&d_shift_buffer[d_fft_size - d_fft_shift], + &d_fft.get_outbuf ()[0], sizeof(gr_complex) * d_fft_shift); + + /* Normalization factor */ + volk_32fc_s32fc_multiply_32fc(d_shift_buffer, d_shift_buffer, + 1.0/d_fft_size, d_fft_size); + + /* Compute the mag^2 */ + volk_32fc_magnitude_squared_32f(d_tmp_buffer, d_shift_buffer, + d_fft_size); + /* Max hold */ + volk_32f_x2_max_32f (d_hold_buffer, d_hold_buffer, d_tmp_buffer, + d_fft_size); + d_fft_cnt++; + if(d_fft_cnt > d_refresh) { + /* Compute the energy in dB */ + for(j = 0; j < d_fft_size; j++){ + d_hold_buffer[j] = 10.0 * log10f(d_hold_buffer[j] + 1.0e-20); + } + + /* Write the result to the file */ + t = (float)(d_samples_cnt / d_samp_rate); + d_fos.write((char *) &t, sizeof(float)); + d_fos.write((char *) d_hold_buffer, d_fft_size * sizeof(float)); + + /* Reset */ + d_fft_cnt = 0; + memset(d_hold_buffer, 0, d_fft_size * sizeof(float)); + } + d_samples_cnt += d_fft_size; + } + } + + void + waterfall_sink_impl::compute_mean (const gr_complex* in, size_t n_fft) + { + size_t i; + size_t j; + float t; + gr_complex *fft_in; + for(i = 0; i < n_fft; i++){ + fft_in = d_fft.get_inbuf (); + memcpy (fft_in, in + i * d_fft_size, d_fft_size * sizeof(gr_complex)); + d_fft.execute (); + /* Perform FFT shift */ + memcpy (d_shift_buffer, &d_fft.get_outbuf ()[d_fft_shift], + sizeof(gr_complex) * (d_fft_size - d_fft_shift)); + memcpy (&d_shift_buffer[d_fft_size - d_fft_shift], + &d_fft.get_outbuf ()[0], sizeof(gr_complex) * d_fft_shift); + + /* Accumulate the complex numbers */ + volk_32f_x2_add_32f(d_hold_buffer, d_hold_buffer, + (float *)d_shift_buffer, 2 * d_fft_size); + d_fft_cnt++; + if(d_fft_cnt > d_refresh) { + /* + * Compute the energy in dB performing the proper normalization + * before any dB calculation, emulating the mean + */ + volk_32fc_s32f_x2_power_spectral_density_32f ( + d_hold_buffer, (gr_complex *)d_hold_buffer, + (float) d_fft_cnt * d_fft_size, 1.0, d_fft_size); + + /* Write the result to the file */ + t = (float)(d_samples_cnt / d_samp_rate); + d_fos.write((char *) &t, sizeof(float)); + d_fos.write((char *) d_hold_buffer, d_fft_size * sizeof(float)); + + /* Reset */ + d_fft_cnt = 0; + memset(d_hold_buffer, 0, 2 * d_fft_size * sizeof(float)); + } + d_samples_cnt += d_fft_size; + } + } + + } /* namespace satnogs */ +} /* namespace gr */ + diff --git a/lib/waterfall_sink_impl.h b/lib/waterfall_sink_impl.h new file mode 100644 index 0000000..cf80b44 --- /dev/null +++ b/lib/waterfall_sink_impl.h @@ -0,0 +1,86 @@ +/* -*- c++ -*- */ +/* + * gr-satnogs: SatNOGS GNU Radio Out-Of-Tree Module + * + * Copyright (C) 2017, Libre Space Foundation + * + * This program 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 of the License, or + * (at your option) any later version. + * + * This program 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 program. If not, see . + */ + +#ifndef INCLUDED_SATNOGS_WATERFALL_SINK_IMPL_H +#define INCLUDED_SATNOGS_WATERFALL_SINK_IMPL_H + +#include +#include +#include +#include +#include + +namespace gr +{ + namespace satnogs + { + + class waterfall_sink_impl : public waterfall_sink + { + private: + /** + * The different types of operation of the waterfall + */ + typedef enum { + WATERFALL_MODE_DECIMATION = 0,//!< WATERFALL_MODE_DECIMATION Performs just a decimation and computes the energy only + WATERFALL_MODE_MAX_HOLD = 1, //!< WATERFALL_MODE_MAX_HOLD compute the max hold energy of all the FFT snapshots between two consecutive pixel rows + WATERFALL_MODE_MEAN = 2 //!< WATERFALL_MODE_MEAN compute the mean energy of all the FFT snapshots between two consecutive pixel rows + } wf_mode_t; + + const double d_samp_rate; + double d_pps; + const size_t d_fft_size; + wf_mode_t d_mode; + size_t d_refresh; + size_t d_fft_cnt; + size_t d_fft_shift; + size_t d_samples_cnt; + fft::fft_complex d_fft; + gr_complex *d_shift_buffer; + float *d_hold_buffer; + float *d_tmp_buffer; + std::ofstream d_fos; + + public: + waterfall_sink_impl (double samp_rate, double center_freq, + double pps, size_t fft_size, + const std::string& filename, int mode); + ~waterfall_sink_impl (); + + + int + work (int noutput_items, gr_vector_const_void_star &input_items, + gr_vector_void_star &output_items); + + void + compute_decimation(const gr_complex *in, size_t n_fft); + + void + compute_max_hold(const gr_complex *in, size_t n_fft); + + void + compute_mean(const gr_complex *in, size_t n_fft); + }; + + } // namespace satnogs +} // namespace gr + +#endif /* INCLUDED_SATNOGS_WATERFALL_SINK_IMPL_H */ + diff --git a/python/__init__.py b/python/__init__.py index 80199ba..58de6ce 100644 --- a/python/__init__.py +++ b/python/__init__.py @@ -22,6 +22,7 @@ This is the GNU Radio SATNOGS module. Place your Python package description here (python/__init__.py). ''' +import sys # import swig generated symbols into the satnogs namespace try: @@ -30,8 +31,8 @@ try: from dsp_settings import * from hw_settings import * from satnogs_upsat_transmitter import * -except ImportError: +except ImportError as err: + sys.stderr.write("Failed to import SatNOGS ({})\n".format(err)) + sys.stderr.write("Consider first to run 'sudo ldconfig'\n") pass -# import any pure python here -# diff --git a/swig/satnogs_swig.i b/swig/satnogs_swig.i index de1f8f0..4c6b730 100644 --- a/swig/satnogs_swig.i +++ b/swig/satnogs_swig.i @@ -30,18 +30,24 @@ #include "satnogs/ax25_encoder_mb.h" #include "satnogs/ax25_decoder_bm.h" #include "satnogs/qb50_deframer.h" +#include "satnogs/waterfall_sink.h" %} %include "satnogs/cw_matched_filter_ff.h" GR_SWIG_BLOCK_MAGIC2(satnogs, cw_matched_filter_ff); + %include "satnogs/morse_tree.h" + %include "satnogs/morse_decoder.h" GR_SWIG_BLOCK_MAGIC2(satnogs, morse_decoder); + %include "satnogs/morse_debug_source.h" GR_SWIG_BLOCK_MAGIC2(satnogs, morse_debug_source); + %include "satnogs/multi_format_msg_sink.h" GR_SWIG_BLOCK_MAGIC2(satnogs, multi_format_msg_sink); + %include "satnogs/cw_to_symbol.h" GR_SWIG_BLOCK_MAGIC2(satnogs, cw_to_symbol); @@ -50,28 +56,44 @@ GR_SWIG_BLOCK_MAGIC2(satnogs, sine_matched_filter_ff); %include "satnogs/udp_msg_source.h" GR_SWIG_BLOCK_MAGIC2(satnogs, udp_msg_source); + %include "satnogs/debug_msg_source.h" GR_SWIG_BLOCK_MAGIC2(satnogs, debug_msg_source); + %include "satnogs/tcp_rigctl_msg_source.h" GR_SWIG_BLOCK_MAGIC2(satnogs, tcp_rigctl_msg_source); + %include "satnogs/frame_encoder.h" GR_SWIG_BLOCK_MAGIC2(satnogs, frame_encoder); + %include "satnogs/doppler_correction_cc.h" GR_SWIG_BLOCK_MAGIC2(satnogs, doppler_correction_cc); + %include "satnogs/upsat_fsk_frame_acquisition.h" GR_SWIG_BLOCK_MAGIC2(satnogs, upsat_fsk_frame_acquisition); + %include "satnogs/upsat_fsk_frame_encoder.h" GR_SWIG_BLOCK_MAGIC2(satnogs, upsat_fsk_frame_encoder); + %include "satnogs/whitening.h" + %include "satnogs/udp_msg_sink.h" GR_SWIG_BLOCK_MAGIC2(satnogs, udp_msg_sink); + %include "satnogs/coarse_doppler_correction_cc.h" GR_SWIG_BLOCK_MAGIC2(satnogs, coarse_doppler_correction_cc); + %include "satnogs/debug_msg_source_raw.h" GR_SWIG_BLOCK_MAGIC2(satnogs, debug_msg_source_raw); + %include "satnogs/ax25_encoder_mb.h" GR_SWIG_BLOCK_MAGIC2(satnogs, ax25_encoder_mb); + %include "satnogs/ax25_decoder_bm.h" GR_SWIG_BLOCK_MAGIC2(satnogs, ax25_decoder_bm); + %include "satnogs/qb50_deframer.h" GR_SWIG_BLOCK_MAGIC2(satnogs, qb50_deframer); + +%include "satnogs/waterfall_sink.h" +GR_SWIG_BLOCK_MAGIC2(satnogs, waterfall_sink);