diff --git a/include/satnogs/waterfall_sink.h b/include/satnogs/waterfall_sink.h
index e946ae0..697bacf 100644
--- a/include/satnogs/waterfall_sink.h
+++ b/include/satnogs/waterfall_sink.h
@@ -2,7 +2,7 @@
/*
* gr-satnogs: SatNOGS GNU Radio Out-Of-Tree Module
*
- * Copyright (C) 2017, Libre Space Foundation
+ * Copyright (C) 2017,2019 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
@@ -31,8 +31,8 @@ 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.
+ * The file has a special header, so plotting tools can reconstruct properly
+ * the spectrum.
*
* \ingroup satnogs
*
@@ -45,25 +45,51 @@ public:
* 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.
+ * The file has a constant sized header of 52 bytes, so that plotting tools can
+ * reconstruct properly the spectrum.
+ *
+ * The structure of the header is the following:
+ * - A 32 byte string containing the timestamp in
+ * ISO-8601 format. This timer has microsecond accuracy.
+ * - A 4 byte integer containing the sampling rate
+ * - A 4 byte integer with the FFT size
+ * - A 4 byte integer containing the number of FFT snapshots for one row
+ * at the waterfall
+ * - A 4 byte float with the center frequency of the observation.
+ * - A 4 byte integer indicating the endianness of the rest of the file. If
+ * set to 0 the file continues in Big endian. Otherwise, in little endian.
+ * The change of the endianness is performed to reduce the overhead at the
+ * station.
+ *
+ * @note All contents of the header are in Network Byte order! The rest
+ * of the file is in native byte order, mainly for performance reasons.
+ * Users can use data of the header to determine if their architecture match
+ * the architecture of the host generated the waterfall file and act
+ * accordingly.
+ *
+ * The file continues with information regarding the spectral content of the
+ * observation.
+ * Each waterfall line is prepended with a int64_t field indicating the
+ * absolute time in microseconds with respect to the start of the waterfall
+ * data (stored in the corresponding header field).
+ * The spectral content is stored in $FFT$ float values already converted in
+ * dB scale.
*
* @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 rps rows 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,
+ make(float samp_rate, float center_freq,
+ float rps, size_t fft_size,
const std::string &filename, int mode = 0);
};
diff --git a/lib/waterfall_sink_impl.cc b/lib/waterfall_sink_impl.cc
index bad35b3..17c3caf 100644
--- a/lib/waterfall_sink_impl.cc
+++ b/lib/waterfall_sink_impl.cc
@@ -2,7 +2,7 @@
/*
* gr-satnogs: SatNOGS GNU Radio Out-Of-Tree Module
*
- * Copyright (C) 2017, Libre Space Foundation
+ * Copyright (C) 2017,2019 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
@@ -25,56 +25,55 @@
#include
#include "waterfall_sink_impl.h"
#include
+#include
+#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)
+waterfall_sink::make(float samp_rate, float center_freq, float rps,
+ 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));
+ new waterfall_sink_impl(samp_rate, center_freq, rps, 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,
+waterfall_sink_impl::waterfall_sink_impl(float samp_rate, float center_freq,
+ float rps, 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_center_freq(center_freq),
d_fft_size(fft_size),
- d_mode((wf_mode_t)mode),
- d_refresh((d_samp_rate / fft_size) / pps),
+ d_mode((wf_mode_t) mode),
+ d_refresh((d_samp_rate / fft_size) / rps),
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());
+ 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());
+ 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");
@@ -89,16 +88,23 @@ waterfall_sink_impl::waterfall_sink_impl(double samp_rate,
}
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));
+ if (d_fos.fail()) {
+ throw std::runtime_error("Could not create file for writing");
}
}
+bool
+waterfall_sink_impl::start()
+{
+ /*
+ * Append header for proper plotting. We do it on the start() to reduce
+ * as much as possible the delay between the start of the observation tagging
+ * and the fist invocation of the work() method.
+ */
+ apply_header();
+ return true;
+}
+
/*
* Our virtual destructor.
*/
@@ -133,7 +139,6 @@ waterfall_sink_impl::work(int noutput_items,
throw std::runtime_error("Wrong waterfall mode");
return -1;
}
-
return n_fft * d_fft_size;
}
@@ -141,11 +146,10 @@ 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) {
+ 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();
@@ -161,8 +165,7 @@ waterfall_sink_impl::compute_decimation(const gr_complex *in, size_t n_fft)
(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));
+ write_timestamp();
d_fos.write((char *) d_hold_buffer, d_fft_size * sizeof(float));
d_fft_cnt = 0;
}
@@ -175,7 +178,6 @@ 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();
@@ -184,29 +186,27 @@ waterfall_sink_impl::compute_max_hold(const gr_complex *in, size_t n_fft)
/* 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);
+ 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);
+ 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) {
+ 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));
+ write_timestamp();
d_fos.write((char *) d_hold_buffer, d_fft_size * sizeof(float));
/* Reset */
@@ -217,12 +217,51 @@ waterfall_sink_impl::compute_max_hold(const gr_complex *in, size_t n_fft)
}
}
+void
+waterfall_sink_impl::apply_header()
+{
+ header_t h;
+ memset(h.start_time, 0, 32);
+ std::chrono::system_clock::time_point tp = std::chrono::system_clock::now();
+ d_start = tp;
+ std::string s = date::format("%FT%TZ",
+ date::floor (tp));
+ std::strncpy(h.start_time, s.c_str(), 32);
+
+ /* Before writing to the file convert all values to Network Byte Order */
+ h.fft_size = htonl(d_fft_size);
+ h.samp_rate = htonl(d_samp_rate);
+ h.nfft_per_row = htonl(d_refresh);
+ uint32_t tmp = htonl(*((uint32_t *) &d_center_freq));
+ memcpy(&h.center_freq, &tmp, sizeof(uint32_t));
+ h.center_freq = *((float *) &tmp);
+ h.endianness = !(1 == htonl(1));
+
+ /*
+ * Write the header. Make a dummy serialization to avoid padding and
+ * alignment issues
+ */
+ d_fos.write(h.start_time, 32);
+ d_fos.write((char *)&h.fft_size, sizeof(uint32_t));
+ d_fos.write((char *)&h.samp_rate, sizeof(uint32_t));
+ d_fos.write((char *)&h.nfft_per_row, sizeof(uint32_t));
+ d_fos.write((char *)&h.center_freq, sizeof(float));
+ d_fos.write((char *)&h.endianness, sizeof(uint32_t));
+}
+
+void
+waterfall_sink_impl::write_timestamp()
+{
+ std::chrono::system_clock::time_point tp = std::chrono::system_clock::now();
+ int64_t x = std::chrono::duration_cast (
+ tp - d_start).count();
+ d_fos.write((char *)&x, sizeof(int64_t));
+}
+
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();
@@ -231,25 +270,24 @@ waterfall_sink_impl::compute_mean(const gr_complex *in, size_t n_fft)
/* 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);
+ 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);
+ 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) {
+ 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,
+ 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));
+ write_timestamp();
d_fos.write((char *) d_hold_buffer, d_fft_size * sizeof(float));
/* Reset */
diff --git a/lib/waterfall_sink_impl.h b/lib/waterfall_sink_impl.h
index f4bbd98..997db4e 100644
--- a/lib/waterfall_sink_impl.h
+++ b/lib/waterfall_sink_impl.h
@@ -2,7 +2,7 @@
/*
* gr-satnogs: SatNOGS GNU Radio Out-Of-Tree Module
*
- * Copyright (C) 2017, Libre Space Foundation
+ * Copyright (C) 2017,2019 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
@@ -26,12 +26,28 @@
#include
#include
#include
+#include
namespace gr {
namespace satnogs {
class waterfall_sink_impl : public waterfall_sink {
private:
+
+ /**
+ * Waterfall header data.
+ * This structure is only for readability purposes and make more clear to
+ * possible users the structure of the header.
+ */
+ typedef struct {
+ char start_time[32]; /**< String with the start of the waterfall in ISO-8601 format */
+ uint32_t samp_rate; /**< The sampling rate of the waterfall */
+ uint32_t fft_size; /**< The FFT size of the flowgraph */
+ uint32_t nfft_per_row; /**< The number of FFTs performed to plot one row */
+ float center_freq; /**< The center frequency of the observation. Just for viasualization purposes */
+ uint32_t endianness; /**< The endianness of the rest of the file. Should be 0 for big endian */
+ } header_t;
+
/**
* The different types of operation of the waterfall
*/
@@ -41,8 +57,8 @@ private:
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 float d_samp_rate;
+ const float d_center_freq;
const size_t d_fft_size;
wf_mode_t d_mode;
size_t d_refresh;
@@ -54,13 +70,21 @@ private:
float *d_hold_buffer;
float *d_tmp_buffer;
std::ofstream d_fos;
+ std::chrono::system_clock::time_point d_start;
+
+ void
+ apply_header();
+
+ void
+ write_timestamp();
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(float samp_rate, float center_freq, float rps,
+ size_t fft_size, const std::string &filename, int mode);
~waterfall_sink_impl();
+ bool
+ start();
int
work(int noutput_items, gr_vector_const_void_star &input_items,