From 0280bd327598342b5562ce11645fae8fcf649e2a Mon Sep 17 00:00:00 2001 From: "Matthias P. Braendli" Date: Thu, 18 Aug 2022 13:15:57 +0200 Subject: Improve DEXTER SFN support --- src/output/UHD.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/output/UHD.cpp') diff --git a/src/output/UHD.cpp b/src/output/UHD.cpp index 3cf5aef..9b22dde 100644 --- a/src/output/UHD.cpp +++ b/src/output/UHD.cpp @@ -411,7 +411,7 @@ double UHD::get_rxgain() const size_t UHD::receive_frame( complexf *buf, size_t num_samples, - struct frame_timestamp& ts, + frame_timestamp& ts, double timeout_secs) { uhd::stream_cmd_t cmd( -- cgit v1.2.3 From 05d3e26409a8f62c7f55851390d61d953f59489a Mon Sep 17 00:00:00 2001 From: "Matthias P. Braendli" Date: Tue, 21 Feb 2023 18:13:26 +0100 Subject: Change mod to output queue behaviour, fix SFN dexter + B200 --- lib/ThreadsafeQueue.h | 54 ++++++++++++++++++++++++++--- src/DabMod.cpp | 90 +++++++++++++++++++++++++++++------------------- src/DabModulator.cpp | 2 ++ src/TimestampDecoder.cpp | 1 + src/TimestampDecoder.h | 2 ++ src/output/Dexter.cpp | 31 ++++++++++------- src/output/SDR.cpp | 65 ++++++++++++++++++---------------- src/output/SDR.h | 1 + src/output/Soapy.cpp | 1 + src/output/UHD.cpp | 3 +- 10 files changed, 168 insertions(+), 82 deletions(-) (limited to 'src/output/UHD.cpp') diff --git a/lib/ThreadsafeQueue.h b/lib/ThreadsafeQueue.h index 815dfe0..8b385d6 100644 --- a/lib/ThreadsafeQueue.h +++ b/lib/ThreadsafeQueue.h @@ -2,7 +2,7 @@ Copyright (C) 2007, 2008, 2009, 2010, 2011 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) - Copyright (C) 2018 + Copyright (C) 2023 Matthias P. Braendli, matthias.braendli@mpb.li An implementation for a threadsafe queue, depends on C++11 @@ -32,6 +32,7 @@ #include #include #include +#include /* This queue is meant to be used by two threads. One producer * that pushes elements into the queue, and one consumer that @@ -69,7 +70,6 @@ public: } size_t queue_size = the_queue.size(); lock.unlock(); - the_rx_notification.notify_one(); return queue_size; @@ -93,11 +93,57 @@ public: return queue_size; } + struct push_overflow_result { bool overflowed; size_t new_size; }; + + /* Push one element into the queue, and if queue is + * full remove one element from the other end. + * + * max_size == 0 is not allowed. + * + * returns the new queue size and a flag if overflow occurred. + */ + push_overflow_result push_overflow(T const& val, size_t max_size) + { + assert(max_size > 0); + std::unique_lock lock(the_mutex); + + bool overflow = false; + while (the_queue.size() >= max_size) { + overflow = true; + the_queue.pop(); + } + the_queue.push(val); + const size_t queue_size = the_queue.size(); + lock.unlock(); + + the_rx_notification.notify_one(); + + return {overflow, queue_size}; + } + + push_overflow_result push_overflow(T&& val, size_t max_size) + { + assert(max_size > 0); + std::unique_lock lock(the_mutex); + + bool overflow = false; + while (the_queue.size() >= max_size) { + overflow = true; + the_queue.pop(); + } + the_queue.emplace(std::move(val)); + const size_t queue_size = the_queue.size(); + lock.unlock(); + + the_rx_notification.notify_one(); + + return {overflow, queue_size}; + } + + /* Push one element into the queue, but wait until the * queue size goes below the threshold. * - * Notify waiting thread. - * * returns the new queue size. */ size_t push_wait_if_full(T const& val, size_t threshold) diff --git a/src/DabMod.cpp b/src/DabMod.cpp index 4b4fda0..e5436ad 100644 --- a/src/DabMod.cpp +++ b/src/DabMod.cpp @@ -25,6 +25,7 @@ along with ODR-DabMod. If not, see . */ +#include #ifdef HAVE_CONFIG_H # include "config.h" #endif @@ -342,6 +343,27 @@ int launch_modulator(int argc, char* argv[]) printModSettings(mod_settings); + { + // This is mostly useful on ARM systems where FFTW planning takes some time. If we do it here + // it will be done before the modulator starts up + etiLog.level(debug) << "Running FFTW planning..."; + constexpr size_t fft_size = 2048; // Transmission Mode I. If different, it'll recalculate on OfdmGenerator + // initialisation + auto *fft_in = (fftwf_complex*)fftwf_malloc(sizeof(fftwf_complex) * fft_size); + auto *fft_out = (fftwf_complex*)fftwf_malloc(sizeof(fftwf_complex) * fft_size); + if (fft_in == nullptr or fft_out == nullptr) { + throw std::runtime_error("FFTW malloc failed"); + } + fftwf_set_timelimit(2); + fftwf_plan plan = fftwf_plan_dft_1d(fft_size, fft_in, fft_out, FFTW_FORWARD, FFTW_MEASURE); + fftwf_destroy_plan(plan); + plan = fftwf_plan_dft_1d(fft_size, fft_in, fft_out, FFTW_BACKWARD, FFTW_MEASURE); + fftwf_destroy_plan(plan); + fftwf_free(fft_in); + fftwf_free(fft_out); + etiLog.level(debug) << "FFTW planning done."; + } + shared_ptr format_converter; if (mod_settings.useFileOutput and (mod_settings.fileOutputFormat == "s8" or @@ -563,48 +585,46 @@ static run_modulator_state_t run_modulator(const mod_settings_t& mod_settings, m ts = m.ediInput->ediReader.getTimestamp(); } - bool fct_good = false; - if (last_eti_fct == -1) { - if (fp != 0) { - // Do not start the flowgraph before we get to FP 0 - // to ensure all blocks are properly aligned. - if (m.ediInput) { - m.ediInput->ediReader.clearFrame(); - } - continue; - } - else { - fct_good = true; - } + // timestamp is good if we run unsynchronised, or if margin is sufficient + bool ts_good = not mod_settings.sdr_device_config.enableSync or + (ts.timestamp_valid and ts.offset_to_system_time() > 0.2); + + if (!ts_good) { + etiLog.level(warn) << "Modulator skipping frame " << fct << + " TS " << (ts.timestamp_valid ? "valid" : "invalid") << + " offset " << (ts.timestamp_valid ? ts.offset_to_system_time() : 0); } else { - const unsigned expected_fct = (last_eti_fct + 1) % 250; - if (fct == expected_fct) { - fct_good = true; + bool modulate = true; + if (last_eti_fct == -1) { + if (fp != 0) { + // Do not start the flowgraph before we get to FP 0 + // to ensure all blocks are properly aligned. + modulate = false; + } + else { + last_eti_fct = fct; + } } else { - etiLog.level(info) << "ETI FCT discontinuity, expected " << - expected_fct << " received " << fct; - if (m.ediInput) { - m.ediInput->ediReader.clearFrame(); + const unsigned expected_fct = (last_eti_fct + 1) % 250; + if (fct == expected_fct) { + last_eti_fct = fct; + } + else { + etiLog.level(info) << "ETI FCT discontinuity, expected " << + expected_fct << " received " << fct; + if (m.ediInput) { + m.ediInput->ediReader.clearFrame(); + } + return run_modulator_state_t::again; } - return run_modulator_state_t::again; } - } - // timestamp is good if we run unsynchronised, or if margin is insufficient - bool ts_good = not mod_settings.sdr_device_config.enableSync or - (ts.timestamp_valid and ts.offset_to_system_time() > 1); - - if (fct_good and ts_good) { - last_eti_fct = fct; - m.framecount++; - m.flowgraph->run(); - } - else { - etiLog.level(warn) << "Skipping frame " << - " TS " << (ts.timestamp_valid ? "valid" : "invalid") << - " offset " << (ts.timestamp_valid ? ts.offset_to_system_time() : 0); + if (modulate) { + m.framecount++; + m.flowgraph->run(); + } } if (m.ediInput) { diff --git a/src/DabModulator.cpp b/src/DabModulator.cpp index 1f16d1d..3d8bd46 100644 --- a/src/DabModulator.cpp +++ b/src/DabModulator.cpp @@ -126,6 +126,7 @@ int DabModulator::process(Buffer* dataOut) PDEBUG("DabModulator::process(dataOut: %p)\n", dataOut); if (not myFlowgraph) { + etiLog.level(debug) << "Setting up DabModulator..."; const unsigned mode = m_settings.dabMode; setMode(mode); @@ -364,6 +365,7 @@ int DabModulator::process(Buffer* dataOut) prev_plugin = p; } } + etiLog.level(debug) << "DabModulator set up."; } //////////////////////////////////////////////////////////////////// diff --git a/src/TimestampDecoder.cpp b/src/TimestampDecoder.cpp index 674f32c..6e97af6 100644 --- a/src/TimestampDecoder.cpp +++ b/src/TimestampDecoder.cpp @@ -110,6 +110,7 @@ frame_timestamp TimestampDecoder::getTimestamp() ts.fct = latestFCT; ts.fp = latestFP; + ts.timestamp_offset = timestamp_offset; ts.offset_changed = offset_changed; offset_changed = false; diff --git a/src/TimestampDecoder.h b/src/TimestampDecoder.h index 2793e02..597b777 100644 --- a/src/TimestampDecoder.h +++ b/src/TimestampDecoder.h @@ -42,6 +42,8 @@ struct frame_timestamp uint32_t timestamp_sec; // seconds in unix epoch uint32_t timestamp_pps; // In units of 1/16384000 s bool timestamp_valid = false; + + double timestamp_offset = 0.0; // copy of the configured modulator offset bool offset_changed = false; frame_timestamp& operator+=(const double& diff); diff --git a/src/output/Dexter.cpp b/src/output/Dexter.cpp index cc10c57..4e24cfb 100644 --- a/src/output/Dexter.cpp +++ b/src/output/Dexter.cpp @@ -46,6 +46,8 @@ namespace Output { static constexpr uint64_t DSP_CLOCK = 2048000uLL * 80; +static constexpr uint64_t IIO_TIMEOUT_MS = 1000; + static constexpr size_t TRANSMISSION_FRAME_LEN = (2656 + 76 * 2552) * 4; static constexpr size_t IIO_BUFFERS = 4; static constexpr size_t IIO_BUFFER_LEN = TRANSMISSION_FRAME_LEN / IIO_BUFFERS; @@ -53,7 +55,7 @@ static constexpr size_t IIO_BUFFER_LEN = TRANSMISSION_FRAME_LEN / IIO_BUFFERS; static string get_iio_error(int err) { char dst[256]; - iio_strerror(err, dst, sizeof(dst)); + iio_strerror(-err, dst, sizeof(dst)); return string(dst); } @@ -75,6 +77,11 @@ Dexter::Dexter(SDRDeviceConfig& config) : throw std::runtime_error("Dexter: Unable to create iio scan context"); } + int r; + if ((r = iio_context_set_timeout(m_ctx, IIO_TIMEOUT_MS)) != 0) { + etiLog.level(error) << "Failed to set IIO timeout " << get_iio_error(r); + } + m_dexter_dsp_tx = iio_context_find_device(m_ctx, "dexter_dsp_tx"); if (!m_dexter_dsp_tx) { throw std::runtime_error("Dexter: Unable to find dexter_dsp_tx iio device"); @@ -85,8 +92,6 @@ Dexter::Dexter(SDRDeviceConfig& config) : throw std::runtime_error("Dexter: Unable to find ad9957_tx0 iio device"); } - int r; - // TODO make DC offset configurable and add to RC if ((r = iio_device_attr_write_longlong(m_dexter_dsp_tx, "dc0", 0)) != 0) { etiLog.level(warn) << "Failed to set dexter_dsp_tx.dc0 = false: " << get_iio_error(r); @@ -97,11 +102,11 @@ Dexter::Dexter(SDRDeviceConfig& config) : } if ((r = iio_device_attr_write_longlong(m_dexter_dsp_tx, "stream0_start_clks", 0)) != 0) { - etiLog.level(warn) << "Failed to set dexter_dsp_tx.stream0_start_clks = 0: " << get_iio_error(r); + etiLog.level(error) << "Failed to set dexter_dsp_tx.stream0_start_clks = 0: " << get_iio_error(r); } if ((r = iio_device_attr_write_longlong(m_dexter_dsp_tx, "gain0", m_conf.txgain)) != 0) { - etiLog.level(warn) << "Failed to set dexter_dsp_tx.stream0_start_clks = 0: " << get_iio_error(r); + etiLog.level(error) << "Failed to set dexter_dsp_tx.stream0_start_clks = 0: " << get_iio_error(r); } if (m_conf.sampleRate != 2048000) { @@ -396,28 +401,28 @@ void Dexter::transmit_frame(const struct FrameData& frame) etiLog.level(error) << "Failed to get dexter_dsp_tx.pps_clks: " << get_iio_error(r); } - const double margin = (double)((int64_t)frame_ts_clocks - pps_clks) / DSP_CLOCK; + const double margin_s = (double)((int64_t)frame_ts_clocks - pps_clks) / DSP_CLOCK; - etiLog.level(debug) << "Dexter: TS CLK " << + etiLog.level(debug) << "DEXTER FCT " << frame.ts.fct << " TS CLK " << ((int64_t)frame.ts.timestamp_sec - (int64_t)m_utc_seconds_at_startup) * DSP_CLOCK << " + " << m_clock_count_at_startup << " + " << (uint64_t)frame.ts.timestamp_pps * TIMESTAMP_PPS_PER_DSP_CLOCKS << " = " << frame_ts_clocks << " DELTA " << - frame_ts_clocks << " - " << pps_clks << " = " << margin; + frame_ts_clocks << " - " << pps_clks << " = " << margin_s; - // Ensure we hand the frame over to HW at least 0.2s before timestamp - if (margin < 0.2) { - etiLog.level(warn) << "Skip frame short margin " << margin; + // Ensure we hand the frame over to HW with a bit of margin + if (margin_s < 0.1) { + etiLog.level(warn) << "Skip frame short margin " << margin_s; num_late++; return; } - if ((r = iio_device_attr_write_longlong(m_dexter_dsp_tx, "stream0_start_clks", frame_ts_clocks)) != 0) { etiLog.level(warn) << "Skip frame, failed to set dexter_dsp_tx.stream0_start_clks = " << frame_ts_clocks << " : " << get_iio_error(r); num_late++; return; } + m_require_timestamp_refresh = false; } channel_up(); @@ -469,6 +474,7 @@ void Dexter::underflow_read_process() set_thread_name("dexter_underflow"); while (m_running) { + this_thread::sleep_for(chrono::seconds(1)); long long attr_value = 0; int r = 0; @@ -481,7 +487,6 @@ void Dexter::underflow_read_process() underflows = underflows_new; } } - this_thread::sleep_for(chrono::seconds(1)); } m_running = false; } diff --git a/src/output/SDR.cpp b/src/output/SDR.cpp index 2b6700f..0b3299a 100644 --- a/src/output/SDR.cpp +++ b/src/output/SDR.cpp @@ -2,7 +2,7 @@ Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) - Copyright (C) 2022 + Copyright (C) 2023 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org @@ -46,17 +46,13 @@ using namespace std; namespace Output { -// Maximum number of frames that can wait in frames +// Maximum number of frames that can wait in frames, when not using synchronised transmission static constexpr size_t FRAMES_MAX_SIZE = 8; // If the timestamp is further in the future than // 100 seconds, abort static constexpr double TIMESTAMP_ABORT_FUTURE = 100; -// Add a delay to increase buffers when -// frames are too far in the future -static constexpr double TIMESTAMP_MARGIN_FUTURE = 0.5; - SDR::SDR(SDRDeviceConfig& config, std::shared_ptr device) : ModOutput(), ModMetadata(), RemoteControllable("sdr"), m_config(config), @@ -127,6 +123,10 @@ int SDR::process(Buffer *dataIn) meta_vec_t SDR::process_metadata(const meta_vec_t& metadataIn) { + double frame_duration_s = + chrono::duration_cast( + transmission_frame_duration(m_config.dabMode)).count() / 1000.0; + if (m_device and m_running) { FrameData frame; frame.buf = std::move(m_frame); @@ -163,9 +163,22 @@ meta_vec_t SDR::process_metadata(const meta_vec_t& metadataIn) m_config.sampleRate); } - size_t num_frames = m_queue.push_wait_if_full(frame, - FRAMES_MAX_SIZE); - etiLog.log(trace, "SDR,push %zu", num_frames); + + const auto max_size = m_config.enableSync ? + (frame.ts.timestamp_offset * 4.0) / frame_duration_s + : FRAMES_MAX_SIZE; + + auto r = m_queue.push_overflow(std::move(frame), max_size); + etiLog.log(trace, "SDR,push %d %zu", r.overflowed, r.new_size); + + if (r.overflowed) { + fprintf(stderr, "o"); + } + else { + fprintf(stderr, "."); + } + + num_queue_overflows += r.overflowed ? 1 : 0; } } else { @@ -186,16 +199,13 @@ void SDR::process_thread_entry() last_tx_time_initialised = false; - size_t last_num_underflows = 0; - size_t pop_prebuffering = FRAMES_MAX_SIZE; - m_running.store(true); try { while (m_running.load()) { struct FrameData frame; etiLog.log(trace, "SDR,wait"); - m_queue.wait_and_pop(frame, pop_prebuffering); + m_queue.wait_and_pop(frame); etiLog.log(trace, "SDR,pop"); if (m_running.load() == false) { @@ -204,19 +214,6 @@ void SDR::process_thread_entry() if (m_device) { handle_frame(frame); - - const auto rs = m_device->get_run_statistics(); - - /* Ensure we fill frames after every underrun and - * at startup to reduce underrun likelihood. */ - if (last_num_underflows < rs.num_underruns) { - pop_prebuffering = FRAMES_MAX_SIZE; - } - else { - pop_prebuffering = 1; - } - - last_num_underflows = rs.num_underruns; } } } @@ -302,6 +299,7 @@ void SDR::handle_frame(struct FrameData& frame) } if (frame.ts.offset_changed) { + etiLog.level(debug) << "TS offset changed"; m_device->require_timestamp_refresh(); } @@ -354,9 +352,19 @@ void SDR::handle_frame(struct FrameData& frame) " frame " << frame.ts.fct << ", tx_second " << tx_second << ", pps " << pps_offset; + m_device->require_timestamp_refresh(); return; } + etiLog.level(debug) << + "OutputSDR: Timestamp at FCT=" << frame.ts.fct << " offset: " << + std::fixed << + time_spec.get_real_secs() - device_time << + " (" << device_time << ")" + " frame " << frame.ts.fct << + ", tx_second " << tx_second << + ", pps " << pps_offset; + if (time_spec.get_real_secs() > device_time + TIMESTAMP_ABORT_FUTURE) { etiLog.level(error) << "OutputSDR: Timestamp way too far in the future at FCT=" << frame.ts.fct << " offset: " << @@ -367,9 +375,8 @@ void SDR::handle_frame(struct FrameData& frame) } if (m_config.muting) { - etiLog.log(info, - "OutputSDR: Muting FCT=%d requested", - frame.ts.fct); + etiLog.log(info, "OutputSDR: Muting FCT=%d requested", frame.ts.fct); + m_device->require_timestamp_refresh(); return; } diff --git a/src/output/SDR.h b/src/output/SDR.h index d7f7b46..5c3b599 100644 --- a/src/output/SDR.h +++ b/src/output/SDR.h @@ -88,6 +88,7 @@ class SDR : public ModOutput, public ModMetadata, public RemoteControllable { bool last_tx_time_initialised = false; uint32_t last_tx_second = 0; uint32_t last_tx_pps = 0; + size_t num_queue_overflows = 0; bool t_last_frame_initialised = false; std::chrono::steady_clock::time_point t_last_frame; diff --git a/src/output/Soapy.cpp b/src/output/Soapy.cpp index c2c5046..c2ae88a 100644 --- a/src/output/Soapy.cpp +++ b/src/output/Soapy.cpp @@ -349,6 +349,7 @@ void Soapy::transmit_frame(const struct FrameData& frame) SoapySDR::errToStr(ret_deact)); } m_tx_stream_active = false; + m_require_timestamp_refresh = false; } if (eob_because_muting) { diff --git a/src/output/UHD.cpp b/src/output/UHD.cpp index 6e38f73..6810249 100644 --- a/src/output/UHD.cpp +++ b/src/output/UHD.cpp @@ -350,6 +350,7 @@ void UHD::transmit_frame(const struct FrameData& frame) frame.ts.timestamp_valid and m_require_timestamp_refresh and samps_to_send <= usrp_max_num_samps ); + m_require_timestamp_refresh = false; //send a single packet size_t num_tx_samps = m_tx_stream->send( @@ -359,7 +360,7 @@ void UHD::transmit_frame(const struct FrameData& frame) num_acc_samps += num_tx_samps; - md_tx.time_spec += uhd::time_spec_t(0, num_tx_samps/m_conf.sampleRate); + md_tx.time_spec += uhd::time_spec_t::from_ticks(num_tx_samps, (double)m_conf.sampleRate); if (num_tx_samps == 0) { etiLog.log(warn, -- cgit v1.2.3 From 89e4bcbcba5883355a9b4777cea2bce0a1afd53d Mon Sep 17 00:00:00 2001 From: "Matthias P. Braendli" Date: Fri, 3 Mar 2023 14:24:27 +0100 Subject: Dexter: calculate frame margin from system time --- src/output/BladeRF.cpp | 2 +- src/output/BladeRF.h | 2 +- src/output/Dexter.cpp | 24 ++++++++++-------------- src/output/Dexter.h | 4 ++-- src/output/Lime.cpp | 2 +- src/output/Lime.h | 2 +- src/output/SDR.cpp | 6 +++--- src/output/SDR.h | 4 ++-- src/output/SDRDevice.h | 2 +- src/output/Soapy.cpp | 2 +- src/output/Soapy.h | 2 +- src/output/UHD.cpp | 2 +- src/output/UHD.h | 2 +- 13 files changed, 26 insertions(+), 30 deletions(-) (limited to 'src/output/UHD.cpp') diff --git a/src/output/BladeRF.cpp b/src/output/BladeRF.cpp index dd48736..db29898 100755 --- a/src/output/BladeRF.cpp +++ b/src/output/BladeRF.cpp @@ -304,7 +304,7 @@ double BladeRF::get_temperature(void) const } -void BladeRF::transmit_frame(const struct FrameData &frame) // SC16 frames +void BladeRF::transmit_frame(struct FrameData&& frame) // SC16 frames { const size_t num_samples = frame.buf.size() / (2*sizeof(int16_t)); diff --git a/src/output/BladeRF.h b/src/output/BladeRF.h index e048daa..1a63fbf 100755 --- a/src/output/BladeRF.h +++ b/src/output/BladeRF.h @@ -74,7 +74,7 @@ class BladeRF : public Output::SDRDevice virtual double get_txgain(void) const override; virtual void set_bandwidth(double bandwidth) override; virtual double get_bandwidth(void) const override; - virtual void transmit_frame(const struct FrameData& frame) override; + virtual void transmit_frame(struct FrameData&& frame) override; virtual RunStatistics get_run_statistics(void) const override; virtual double get_real_secs(void) const override; diff --git a/src/output/Dexter.cpp b/src/output/Dexter.cpp index 4e24cfb..b6e6700 100644 --- a/src/output/Dexter.cpp +++ b/src/output/Dexter.cpp @@ -2,7 +2,7 @@ Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) - Copyright (C) 2022 + Copyright (C) 2023 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org @@ -369,7 +369,7 @@ double Dexter::get_temperature(void) const return std::numeric_limits::quiet_NaN(); } -void Dexter::transmit_frame(const struct FrameData& frame) +void Dexter::transmit_frame(struct FrameData&& frame) { if (frame.buf.size() != TRANSMISSION_FRAME_LEN) { etiLog.level(debug) << "Dexter::transmit_frame Expected " << @@ -379,6 +379,8 @@ void Dexter::transmit_frame(const struct FrameData& frame) const bool require_timestamped_tx = (m_conf.enableSync and frame.ts.timestamp_valid); + const double margin_s = frame.ts.offset_to_system_time(); + if (m_buffer == nullptr) { if (require_timestamped_tx) { /* @@ -387,36 +389,28 @@ void Dexter::transmit_frame(const struct FrameData& frame) timeS - m_utc_seconds_at_startup; */ - // 10 because timestamp_pps is represented in 16.384 MHz clocks constexpr uint64_t TIMESTAMP_PPS_PER_DSP_CLOCKS = DSP_CLOCK / 16384000; + // TIMESTAMP_PPS_PER_DSP_CLOCKS=10 because timestamp_pps is represented in 16.384 MHz clocks uint64_t frame_ts_clocks = // at second level ((int64_t)frame.ts.timestamp_sec - (int64_t)m_utc_seconds_at_startup) * DSP_CLOCK + m_clock_count_at_startup + // at subsecond level (uint64_t)frame.ts.timestamp_pps * TIMESTAMP_PPS_PER_DSP_CLOCKS; - long long pps_clks = 0; - int r; - if ((r = iio_device_attr_read_longlong(m_dexter_dsp_tx, "pps_clks", &pps_clks)) != 0) { - etiLog.level(error) << "Failed to get dexter_dsp_tx.pps_clks: " << get_iio_error(r); - } - - const double margin_s = (double)((int64_t)frame_ts_clocks - pps_clks) / DSP_CLOCK; - etiLog.level(debug) << "DEXTER FCT " << frame.ts.fct << " TS CLK " << ((int64_t)frame.ts.timestamp_sec - (int64_t)m_utc_seconds_at_startup) * DSP_CLOCK << " + " << m_clock_count_at_startup << " + " << (uint64_t)frame.ts.timestamp_pps * TIMESTAMP_PPS_PER_DSP_CLOCKS << " = " << - frame_ts_clocks << " DELTA " << - frame_ts_clocks << " - " << pps_clks << " = " << margin_s; + frame_ts_clocks << " DELTA " << margin_s; // Ensure we hand the frame over to HW with a bit of margin - if (margin_s < 0.1) { + if (margin_s < 0.2) { etiLog.level(warn) << "Skip frame short margin " << margin_s; num_late++; return; } + int r; if ((r = iio_device_attr_write_longlong(m_dexter_dsp_tx, "stream0_start_clks", frame_ts_clocks)) != 0) { etiLog.level(warn) << "Skip frame, failed to set dexter_dsp_tx.stream0_start_clks = " << frame_ts_clocks << " : " << get_iio_error(r); num_late++; @@ -428,6 +422,8 @@ void Dexter::transmit_frame(const struct FrameData& frame) channel_up(); } + etiLog.level(debug) << "DEXTER TX " << frame.ts.fct << " TS margin " << margin_s; + if (m_require_timestamp_refresh) { etiLog.level(debug) << "DEXTER REQUIRE REFRESH"; channel_down(); diff --git a/src/output/Dexter.h b/src/output/Dexter.h index 36d0ef7..2bd63b1 100644 --- a/src/output/Dexter.h +++ b/src/output/Dexter.h @@ -2,7 +2,7 @@ Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) - Copyright (C) 2022 + Copyright (C) 2023 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org @@ -64,7 +64,7 @@ class Dexter : public Output::SDRDevice virtual double get_txgain() const override; virtual void set_bandwidth(double bandwidth) override; virtual double get_bandwidth() const override; - virtual void transmit_frame(const struct FrameData& frame) override; + virtual void transmit_frame(struct FrameData&& frame) override; virtual RunStatistics get_run_statistics() const override; virtual double get_real_secs() const override; diff --git a/src/output/Lime.cpp b/src/output/Lime.cpp index d3e4640..bf466c3 100644 --- a/src/output/Lime.cpp +++ b/src/output/Lime.cpp @@ -389,7 +389,7 @@ float Lime::get_fifo_fill_percent(void) const return m_last_fifo_fill_percent * 100; } -void Lime::transmit_frame(const struct FrameData &frame) +void Lime::transmit_frame(struct FrameData&& frame) { if (not m_device) throw runtime_error("Lime device not set up"); diff --git a/src/output/Lime.h b/src/output/Lime.h index a4603c0..95c5c48 100644 --- a/src/output/Lime.h +++ b/src/output/Lime.h @@ -66,7 +66,7 @@ class Lime : public Output::SDRDevice virtual double get_txgain(void) const override; virtual void set_bandwidth(double bandwidth) override; virtual double get_bandwidth(void) const override; - virtual void transmit_frame(const struct FrameData &frame) override; + virtual void transmit_frame(struct FrameData&& frame) override; virtual RunStatistics get_run_statistics(void) const override; virtual double get_real_secs(void) const override; diff --git a/src/output/SDR.cpp b/src/output/SDR.cpp index 0b3299a..e22617e 100644 --- a/src/output/SDR.cpp +++ b/src/output/SDR.cpp @@ -213,7 +213,7 @@ void SDR::process_thread_entry() } if (m_device) { - handle_frame(frame); + handle_frame(std::move(frame)); } } } @@ -260,7 +260,7 @@ void SDR::sleep_through_frame() t_last_frame += wait_time; } -void SDR::handle_frame(struct FrameData& frame) +void SDR::handle_frame(struct FrameData&& frame) { // Assumes m_device is valid @@ -386,7 +386,7 @@ void SDR::handle_frame(struct FrameData& frame) " TS " << frame.ts.to_string(); } - m_device->transmit_frame(frame); + m_device->transmit_frame(std::move(frame)); } // ======================================= diff --git a/src/output/SDR.h b/src/output/SDR.h index 5c3b599..eb0ed9d 100644 --- a/src/output/SDR.h +++ b/src/output/SDR.h @@ -2,7 +2,7 @@ Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) - Copyright (C) 2022 + Copyright (C) 2023 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org @@ -69,7 +69,7 @@ class SDR : public ModOutput, public ModMetadata, public RemoteControllable { private: void process_thread_entry(void); - void handle_frame(struct FrameData &frame); + void handle_frame(struct FrameData&& frame); void sleep_through_frame(void); SDRDeviceConfig& m_config; diff --git a/src/output/SDRDevice.h b/src/output/SDRDevice.h index f4b6c34..ffa1a3b 100644 --- a/src/output/SDRDevice.h +++ b/src/output/SDRDevice.h @@ -123,7 +123,7 @@ class SDRDevice { virtual double get_tx_freq(void) const = 0; virtual void set_txgain(double txgain) = 0; virtual double get_txgain(void) const = 0; - virtual void transmit_frame(const struct FrameData& frame) = 0; + virtual void transmit_frame(struct FrameData&& frame) = 0; virtual RunStatistics get_run_statistics(void) const = 0; virtual double get_real_secs(void) const = 0; virtual void set_rxgain(double rxgain) = 0; diff --git a/src/output/Soapy.cpp b/src/output/Soapy.cpp index c2ae88a..8c52546 100644 --- a/src/output/Soapy.cpp +++ b/src/output/Soapy.cpp @@ -272,7 +272,7 @@ double Soapy::get_temperature(void) const return std::numeric_limits::quiet_NaN(); } -void Soapy::transmit_frame(const struct FrameData& frame) +void Soapy::transmit_frame(struct FrameData&& frame) { if (not m_device) throw runtime_error("Soapy device not set up"); diff --git a/src/output/Soapy.h b/src/output/Soapy.h index ca2618b..f3e1ee2 100644 --- a/src/output/Soapy.h +++ b/src/output/Soapy.h @@ -65,7 +65,7 @@ class Soapy : public Output::SDRDevice virtual double get_txgain(void) const override; virtual void set_bandwidth(double bandwidth) override; virtual double get_bandwidth(void) const override; - virtual void transmit_frame(const struct FrameData& frame) override; + virtual void transmit_frame(struct FrameData&& frame) override; virtual RunStatistics get_run_statistics(void) const override; virtual double get_real_secs(void) const override; diff --git a/src/output/UHD.cpp b/src/output/UHD.cpp index 6810249..c325272 100644 --- a/src/output/UHD.cpp +++ b/src/output/UHD.cpp @@ -315,7 +315,7 @@ double UHD::get_bandwidth(void) const return m_usrp->get_tx_bandwidth(); } -void UHD::transmit_frame(const struct FrameData& frame) +void UHD::transmit_frame(struct FrameData&& frame) { const double tx_timeout = 20.0; const size_t sizeIn = frame.buf.size() / sizeof(complexf); diff --git a/src/output/UHD.h b/src/output/UHD.h index 4c1a4f0..164254c 100644 --- a/src/output/UHD.h +++ b/src/output/UHD.h @@ -79,7 +79,7 @@ class UHD : public Output::SDRDevice virtual double get_txgain(void) const override; virtual void set_bandwidth(double bandwidth) override; virtual double get_bandwidth(void) const override; - virtual void transmit_frame(const struct FrameData& frame) override; + virtual void transmit_frame(struct FrameData&& frame) override; virtual RunStatistics get_run_statistics(void) const override; virtual double get_real_secs(void) const override; -- cgit v1.2.3 From fd46d43f51796a6bbdd629e08535bacbc853f283 Mon Sep 17 00:00:00 2001 From: "Matthias P. Braendli" Date: Wed, 29 Mar 2023 16:30:06 +0200 Subject: Replace RunStatistics to permit representation of SDR device specific stats --- src/output/BladeRF.cpp | 22 +++---- src/output/BladeRF.h | 9 +-- src/output/Dexter.cpp | 153 ++++++++++++++++++++++++++++++------------------- src/output/Dexter.h | 7 ++- src/output/Lime.cpp | 31 +++++----- src/output/Lime.h | 8 +-- src/output/SDR.cpp | 77 +++++++++++-------------- src/output/SDRDevice.h | 22 +++---- src/output/Soapy.cpp | 17 +++--- src/output/Soapy.h | 6 +- src/output/UHD.cpp | 24 ++++---- src/output/UHD.h | 4 +- 12 files changed, 197 insertions(+), 183 deletions(-) (limited to 'src/output/UHD.cpp') diff --git a/src/output/BladeRF.cpp b/src/output/BladeRF.cpp index db29898..2dd7176 100755 --- a/src/output/BladeRF.cpp +++ b/src/output/BladeRF.cpp @@ -239,13 +239,10 @@ double BladeRF::get_bandwidth(void) const return (double)bw; } -SDRDevice::RunStatistics BladeRF::get_run_statistics(void) const +SDRDevice::run_statistics_t BladeRF::get_run_statistics(void) const { - RunStatistics rs; - rs.num_underruns = underflows; - rs.num_overruns = overflows; - rs.num_late_packets = late_packets; - rs.num_frames_modulated = num_frames_modulated; + run_statistics_t rs; + rs["frames"] = num_frames_modulated; return rs; } @@ -287,23 +284,22 @@ const char *BladeRF::device_name(void) const return "BladeRF"; } -double BladeRF::get_temperature(void) const +std::optional BladeRF::get_temperature(void) const { if (not m_device) throw runtime_error("BladeRF device not set up"); float temp = 0.0; - int status = bladerf_get_rfic_temperature(m_device, &temp); - if (status < 0) - { + if (status >= 0) { + return (double)temp; + } + else { etiLog.level(error) << "Error getting BladeRF temperature: %s " << bladerf_strerror(status); + return std::nullopt; } - - return (double)temp; } - void BladeRF::transmit_frame(struct FrameData&& frame) // SC16 frames { const size_t num_samples = frame.buf.size() / (2*sizeof(int16_t)); diff --git a/src/output/BladeRF.h b/src/output/BladeRF.h index 1a63fbf..eb3e58b 100755 --- a/src/output/BladeRF.h +++ b/src/output/BladeRF.h @@ -75,7 +75,7 @@ class BladeRF : public Output::SDRDevice virtual void set_bandwidth(double bandwidth) override; virtual double get_bandwidth(void) const override; virtual void transmit_frame(struct FrameData&& frame) override; - virtual RunStatistics get_run_statistics(void) const override; + virtual run_statistics_t get_run_statistics(void) const override; virtual double get_real_secs(void) const override; virtual void set_rxgain(double rxgain) override; @@ -90,7 +90,7 @@ class BladeRF : public Output::SDRDevice virtual bool is_clk_source_ok(void) const override; virtual const char* device_name(void) const override; - virtual double get_temperature(void) const override; + virtual std::optional get_temperature(void) const override; private: @@ -99,12 +99,7 @@ class BladeRF : public Output::SDRDevice bladerf_channel m_channel = BLADERF_CHANNEL_TX(0); // channel TX0 //struct bladerf_stream* m_stream; /* used for asynchronous api */ - size_t underflows = 0; - size_t overflows = 0; - size_t late_packets = 0; size_t num_frames_modulated = 0; - //size_t num_underflows_previous = 0; - //size_t num_late_packets_previous = 0; }; } // namespace Output diff --git a/src/output/Dexter.cpp b/src/output/Dexter.cpp index d892486..dd882f2 100644 --- a/src/output/Dexter.cpp +++ b/src/output/Dexter.cpp @@ -74,7 +74,7 @@ Dexter::Dexter(SDRDeviceConfig& config) : m_ctx = iio_create_local_context(); if (not m_ctx) { - throw std::runtime_error("Dexter: Unable to create iio scan context"); + throw std::runtime_error("Dexter: Unable to create iio context"); } int r; @@ -94,11 +94,11 @@ Dexter::Dexter(SDRDeviceConfig& config) : // TODO make DC offset configurable and add to RC if ((r = iio_device_attr_write_longlong(m_dexter_dsp_tx, "dc0", 0)) != 0) { - etiLog.level(warn) << "Failed to set dexter_dsp_tx.dc0 = false: " << get_iio_error(r); + throw std::runtime_error("Failed to set dexter_dsp_tx.dc0 = false: " + get_iio_error(r)); } if ((r = iio_device_attr_write_longlong(m_dexter_dsp_tx, "dc1", 0)) != 0) { - etiLog.level(warn) << "Failed to set dexter_dsp_tx.dc1 = false: " << get_iio_error(r); + throw std::runtime_error("Failed to set dexter_dsp_tx.dc1 = false: " + get_iio_error(r)); } if (m_conf.sampleRate != 2048000) { @@ -167,14 +167,17 @@ Dexter::Dexter(SDRDeviceConfig& config) : m_utc_seconds_at_startup = time_now.tv_sec; m_clock_count_at_startup = pps_clks2; - // Reset start_clks - if ((r = iio_device_attr_write_longlong(m_dexter_dsp_tx, "stream0_start_clks", 0)) != 0) { - etiLog.level(warn) << "Failed to set dexter_dsp_tx.stream0_start_clks = " << 0 << " : " << get_iio_error(r); + // The FIFO should not contain data, but setting gain=0 before setting start_clks to zero is an additional security + if ((r = iio_device_attr_write_longlong(m_dexter_dsp_tx, "gain0", 0)) != 0) { + throw std::runtime_error("Failed to set dexter_dsp_tx.gain0 = 0 : " + get_iio_error(r)); } - if ((r = iio_device_attr_write_longlong(m_dexter_dsp_tx, "gain0", 0)) != 0) { - etiLog.level(error) << "Failed to set dexter_dsp_tx.gain0 = 0" << - " : " << get_iio_error(r); + if ((r = iio_device_attr_write_longlong(m_dexter_dsp_tx, "stream0_flush_fifo_trigger", 1)) != 0) { + throw std::runtime_error("Failed to set dexter_dsp_tx.stream0_flush_fifo_trigger = 1 : " + get_iio_error(r)); + } + + if ((r = iio_device_attr_write_longlong(m_dexter_dsp_tx, "stream0_start_clks", 0)) != 0) { + throw std::runtime_error("Failed to set dexter_dsp_tx.stream0_start_clks = 0 : " + get_iio_error(r)); } constexpr int CHANNEL_INDEX = 0; @@ -208,11 +211,8 @@ Dexter::Dexter(SDRDeviceConfig& config) : " : " << get_iio_error(r); } -#warning "TODO underflow thread" - /* Disabled because it still provokes failed to push buffer Unknown error -110 m_running = true; m_underflow_read_thread = std::thread(&Dexter::underflow_read_process, this); - */ } void Dexter::channel_up() @@ -261,9 +261,18 @@ Dexter::~Dexter() m_buffer = nullptr; } + if (m_tx_channel) { + iio_channel_disable(m_tx_channel); + } + iio_context_destroy(m_ctx); m_ctx = nullptr; } + + if (m_underflow_ctx) { + iio_context_destroy(m_underflow_ctx); + m_underflow_ctx = nullptr; + } } void Dexter::tune(double lo_offset, double frequency) @@ -296,12 +305,12 @@ void Dexter::set_txgain(double txgain) { int r = 0; if ((r = iio_device_attr_write_longlong(m_dexter_dsp_tx, "gain0", txgain)) != 0) { - etiLog.level(warn) << "Failed to set dexter_dsp_tx.gain0 = 0: " << get_iio_error(r); + etiLog.level(warn) << "Failed to set dexter_dsp_tx.gain0 = " << txgain << ": " << get_iio_error(r); } long long txgain_readback = 0; if ((r = iio_device_attr_read_longlong(m_dexter_dsp_tx, "gain0", &txgain_readback)) != 0) { - etiLog.level(warn) << "Failed to set dexter_dsp_tx.gain0 = 0: " << get_iio_error(r); + etiLog.level(warn) << "Failed to read dexter_dsp_tx.gain0: " << get_iio_error(r); } else { m_conf.txgain = txgain_readback; @@ -313,14 +322,14 @@ double Dexter::get_txgain(void) const long long txgain_readback = 0; int r = 0; if ((r = iio_device_attr_read_longlong(m_dexter_dsp_tx, "gain0", &txgain_readback)) != 0) { - etiLog.level(warn) << "Failed to set dexter_dsp_tx.gain0 = 0: " << get_iio_error(r); + etiLog.level(warn) << "Failed to read dexter_dsp_tx.gain0: " << get_iio_error(r); } return txgain_readback; } void Dexter::set_bandwidth(double bandwidth) { - // TODO + return; } double Dexter::get_bandwidth(void) const @@ -328,38 +337,51 @@ double Dexter::get_bandwidth(void) const return 0; } -SDRDevice::RunStatistics Dexter::get_run_statistics(void) const +SDRDevice::run_statistics_t Dexter::get_run_statistics(void) const { - RunStatistics rs; + run_statistics_t rs; { - std::unique_lock lock(m_underflows_mutex); - rs.num_underruns = underflows; + std::unique_lock lock(m_attr_thread_mutex); + rs["underruns"] = underflows; + } + rs["overruns"] = 0; + rs["late_packets"] = num_late; + rs["frames"] = num_frames_modulated; + + long long clks = 0; + int r = 0; + if ((r = iio_device_attr_read_longlong(m_dexter_dsp_tx, "clks", &clks)) == 0) { + rs["clks"] = (size_t)clks; + } + else { + rs["clks"] = (ssize_t)-1; + etiLog.level(error) << "Failed to get dexter_dsp_tx.clks: " << get_iio_error(r); + } + + long long fifo_not_empty_clks = 0; + + if ((r = iio_device_attr_read_longlong( + m_dexter_dsp_tx, "stream0_fifo_not_empty_clks", &fifo_not_empty_clks)) == 0) { + rs["fifo_not_empty_clks"] = (size_t)fifo_not_empty_clks; + } + else { + rs["fifo_not_empty_clks"] = (ssize_t)-1; + etiLog.level(error) << "Failed to get dexter_dsp_tx.fifo_not_empty_clks: " << get_iio_error(r); } - rs.num_overruns = 0; - rs.num_late_packets = num_late; - rs.num_frames_modulated = num_frames_modulated; return rs; } double Dexter::get_real_secs(void) const { - struct timespec time_now; - fill_time(&time_now); - return (double)time_now.tv_sec + time_now.tv_nsec / 1000000000.0; - - /* We don't use actual device time, because we only have clock counter on pps edge available, not - * current clock counter. */ -#if 0 - long long pps_clks = 0; + long long clks = 0; int r = 0; - if ((r = iio_device_attr_read_longlong(m_dexter_dsp_tx, "pps_clks", &pps_clks)) != 0) { - etiLog.level(error) << "Failed to get dexter_dsp_tx.pps_clks: " << get_iio_error(r); + if ((r = iio_device_attr_read_longlong(m_dexter_dsp_tx, "clks", &clks)) != 0) { + etiLog.level(error) << "Failed to get dexter_dsp_tx.clks: " << get_iio_error(r); throw std::runtime_error("Dexter: Cannot read IIO attribute"); } - return (double)m_utc_seconds_at_startup + (double)(pps_clks - m_clock_count_at_startup) / (double)DSP_CLOCK; -#endif + return (double)m_utc_seconds_at_startup + (double)(clks - m_clock_count_at_startup) / (double)DSP_CLOCK; } void Dexter::set_rxgain(double rxgain) @@ -395,11 +417,10 @@ const char* Dexter::device_name(void) const return "Dexter"; } -double Dexter::get_temperature(void) const +std::optional Dexter::get_temperature(void) const { - // TODO - // XADC contains temperature, but value is weird - return std::numeric_limits::quiet_NaN(); + // TODO XADC contains temperature, but value is weird + return std::nullopt; } void Dexter::transmit_frame(struct FrameData&& frame) @@ -413,29 +434,32 @@ void Dexter::transmit_frame(struct FrameData&& frame) const bool require_timestamped_tx = (m_conf.enableSync and frame.ts.timestamp_valid); - const double margin_s = frame.ts.offset_to_system_time(); - if (not m_channel_is_up) { if (require_timestamped_tx) { - /* - uint64_t timeS = frame.ts.timestamp_sec; - etiLog.level(debug) << "Dexter: TS S " << timeS << " - " << m_utc_seconds_at_startup << " = " << - timeS - m_utc_seconds_at_startup; - */ - constexpr uint64_t TIMESTAMP_PPS_PER_DSP_CLOCKS = DSP_CLOCK / 16384000; // TIMESTAMP_PPS_PER_DSP_CLOCKS=10 because timestamp_pps is represented in 16.384 MHz clocks - uint64_t frame_ts_clocks = + uint64_t frame_start_clocks = // at second level ((int64_t)frame.ts.timestamp_sec - (int64_t)m_utc_seconds_at_startup) * DSP_CLOCK + m_clock_count_at_startup + // at subsecond level (uint64_t)frame.ts.timestamp_pps * TIMESTAMP_PPS_PER_DSP_CLOCKS; + const double margin_s = frame.ts.offset_to_system_time(); + + long long clks = 0; + int r = 0; + if ((r = iio_device_attr_read_longlong(m_dexter_dsp_tx, "clks", &clks)) != 0) { + etiLog.level(error) << "Failed to get dexter_dsp_tx.clks: " << get_iio_error(r); + throw std::runtime_error("Dexter: Cannot read IIO attribute"); + } + + const double margin_device_s = (double)(frame_start_clocks - clks) / DSP_CLOCK; + etiLog.level(debug) << "DEXTER FCT " << frame.ts.fct << " TS CLK " << ((int64_t)frame.ts.timestamp_sec - (int64_t)m_utc_seconds_at_startup) * DSP_CLOCK << " + " << m_clock_count_at_startup << " + " << (uint64_t)frame.ts.timestamp_pps * TIMESTAMP_PPS_PER_DSP_CLOCKS << " = " << - frame_ts_clocks << " DELTA " << margin_s; + frame_start_clocks << " DELTA " << margin_s << " " << margin_device_s; // Ensure we hand the frame over to HW with a bit of margin if (margin_s < 0.2) { @@ -444,9 +468,8 @@ void Dexter::transmit_frame(struct FrameData&& frame) return; } - int r; - if ((r = iio_device_attr_write_longlong(m_dexter_dsp_tx, "stream0_start_clks", frame_ts_clocks)) != 0) { - etiLog.level(warn) << "Skip frame, failed to set dexter_dsp_tx.stream0_start_clks = " << frame_ts_clocks << " : " << get_iio_error(r); + if ((r = iio_device_attr_write_longlong(m_dexter_dsp_tx, "stream0_start_clks", frame_start_clocks)) != 0) { + etiLog.level(warn) << "Skip frame, failed to set dexter_dsp_tx.stream0_start_clks = " << frame_start_clocks << " : " << get_iio_error(r); num_late++; return; } @@ -486,7 +509,7 @@ void Dexter::transmit_frame(struct FrameData&& frame) } { - std::unique_lock lock(m_underflows_mutex); + std::unique_lock lock(m_attr_thread_mutex); size_t u = underflows; lock.unlock(); @@ -500,19 +523,29 @@ void Dexter::transmit_frame(struct FrameData&& frame) void Dexter::underflow_read_process() { + m_underflow_ctx = iio_create_local_context(); + if (not m_underflow_ctx) { + throw std::runtime_error("Dexter: Unable to create iio context for underflow"); + } + + auto dexter_dsp_tx = iio_context_find_device(m_ctx, "dexter_dsp_tx"); + if (not dexter_dsp_tx) { + throw std::runtime_error("Dexter: Unable to find dexter_dsp_tx iio device"); + } + set_thread_name("dexter_underflow"); while (m_running) { this_thread::sleep_for(chrono::seconds(1)); - long long attr_value = 0; - int r = 0; + long long underflows_attr = 0; + + int r = iio_device_attr_read_longlong(m_dexter_dsp_tx, "buffer_underflows0", &underflows_attr); - if ((r = iio_device_attr_read_longlong(m_dexter_dsp_tx, "buffer_underflows0", &attr_value)) == 0) { - size_t underflows_new = attr_value; + if (r == 0) { + size_t underflows_new = underflows_attr; - std::unique_lock lock(m_underflows_mutex); - etiLog.level(debug) << "UNDERFLOWS INC BY " << attr_value - (ssize_t)underflows; - if (underflows_new != underflows and attr_value != 0) { + std::unique_lock lock(m_attr_thread_mutex); + if (underflows_new != underflows and underflows_attr != 0) { underflows = underflows_new; } } diff --git a/src/output/Dexter.h b/src/output/Dexter.h index 2f0524e..7925de7 100644 --- a/src/output/Dexter.h +++ b/src/output/Dexter.h @@ -65,7 +65,7 @@ class Dexter : public Output::SDRDevice virtual void set_bandwidth(double bandwidth) override; virtual double get_bandwidth() const override; virtual void transmit_frame(struct FrameData&& frame) override; - virtual RunStatistics get_run_statistics() const override; + virtual run_statistics_t get_run_statistics() const override; virtual double get_real_secs() const override; virtual void set_rxgain(double rxgain) override; @@ -80,7 +80,7 @@ class Dexter : public Output::SDRDevice virtual bool is_clk_source_ok() const override; virtual const char* device_name() const override; - virtual double get_temperature() const override; + virtual std::optional get_temperature() const override; private: void channel_up(); @@ -98,10 +98,11 @@ class Dexter : public Output::SDRDevice struct iio_buffer *m_buffer = nullptr; /* Underflows are counted in a separate thread */ + struct iio_context* m_underflow_ctx = nullptr; std::atomic m_running = ATOMIC_VAR_INIT(false); std::thread m_underflow_read_thread; void underflow_read_process(); - mutable std::mutex m_underflows_mutex; + mutable std::mutex m_attr_thread_mutex; size_t underflows = 0; size_t prev_underflows = 0; diff --git a/src/output/Lime.cpp b/src/output/Lime.cpp index bf466c3..83c54ad 100644 --- a/src/output/Lime.cpp +++ b/src/output/Lime.cpp @@ -323,13 +323,14 @@ double Lime::get_bandwidth(void) const return bw; } -SDRDevice::RunStatistics Lime::get_run_statistics(void) const +SDRDevice::run_statistics_t Lime::get_run_statistics(void) const { - RunStatistics rs; - rs.num_underruns = underflows; - rs.num_overruns = overflows; - rs.num_late_packets = late_packets; - rs.num_frames_modulated = num_frames_modulated; + run_statistics_t rs; + rs["underruns"] = underflows; + rs["overruns"] = overflows; + rs["dropped_packets"] = dropped_packets; + rs["frames"] = num_frames_modulated; + rs["fifo_fill"] = m_last_fifo_fill_percent * 100; return rs; } @@ -371,23 +372,21 @@ const char *Lime::device_name(void) const return "Lime"; } -double Lime::get_temperature(void) const +std::optional Lime::get_temperature(void) const { if (not m_device) throw runtime_error("Lime device not set up"); - float_type temp = numeric_limits::quiet_NaN(); - if (LMS_GetChipTemperature(m_device, 0, &temp) < 0) - { + float_type temp = 0; + if (LMS_GetChipTemperature(m_device, 0, &temp) >= 0) { + return temp; + } + else { etiLog.level(error) << "Error getting LimeSDR temperature: %s " << LMS_GetLastErrorMessage(); + return std::nullopt; } - return temp; } -float Lime::get_fifo_fill_percent(void) const -{ - return m_last_fifo_fill_percent * 100; -} void Lime::transmit_frame(struct FrameData&& frame) { @@ -411,7 +410,7 @@ void Lime::transmit_frame(struct FrameData&& frame) LMS_GetStreamStatus(&m_tx_stream, &LimeStatus); overflows += LimeStatus.overrun; underflows += LimeStatus.underrun; - late_packets += LimeStatus.droppedPackets; + dropped_packets += LimeStatus.droppedPackets; #ifdef LIMEDEBUG etiLog.level(info) << LimeStatus.fifoFilledCount << "/" << LimeStatus.fifoSize << ":" << numSamples << "Rate" << LimeStatus.linkRate / (2 * 2.0); diff --git a/src/output/Lime.h b/src/output/Lime.h index 95c5c48..e09e82d 100644 --- a/src/output/Lime.h +++ b/src/output/Lime.h @@ -67,7 +67,7 @@ class Lime : public Output::SDRDevice virtual void set_bandwidth(double bandwidth) override; virtual double get_bandwidth(void) const override; virtual void transmit_frame(struct FrameData&& frame) override; - virtual RunStatistics get_run_statistics(void) const override; + virtual run_statistics_t get_run_statistics(void) const override; virtual double get_real_secs(void) const override; virtual void set_rxgain(double rxgain) override; @@ -82,8 +82,7 @@ class Lime : public Output::SDRDevice virtual bool is_clk_source_ok(void) const override; virtual const char *device_name(void) const override; - virtual double get_temperature(void) const override; - virtual float get_fifo_fill_percent(void) const; + virtual std::optional get_temperature(void) const override; private: SDRDeviceConfig &m_conf; @@ -95,11 +94,10 @@ class Lime : public Output::SDRDevice std::vector interpolatebuf; std::vector m_i16samples; std::atomic m_last_fifo_fill_percent = ATOMIC_VAR_INIT(0); - size_t underflows = 0; size_t overflows = 0; - size_t late_packets = 0; + size_t dropped_packets = 0; size_t num_frames_modulated = 0; }; diff --git a/src/output/SDR.cpp b/src/output/SDR.cpp index 726fb94..a4f5873 100644 --- a/src/output/SDR.cpp +++ b/src/output/SDR.cpp @@ -87,6 +87,11 @@ SDR::SDR(SDRDeviceConfig& config, std::shared_ptr device) : RC_ADD_PARAMETER(fifo_fill, "A value representing the Lime FIFO fullness [percent]"); } #endif // HAVE_LIMESDR + +#ifdef HAVE_DEXTER + RC_ADD_PARAMETER(clks, "DEXTER internal clk counter value"); + RC_ADD_PARAMETER(fifo_not_empty_clks, "DEXTER internal clk counter value when FIFO was last empty"); +#endif // HAVE_DEXTER } SDR::~SDR() @@ -402,6 +407,7 @@ void SDR::set_parameter(const string& parameter, const string& value) ss >> m_config.muting; } else if (parameter == "underruns" or + parameter == "overruns" or parameter == "latepackets" or parameter == "frames" or parameter == "gpsdo_num_sv" or @@ -440,55 +446,40 @@ const string SDR::get_parameter(const string& parameter) const if (not m_device) { throw ParameterError("OutputSDR has no device"); } - const double temp = m_device->get_temperature(); - if (std::isnan(temp)) { - throw ParameterError("Temperature not available"); + const std::optional temp = m_device->get_temperature(); + if (temp) { + ss << *temp; } else { - ss << temp; + throw ParameterError("Temperature not available"); } } - else if (parameter == "underruns" or - parameter == "latepackets" or - parameter == "frames" ) { - if (not m_device) { - throw ParameterError("OutputSDR has no device"); - } - const auto stat = m_device->get_run_statistics(); - - if (parameter == "underruns") { - ss << stat.num_underruns; - } - else if (parameter == "latepackets") { - ss << stat.num_late_packets; - } - else if (parameter == "frames") { - ss << stat.num_frames_modulated; + else { + if (m_device) { + const auto stat = m_device->get_run_statistics(); + try { + const auto& value = stat.at(parameter); + if (std::holds_alternative(value)) { + ss << std::get(value); + } + else if (std::holds_alternative(value)) { + ss << std::get(value); + } + else if (std::holds_alternative(value)) { + ss << std::get(value); + } + else if (std::holds_alternative(value)) { + ss << (std::get(value) ? 1 : 0); + } + else { + throw std::logic_error("variant alternative not handled"); + } + return ss.str(); + } + catch (const std::out_of_range&) { + } } - } - else if (parameter == "gpsdo_num_sv") { - const auto stat = m_device->get_run_statistics(); - ss << stat.gpsdo_num_sv; - } - else if (parameter == "gpsdo_holdover") { - const auto stat = m_device->get_run_statistics(); - ss << (stat.gpsdo_holdover ? 1 : 0); - } -#ifdef HAVE_LIMESDR - else if (parameter == "fifo_fill") { - const auto dev = std::dynamic_pointer_cast(m_device); - if (dev) { - ss << dev->get_fifo_fill_percent(); - } - else { - ss << "Parameter '" << parameter << - "' is not exported by controllable " << get_rc_name(); - throw ParameterError(ss.str()); - } - } -#endif // HAVE_LIMESDR - else { ss << "Parameter '" << parameter << "' is not exported by controllable " << get_rc_name(); throw ParameterError(ss.str()); diff --git a/src/output/SDRDevice.h b/src/output/SDRDevice.h index ffa1a3b..337d501 100644 --- a/src/output/SDRDevice.h +++ b/src/output/SDRDevice.h @@ -2,7 +2,7 @@ Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) - Copyright (C) 2022 + Copyright (C) 2023 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org @@ -38,6 +38,9 @@ DESCRIPTION: #include #include #include +#include +#include +#include #include "TimestampDecoder.h" @@ -109,22 +112,15 @@ struct FrameData { // All SDR Devices must implement the SDRDevice interface class SDRDevice { public: - struct RunStatistics { - size_t num_underruns = 0; - size_t num_late_packets = 0; - size_t num_overruns = 0; - size_t num_frames_modulated = 0; - - int gpsdo_num_sv = 0; - bool gpsdo_holdover = false; - }; + using run_statistic_t = std::variant; + using run_statistics_t = std::unordered_map; virtual void tune(double lo_offset, double frequency) = 0; virtual double get_tx_freq(void) const = 0; virtual void set_txgain(double txgain) = 0; virtual double get_txgain(void) const = 0; virtual void transmit_frame(struct FrameData&& frame) = 0; - virtual RunStatistics get_run_statistics(void) const = 0; + virtual run_statistics_t get_run_statistics(void) const = 0; virtual double get_real_secs(void) const = 0; virtual void set_rxgain(double rxgain) = 0; virtual double get_rxgain(void) const = 0; @@ -136,8 +132,8 @@ class SDRDevice { frame_timestamp& ts, double timeout_secs) = 0; - // Returns device temperature in degrees C or NaN if not available - virtual double get_temperature(void) const = 0; + // Returns device temperature in degrees C + virtual std::optional get_temperature(void) const = 0; // Return true if GPS and reference clock inputs are ok virtual bool is_clk_source_ok(void) const = 0; diff --git a/src/output/Soapy.cpp b/src/output/Soapy.cpp index 8c52546..00df9dc 100644 --- a/src/output/Soapy.cpp +++ b/src/output/Soapy.cpp @@ -180,13 +180,13 @@ double Soapy::get_bandwidth(void) const return m_device->getBandwidth(SOAPY_SDR_TX, 0); } -SDRDevice::RunStatistics Soapy::get_run_statistics(void) const +SDRDevice::run_statistics_t Soapy::get_run_statistics(void) const { - RunStatistics rs; - rs.num_underruns = underflows; - rs.num_overruns = overflows; - rs.num_late_packets = late_packets; - rs.num_frames_modulated = num_frames_modulated; + run_statistics_t rs; + rs["underruns"] = underflows; + rs["overruns"] = overflows; + rs["timeouts"] = timeouts; + rs["frames"] = num_frames_modulated; return rs; } @@ -265,11 +265,11 @@ const char* Soapy::device_name(void) const return "Soapy"; } -double Soapy::get_temperature(void) const +std::optional Soapy::get_temperature(void) const { // TODO Unimplemented // LimeSDR exports 'lms7_temp' - return std::numeric_limits::quiet_NaN(); + return std::nullopt; } void Soapy::transmit_frame(struct FrameData&& frame) @@ -320,6 +320,7 @@ void Soapy::transmit_frame(struct FrameData&& frame) m_tx_stream, buffs, samps_to_send, flags, timeNs); if (num_sent == SOAPY_SDR_TIMEOUT) { + timeouts++; continue; } else if (num_sent == SOAPY_SDR_OVERFLOW) { diff --git a/src/output/Soapy.h b/src/output/Soapy.h index f3e1ee2..b98ac21 100644 --- a/src/output/Soapy.h +++ b/src/output/Soapy.h @@ -66,7 +66,7 @@ class Soapy : public Output::SDRDevice virtual void set_bandwidth(double bandwidth) override; virtual double get_bandwidth(void) const override; virtual void transmit_frame(struct FrameData&& frame) override; - virtual RunStatistics get_run_statistics(void) const override; + virtual run_statistics_t get_run_statistics(void) const override; virtual double get_real_secs(void) const override; virtual void set_rxgain(double rxgain) override; @@ -81,7 +81,7 @@ class Soapy : public Output::SDRDevice virtual bool is_clk_source_ok(void) const override; virtual const char* device_name(void) const override; - virtual double get_temperature(void) const override; + virtual std::optional get_temperature(void) const override; private: SDRDeviceConfig& m_conf; @@ -91,9 +91,9 @@ class Soapy : public Output::SDRDevice SoapySDR::Stream *m_rx_stream = nullptr; bool m_rx_stream_active = false; + size_t timeouts = 0; size_t underflows = 0; size_t overflows = 0; - size_t late_packets = 0; size_t num_frames_modulated = 0; }; diff --git a/src/output/UHD.cpp b/src/output/UHD.cpp index c325272..7f07ff2 100644 --- a/src/output/UHD.cpp +++ b/src/output/UHD.cpp @@ -377,18 +377,22 @@ void UHD::transmit_frame(struct FrameData&& frame) } -SDRDevice::RunStatistics UHD::get_run_statistics(void) const +SDRDevice::run_statistics_t UHD::get_run_statistics(void) const { - RunStatistics rs; - rs.num_underruns = num_underflows; - rs.num_overruns = num_overflows; - rs.num_late_packets = num_late_packets; - rs.num_frames_modulated = num_frames_modulated; + run_statistics_t rs; + rs["underruns"] = num_underflows; + rs["overruns"] = num_overflows; + rs["late_packets"] = num_late_packets; + rs["frames"] = num_frames_modulated; if (m_device_time) { const auto gpsdo_stat = m_device_time->get_gnss_stats(); - rs.gpsdo_holdover = gpsdo_stat.holdover; - rs.gpsdo_num_sv = gpsdo_stat.num_sv; + rs["gpsdo_holdover"] = gpsdo_stat.holdover; + rs["gpsdo_num_sv"] = gpsdo_stat.num_sv; + } + else { + rs["gpsdo_holdover"] = true; + rs["gpsdo_num_sv"] = 0; } return rs; } @@ -472,13 +476,13 @@ const char* UHD::device_name(void) const return "UHD"; } -double UHD::get_temperature(void) const +std::optional UHD::get_temperature(void) const { try { return std::round(m_usrp->get_tx_sensor("temp", 0).to_real()); } catch (const uhd::lookup_error &e) { - return std::numeric_limits::quiet_NaN(); + return std::nullopt; } } diff --git a/src/output/UHD.h b/src/output/UHD.h index 164254c..5823c0e 100644 --- a/src/output/UHD.h +++ b/src/output/UHD.h @@ -80,7 +80,7 @@ class UHD : public Output::SDRDevice virtual void set_bandwidth(double bandwidth) override; virtual double get_bandwidth(void) const override; virtual void transmit_frame(struct FrameData&& frame) override; - virtual RunStatistics get_run_statistics(void) const override; + virtual run_statistics_t get_run_statistics(void) const override; virtual double get_real_secs(void) const override; virtual void set_rxgain(double rxgain) override; @@ -95,7 +95,7 @@ class UHD : public Output::SDRDevice virtual bool is_clk_source_ok(void) const override; virtual const char* device_name(void) const override; - virtual double get_temperature(void) const override; + virtual std::optional get_temperature(void) const override; private: SDRDeviceConfig& m_conf; -- cgit v1.2.3 From f0bb1e24952c7e261ba13907c0a5d8c3e1d198ca Mon Sep 17 00:00:00 2001 From: "Matthias P. Braendli" Date: Thu, 27 Apr 2023 13:43:22 +0200 Subject: Improve dexter clock handling --- src/DabModulator.cpp | 3 ++- src/output/BladeRF.cpp | 2 +- src/output/BladeRF.h | 2 +- src/output/Dexter.cpp | 73 +++++++++++++++++++++++++++++++++++--------------- src/output/Dexter.h | 3 ++- src/output/Lime.cpp | 2 +- src/output/Lime.h | 2 +- src/output/SDR.cpp | 29 ++------------------ src/output/SDR.h | 4 --- src/output/SDRDevice.h | 2 +- src/output/Soapy.cpp | 2 +- src/output/Soapy.h | 2 +- src/output/UHD.cpp | 2 +- src/output/UHD.h | 2 +- 14 files changed, 66 insertions(+), 64 deletions(-) (limited to 'src/output/UHD.cpp') diff --git a/src/DabModulator.cpp b/src/DabModulator.cpp index 67764ba..64ebf03 100644 --- a/src/DabModulator.cpp +++ b/src/DabModulator.cpp @@ -27,6 +27,7 @@ #include #include +#include #include "DabModulator.h" #include "PcDebug.h" @@ -348,7 +349,7 @@ int DabModulator::process(Buffer* dataOut) } shared_ptr prev_plugin = static_pointer_cast(cifSig); - const std::list > plugins({ + const std::vector > plugins({ static_pointer_cast(cifCicEq), static_pointer_cast(cifOfdm), static_pointer_cast(cifGain), diff --git a/src/output/BladeRF.cpp b/src/output/BladeRF.cpp index 2dd7176..c16b64d 100755 --- a/src/output/BladeRF.cpp +++ b/src/output/BladeRF.cpp @@ -273,7 +273,7 @@ size_t BladeRF::receive_frame( return 0; } -bool BladeRF::is_clk_source_ok() const +bool BladeRF::is_clk_source_ok() { // TODO return true; diff --git a/src/output/BladeRF.h b/src/output/BladeRF.h index eb3e58b..fa3419e 100755 --- a/src/output/BladeRF.h +++ b/src/output/BladeRF.h @@ -87,7 +87,7 @@ class BladeRF : public Output::SDRDevice double timeout_secs) override; // Return true if GPS and reference clock inputs are ok - virtual bool is_clk_source_ok(void) const override; + virtual bool is_clk_source_ok(void) override; virtual const char* device_name(void) const override; virtual std::optional get_temperature(void) const override; diff --git a/src/output/Dexter.cpp b/src/output/Dexter.cpp index 59baf7e..25de030 100644 --- a/src/output/Dexter.cpp +++ b/src/output/Dexter.cpp @@ -277,7 +277,9 @@ void Dexter::handle_hw_time() m_utc_seconds_at_startup = time_now.tv_sec; m_clock_count_at_startup = pps_clks2; m_holdover_since = chrono::steady_clock::time_point::min(); + m_holdover_since_t = 0; m_clock_state = DexterClockState::Normal; + etiLog.level(debug) << "Dexter: switch clock state Startup -> Normal"; } } break; @@ -291,20 +293,31 @@ void Dexter::handle_hw_time() if (pps_loss_of_signal == 1) { m_holdover_since = chrono::steady_clock::now(); + m_holdover_since_t = chrono::system_clock::to_time_t(chrono::system_clock::now()); m_clock_state = DexterClockState::Holdover; + etiLog.level(debug) << "Dexter: switch clock state Normal -> Holdover"; } } break; case DexterClockState::Holdover: { using namespace chrono; + + long long pps_loss_of_signal = 0; + if ((r = iio_device_attr_read_longlong(m_dexter_dsp_tx, "pps_loss_of_signal", &pps_loss_of_signal)) != 0) { + etiLog.level(error) << "Failed to get dexter_dsp_tx.pps_loss_of_signal: " << get_iio_error(r); + throw std::runtime_error("Dexter: Cannot read IIO attribute"); + } + const duration d = steady_clock::now() - m_holdover_since; const auto max_holdover_duration = seconds(m_conf.maxGPSHoldoverTime); - if (d > max_holdover_duration) { + if (d > max_holdover_duration or pps_loss_of_signal == 0) { m_clock_state = DexterClockState::Startup; m_utc_seconds_at_startup = 0; m_clock_count_at_startup = 0; m_holdover_since = chrono::steady_clock::time_point::min(); + m_holdover_since_t = 0; + etiLog.level(debug) << "Dexter: switch clock state Holdover -> Startup"; } } break; @@ -383,6 +396,21 @@ double Dexter::get_bandwidth(void) const return 0; } +template +static void attr_to_stat( + Dexter::run_statistics_t& rs, iio_device *dexter_dsp_tx, + const char* attr_name, const char* stat_name) { + long long attr_value = 0; + int r = 0; + if ((r = iio_device_attr_read_longlong(dexter_dsp_tx, attr_name, &attr_value)) == 0) { + rs[stat_name] = (T)attr_value; + } + else { + rs[stat_name] = (ssize_t)-1; + etiLog.level(error) << "Failed to get dexter_dsp_tx." << attr_name << ": " << get_iio_error(r); + } +}; + SDRDevice::run_statistics_t Dexter::get_run_statistics(void) const { run_statistics_t rs; @@ -393,24 +421,25 @@ SDRDevice::run_statistics_t Dexter::get_run_statistics(void) const rs["latepackets"] = num_late; rs["frames"] = num_frames_modulated; - auto attr_to_stat = [&](const char* attr_name, const char* stat_name) { - long long attr_value = 0; - int r = 0; - if ((r = iio_device_attr_read_longlong(m_dexter_dsp_tx, attr_name, &attr_value)) == 0) { - rs[stat_name] = (size_t)attr_value; - } - else { - rs[stat_name] = (ssize_t)-1; - etiLog.level(error) << "Failed to get dexter_dsp_tx." << attr_name << ": " << get_iio_error(r); - } - }; + attr_to_stat(rs, m_dexter_dsp_tx, "clks", "clks"); + attr_to_stat(rs, m_dexter_dsp_tx, "stream0_fifo_not_empty_clks", "fifo_not_empty_clks"); + attr_to_stat(rs, m_dexter_dsp_tx, "gpsdo_locked", "gpsdo_locked"); + attr_to_stat(rs, m_dexter_dsp_tx, "pps_clk_error_hz", "pps_clk_error_hz"); + attr_to_stat(rs, m_dexter_dsp_tx, "pps_cnt", "pps_cnt"); + attr_to_stat(rs, m_dexter_dsp_tx, "pps_loss_of_signal", "pps_loss_of_signal"); + attr_to_stat(rs, m_dexter_dsp_tx, "dsp_version", "dsp_version"); - attr_to_stat("clks", "clks"); - attr_to_stat("stream0_fifo_not_empty_clks", "fifo_not_empty_clks"); - attr_to_stat("gpsdo_locked", "gpsdo_locked"); - attr_to_stat("pps_clk_error_hz", "pps_clk_error_hz"); - attr_to_stat("pps_cnt", "pps_cnt"); - attr_to_stat("dsp_version", "dsp_version"); + rs["in_holdover_since"] = 0; + switch (m_clock_state) { + case DexterClockState::Startup: + rs["clock_state"] = "startup"; break; + case DexterClockState::Normal: + rs["clock_state"] = "normal"; break; + case DexterClockState::Holdover: + rs["clock_state"] = "holdover"; + rs["in_holdover_since"] = m_holdover_since_t; + break; + } constexpr double VMINFACT = 0.85; constexpr double VMAXFACT = 1.15; @@ -589,9 +618,11 @@ size_t Dexter::receive_frame( } -bool Dexter::is_clk_source_ok() const +bool Dexter::is_clk_source_ok() { - return true; + handle_hw_time(); + + return m_clock_state != DexterClockState::Startup; } const char* Dexter::device_name(void) const @@ -623,8 +654,6 @@ void Dexter::transmit_frame(struct FrameData&& frame) const bool require_timestamped_tx = (m_conf.enableSync and frame.ts.timestamp_valid); - handle_hw_time(); - if (not m_channel_is_up) { if (require_timestamped_tx) { if (m_clock_state == DexterClockState::Startup) { diff --git a/src/output/Dexter.h b/src/output/Dexter.h index f70bb14..d4f425f 100644 --- a/src/output/Dexter.h +++ b/src/output/Dexter.h @@ -84,7 +84,7 @@ class Dexter : public Output::SDRDevice double timeout_secs) override; // Return true if GPS and reference clock inputs are ok - virtual bool is_clk_source_ok() const override; + virtual bool is_clk_source_ok() override; virtual const char* device_name() const override; virtual std::optional get_temperature() const override; @@ -129,6 +129,7 @@ class Dexter : public Output::SDRDevice // Only valid when m_clock_state Holdover std::chrono::steady_clock::time_point m_holdover_since = std::chrono::steady_clock::time_point::min(); + std::time_t m_holdover_since_t = 0; }; } // namespace Output diff --git a/src/output/Lime.cpp b/src/output/Lime.cpp index 83c54ad..8fb90bb 100644 --- a/src/output/Lime.cpp +++ b/src/output/Lime.cpp @@ -361,7 +361,7 @@ size_t Lime::receive_frame( return 0; } -bool Lime::is_clk_source_ok() const +bool Lime::is_clk_source_ok() { // TODO return true; diff --git a/src/output/Lime.h b/src/output/Lime.h index e09e82d..4510bf2 100644 --- a/src/output/Lime.h +++ b/src/output/Lime.h @@ -79,7 +79,7 @@ class Lime : public Output::SDRDevice double timeout_secs) override; // Return true if GPS and reference clock inputs are ok - virtual bool is_clk_source_ok(void) const override; + virtual bool is_clk_source_ok(void) override; virtual const char *device_name(void) const override; virtual std::optional get_temperature(void) const override; diff --git a/src/output/SDR.cpp b/src/output/SDR.cpp index 860d8ed..11321f2 100644 --- a/src/output/SDR.cpp +++ b/src/output/SDR.cpp @@ -258,44 +258,19 @@ const char* SDR::name() return m_name.c_str(); } -void SDR::sleep_through_frame() -{ - using namespace std::chrono; - - const auto now = steady_clock::now(); - - if (not t_last_frame_initialised) { - t_last_frame = now; - t_last_frame_initialised = true; - } - - const auto delta = now - t_last_frame; - const auto wait_time = transmission_frame_duration(m_config.dabMode); - - if (wait_time > delta) { - this_thread::sleep_for(wait_time - delta); - } - - t_last_frame += wait_time; -} void SDR::handle_frame(struct FrameData&& frame) { // Assumes m_device is valid if (not m_device->is_clk_source_ok()) { - sleep_through_frame(); return; } const auto& time_spec = frame.ts; - if (m_config.enableSync and m_config.muteNoTimestamps and - not time_spec.timestamp_valid) { - sleep_through_frame(); - etiLog.log(info, - "OutputSDR: Muting sample %d : no timestamp\n", - frame.ts.fct); + if (m_config.enableSync and m_config.muteNoTimestamps and not time_spec.timestamp_valid) { + etiLog.log(info, "OutputSDR: Muting sample %d : no timestamp\n", frame.ts.fct); return; } diff --git a/src/output/SDR.h b/src/output/SDR.h index 9f08348..94c972b 100644 --- a/src/output/SDR.h +++ b/src/output/SDR.h @@ -72,7 +72,6 @@ class SDR : public ModOutput, public ModMetadata, public RemoteControllable { private: void process_thread_entry(void); void handle_frame(struct FrameData&& frame); - void sleep_through_frame(void); SDRDeviceConfig& m_config; @@ -91,9 +90,6 @@ class SDR : public ModOutput, public ModMetadata, public RemoteControllable { uint32_t last_tx_second = 0; uint32_t last_tx_pps = 0; size_t num_queue_overflows = 0; - - bool t_last_frame_initialised = false; - std::chrono::steady_clock::time_point t_last_frame; }; } diff --git a/src/output/SDRDevice.h b/src/output/SDRDevice.h index 628372a..26272be 100644 --- a/src/output/SDRDevice.h +++ b/src/output/SDRDevice.h @@ -135,7 +135,7 @@ class SDRDevice { virtual std::optional get_temperature(void) const = 0; // Return true if GPS and reference clock inputs are ok - virtual bool is_clk_source_ok(void) const = 0; + virtual bool is_clk_source_ok(void) = 0; virtual const char* device_name(void) const = 0; diff --git a/src/output/Soapy.cpp b/src/output/Soapy.cpp index 00df9dc..4d33e39 100644 --- a/src/output/Soapy.cpp +++ b/src/output/Soapy.cpp @@ -254,7 +254,7 @@ size_t Soapy::receive_frame( } -bool Soapy::is_clk_source_ok() const +bool Soapy::is_clk_source_ok() { // TODO return true; diff --git a/src/output/Soapy.h b/src/output/Soapy.h index b98ac21..4fce11a 100644 --- a/src/output/Soapy.h +++ b/src/output/Soapy.h @@ -78,7 +78,7 @@ class Soapy : public Output::SDRDevice double timeout_secs) override; // Return true if GPS and reference clock inputs are ok - virtual bool is_clk_source_ok(void) const override; + virtual bool is_clk_source_ok(void) override; virtual const char* device_name(void) const override; virtual std::optional get_temperature(void) const override; diff --git a/src/output/UHD.cpp b/src/output/UHD.cpp index 7f07ff2..6638b6c 100644 --- a/src/output/UHD.cpp +++ b/src/output/UHD.cpp @@ -439,7 +439,7 @@ size_t UHD::receive_frame( } // Return true if GPS and reference clock inputs are ok -bool UHD::is_clk_source_ok(void) const +bool UHD::is_clk_source_ok(void) { bool ok = true; diff --git a/src/output/UHD.h b/src/output/UHD.h index 97a821e..9891c7a 100644 --- a/src/output/UHD.h +++ b/src/output/UHD.h @@ -84,7 +84,7 @@ class UHD : public Output::SDRDevice double timeout_secs) override; // Return true if GPS and reference clock inputs are ok - virtual bool is_clk_source_ok(void) const override; + virtual bool is_clk_source_ok(void) override; virtual const char* device_name(void) const override; virtual std::optional get_temperature(void) const override; -- cgit v1.2.3 From 343df6eb8792b3efd33f4426766865ae03ccf316 Mon Sep 17 00:00:00 2001 From: "Matthias P. Braendli" Date: Wed, 19 Jul 2023 22:12:18 +0200 Subject: Add events --- Makefile.am | 2 + doc/receive_events.py | 59 +++++++++++++++++++++++++++++ lib/Json.cpp | 2 +- lib/Json.h | 10 +---- lib/RemoteControl.cpp | 2 +- src/ConfigParser.cpp | 8 +++- src/DabMod.cpp | 3 ++ src/DabModulator.cpp | 4 +- src/Events.cpp | 87 +++++++++++++++++++++++++++++++++++++++++++ src/Events.h | 76 +++++++++++++++++++++++++++++++++++++ src/FIRFilter.cpp | 4 +- src/GainControl.cpp | 10 ++--- src/GuardIntervalInserter.cpp | 2 +- src/MemlessPoly.cpp | 6 +-- src/TII.cpp | 8 ++-- src/TimestampDecoder.cpp | 10 ++--- src/output/Dexter.cpp | 16 ++++---- src/output/SDR.cpp | 16 ++++---- src/output/Soapy.cpp | 8 ++-- src/output/UHD.cpp | 16 ++++---- 20 files changed, 287 insertions(+), 62 deletions(-) create mode 100755 doc/receive_events.py create mode 100644 src/Events.cpp create mode 100644 src/Events.h (limited to 'src/output/UHD.cpp') diff --git a/Makefile.am b/Makefile.am index 6e7c9ce..5c75c62 100644 --- a/Makefile.am +++ b/Makefile.am @@ -54,6 +54,8 @@ odr_dabmod_SOURCES = src/DabMod.cpp \ src/EtiReader.h \ src/Eti.cpp \ src/Eti.h \ + src/Events.cpp \ + src/Events.h \ src/FicSource.cpp \ src/FicSource.h \ src/PuncturingRule.cpp \ diff --git a/doc/receive_events.py b/doc/receive_events.py new file mode 100755 index 0000000..dca27cd --- /dev/null +++ b/doc/receive_events.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python +# +# This is an example program that shows +# how to receive runtime events from ODR-DabMod +# +# LICENSE: see bottom of file + +import sys +import zmq +import json +from pprint import pprint + +context = zmq.Context() +sock = context.socket(zmq.SUB) + +ep = "tcp://127.0.0.1:5557" +print(f"Receive from {ep}") +sock.connect(ep) + +# subscribe to all events +sock.setsockopt(zmq.SUBSCRIBE, bytes([])) + +while True: + parts = sock.recv_multipart() + if len(parts) == 2: + print("Received event '{}'".format(parts[0].decode())) + pprint(json.loads(parts[1].decode())) + + else: + print("Received strange event:") + pprint(parts) + + print() + + +# This is free and unencumbered software released into the public domain. +# +# Anyone is free to copy, modify, publish, use, compile, sell, or +# distribute this software, either in source code form or as a compiled +# binary, for any purpose, commercial or non-commercial, and by any +# means. +# +# In jurisdictions that recognize copyright laws, the author or authors +# of this software dedicate any and all copyright interest in the +# software to the public domain. We make this dedication for the benefit +# of the public at large and to the detriment of our heirs and +# successors. We intend this dedication to be an overt act of +# relinquishment in perpetuity of all present and future rights to this +# software under copyright law. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +# IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. +# +# For more information, please refer to diff --git a/lib/Json.cpp b/lib/Json.cpp index 9bda8c3..da5b078 100644 --- a/lib/Json.cpp +++ b/lib/Json.cpp @@ -67,7 +67,7 @@ namespace json { ss << "\"" << escape_json(element.first) << "\": "; - const auto& value = element.second.data; + const auto& value = element.second.v; if (std::holds_alternative(value)) { ss << "\"" << escape_json(std::get(value)) << "\""; } diff --git a/lib/Json.h b/lib/Json.h index 26da9a8..ee67f35 100644 --- a/lib/Json.h +++ b/lib/Json.h @@ -51,15 +51,7 @@ namespace json { size_t, ssize_t, bool, - std::nullopt_t> data; - - template - value_t operator=(const T& map) { - value_t v; - v.data = map; - return v; - } - + std::nullopt_t> v; }; std::string map_to_json(const map_t& values); diff --git a/lib/RemoteControl.cpp b/lib/RemoteControl.cpp index b544461..fbe0662 100644 --- a/lib/RemoteControl.cpp +++ b/lib/RemoteControl.cpp @@ -109,7 +109,7 @@ std::list< std::vector > RemoteControllers::get_param_list_values(c std::string RemoteControllers::get_showjson() { json::map_t root; for (auto &controllable : rcs.controllables) { - root[controllable->get_rc_name()].data = controllable->get_all_values(); + root[controllable->get_rc_name()].v = controllable->get_all_values(); } return json::map_to_json(root); diff --git a/src/ConfigParser.cpp b/src/ConfigParser.cpp index cb4dc24..68ee74b 100644 --- a/src/ConfigParser.cpp +++ b/src/ConfigParser.cpp @@ -37,6 +37,7 @@ #include "ConfigParser.h" #include "Utils.h" #include "Log.h" +#include "Events.h" #include "DabModulator.h" #include "output/SDR.h" @@ -114,11 +115,16 @@ static void parse_configfile( mod_settings.inputTransport = pt.Get("input.transport", "file"); - mod_settings.edi_max_delay_ms = pt.GetReal("input.edi_max_delay", 0.0f); + mod_settings.edi_max_delay_ms = pt.GetReal("input.edi_max_delay", 0.0); mod_settings.inputName = pt.Get("input.source", "/dev/stdin"); // log parameters: + const string events_endpoint = pt.Get("log.events_endpoint", ""); + if (not events_endpoint.empty()) { + events.bind(events_endpoint); + } + if (pt.GetInteger("log.syslog", 0) == 1) { etiLog.register_backend(make_shared()); } diff --git a/src/DabMod.cpp b/src/DabMod.cpp index 805fab5..fdd9e93 100644 --- a/src/DabMod.cpp +++ b/src/DabMod.cpp @@ -47,6 +47,7 @@ # include #endif +#include "Events.h" #include "Utils.h" #include "Log.h" #include "DabModulator.h" @@ -324,6 +325,8 @@ int launch_modulator(int argc, char* argv[]) mod_settings_t mod_settings; parse_args(argc, argv, mod_settings); + etiLog.register_backend(make_shared()); + etiLog.level(info) << "Configuration parsed. Starting up version " << #if defined(GITVERSION) GITVERSION; diff --git a/src/DabModulator.cpp b/src/DabModulator.cpp index 0fe9c6d..4a29132 100644 --- a/src/DabModulator.cpp +++ b/src/DabModulator.cpp @@ -438,7 +438,7 @@ const string DabModulator::get_parameter(const string& parameter) const const json::map_t DabModulator::get_all_values() const { json::map_t map; - map["rate"] = m_settings.outputRate; - map["num_clipped_samples"] = m_formatConverter ? m_formatConverter->get_num_clipped_samples() : 0; + map["rate"].v = m_settings.outputRate; + map["num_clipped_samples"].v = m_formatConverter ? m_formatConverter->get_num_clipped_samples() : 0; return map; } diff --git a/src/Events.cpp b/src/Events.cpp new file mode 100644 index 0000000..d65b73a --- /dev/null +++ b/src/Events.cpp @@ -0,0 +1,87 @@ +/* + Copyright (C) 2007, 2008, 2009, 2010, 2011, 2012 + Her Majesty the Queen in Right of Canada (Communications Research + Center Canada) + + Copyright (C) 2023 + Matthias P. Braendli, matthias.braendli@mpb.li + + http://www.opendigitalradio.org + */ +/* + 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 . + */ +#include +#include +#include +#include +#include +#include +#include + +#include "Events.h" + +EventSender events; + +EventSender::EventSender() : + m_zmq_context(1), + m_socket(m_zmq_context, zmq::socket_type::pub) +{ + int linger = 2000; + m_socket.setsockopt(ZMQ_LINGER, &linger, sizeof(linger)); +} + +EventSender::~EventSender() +{ } + +void EventSender::bind(const std::string& bind_endpoint) +{ + m_socket.bind(bind_endpoint); +} + +void EventSender::send(const std::string& event_name, const json::map_t& detail) +{ + zmq::message_t zmsg1(event_name.data(), event_name.size()); + const auto detail_json = json::map_to_json(detail); + zmq::message_t zmsg2(detail_json.data(), detail_json.size()); + + try { + m_socket.send(zmsg1, zmq::send_flags::sndmore); + m_socket.send(zmsg2, zmq::send_flags::none); + } + catch (const zmq::error_t& err) { + fprintf(stderr, "Cannot send event %s: %s", event_name.c_str(), err.what()); + } +} + + +void LogToEventSender::log(log_level_t level, const std::string& message) +{ + std::string event_name; + if (level == log_level_t::warn) { event_name = "warn"; } + else if (level == log_level_t::error) { event_name = "error"; } + else if (level == log_level_t::alert) { event_name = "alert"; } + else if (level == log_level_t::emerg) { event_name = "emerg"; } + + if (not event_name.empty()) { + json::map_t detail; + detail["message"].v = message; + events.send(event_name, detail); + } +} + +std::string LogToEventSender::get_name() const +{ + return "EventSender"; +} diff --git a/src/Events.h b/src/Events.h new file mode 100644 index 0000000..215c5a8 --- /dev/null +++ b/src/Events.h @@ -0,0 +1,76 @@ +/* + Copyright (C) 2007, 2008, 2009, 2010, 2011, 2012 + Her Majesty the Queen in Right of Canada (Communications Research + Center Canada) + + Copyright (C) 2023 + Matthias P. Braendli, matthias.braendli@mpb.li + + http://www.opendigitalradio.org + + This module adds remote-control capability to some of the dabmux/dabmod modules. + */ +/* + 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 . + */ + +#pragma once + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#if defined(HAVE_ZEROMQ) +# include "zmq.hpp" +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include "Log.h" +#include "Json.h" + +class EventSender { + public: + EventSender(); + EventSender(const EventSender& other) = delete; + const EventSender& operator=(const EventSender& other) = delete; + EventSender(EventSender&& other) = delete; + EventSender& operator=(EventSender&& other) = delete; + ~EventSender(); + + void bind(const std::string& bind_endpoint); + + void send(const std::string& event_name, const json::map_t& detail); + private: + zmq::context_t m_zmq_context; + zmq::socket_t m_socket; +}; + +class LogToEventSender: public LogBackend { + public: + virtual ~LogToEventSender() {}; + virtual void log(log_level_t level, const std::string& message); + virtual std::string get_name() const; +}; + +/* events is a singleton used in all parts of the program to output log messages. + * It is constructed in Events.cpp */ +extern EventSender events; + diff --git a/src/FIRFilter.cpp b/src/FIRFilter.cpp index d2a6121..57e7127 100644 --- a/src/FIRFilter.cpp +++ b/src/FIRFilter.cpp @@ -350,7 +350,7 @@ const string FIRFilter::get_parameter(const string& parameter) const const json::map_t FIRFilter::get_all_values() const { json::map_t map; - map["ntaps"] = m_taps.size(); - map["tapsfile"] = m_taps_file; + map["ntaps"].v = m_taps.size(); + map["tapsfile"].v = m_taps_file; return map; } diff --git a/src/GainControl.cpp b/src/GainControl.cpp index beb93f6..84cf065 100644 --- a/src/GainControl.cpp +++ b/src/GainControl.cpp @@ -586,18 +586,18 @@ const string GainControl::get_parameter(const string& parameter) const const json::map_t GainControl::get_all_values() const { json::map_t map; - map["digital"] = m_digGain; + map["digital"].v = m_digGain; switch (m_gainmode) { case GainMode::GAIN_FIX: - map["mode"] = "fix"; + map["mode"].v = "fix"; break; case GainMode::GAIN_MAX: - map["mode"] = "max"; + map["mode"].v = "max"; break; case GainMode::GAIN_VAR: - map["mode"] = "var"; + map["mode"].v = "var"; break; } - map["var"] = m_var_variance_rc; + map["var"].v = m_var_variance_rc; return map; } diff --git a/src/GuardIntervalInserter.cpp b/src/GuardIntervalInserter.cpp index 80394b7..3c2db14 100644 --- a/src/GuardIntervalInserter.cpp +++ b/src/GuardIntervalInserter.cpp @@ -306,6 +306,6 @@ const std::string GuardIntervalInserter::get_parameter(const std::string& parame const json::map_t GuardIntervalInserter::get_all_values() const { json::map_t map; - map["windowlen"] = d_windowOverlap; + map["windowlen"].v = d_windowOverlap; return map; } diff --git a/src/MemlessPoly.cpp b/src/MemlessPoly.cpp index 30d4ce9..184b5bd 100644 --- a/src/MemlessPoly.cpp +++ b/src/MemlessPoly.cpp @@ -470,8 +470,8 @@ const string MemlessPoly::get_parameter(const string& parameter) const const json::map_t MemlessPoly::get_all_values() const { json::map_t map; - map["ncoefs"] = m_coefs_am.size(); - map["coefs"] = serialise_coefficients(); - map["coeffile"] = m_coefs_file; + map["ncoefs"].v = m_coefs_am.size(); + map["coefs"].v = serialise_coefficients(); + map["coeffile"].v = m_coefs_file; return map; } diff --git a/src/TII.cpp b/src/TII.cpp index 9068630..2656cbf 100644 --- a/src/TII.cpp +++ b/src/TII.cpp @@ -388,9 +388,9 @@ const std::string TII::get_parameter(const std::string& parameter) const const json::map_t TII::get_all_values() const { json::map_t map; - map["enable"] = m_conf.enable; - map["pattern"] = m_conf.pattern; - map["comb"] = m_conf.comb; - map["old_variant"] = m_conf.old_variant; + map["enable"].v = m_conf.enable; + map["pattern"].v = m_conf.pattern; + map["comb"].v = m_conf.comb; + map["old_variant"].v = m_conf.old_variant; return map; } diff --git a/src/TimestampDecoder.cpp b/src/TimestampDecoder.cpp index 4277e55..a7972c9 100644 --- a/src/TimestampDecoder.cpp +++ b/src/TimestampDecoder.cpp @@ -304,19 +304,19 @@ const std::string TimestampDecoder::get_parameter( const json::map_t TimestampDecoder::get_all_values() const { json::map_t map; - map["offset"] = timestamp_offset; + map["offset"].v = timestamp_offset; if (full_timestamp_received) { - map["timestamp"] = time_secs + ((double)time_pps / 16384000.0); + map["timestamp"].v = time_secs + ((double)time_pps / 16384000.0); } else { - map["timestamp"] = std::nullopt; + map["timestamp"].v = std::nullopt; } if (full_timestamp_received) { - map["timestamp0"] = time_secs_of_frame0 + ((double)time_pps_of_frame0 / 16384000.0); + map["timestamp0"].v = time_secs_of_frame0 + ((double)time_pps_of_frame0 / 16384000.0); } else { - map["timestamp0"] = std::nullopt; + map["timestamp0"].v = std::nullopt; } return map; } diff --git a/src/output/Dexter.cpp b/src/output/Dexter.cpp index 132636c..e52f774 100644 --- a/src/output/Dexter.cpp +++ b/src/output/Dexter.cpp @@ -470,20 +470,20 @@ SDRDevice::run_statistics_t Dexter::get_run_statistics(void) const run_statistics_t rs; { std::unique_lock lock(m_attr_thread_mutex); - rs["underruns"] = underflows; + rs["underruns"].v = underflows; } - rs["latepackets"] = num_late; - rs["frames"] = num_frames_modulated; + rs["latepackets"].v = num_late; + rs["frames"].v = num_frames_modulated; - rs["in_holdover_since"] = 0; + rs["in_holdover_since"].v = 0; switch (m_clock_state) { case DexterClockState::Startup: - rs["clock_state"] = "startup"; break; + rs["clock_state"].v = "startup"; break; case DexterClockState::Normal: - rs["clock_state"] = "normal"; break; + rs["clock_state"].v = "normal"; break; case DexterClockState::Holdover: - rs["clock_state"] = "holdover"; - rs["in_holdover_since"] = m_holdover_since_t; + rs["clock_state"].v = "holdover"; + rs["in_holdover_since"].v = m_holdover_since_t; break; } diff --git a/src/output/SDR.cpp b/src/output/SDR.cpp index 4fc3277..6c03b53 100644 --- a/src/output/SDR.cpp +++ b/src/output/SDR.cpp @@ -450,7 +450,7 @@ const string SDR::get_parameter(const string& parameter) const if (m_device) { const auto stat = m_device->get_run_statistics(); try { - const auto& value = stat.at(parameter).data; + const auto& value = stat.at(parameter).v; if (std::holds_alternative(value)) { ss << std::get(value); } @@ -489,19 +489,19 @@ const json::map_t SDR::get_all_values() const { json::map_t stat = m_device->get_run_statistics(); - stat["txgain"] = m_config.txgain; - stat["rxgain"] = m_config.rxgain; - stat["freq"] = m_config.frequency; - stat["muting"] = m_config.muting; - stat["temp"] = std::nullopt; + stat["txgain"].v = m_config.txgain; + stat["rxgain"].v = m_config.rxgain; + stat["freq"].v = m_config.frequency; + stat["muting"].v = m_config.muting; + stat["temp"].v = std::nullopt; if (m_device) { const std::optional temp = m_device->get_temperature(); if (temp) { - stat["temp"] = *temp; + stat["temp"].v = *temp; } } - stat["queued_frames_ms"] = m_queue.size() * + stat["queued_frames_ms"].v = m_queue.size() * (size_t)chrono::duration_cast(transmission_frame_duration(m_config.dabMode)) .count(); diff --git a/src/output/Soapy.cpp b/src/output/Soapy.cpp index 4d33e39..7931860 100644 --- a/src/output/Soapy.cpp +++ b/src/output/Soapy.cpp @@ -183,10 +183,10 @@ double Soapy::get_bandwidth(void) const SDRDevice::run_statistics_t Soapy::get_run_statistics(void) const { run_statistics_t rs; - rs["underruns"] = underflows; - rs["overruns"] = overflows; - rs["timeouts"] = timeouts; - rs["frames"] = num_frames_modulated; + rs["underruns"].v = underflows; + rs["overruns"].v = overflows; + rs["timeouts"].v = timeouts; + rs["frames"].v = num_frames_modulated; return rs; } diff --git a/src/output/UHD.cpp b/src/output/UHD.cpp index 6638b6c..094e021 100644 --- a/src/output/UHD.cpp +++ b/src/output/UHD.cpp @@ -380,19 +380,19 @@ void UHD::transmit_frame(struct FrameData&& frame) SDRDevice::run_statistics_t UHD::get_run_statistics(void) const { run_statistics_t rs; - rs["underruns"] = num_underflows; - rs["overruns"] = num_overflows; - rs["late_packets"] = num_late_packets; - rs["frames"] = num_frames_modulated; + rs["underruns"].v = num_underflows; + rs["overruns"].v = num_overflows; + rs["late_packets"].v = num_late_packets; + rs["frames"].v = num_frames_modulated; if (m_device_time) { const auto gpsdo_stat = m_device_time->get_gnss_stats(); - rs["gpsdo_holdover"] = gpsdo_stat.holdover; - rs["gpsdo_num_sv"] = gpsdo_stat.num_sv; + rs["gpsdo_holdover"].v = gpsdo_stat.holdover; + rs["gpsdo_num_sv"].v = gpsdo_stat.num_sv; } else { - rs["gpsdo_holdover"] = true; - rs["gpsdo_num_sv"] = 0; + rs["gpsdo_holdover"].v = true; + rs["gpsdo_num_sv"].v = 0; } return rs; } -- cgit v1.2.3