From 6087160593e74aff9147153c69ea23849fc8b921 Mon Sep 17 00:00:00 2001 From: "Matthias P. Braendli" Date: Wed, 27 Jul 2022 12:05:05 +0200 Subject: Add PrecisionWave DEXTER support --- Makefile.am | 2 + configure.ac | 14 ++- doc/example.ini | 7 +- src/ConfigParser.cpp | 24 ++++ src/ConfigParser.h | 4 +- src/DabMod.cpp | 26 ++++- src/output/Dexter.cpp | 301 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/output/Dexter.h | 101 +++++++++++++++++ 8 files changed, 469 insertions(+), 10 deletions(-) create mode 100644 src/output/Dexter.cpp create mode 100644 src/output/Dexter.h diff --git a/Makefile.am b/Makefile.am index 39280fb..46f2c21 100644 --- a/Makefile.am +++ b/Makefile.am @@ -138,6 +138,8 @@ odr_dabmod_SOURCES += \ src/output/SDR.cpp \ src/output/SDR.h \ src/output/SDRDevice.h \ + src/output/Dexter.cpp \ + src/output/Dexter.h \ src/output/Soapy.cpp \ src/output/Soapy.h \ src/output/UHD.cpp \ diff --git a/configure.ac b/configure.ac index 7590896..9b45a89 100644 --- a/configure.ac +++ b/configure.ac @@ -55,6 +55,9 @@ AC_ARG_ENABLE([native], AC_ARG_ENABLE([easydabv3], [AS_HELP_STRING([--enable-easydabv3], [Build for EasyDABv3 board])], [], [enable_easydabv3=no]) +AC_ARG_ENABLE([dexter], + [AS_HELP_STRING([--enable-dexter], [Build for PrecisionWave Dexter board])], + [], [enable_dexter=no]) AC_ARG_ENABLE([limesdr], [AS_HELP_STRING([--enable-limesdr], [Build for LimeSDR board])], [], [enable_limesdr=no]) @@ -113,13 +116,17 @@ AS_IF([test "x$enable_limesdr" = "xyes"], [AC_CHECK_LIB([LimeSuite], [LMS_Init], [LIMESDR_LIBS="-lLimeSuite"], [AC_MSG_ERROR([LimeSDR LimeSuite is required])])]) +AS_IF([test "x$enable_dexter" = "xyes"], + [AC_CHECK_LIB([iio], [iio_create_scan_context], [IIO_LIBS="-liio"], + [AC_MSG_ERROR([libiio is required])])]) + AS_IF([test "x$enable_bladerf" = "xyes"], [AC_CHECK_LIB([bladeRF], [bladerf_open], [BLADERF_LIBS="-lbladeRF"], [AC_MSG_ERROR([BladeRF library is required])])]) AC_SUBST([CFLAGS], ["$CFLAGS $EXTRA $FFTW_CFLAGS $SOAPYSDR_CFLAGS $PTHREAD_CFLAGS"]) AC_SUBST([CXXFLAGS], ["$CXXFLAGS $EXTRA $FFTW_CFLAGS $SOAPYSDR_CFLAGS $PTHREAD_CFLAGS"]) -AC_SUBST([LIBS], ["$FFTW_LIBS $SOAPYSDR_LIBS $PTHREAD_LIBS $ZMQ_LIBS $LIMESDR_LIBS $BLADERF_LIBS"]) +AC_SUBST([LIBS], ["$FFTW_LIBS $SOAPYSDR_LIBS $PTHREAD_LIBS $ZMQ_LIBS $LIMESDR_LIBS $IIO_LIBS $BLADERF_LIBS"]) AS_IF([test "x$enable_easydabv3" = "xyes" && test "x$enable_output_uhd" == "xyes"], AC_MSG_ERROR([Cannot enable both EasyDABv3 and UHD output])) @@ -144,6 +151,9 @@ AS_IF([test "x$enable_soapysdr" = "xyes"], AS_IF([test "x$enable_limesdr" = "xyes"], [AC_DEFINE(HAVE_LIMESDR, [1], [Define if LimeSDR output is enabled]) ]) +AS_IF([test "x$enable_dexter" = "xyes"], + [AC_DEFINE(HAVE_DEXTER, [1], [Define if Dexter output is enabled])]) + AS_IF([test "x$enable_bladerf" = "xyes"], [AC_DEFINE(HAVE_BLADERF, [1], [Define if BladeRF output is enabled]) ]) @@ -217,7 +227,7 @@ echo "***********************************************" echo enabled="" disabled="" -for feat in prof trace output_uhd zeromq soapysdr easydabv3 limesdr bladerf +for feat in prof trace output_uhd zeromq soapysdr easydabv3 limesdr bladerf dexter do eval var=\$enable_$feat AS_IF([test "x$var" = "xyes"], diff --git a/doc/example.ini b/doc/example.ini index aca7634..2105535 100644 --- a/doc/example.ini +++ b/doc/example.ini @@ -164,7 +164,7 @@ enabled=0 polycoeffile=polyCoefs [output] -; choose output: possible values: uhd, file, zmq, soapysdr, limesdr, bladerf +; choose output: possible values: uhd, file, zmq, dexter, soapysdr, limesdr, bladerf output=uhd [fileoutput] @@ -322,6 +322,11 @@ channel=13C ; Set to 0 to disable ;dpd_port=50055 +[dexteroutput] +txgain=32768 +;frequency=234208000 +channel=13C + [limeoutput] ; Lime output directly runs against the LMS device driver. It does not support SFN nor predistortion. device= diff --git a/src/ConfigParser.cpp b/src/ConfigParser.cpp index ee7acc3..44d52e6 100644 --- a/src/ConfigParser.cpp +++ b/src/ConfigParser.cpp @@ -312,6 +312,30 @@ static void parse_configfile( mod_settings.useSoapyOutput = true; } #endif +#if defined(HAVE_DEXTER) + else if (output_selected == "dexter") { + auto& outputdexter_conf = mod_settings.sdr_device_config; + outputdexter_conf.txgain = pt.GetReal("dexteroutput.txgain", 0.0); + outputdexter_conf.lo_offset = pt.GetReal("dexteroutput.lo_offset", 0.0); + outputdexter_conf.frequency = pt.GetReal("dexteroutput.frequency", 0); + std::string chan = pt.Get("dexteroutput.channel", ""); + outputdexter_conf.dabMode = mod_settings.dabMode; + + if (outputdexter_conf.frequency == 0 && chan == "") { + std::cerr << " dexter output enabled, but neither frequency nor channel defined.\n"; + throw std::runtime_error("Configuration error"); + } + else if (outputdexter_conf.frequency == 0) { + outputdexter_conf.frequency = parseChannel(chan); + } + else if (outputdexter_conf.frequency != 0 && chan != "") { + std::cerr << " dexter output: cannot define both frequency and channel.\n"; + throw std::runtime_error("Configuration error"); + } + + mod_settings.useDexterOutput = true; + } +#endif #if defined(HAVE_LIMESDR) else if (output_selected == "limesdr") { auto& outputlime_conf = mod_settings.sdr_device_config; diff --git a/src/ConfigParser.h b/src/ConfigParser.h index 574caa2..8f2a1d2 100644 --- a/src/ConfigParser.h +++ b/src/ConfigParser.h @@ -51,9 +51,9 @@ struct mod_settings_t { bool fileOutputShowMetadata = false; bool useUHDOutput = false; bool useSoapyOutput = false; + bool useDexterOutput = false; bool useLimeOutput = false; bool useBladeRFOutput = false; - const std::string BladeRFOutputFormat = "s16"; // to transmit SC16 IQ size_t outputRate = 2048000; size_t clockRate = 0; @@ -87,7 +87,7 @@ struct mod_settings_t { // Settings for the OFDM windowing size_t ofdmWindowOverlap = 0; -#if defined(HAVE_OUTPUT_UHD) || defined(HAVE_SOAPYSDR) || defined(HAVE_LIMESDR) || defined(HAVE_BLADERF) +#if defined(HAVE_OUTPUT_UHD) || defined(HAVE_SOAPYSDR) || defined(HAVE_LIMESDR) || defined(HAVE_BLADERF) || defined(HAVE_DEXTER) Output::SDRDeviceConfig sdr_device_config; #endif diff --git a/src/DabMod.cpp b/src/DabMod.cpp index f97c05d..15cdbaa 100644 --- a/src/DabMod.cpp +++ b/src/DabMod.cpp @@ -56,6 +56,7 @@ #include "output/SDR.h" #include "output/UHD.h" #include "output/Soapy.h" +#include "output/Dexter.h" #include "output/Lime.h" #include "output/BladeRF.h" #include "OutputZeroMQ.h" @@ -151,6 +152,11 @@ static void printModSettings(const mod_settings_t& mod_settings) mod_settings.sdr_device_config.masterClockRate << "\n"; } #endif +#if defined(HAVE_DEXTER) + else if (mod_settings.useDexterOutput) { + ss << " PrecisionWave DEXTER\n"; + } +#endif #if defined(HAVE_LIMESDR) else if (mod_settings.useLimeOutput) { ss << " LimeSDR\n" @@ -192,8 +198,7 @@ static void printModSettings(const mod_settings_t& mod_settings) fprintf(stderr, "%s", ss.str().c_str()); } -static shared_ptr prepare_output( - mod_settings_t& s) +static shared_ptr prepare_output(mod_settings_t& s) { shared_ptr output; @@ -249,6 +254,16 @@ static shared_ptr prepare_output( rcs.enrol((Output::SDR*)output.get()); } #endif +#if defined(HAVE_DEXTER) + else if (s.useDexterOutput) { + /* We normalise specifically range [-32768; 32767] */ + s.normalise = 32767.0f / normalise_factor; + s.sdr_device_config.sampleRate = s.outputRate; + auto dexterdevice = make_shared(s.sdr_device_config); + output = make_shared(s.sdr_device_config, dexterdevice); + rcs.enrol((Output::SDR*)output.get()); + } +#endif #if defined(HAVE_LIMESDR) else if (s.useLimeOutput) { /* We normalise the same way as for the UHD output */ @@ -319,6 +334,7 @@ int launch_modulator(int argc, char* argv[]) mod_settings.useUHDOutput or mod_settings.useZeroMQOutput or mod_settings.useSoapyOutput or + mod_settings.useDexterOutput or mod_settings.useLimeOutput or mod_settings.useBladeRFOutput)) { throw std::runtime_error("Configuration error: Output not specified"); @@ -333,9 +349,9 @@ int launch_modulator(int argc, char* argv[]) mod_settings.fileOutputFormat == "s16")) { format_converter = make_shared(mod_settings.fileOutputFormat); } - else if (mod_settings.useBladeRFOutput) { - format_converter = make_shared(mod_settings.BladeRFOutputFormat); - } + else if (mod_settings.useBladeRFOutput or mod_settings.useDexterOutput) { + format_converter = make_shared("s16"); + } auto output = prepare_output(mod_settings); diff --git a/src/output/Dexter.cpp b/src/output/Dexter.cpp new file mode 100644 index 0000000..605c61a --- /dev/null +++ b/src/output/Dexter.cpp @@ -0,0 +1,301 @@ +/* + Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Her Majesty the + Queen in Right of Canada (Communications Research Center Canada) + + Copyright (C) 2022 + Matthias P. Braendli, matthias.braendli@mpb.li + + http://opendigitalradio.org + +DESCRIPTION: + It is an output driver using libiio targeting the PrecisionWave DEXTER board. +*/ + +/* + This file is part of ODR-DabMod. + + ODR-DabMod 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. + + ODR-DabMod 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 ODR-DabMod. If not, see . + */ + +#include "output/Dexter.h" + +#ifdef HAVE_DEXTER + +#include +#include +#include +#include + +#include "Log.h" +#include "Utils.h" + +using namespace std; + +namespace Output { + +static constexpr size_t TRANSMISSION_FRAME_LEN = (2656 + 76 * 2552) * 4; + +static string get_iio_error(int err) +{ + char dst[256]; + iio_strerror(err, dst, sizeof(dst)); + return string(dst); +} + +Dexter::Dexter(SDRDeviceConfig& config) : + SDRDevice(), + m_conf(config) +{ + etiLog.level(info) << "Dexter:Creating the device"; + + m_ctx = iio_create_local_context(); + if (!m_ctx) { + throw std::runtime_error("Dexter: Unable to create iio scan context"); + } + + 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"); + } + + m_ad9957_tx0 = iio_context_find_device(m_ctx, "ad9957_tx0"); + if (!m_ad9957_tx0) { + 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); + } + + 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); + } + + 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); + } + + 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); + } + + //iio_device_attr_read_longlong(const struct iio_device *dev, const char *attr, long long *val); + + if (m_conf.sampleRate != 2048000) { + throw std::runtime_error("Dexter: Only 2048000 samplerate supported"); + } + + tune(m_conf.lo_offset, m_conf.frequency); + // TODO m_conf.frequency = m_dexter_dsp_tx->getFrequency(SOAPY_SDR_TX, 0); + etiLog.level(info) << "Dexter:Actual frequency: " << + std::fixed << std::setprecision(3) << + m_conf.frequency / 1000.0 << " kHz."; + + // skip: Set bandwidth + + // skip: antenna + + // TODO: set H/W time + + // Prepare streams + constexpr int CHANNEL_INDEX = 0; + m_tx_channel = iio_device_get_channel(m_ad9957_tx0, CHANNEL_INDEX); + if (m_tx_channel == nullptr) { + throw std::runtime_error("Dexter: Cannot create IIO channel."); + } + + iio_channel_enable(m_tx_channel); + + m_buffer = iio_device_create_buffer(m_ad9957_tx0, TRANSMISSION_FRAME_LEN/sizeof(int16_t), 0); + if (!m_buffer) { + throw std::runtime_error("Dexter: Cannot create IIO buffer."); + } +} + +Dexter::~Dexter() +{ + if (m_ctx) { + if (m_dexter_dsp_tx) { + iio_device_attr_write_longlong(m_dexter_dsp_tx, "gain0", 0); + } + + if (m_buffer) { + iio_buffer_destroy(m_buffer); + } + + iio_context_destroy(m_ctx); + m_ctx = nullptr; + } +} + +void Dexter::tune(double lo_offset, double frequency) +{ + // TODO lo_offset + long long freq = m_conf.frequency - 204800000; + int r = 0; + + if ((r = iio_device_attr_write_longlong(m_dexter_dsp_tx, "frequency0", freq)) != 0) { + etiLog.level(warn) << "Failed to set dexter_dsp_tx.frequency0 = " << freq << " : " << get_iio_error(r); + } +} + +double Dexter::get_tx_freq(void) const +{ + long long frequency = 0; + int r = 0; + + if ((r = iio_device_attr_read_longlong(m_dexter_dsp_tx, "frequency0", &frequency)) != 0) { + etiLog.level(warn) << "Failed to read dexter_dsp_tx.frequency0 = " << + frequency << " : " << get_iio_error(r); + return 0; + } + else { + return frequency + 204800000; + } +} + +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.stream0_start_clks = 0: " << 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.stream0_start_clks = 0: " << get_iio_error(r); + } + else { + m_conf.txgain = txgain_readback; + } +} + +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.stream0_start_clks = 0: " << get_iio_error(r); + } + return txgain_readback; +} + +void Dexter::set_bandwidth(double bandwidth) +{ + // TODO +} + +double Dexter::get_bandwidth(void) const +{ + return 0; +} + +SDRDevice::RunStatistics Dexter::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; + return rs; +} + + +double Dexter::get_real_secs(void) const +{ + // TODO + return 0; +} + +void Dexter::set_rxgain(double rxgain) +{ + // TODO +} + +double Dexter::get_rxgain(void) const +{ + // TODO + return 0; +} + +size_t Dexter::receive_frame( + complexf *buf, + size_t num_samples, + struct frame_timestamp& ts, + double timeout_secs) +{ + // TODO + return 0; +} + + +bool Dexter::is_clk_source_ok() const +{ + // TODO + return true; +} + +const char* Dexter::device_name(void) const +{ + return "Dexter"; +} + +double Dexter::get_temperature(void) const +{ + // TODO + // XADC contains temperature, but value is weird + return std::numeric_limits::quiet_NaN(); +} + +void Dexter::transmit_frame(const struct FrameData& frame) +{ + long long int timeNs = frame.ts.get_ns(); + const bool has_time_spec = (m_conf.enableSync and frame.ts.timestamp_valid); + + if (frame.buf.size() != TRANSMISSION_FRAME_LEN) { + etiLog.level(debug) << "Dexter::transmit_frame Expected " << + TRANSMISSION_FRAME_LEN << " got " << frame.buf.size(); + throw std::runtime_error("Dexter: invalid buffer size"); + } + + // DabMod::launch_modulator ensures we get int16_t IQ here + //const size_t num_samples = frame.buf.size() / (2*sizeof(int16_t)); + //const int16_t *buf = reinterpret_cast(frame.buf.data()); + + memcpy(iio_buffer_start(m_buffer), frame.buf.data(), frame.buf.size()); + ssize_t pushed = iio_buffer_push(m_buffer); + if (pushed < 0) { + etiLog.level(error) << "Dexter: failed to push buffer " << get_iio_error(pushed); + } + + num_frames_modulated++; + // TODO overflows, late_packets + + long long attr_value = 0; + int r = 0; + + if ((r = iio_device_attr_read_longlong(m_dexter_dsp_tx, "buffer_underflows0", &attr_value)) == 0) { + fprintf(stderr, "buffer_underflows0 %lld\n", attr_value); + underflows = attr_value; + } +} + +} // namespace Output + +#endif // HAVE_DEXTER + + diff --git a/src/output/Dexter.h b/src/output/Dexter.h new file mode 100644 index 0000000..7a7f6c1 --- /dev/null +++ b/src/output/Dexter.h @@ -0,0 +1,101 @@ +/* + Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Her Majesty the + Queen in Right of Canada (Communications Research Center Canada) + + Copyright (C) 2022 + Matthias P. Braendli, matthias.braendli@mpb.li + + http://opendigitalradio.org + +DESCRIPTION: + It is an output driver using libiio targeting the PrecisionWave DEXTER board. +*/ + +/* + This file is part of ODR-DabMod. + + ODR-DabMod 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. + + ODR-DabMod 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 ODR-DabMod. If not, see . + */ + +#pragma once + +#ifdef HAVE_CONFIG_H +# include +#endif + +#ifdef HAVE_DEXTER +#include "iio.h" + +#include +#include + +#include "output/SDR.h" +#include "ModPlugin.h" +#include "EtiReader.h" +#include "RemoteControl.h" + +namespace Output { + +class Dexter : public Output::SDRDevice +{ + public: + Dexter(SDRDeviceConfig& config); + Dexter(const Dexter& other) = delete; + Dexter& operator=(const Dexter& other) = delete; + ~Dexter(); + + virtual void tune(double lo_offset, double frequency) override; + virtual double get_tx_freq(void) const override; + virtual void set_txgain(double txgain) override; + 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 RunStatistics get_run_statistics(void) const override; + virtual double get_real_secs(void) const override; + + virtual void set_rxgain(double rxgain) override; + virtual double get_rxgain(void) const override; + virtual size_t receive_frame( + complexf *buf, + size_t num_samples, + struct frame_timestamp& ts, + double timeout_secs) override; + + // Return true if GPS and reference clock inputs are ok + virtual bool is_clk_source_ok(void) const override; + virtual const char* device_name(void) const override; + + virtual double get_temperature(void) const override; + + private: + SDRDeviceConfig& m_conf; + + struct iio_context* m_ctx = nullptr; + struct iio_device* m_dexter_dsp_tx = nullptr; + + struct iio_device* m_ad9957_tx0 = nullptr; + struct iio_channel* m_tx_channel = nullptr; + struct iio_buffer *m_buffer = nullptr; + + size_t underflows = 0; + size_t overflows = 0; + size_t late_packets = 0; + size_t num_frames_modulated = 0; +}; + +} // namespace Output + +#endif //HAVE_DEXTER + -- cgit v1.2.3 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/ConfigParser.cpp | 3 +- src/DabMod.cpp | 45 ++++++++++--- src/EtiReader.cpp | 34 ++++------ src/EtiReader.h | 17 +++-- src/FicSource.cpp | 69 +++++++++----------- src/FicSource.h | 21 +++--- src/FormatConverter.cpp | 27 ++++++-- src/FormatConverter.h | 5 +- src/ModPlugin.h | 4 +- src/OutputFile.cpp | 31 ++++----- src/TimestampDecoder.cpp | 32 ++++++--- src/TimestampDecoder.h | 4 +- src/output/BladeRF.cpp | 2 +- src/output/BladeRF.h | 4 +- src/output/Dexter.cpp | 164 +++++++++++++++++++++++++++++++++++++++++------ src/output/Dexter.h | 10 ++- src/output/Feedback.cpp | 2 +- src/output/Feedback.h | 2 +- src/output/Lime.cpp | 2 +- src/output/Lime.h | 2 +- src/output/SDR.cpp | 15 +++-- src/output/SDR.h | 2 + src/output/SDRDevice.h | 5 +- src/output/Soapy.cpp | 2 +- src/output/Soapy.h | 2 +- src/output/UHD.cpp | 2 +- src/output/UHD.h | 2 +- src/output/USRPTime.cpp | 9 ++- 28 files changed, 351 insertions(+), 168 deletions(-) diff --git a/src/ConfigParser.cpp b/src/ConfigParser.cpp index 44d52e6..9190c60 100644 --- a/src/ConfigParser.cpp +++ b/src/ConfigParser.cpp @@ -409,7 +409,7 @@ static void parse_configfile( } -#if defined(HAVE_OUTPUT_UHD) +#if defined(HAVE_OUTPUT_UHD) || defined(HAVE_DEXTER) mod_settings.sdr_device_config.enableSync = (pt.GetInteger("delaymanagement.synchronous", 0) == 1); mod_settings.sdr_device_config.muteNoTimestamps = (pt.GetInteger("delaymanagement.mutenotimestamps", 0) == 1); if (mod_settings.sdr_device_config.enableSync) { @@ -430,7 +430,6 @@ static void parse_configfile( throw std::runtime_error("Configuration error"); } } - #endif diff --git a/src/DabMod.cpp b/src/DabMod.cpp index 15cdbaa..278f8ce 100644 --- a/src/DabMod.cpp +++ b/src/DabMod.cpp @@ -115,7 +115,7 @@ enum class run_modulator_state_t { reconfigure // Some sort of change of configuration we cannot handle happened }; -static run_modulator_state_t run_modulator(modulator_data& m); +static run_modulator_state_t run_modulator(const mod_settings_t& mod_settings, modulator_data& m); static void printModSettings(const mod_settings_t& mod_settings) { @@ -426,6 +426,10 @@ int launch_modulator(int argc, char* argv[]) if (format_converter) { flowgraph.connect(modulator, format_converter); flowgraph.connect(format_converter, output); + + if (auto o = dynamic_pointer_cast(output)) { + o->set_sample_size(format_converter->get_format_size()); + } } else { flowgraph.connect(modulator, output); @@ -435,7 +439,7 @@ int launch_modulator(int argc, char* argv[]) etiLog.level(info) << inputReader->GetPrintableInfo(); } - run_modulator_state_t st = run_modulator(m); + run_modulator_state_t st = run_modulator(mod_settings, m); etiLog.log(trace, "DABMOD,run_modulator() = %d", st); switch (st) { @@ -505,12 +509,13 @@ struct zmq_input_timeout : public std::exception } }; -static run_modulator_state_t run_modulator(modulator_data& m) +static run_modulator_state_t run_modulator(const mod_settings_t& mod_settings, modulator_data& m) { auto ret = run_modulator_state_t::failure; try { int last_eti_fct = -1; auto last_frame_received = chrono::steady_clock::now(); + frame_timestamp ts; Buffer data; if (m.inputReader) { data.setLength(6144); @@ -584,6 +589,7 @@ static run_modulator_state_t run_modulator(modulator_data& m) fct = m.etiReader->getFct(); fp = m.etiReader->getFp(); + ts = m.ediInput->ediReader.getTimestamp(); } else if (m.ediInput) { while (running and not m.ediInput->ediReader.isFrameReady()) { @@ -612,9 +618,10 @@ static run_modulator_state_t run_modulator(modulator_data& m) fct = m.ediInput->ediReader.getFct(); fp = m.ediInput->ediReader.getFp(); + ts = m.ediInput->ediReader.getTimestamp(); } - const unsigned expected_fct = (last_eti_fct + 1) % 250; + bool fct_good = false; if (last_eti_fct == -1) { if (fp != 0) { // Do not start the flowgraph before we get to FP 0 @@ -625,19 +632,37 @@ static run_modulator_state_t run_modulator(modulator_data& m) continue; } else { - last_eti_fct = fct; - m.framecount++; - m.flowgraph->run(); + fct_good = true; + } + } + else { + const unsigned expected_fct = (last_eti_fct + 1) % 250; + if (fct == expected_fct) { + fct_good = true; + } + 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; } } - else if (fct == expected_fct) { + + // timestamp is good if we run unsynchronised, or if it's in the future + bool ts_good = not mod_settings.sdr_device_config.enableSync or + (ts.timestamp_valid and ts.offset_to_system_time() > 0); + + if (fct_good and ts_good) { last_eti_fct = fct; m.framecount++; m.flowgraph->run(); } else { - etiLog.level(info) << "ETI FCT discontinuity, expected " << - expected_fct << " received " << fct; + etiLog.level(warn) << "Skipping frame " << fct << " FCT " << + (fct_good ? "good" : "bad") << " TS " << + (ts_good ? "good" : "bad"); if (m.ediInput) { m.ediInput->ediReader.clearFrame(); } diff --git a/src/EtiReader.cpp b/src/EtiReader.cpp index d1c7622..e992e62 100644 --- a/src/EtiReader.cpp +++ b/src/EtiReader.cpp @@ -78,6 +78,11 @@ unsigned EtiReader::getFct() return eti_fc.FCT; } +frame_timestamp EtiReader::getTimestamp() +{ + return myTimestampDecoder.getTimestamp(); +} + const std::vector > EtiReader::getSubchannels() const { @@ -278,22 +283,15 @@ int EtiReader::loadEtiData(const Buffer& dataIn) return dataIn.getLength() - input_size; } -bool EtiReader::sourceContainsTimestamp() -{ - return (ntohl(eti_tist.TIST) & 0xFFFFFF) != 0xFFFFFF; - /* See ETS 300 799, Annex C.2.2 */ -} - uint32_t EtiReader::getPPSOffset() { - if (!sourceContainsTimestamp()) { - //fprintf(stderr, "****** SOURCE NO TS\n"); + const uint32_t timestamp = ntohl(eti_tist.TIST) & 0xFFFFFF; + + /* See ETS 300 799, Annex C.2.2 */ + if (timestamp == 0xFFFFFF) { return 0.0; } - uint32_t timestamp = ntohl(eti_tist.TIST) & 0xFFFFFF; - //fprintf(stderr, "****** TIST 0x%x\n", timestamp); - return timestamp; } @@ -329,6 +327,11 @@ unsigned EdiReader::getFct() return m_fc.fct(); } +frame_timestamp EdiReader::getTimestamp() +{ + return m_timestamp_decoder.getTimestamp(); +} + const std::vector > EdiReader::getSubchannels() const { std::vector > sources; @@ -346,15 +349,6 @@ const std::vector > EdiReader::getSubchannels( return sources; } -bool EdiReader::sourceContainsTimestamp() -{ - if (not (m_frameReady and m_fc_valid)) { - throw std::runtime_error("Trying to get timestamp before it is ready"); - } - - return m_fc.tsta != 0xFFFFFF; -} - bool EdiReader::isFrameReady() { return m_frameReady; diff --git a/src/EtiReader.h b/src/EtiReader.h index d97acf6..fb2c84c 100644 --- a/src/EtiReader.h +++ b/src/EtiReader.h @@ -59,8 +59,8 @@ public: /* Get the current Frame Count */ virtual unsigned getFct() = 0; - /* Returns true if we have valid time stamps in the ETI*/ - virtual bool sourceContainsTimestamp() = 0; + /* Returns current Timestamp */ + virtual frame_timestamp getTimestamp() = 0; /* Return the FIC source to be used for modulation */ virtual std::shared_ptr& getFic(void); @@ -97,18 +97,17 @@ class EtiReader : public EtiSource public: EtiReader(double& tist_offset_s); - virtual unsigned getMode(); - virtual unsigned getFp(); - virtual unsigned getFct(); + virtual unsigned getMode() override; + virtual unsigned getFp() override; + virtual unsigned getFct() override; + virtual frame_timestamp getTimestamp() override; /* Read ETI data from dataIn. Returns the number of bytes * read from the buffer. */ int loadEtiData(const Buffer& dataIn); - virtual bool sourceContainsTimestamp(); - - virtual const std::vector > getSubchannels() const; + virtual const std::vector > getSubchannels() const override; private: /* Transform the ETI TIST to a PPS offset in units of 1/16384000 s */ @@ -141,7 +140,7 @@ public: virtual unsigned getMode() override; virtual unsigned getFp() override; virtual unsigned getFct() override; - virtual bool sourceContainsTimestamp() override; + virtual frame_timestamp getTimestamp() override; virtual const std::vector > getSubchannels() const override; virtual bool isFrameReady(void); diff --git a/src/FicSource.cpp b/src/FicSource.cpp index 2b95085..d824058 100644 --- a/src/FicSource.cpp +++ b/src/FicSource.cpp @@ -2,7 +2,7 @@ Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) - Copyright (C) 2018 + Copyright (C) 2022 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org @@ -27,7 +27,6 @@ #include "FicSource.h" #include "PcDebug.h" #include "Log.h" -#include "TimestampDecoder.h" #include #include @@ -36,46 +35,45 @@ #include -const std::vector& FicSource::get_rules() -{ - return d_puncturing_rules; -} - - FicSource::FicSource(unsigned ficf, unsigned mid) : ModInput() { // PDEBUG("FicSource::FicSource(...)\n"); // PDEBUG(" Start address: %i\n", d_start_address); -// PDEBUG(" Framesize: %i\n", d_framesize); +// PDEBUG(" Framesize: %i\n", m_framesize); // PDEBUG(" Protection: %i\n", d_protection); if (ficf == 0) { - d_framesize = 0; - d_buffer.setLength(0); + m_buffer.setLength(0); return; } if (mid == 3) { - d_framesize = 32 * 4; - d_puncturing_rules.emplace_back(29 * 16, 0xeeeeeeee); - d_puncturing_rules.emplace_back(3 * 16, 0xeeeeeeec); + m_framesize = 32 * 4; + m_puncturing_rules.emplace_back(29 * 16, 0xeeeeeeee); + m_puncturing_rules.emplace_back(3 * 16, 0xeeeeeeec); } else { - d_framesize = 24 * 4; - d_puncturing_rules.emplace_back(21 * 16, 0xeeeeeeee); - d_puncturing_rules.emplace_back(3 * 16, 0xeeeeeeec); + m_framesize = 24 * 4; + m_puncturing_rules.emplace_back(21 * 16, 0xeeeeeeee); + m_puncturing_rules.emplace_back(3 * 16, 0xeeeeeeec); } - d_buffer.setLength(d_framesize); + m_buffer.setLength(m_framesize); +} + +size_t FicSource::getFramesize() const +{ + return m_framesize; } -size_t FicSource::getFramesize() +const std::vector& FicSource::get_rules() const { - return d_framesize; + return m_puncturing_rules; } + void FicSource::loadFicData(const Buffer& fic) { - d_buffer = fic; + m_buffer = fic; } int FicSource::process(Buffer* outputData) @@ -83,34 +81,31 @@ int FicSource::process(Buffer* outputData) PDEBUG("FicSource::process (outputData: %p, outputSize: %zu)\n", outputData, outputData->getLength()); - if (d_buffer.getLength() != d_framesize) { + if (m_buffer.getLength() != m_framesize) { throw std::runtime_error( - "ERROR: FicSource::process.outputSize != d_framesize: " + - std::to_string(d_buffer.getLength()) + " != " + - std::to_string(d_framesize)); + "ERROR: FicSource::process.outputSize != m_framesize: " + + std::to_string(m_buffer.getLength()) + " != " + + std::to_string(m_framesize)); } - *outputData = d_buffer; + *outputData = m_buffer; return outputData->getLength(); } -void FicSource::loadTimestamp(const std::shared_ptr& ts) +void FicSource::loadTimestamp(const frame_timestamp& ts) { - d_ts = ts; + m_ts_valid = true; + m_ts = ts; } - meta_vec_t FicSource::process_metadata(const meta_vec_t& metadataIn) { - if (not d_ts) { - return {}; - } - - using namespace std; meta_vec_t md_vec; - flowgraph_metadata meta; - meta.ts = d_ts; - md_vec.push_back(meta); + if (m_ts_valid) { + flowgraph_metadata meta; + meta.ts = m_ts; + md_vec.push_back(meta); + } return md_vec; } diff --git a/src/FicSource.h b/src/FicSource.h index 93c1a7f..01dba2d 100644 --- a/src/FicSource.h +++ b/src/FicSource.h @@ -2,7 +2,7 @@ Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) - Copyright (C) 2016 + Copyright (C) 2022 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org @@ -33,6 +33,7 @@ #include "PuncturingRule.h" #include "Eti.h" #include "ModPlugin.h" +#include "TimestampDecoder.h" #include #include @@ -41,21 +42,21 @@ class FicSource : public ModInput, public ModMetadata public: FicSource(unsigned ficf, unsigned mid); - size_t getFramesize(); - const std::vector& get_rules(); + size_t getFramesize() const; + const std::vector& get_rules() const; void loadFicData(const Buffer& fic); int process(Buffer* outputData) override; const char* name() override { return "FicSource"; } - void loadTimestamp(const std::shared_ptr& ts); - virtual meta_vec_t process_metadata( - const meta_vec_t& metadataIn) override; + void loadTimestamp(const frame_timestamp& ts); + virtual meta_vec_t process_metadata(const meta_vec_t& metadataIn) override; private: - size_t d_framesize; - Buffer d_buffer; - std::shared_ptr d_ts; - std::vector d_puncturing_rules; + size_t m_framesize = 0; + Buffer m_buffer; + frame_timestamp m_ts; + bool m_ts_valid = false; + std::vector m_puncturing_rules; }; diff --git a/src/FormatConverter.cpp b/src/FormatConverter.cpp index 0f86d42..cda8a4d 100644 --- a/src/FormatConverter.cpp +++ b/src/FormatConverter.cpp @@ -2,7 +2,7 @@ Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) - Copyright (C) 2017 + Copyright (C) 2022 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org @@ -34,10 +34,6 @@ #include #include -#ifdef __SSE__ -# include -#endif - FormatConverter::FormatConverter(const std::string& format) : ModCodec(), m_format(format) @@ -68,7 +64,7 @@ int FormatConverter::process(Buffer* const dataIn, Buffer* dataOut) out[i] = in[i] + 128; } } - else { + else if (m_format == "s8") { dataOut->setLength(sizeIn * sizeof(int8_t)); int8_t* out = reinterpret_cast(dataOut->getData()); @@ -76,6 +72,9 @@ int FormatConverter::process(Buffer* const dataIn, Buffer* dataOut) out[i] = in[i]; } } + else { + throw std::runtime_error("FormatConverter: Invalid format " + m_format); + } return dataOut->getLength(); } @@ -85,3 +84,19 @@ const char* FormatConverter::name() return "FormatConverter"; } +size_t FormatConverter::get_format_size() const +{ + // Returns 2*sizeof(SAMPLE_TYPE) because we have I + Q + if (m_format == "s16") { + return 4; + } + else if (m_format == "u8") { + return 2; + } + else if (m_format == "s8") { + return 2; + } + else { + throw std::runtime_error("FormatConverter: Invalid format " + m_format); + } +} diff --git a/src/FormatConverter.h b/src/FormatConverter.h index cc8a606..ceb2e17 100644 --- a/src/FormatConverter.h +++ b/src/FormatConverter.h @@ -2,7 +2,7 @@ Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) - Copyright (C) 2017 + Copyright (C) 2022 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org @@ -40,11 +40,14 @@ class FormatConverter : public ModCodec { public: + // Allowed formats: s8, u8 and s16 FormatConverter(const std::string& format); int process(Buffer* const dataIn, Buffer* dataOut); const char* name(); + size_t get_format_size() const; + private: std::string m_format; }; diff --git a/src/ModPlugin.h b/src/ModPlugin.h index 7f03618..470508f 100644 --- a/src/ModPlugin.h +++ b/src/ModPlugin.h @@ -32,6 +32,7 @@ #include "Buffer.h" #include "ThreadsafeQueue.h" +#include "TimestampDecoder.h" #include #include #include @@ -41,9 +42,8 @@ // All flowgraph elements derive from ModPlugin, or a variant of it. // Some ModPlugins also support handling metadata. -struct frame_timestamp; struct flowgraph_metadata { - std::shared_ptr ts; + frame_timestamp ts; }; using meta_vec_t = std::vector; diff --git a/src/OutputFile.cpp b/src/OutputFile.cpp index acaebad..2ee838c 100644 --- a/src/OutputFile.cpp +++ b/src/OutputFile.cpp @@ -74,28 +74,23 @@ meta_vec_t OutputFile::process_metadata(const meta_vec_t& metadataIn) frame_timestamp first_ts; for (const auto& md : metadataIn) { - if (md.ts) { - // The following code assumes TM I, where we get called every 96ms. - // Support for other transmission modes skipped because this is mostly - // debugging code. + // The following code assumes TM I, where we get called every 96ms. + // Support for other transmission modes skipped because this is mostly + // debugging code. - if (md.ts->fp == 0 or md.ts->fp == 4) { - first_ts = *md.ts; - } + if (md.ts.fp == 0 or md.ts.fp == 4) { + first_ts = md.ts; + } - ss << " FCT=" << md.ts->fct << - " FP=" << (int)md.ts->fp; - if (md.ts->timestamp_valid) { - ss << " TS=" << md.ts->timestamp_sec << " + " << - std::fixed - << (double)md.ts->timestamp_pps / 163840000.0 << ";"; - } - else { - ss << " TS invalid;"; - } + ss << " FCT=" << md.ts.fct << + " FP=" << (int)md.ts.fp; + if (md.ts.timestamp_valid) { + ss << " TS=" << md.ts.timestamp_sec << " + " << + std::fixed + << (double)md.ts.timestamp_pps / 163840000.0 << ";"; } else { - ss << " void, "; + ss << " TS invalid;"; } } diff --git a/src/TimestampDecoder.cpp b/src/TimestampDecoder.cpp index 3cfa0cc..54a5817 100644 --- a/src/TimestampDecoder.cpp +++ b/src/TimestampDecoder.cpp @@ -36,6 +36,20 @@ //#define MDEBUG(fmt, args...) fprintf (LOG, "*****" fmt , ## args) #define MDEBUG(fmt, args...) PDEBUG(fmt, ## args) +double frame_timestamp::offset_to_system_time() const +{ + if (not timestamp_valid) { + throw new std::runtime_error("Cannot calculate offset for invalid timestamp"); + } + + struct timespec t; + if (clock_gettime(CLOCK_REALTIME, &t) != 0) { + throw std::runtime_error(std::string("Failed to retrieve CLOCK_REALTIME") + strerror(errno)); + } + + return get_real_secs() - (double)t.tv_sec - (t.tv_nsec / 1000000000.0); +} + frame_timestamp& frame_timestamp::operator+=(const double& diff) { double offset_pps, offset_secs; @@ -75,20 +89,20 @@ TimestampDecoder::TimestampDecoder(double& offset_s) : timestamp_offset << " offset"; } -std::shared_ptr TimestampDecoder::getTimestamp() +frame_timestamp TimestampDecoder::getTimestamp() { - auto ts = std::make_shared(); + frame_timestamp ts; - ts->timestamp_valid = full_timestamp_received; - ts->timestamp_sec = time_secs; - ts->timestamp_pps = time_pps; - ts->fct = latestFCT; - ts->fp = latestFP; + ts.timestamp_valid = full_timestamp_received; + ts.timestamp_sec = time_secs; + ts.timestamp_pps = time_pps; + ts.fct = latestFCT; + ts.fp = latestFP; - ts->timestamp_refresh = offset_changed; + ts.timestamp_refresh = offset_changed; offset_changed = false; - *ts += timestamp_offset; + ts += timestamp_offset; return ts; } diff --git a/src/TimestampDecoder.h b/src/TimestampDecoder.h index d083061..3616bab 100644 --- a/src/TimestampDecoder.h +++ b/src/TimestampDecoder.h @@ -56,6 +56,8 @@ struct frame_timestamp return timestamp_pps / 16384000.0; } + double offset_to_system_time() const; + double get_real_secs() const { double t = timestamp_sec; t += pps_offset(); @@ -93,7 +95,7 @@ class TimestampDecoder : public RemoteControllable */ TimestampDecoder(double& offset_s); - std::shared_ptr getTimestamp(void); + frame_timestamp getTimestamp(void); /* Update timestamp data from ETI */ void updateTimestampEti( diff --git a/src/output/BladeRF.cpp b/src/output/BladeRF.cpp index a6ad0cc..dd48736 100755 --- a/src/output/BladeRF.cpp +++ b/src/output/BladeRF.cpp @@ -269,7 +269,7 @@ double BladeRF::get_rxgain(void) const size_t BladeRF::receive_frame( complexf *buf, size_t num_samples, - struct frame_timestamp &ts, + frame_timestamp &ts, double timeout_secs) { // TODO diff --git a/src/output/BladeRF.h b/src/output/BladeRF.h index bc6db38..e048daa 100755 --- a/src/output/BladeRF.h +++ b/src/output/BladeRF.h @@ -83,7 +83,7 @@ class BladeRF : public Output::SDRDevice virtual size_t receive_frame( complexf *buf, size_t num_samples, - struct frame_timestamp& ts, + frame_timestamp& ts, double timeout_secs) override; // Return true if GPS and reference clock inputs are ok @@ -109,4 +109,4 @@ class BladeRF : public Output::SDRDevice } // namespace Output -#endif // HAVE_BLADERF \ No newline at end of file +#endif // HAVE_BLADERF diff --git a/src/output/Dexter.cpp b/src/output/Dexter.cpp index 605c61a..e4f672b 100644 --- a/src/output/Dexter.cpp +++ b/src/output/Dexter.cpp @@ -44,7 +44,11 @@ using namespace std; namespace Output { +static constexpr uint64_t DSP_CLOCK = 2048000uLL * 80; + 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; static string get_iio_error(int err) { @@ -53,6 +57,13 @@ static string get_iio_error(int err) return string(dst); } +static void fill_time(struct timespec *t) +{ + if (clock_gettime(CLOCK_REALTIME, t) != 0) { + throw std::runtime_error(string("Failed to retrieve CLOCK_REALTIME") + strerror(errno)); + } +} + Dexter::Dexter(SDRDeviceConfig& config) : SDRDevice(), m_conf(config) @@ -93,8 +104,6 @@ Dexter::Dexter(SDRDeviceConfig& config) : etiLog.level(warn) << "Failed to set dexter_dsp_tx.stream0_start_clks = 0: " << get_iio_error(r); } - //iio_device_attr_read_longlong(const struct iio_device *dev, const char *attr, long long *val); - if (m_conf.sampleRate != 2048000) { throw std::runtime_error("Dexter: Only 2048000 samplerate supported"); } @@ -109,7 +118,62 @@ Dexter::Dexter(SDRDeviceConfig& config) : // skip: antenna - // TODO: set H/W time + // get H/W time + /* Procedure: + * Wait 200ms after second change, fetch pps_clks attribute + * idem at the next second, and check that pps_clks incremented by DSP_CLOCK + * If ok, store the correspondence between current second change (measured in UTC clock time) + * and the counter value at pps rising edge. */ + + etiLog.level(info) << "Dexter: Waiting for second change..."; + + struct timespec time_at_startup; + fill_time(&time_at_startup); + time_at_startup.tv_nsec = 0; + + struct timespec time_now; + do { + fill_time(&time_now); + this_thread::sleep_for(chrono::milliseconds(1)); + } while (time_at_startup.tv_sec == time_now.tv_sec); + this_thread::sleep_for(chrono::milliseconds(200)); + + long long pps_clks = 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); + throw std::runtime_error("Dexter: Cannot read IIO attribute"); + } + + time_t tnow = time_now.tv_sec; + etiLog.level(info) << "Dexter: pps_clks " << pps_clks << " at UTC " << + put_time(std::gmtime(&tnow), "%Y-%m-%d %H:%M:%S"); + + time_at_startup.tv_sec = time_now.tv_sec; + do { + fill_time(&time_now); + this_thread::sleep_for(chrono::milliseconds(1)); + } while (time_at_startup.tv_sec == time_now.tv_sec); + this_thread::sleep_for(chrono::milliseconds(200)); + + long long pps_clks2 = 0; + if ((r = iio_device_attr_read_longlong(m_dexter_dsp_tx, "pps_clks", &pps_clks2)) != 0) { + etiLog.level(error) << "Failed to get dexter_dsp_tx.pps_clks: " << get_iio_error(r); + throw std::runtime_error("Dexter: Cannot read IIO attribute"); + } + tnow = time_now.tv_sec; + etiLog.level(info) << "Dexter: pps_clks increased by " << pps_clks2 - pps_clks << " at UTC " << + put_time(std::gmtime(&tnow), "%Y-%m-%d %H:%M:%S"); + + if ((uint64_t)pps_clks + DSP_CLOCK != (uint64_t)pps_clks2) { + throw std::runtime_error("Dexter: Wrong increase of pps_clks, expected " + to_string(DSP_CLOCK)); + } + 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); + } // Prepare streams constexpr int CHANNEL_INDEX = 0; @@ -120,7 +184,7 @@ Dexter::Dexter(SDRDeviceConfig& config) : iio_channel_enable(m_tx_channel); - m_buffer = iio_device_create_buffer(m_ad9957_tx0, TRANSMISSION_FRAME_LEN/sizeof(int16_t), 0); + m_buffer = iio_device_create_buffer(m_ad9957_tx0, IIO_BUFFER_LEN/sizeof(int16_t), 0); if (!m_buffer) { throw std::runtime_error("Dexter: Cannot create IIO buffer."); } @@ -208,8 +272,8 @@ SDRDevice::RunStatistics Dexter::get_run_statistics(void) const { RunStatistics rs; rs.num_underruns = underflows; - rs.num_overruns = overflows; - rs.num_late_packets = late_packets; + rs.num_overruns = 0; + rs.num_late_packets = num_late; rs.num_frames_modulated = num_frames_modulated; return rs; } @@ -217,8 +281,22 @@ SDRDevice::RunStatistics Dexter::get_run_statistics(void) const double Dexter::get_real_secs(void) const { - // TODO - return 0; + 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; + 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); + 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 } void Dexter::set_rxgain(double rxgain) @@ -235,7 +313,7 @@ double Dexter::get_rxgain(void) const size_t Dexter::receive_frame( complexf *buf, size_t num_samples, - struct frame_timestamp& ts, + frame_timestamp& ts, double timeout_secs) { // TODO @@ -263,34 +341,82 @@ double Dexter::get_temperature(void) const void Dexter::transmit_frame(const struct FrameData& frame) { - long long int timeNs = frame.ts.get_ns(); - const bool has_time_spec = (m_conf.enableSync and frame.ts.timestamp_valid); - if (frame.buf.size() != TRANSMISSION_FRAME_LEN) { etiLog.level(debug) << "Dexter::transmit_frame Expected " << TRANSMISSION_FRAME_LEN << " got " << frame.buf.size(); throw std::runtime_error("Dexter: invalid buffer size"); } + const bool has_time_spec = (m_conf.enableSync and frame.ts.timestamp_valid); + + if (has_time_spec) { + /* + 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; + */ + + // 10 because timestamp_pps is represented in 16.384 MHz clocks + constexpr uint64_t TIMESTAMP_PPS_PER_DSP_CLOCKS = DSP_CLOCK / 16384000; + 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); + } + + etiLog.level(debug) << "Dexter: 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 << " = " << + (double)((int64_t)frame_ts_clocks - pps_clks) / DSP_CLOCK; + + // Ensure we hand the frame over to HW at least 0.1s before timestamp + if (((int64_t)frame_ts_clocks - pps_clks) < (int64_t)DSP_CLOCK / 10) { + etiLog.level(warn) << "Skip frame short margin"; + 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; + } + } + // DabMod::launch_modulator ensures we get int16_t IQ here //const size_t num_samples = frame.buf.size() / (2*sizeof(int16_t)); //const int16_t *buf = reinterpret_cast(frame.buf.data()); - memcpy(iio_buffer_start(m_buffer), frame.buf.data(), frame.buf.size()); - ssize_t pushed = iio_buffer_push(m_buffer); - if (pushed < 0) { - etiLog.level(error) << "Dexter: failed to push buffer " << get_iio_error(pushed); + for (size_t i = 0; i < IIO_BUFFERS; i++) { + constexpr size_t buflen = TRANSMISSION_FRAME_LEN / IIO_BUFFERS; + + memcpy(iio_buffer_start(m_buffer), frame.buf.data() + (i * buflen), buflen); + ssize_t pushed = iio_buffer_push(m_buffer); + if (pushed < 0) { + etiLog.level(error) << "Dexter: failed to push buffer " << get_iio_error(pushed); + } } num_frames_modulated++; - // TODO overflows, late_packets long long attr_value = 0; int r = 0; if ((r = iio_device_attr_read_longlong(m_dexter_dsp_tx, "buffer_underflows0", &attr_value)) == 0) { - fprintf(stderr, "buffer_underflows0 %lld\n", attr_value); - underflows = attr_value; + if ((size_t)attr_value != underflows and underflows != 0) { + etiLog.level(warn) << "Dexter: underflow! " << underflows << " -> " << attr_value; + underflows = attr_value; + } } } diff --git a/src/output/Dexter.h b/src/output/Dexter.h index 7a7f6c1..5418b73 100644 --- a/src/output/Dexter.h +++ b/src/output/Dexter.h @@ -39,6 +39,7 @@ DESCRIPTION: #include #include +#include #include "output/SDR.h" #include "ModPlugin.h" @@ -70,7 +71,7 @@ class Dexter : public Output::SDRDevice virtual size_t receive_frame( complexf *buf, size_t num_samples, - struct frame_timestamp& ts, + frame_timestamp& ts, double timeout_secs) override; // Return true if GPS and reference clock inputs are ok @@ -90,9 +91,12 @@ class Dexter : public Output::SDRDevice struct iio_buffer *m_buffer = nullptr; size_t underflows = 0; - size_t overflows = 0; - size_t late_packets = 0; + size_t num_late = 0; size_t num_frames_modulated = 0; + + uint64_t m_utc_seconds_at_startup; + uint64_t m_clock_count_at_startup = 0; + uint64_t m_clock_count_frame = 0; }; } // namespace Output diff --git a/src/output/Feedback.cpp b/src/output/Feedback.cpp index 88d8319..d112b5a 100644 --- a/src/output/Feedback.cpp +++ b/src/output/Feedback.cpp @@ -84,7 +84,7 @@ DPDFeedbackServer::~DPDFeedbackServer() void DPDFeedbackServer::set_tx_frame( const std::vector &buf, - const struct frame_timestamp &buf_ts) + const frame_timestamp &buf_ts) { if (not m_running) { throw runtime_error("DPDFeedbackServer not running"); diff --git a/src/output/Feedback.h b/src/output/Feedback.h index aef86b0..b31347f 100644 --- a/src/output/Feedback.h +++ b/src/output/Feedback.h @@ -94,7 +94,7 @@ class DPDFeedbackServer { ~DPDFeedbackServer(); void set_tx_frame(const std::vector &buf, - const struct frame_timestamp& ts); + const frame_timestamp& ts); private: // Thread that reacts to burstRequests and receives from the SDR device diff --git a/src/output/Lime.cpp b/src/output/Lime.cpp index 6f7eed5..d3e4640 100644 --- a/src/output/Lime.cpp +++ b/src/output/Lime.cpp @@ -353,7 +353,7 @@ double Lime::get_rxgain(void) const size_t Lime::receive_frame( complexf *buf, size_t num_samples, - struct frame_timestamp &ts, + frame_timestamp &ts, double timeout_secs) { // TODO diff --git a/src/output/Lime.h b/src/output/Lime.h index 72a018e..a4603c0 100644 --- a/src/output/Lime.h +++ b/src/output/Lime.h @@ -75,7 +75,7 @@ class Lime : public Output::SDRDevice virtual size_t receive_frame( complexf *buf, size_t num_samples, - struct frame_timestamp &ts, + frame_timestamp &ts, double timeout_secs) override; // Return true if GPS and reference clock inputs are ok diff --git a/src/output/SDR.cpp b/src/output/SDR.cpp index 6078fc7..f1ed2b0 100644 --- a/src/output/SDR.cpp +++ b/src/output/SDR.cpp @@ -104,6 +104,12 @@ SDR::~SDR() } } +void SDR::set_sample_size(size_t size) +{ + etiLog.level(debug) << "Setting sample size to " << size; + m_size = size; +} + int SDR::process(Buffer *dataIn) { if (not m_running) { @@ -125,6 +131,7 @@ meta_vec_t SDR::process_metadata(const meta_vec_t& metadataIn) if (m_device and m_running) { FrameData frame; frame.buf = std::move(m_frame); + frame.sampleSize = m_size; if (metadataIn.empty()) { etiLog.level(info) << @@ -138,7 +145,7 @@ meta_vec_t SDR::process_metadata(const meta_vec_t& metadataIn) * This behaviour is different to earlier versions of ODR-DabMod, * which took the timestamp from the latest ETI frame. */ - frame.ts = *(metadataIn[0].ts); + frame.ts = metadataIn[0].ts; // TODO check device running @@ -261,8 +268,6 @@ void SDR::handle_frame(struct FrameData& frame) { // Assumes m_device is valid - constexpr double tx_timeout = 20.0; - if (not m_device->is_clk_source_ok()) { sleep_through_frame(); return; @@ -298,7 +303,7 @@ void SDR::handle_frame(struct FrameData& frame) } if (last_tx_time_initialised) { - const size_t sizeIn = frame.buf.size() / sizeof(complexf); + const size_t sizeIn = frame.buf.size() / frame.sampleSize; // Checking units for the increment calculation: // samps * ticks/s / (samps/s) @@ -337,7 +342,7 @@ void SDR::handle_frame(struct FrameData& frame) etiLog.log(trace, "SDR,tist %f", time_spec.get_real_secs()); - if (time_spec.get_real_secs() + tx_timeout < device_time) { + if (time_spec.get_real_secs() < device_time) { etiLog.level(warn) << "OutputSDR: Timestamp in the past at FCT=" << frame.ts.fct << " offset: " << std::fixed << diff --git a/src/output/SDR.h b/src/output/SDR.h index ee89243..4dfde73 100644 --- a/src/output/SDR.h +++ b/src/output/SDR.h @@ -51,6 +51,7 @@ class SDR : public ModOutput, public ModMetadata, public RemoteControllable { SDR operator=(const SDR& other) = delete; virtual ~SDR(); + virtual void set_sample_size(size_t size); virtual int process(Buffer *dataIn) override; virtual meta_vec_t process_metadata(const meta_vec_t& metadataIn) override; @@ -75,6 +76,7 @@ class SDR : public ModOutput, public ModMetadata, public RemoteControllable { std::atomic m_running = ATOMIC_VAR_INIT(false); std::thread m_device_thread; + size_t m_size = sizeof(complexf); std::vector m_frame; ThreadsafeQueue m_queue; diff --git a/src/output/SDRDevice.h b/src/output/SDRDevice.h index bb63f60..0bba74a 100644 --- a/src/output/SDRDevice.h +++ b/src/output/SDRDevice.h @@ -98,10 +98,11 @@ struct SDRDeviceConfig { struct FrameData { // Buffer holding frame data std::vector buf; + size_t sampleSize = sizeof(complexf); // A full timestamp contains a TIST according to standard // and time information within MNSC with tx_second. - struct frame_timestamp ts; + frame_timestamp ts; }; @@ -132,7 +133,7 @@ class SDRDevice { virtual size_t receive_frame( complexf *buf, size_t num_samples, - struct frame_timestamp& ts, + frame_timestamp& ts, double timeout_secs) = 0; // Returns device temperature in degrees C or NaN if not available diff --git a/src/output/Soapy.cpp b/src/output/Soapy.cpp index f138e9a..50f91a4 100644 --- a/src/output/Soapy.cpp +++ b/src/output/Soapy.cpp @@ -216,7 +216,7 @@ double Soapy::get_rxgain(void) const size_t Soapy::receive_frame( complexf *buf, size_t num_samples, - struct frame_timestamp& ts, + frame_timestamp& ts, double timeout_secs) { int flags = 0; diff --git a/src/output/Soapy.h b/src/output/Soapy.h index 4ee53ca..ca2618b 100644 --- a/src/output/Soapy.h +++ b/src/output/Soapy.h @@ -74,7 +74,7 @@ class Soapy : public Output::SDRDevice virtual size_t receive_frame( complexf *buf, size_t num_samples, - struct frame_timestamp& ts, + frame_timestamp& ts, double timeout_secs) override; // Return true if GPS and reference clock inputs are ok 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( diff --git a/src/output/UHD.h b/src/output/UHD.h index 29867fb..4c1a4f0 100644 --- a/src/output/UHD.h +++ b/src/output/UHD.h @@ -88,7 +88,7 @@ class UHD : public Output::SDRDevice virtual size_t receive_frame( complexf *buf, size_t num_samples, - struct frame_timestamp& ts, + frame_timestamp& ts, double timeout_secs) override; // Return true if GPS and reference clock inputs are ok diff --git a/src/output/USRPTime.cpp b/src/output/USRPTime.cpp index d1197ec..5a11851 100644 --- a/src/output/USRPTime.cpp +++ b/src/output/USRPTime.cpp @@ -46,11 +46,14 @@ USRPTime::USRPTime( m_conf(conf), time_last_check(timepoint_t::clock::now()) { - if (m_conf.pps_src == "none") { + if (m_conf.refclk_src == "internal" and m_conf.pps_src != "none") { + etiLog.level(warn) << "OutputUHD: Unusal refclk and pps source settings. Setting time once, no monitoring."; + set_usrp_time_from_pps(); + } + else if (m_conf.pps_src == "none") { if (m_conf.enableSync) { etiLog.level(warn) << - "OutputUHD: WARNING:" - " you are using synchronous transmission without PPS input!"; + "OutputUHD: you are using synchronous transmission without PPS input!"; } set_usrp_time_from_localtime(); -- cgit v1.2.3 From 7441d427947aaa79b88e1eae157981c95d7ca5fa Mon Sep 17 00:00:00 2001 From: "Matthias P. Braendli" Date: Wed, 24 Aug 2022 16:14:15 +0200 Subject: Add TS debugging output --- src/TimestampDecoder.cpp | 11 +++++++++++ src/TimestampDecoder.h | 4 +++- src/output/Dexter.cpp | 2 ++ src/output/SDR.cpp | 6 ++++++ 4 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/TimestampDecoder.cpp b/src/TimestampDecoder.cpp index 54a5817..c701186 100644 --- a/src/TimestampDecoder.cpp +++ b/src/TimestampDecoder.cpp @@ -50,6 +50,17 @@ double frame_timestamp::offset_to_system_time() const return get_real_secs() - (double)t.tv_sec - (t.tv_nsec / 1000000000.0); } +std::string frame_timestamp::to_string() const +{ + time_t s = timestamp_sec; + std::stringstream ss; + char timestr[100]; + if (std::strftime(timestr, sizeof(timestr), "%Y-%m-%dZ%H:%M:%S", std::gmtime(&s))) { + ss << timestr << " + " << ((double)timestamp_pps / 16384000.0); + } + return ss.str(); +} + frame_timestamp& frame_timestamp::operator+=(const double& diff) { double offset_pps, offset_secs; diff --git a/src/TimestampDecoder.h b/src/TimestampDecoder.h index 3616bab..4e94d4c 100644 --- a/src/TimestampDecoder.h +++ b/src/TimestampDecoder.h @@ -39,7 +39,7 @@ struct frame_timestamp int32_t fct; uint8_t fp; // Frame Phase - uint32_t timestamp_sec; + uint32_t timestamp_sec; // seconds in unix epoch uint32_t timestamp_pps; // In units of 1/16384000 s bool timestamp_valid = false; bool timestamp_refresh; @@ -76,6 +76,8 @@ struct frame_timestamp timestamp_pps = lrint(subsecond * 16384000.0); } + std::string to_string() const; + void print(const char* t) const { etiLog.log(debug, "%s \n", diff --git a/src/output/Dexter.cpp b/src/output/Dexter.cpp index e4f672b..b389b31 100644 --- a/src/output/Dexter.cpp +++ b/src/output/Dexter.cpp @@ -370,6 +370,7 @@ void Dexter::transmit_frame(const struct FrameData& frame) etiLog.level(error) << "Failed to get dexter_dsp_tx.pps_clks: " << get_iio_error(r); } + /* etiLog.level(debug) << "Dexter: TS CLK " << ((int64_t)frame.ts.timestamp_sec - (int64_t)m_utc_seconds_at_startup) * DSP_CLOCK << " + " << m_clock_count_at_startup << " + " << @@ -377,6 +378,7 @@ void Dexter::transmit_frame(const struct FrameData& frame) frame_ts_clocks << " DELTA " << frame_ts_clocks << " - " << pps_clks << " = " << (double)((int64_t)frame_ts_clocks - pps_clks) / DSP_CLOCK; + */ // Ensure we hand the frame over to HW at least 0.1s before timestamp if (((int64_t)frame_ts_clocks - pps_clks) < (int64_t)DSP_CLOCK / 10) { diff --git a/src/output/SDR.cpp b/src/output/SDR.cpp index f1ed2b0..bd02cab 100644 --- a/src/output/SDR.cpp +++ b/src/output/SDR.cpp @@ -370,6 +370,12 @@ void SDR::handle_frame(struct FrameData& frame) return; } + if (frame.ts.fct == 0) { + etiLog.level(debug) << + "OutputSDR: TX FCT=" << frame.ts.fct << + " TS " << frame.ts.to_string(); + } + m_device->transmit_frame(frame); } -- cgit v1.2.3 From a2be0c3ab77dab50ded4850f38d2b796b322d0c4 Mon Sep 17 00:00:00 2001 From: "Matthias P. Braendli" Date: Fri, 14 Oct 2022 20:00:26 +0200 Subject: Add timestamp refresh logic --- src/DabMod.cpp | 4 ++- src/output/Dexter.cpp | 67 +++++++++++++++++++++++++++++++++++++-------------- src/output/Dexter.h | 8 ++++++ src/output/SDR.cpp | 1 + 4 files changed, 61 insertions(+), 19 deletions(-) diff --git a/src/DabMod.cpp b/src/DabMod.cpp index 278f8ce..5a4da9a 100644 --- a/src/DabMod.cpp +++ b/src/DabMod.cpp @@ -662,7 +662,9 @@ static run_modulator_state_t run_modulator(const mod_settings_t& mod_settings, m else { etiLog.level(warn) << "Skipping frame " << fct << " FCT " << (fct_good ? "good" : "bad") << " TS " << - (ts_good ? "good" : "bad"); + (ts_good ? "good, " : "bad, ") << + (ts.timestamp_valid ? (ts.offset_to_system_time() > 0 ? "in the future" : "in the past") : "invalid"); + if (m.ediInput) { m.ediInput->ediReader.clearFrame(); } diff --git a/src/output/Dexter.cpp b/src/output/Dexter.cpp index b389b31..9437ae6 100644 --- a/src/output/Dexter.cpp +++ b/src/output/Dexter.cpp @@ -347,9 +347,13 @@ void Dexter::transmit_frame(const struct FrameData& frame) throw std::runtime_error("Dexter: invalid buffer size"); } - const bool has_time_spec = (m_conf.enableSync and frame.ts.timestamp_valid); + const bool require_timestamped_tx = (m_conf.enableSync and frame.ts.timestamp_valid); - if (has_time_spec) { + if (not require_timestamped_tx) { + etiLog.level(debug) << "TIMESTAMP_STATE STREAMING 1"; + timestamp_state = timestamp_state_t::STREAMING; + } + else if (require_timestamped_tx and timestamp_state == timestamp_state_t::REQUIRES_SET) { /* uint64_t timeS = frame.ts.timestamp_sec; etiLog.level(debug) << "Dexter: TS S " << timeS << " - " << m_utc_seconds_at_startup << " = " << @@ -393,31 +397,58 @@ void Dexter::transmit_frame(const struct FrameData& frame) num_late++; return; } + timestamp_state = timestamp_state_t::STREAMING; + etiLog.level(debug) << "TIMESTAMP_STATE STREAMING 2"; + } + + if (frame.ts.timestamp_refresh) { + etiLog.level(debug) << "TIMESTAMP_STATE WAIT_FOR_UNDERRUN"; + timestamp_state = timestamp_state_t::WAIT_FOR_UNDERRUN; + long long attr_value = 0; + int r = 0; + + if ((r = iio_device_attr_read_longlong(m_dexter_dsp_tx, "buffer_underflows0", &attr_value)) == 0) { + underflows = attr_value; + etiLog.level(debug) << "UNDERFLOWS CAPTURE " << underflows; + } } // DabMod::launch_modulator ensures we get int16_t IQ here //const size_t num_samples = frame.buf.size() / (2*sizeof(int16_t)); //const int16_t *buf = reinterpret_cast(frame.buf.data()); - for (size_t i = 0; i < IIO_BUFFERS; i++) { - constexpr size_t buflen = TRANSMISSION_FRAME_LEN / IIO_BUFFERS; - - memcpy(iio_buffer_start(m_buffer), frame.buf.data() + (i * buflen), buflen); - ssize_t pushed = iio_buffer_push(m_buffer); - if (pushed < 0) { - etiLog.level(error) << "Dexter: failed to push buffer " << get_iio_error(pushed); + if (timestamp_state == timestamp_state_t::STREAMING) { + for (size_t i = 0; i < IIO_BUFFERS; i++) { + constexpr size_t buflen = TRANSMISSION_FRAME_LEN / IIO_BUFFERS; + + memcpy(iio_buffer_start(m_buffer), frame.buf.data() + (i * buflen), buflen); + ssize_t pushed = iio_buffer_push(m_buffer); + if (pushed < 0) { + etiLog.level(error) << "Dexter: failed to push buffer " << get_iio_error(pushed); + etiLog.level(debug) << "TIMESTAMP_STATE REQUIRES_SET"; + timestamp_state = timestamp_state_t::REQUIRES_SET; + } } + num_frames_modulated++; } - num_frames_modulated++; - - long long attr_value = 0; - int r = 0; - - if ((r = iio_device_attr_read_longlong(m_dexter_dsp_tx, "buffer_underflows0", &attr_value)) == 0) { - if ((size_t)attr_value != underflows and underflows != 0) { - etiLog.level(warn) << "Dexter: underflow! " << underflows << " -> " << attr_value; - underflows = attr_value; +#warning "We should update underflows all the time" + if (timestamp_state == timestamp_state_t::WAIT_FOR_UNDERRUN) { + long long attr_value = 0; + int r = 0; + + if ((r = iio_device_attr_read_longlong(m_dexter_dsp_tx, "buffer_underflows0", &attr_value)) == 0) { + size_t underflows_new = attr_value; + etiLog.level(debug) << "UNDERFLOWS COMPARE " << underflows_new; + + if (underflows_new != underflows and attr_value != 0) { + etiLog.level(warn) << "Dexter: underflow! " << underflows << " -> " << underflows_new; + underflows = underflows_new; + if (timestamp_state == timestamp_state_t::WAIT_FOR_UNDERRUN) { + etiLog.level(debug) << "TIMESTAMP_STATE REQUIRES_SET"; + timestamp_state = timestamp_state_t::REQUIRES_SET; + } + } } } } diff --git a/src/output/Dexter.h b/src/output/Dexter.h index 5418b73..3e9c34f 100644 --- a/src/output/Dexter.h +++ b/src/output/Dexter.h @@ -97,6 +97,14 @@ class Dexter : public Output::SDRDevice uint64_t m_utc_seconds_at_startup; uint64_t m_clock_count_at_startup = 0; uint64_t m_clock_count_frame = 0; + + enum class timestamp_state_t { + REQUIRES_SET, + STREAMING, + WAIT_FOR_UNDERRUN, + }; + + timestamp_state_t timestamp_state = timestamp_state_t::REQUIRES_SET; }; } // namespace Output diff --git a/src/output/SDR.cpp b/src/output/SDR.cpp index bd02cab..53f68c2 100644 --- a/src/output/SDR.cpp +++ b/src/output/SDR.cpp @@ -331,6 +331,7 @@ void SDR::handle_frame(struct FrameData& frame) "(" << tx_pps << ")"; frame.ts.timestamp_refresh = true; +#error "wrong, as the frame could be discarded" } } -- cgit v1.2.3 From d2c8a1f40be73417964523e5a942d7d4c558e967 Mon Sep 17 00:00:00 2001 From: "Matthias P. Braendli" Date: Wed, 26 Oct 2022 18:04:55 +0200 Subject: Add dexter underflow thread, still problematic --- src/DabMod.cpp | 3 +- src/output/Dexter.cpp | 76 +++++++++++++++++++++++++++++++++++---------------- src/output/Dexter.h | 29 ++++++++++++++------ src/output/SDR.cpp | 1 - 4 files changed, 73 insertions(+), 36 deletions(-) diff --git a/src/DabMod.cpp b/src/DabMod.cpp index 5a4da9a..45f4d0a 100644 --- a/src/DabMod.cpp +++ b/src/DabMod.cpp @@ -493,8 +493,7 @@ int launch_modulator(int argc, char* argv[]) break; } - etiLog.level(info) << m.framecount << " DAB frames encoded"; - etiLog.level(info) << ((float)m.framecount * 0.024f) << " seconds encoded"; + etiLog.level(info) << m.framecount << " DAB frames, " << ((float)m.framecount * 0.024f) << " seconds encoded"; } etiLog.level(info) << "Terminating"; diff --git a/src/output/Dexter.cpp b/src/output/Dexter.cpp index ad4711c..5904824 100644 --- a/src/output/Dexter.cpp +++ b/src/output/Dexter.cpp @@ -188,10 +188,21 @@ Dexter::Dexter(SDRDeviceConfig& config) : if (!m_buffer) { throw std::runtime_error("Dexter: Cannot create IIO buffer."); } + +#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); + */ } Dexter::~Dexter() { + m_running = false; + if (m_underflow_read_thread.joinable()) { + m_underflow_read_thread.join(); + } + if (m_ctx) { if (m_dexter_dsp_tx) { iio_device_attr_write_longlong(m_dexter_dsp_tx, "gain0", 0); @@ -271,7 +282,10 @@ double Dexter::get_bandwidth(void) const SDRDevice::RunStatistics Dexter::get_run_statistics(void) const { RunStatistics rs; - rs.num_underruns = underflows; + { + std::unique_lock lock(m_underflows_mutex); + rs.num_underruns = underflows; + } rs.num_overruns = 0; rs.num_late_packets = num_late; rs.num_frames_modulated = num_frames_modulated; @@ -374,19 +388,18 @@ 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; + etiLog.level(debug) << "Dexter: 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 << " = " << - (double)((int64_t)frame_ts_clocks - pps_clks) / DSP_CLOCK; - */ + frame_ts_clocks << " - " << pps_clks << " = " << margin; - // Ensure we hand the frame over to HW at least 0.1s before timestamp - if (((int64_t)frame_ts_clocks - pps_clks) < (int64_t)DSP_CLOCK / 10) { - etiLog.level(warn) << "Skip frame short margin"; + // 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; num_late++; return; } @@ -404,13 +417,6 @@ void Dexter::transmit_frame(const struct FrameData& frame) if (m_require_timestamp_refresh) { etiLog.level(debug) << "TIMESTAMP_STATE WAIT_FOR_UNDERRUN"; timestamp_state = timestamp_state_t::WAIT_FOR_UNDERRUN; - long long attr_value = 0; - int r = 0; - - if ((r = iio_device_attr_read_longlong(m_dexter_dsp_tx, "buffer_underflows0", &attr_value)) == 0) { - underflows = attr_value; - etiLog.level(debug) << "UNDERFLOWS CAPTURE " << underflows; - } } // DabMod::launch_modulator ensures we get int16_t IQ here @@ -424,33 +430,55 @@ void Dexter::transmit_frame(const struct FrameData& frame) memcpy(iio_buffer_start(m_buffer), frame.buf.data() + (i * buflen), buflen); ssize_t pushed = iio_buffer_push(m_buffer); if (pushed < 0) { - etiLog.level(error) << "Dexter: failed to push buffer " << get_iio_error(pushed); + etiLog.level(error) << "Dexter: failed to push buffer " << get_iio_error(pushed) << + " after " << num_buffers_pushed << " bufs"; + num_buffers_pushed = 0; etiLog.level(debug) << "TIMESTAMP_STATE REQUIRES_SET"; timestamp_state = timestamp_state_t::REQUIRES_SET; + break; } + num_buffers_pushed++; } num_frames_modulated++; } -#warning "We should update underflows all the time" - if (timestamp_state == timestamp_state_t::WAIT_FOR_UNDERRUN) { + { + std::unique_lock lock(m_underflows_mutex); + size_t u = underflows; + lock.unlock(); + + if (u != 0 and u != prev_underflows) { + etiLog.level(warn) << "Dexter: underflow! " << prev_underflows << " -> " << u; + if (timestamp_state == timestamp_state_t::WAIT_FOR_UNDERRUN) { + etiLog.level(debug) << "TIMESTAMP_STATE REQUIRES_SET"; + timestamp_state = timestamp_state_t::REQUIRES_SET; + } + } + + prev_underflows = u; + } +} + +void Dexter::underflow_read_process() +{ + set_thread_name("dexter_underflow"); + + while (m_running) { long long attr_value = 0; int r = 0; if ((r = iio_device_attr_read_longlong(m_dexter_dsp_tx, "buffer_underflows0", &attr_value)) == 0) { size_t underflows_new = attr_value; - etiLog.level(debug) << "UNDERFLOWS COMPARE " << underflows_new; + 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) { - etiLog.level(warn) << "Dexter: underflow! " << underflows << " -> " << underflows_new; underflows = underflows_new; - if (timestamp_state == timestamp_state_t::WAIT_FOR_UNDERRUN) { - etiLog.level(debug) << "TIMESTAMP_STATE REQUIRES_SET"; - timestamp_state = timestamp_state_t::REQUIRES_SET; - } } } + this_thread::sleep_for(chrono::seconds(1)); } + m_running = false; } } // namespace Output diff --git a/src/output/Dexter.h b/src/output/Dexter.h index 3e9c34f..a3c827b 100644 --- a/src/output/Dexter.h +++ b/src/output/Dexter.h @@ -40,6 +40,8 @@ DESCRIPTION: #include #include #include +#include +#include #include "output/SDR.h" #include "ModPlugin.h" @@ -54,20 +56,20 @@ class Dexter : public Output::SDRDevice Dexter(SDRDeviceConfig& config); Dexter(const Dexter& other) = delete; Dexter& operator=(const Dexter& other) = delete; - ~Dexter(); + virtual ~Dexter(); virtual void tune(double lo_offset, double frequency) override; virtual double get_tx_freq(void) const override; virtual void set_txgain(double txgain) override; - virtual double get_txgain(void) const override; + virtual double get_txgain() const override; virtual void set_bandwidth(double bandwidth) override; - virtual double get_bandwidth(void) const override; + virtual double get_bandwidth() const override; virtual void transmit_frame(const struct FrameData& frame) override; - virtual RunStatistics get_run_statistics(void) const override; - virtual double get_real_secs(void) const override; + virtual RunStatistics get_run_statistics() const override; + virtual double get_real_secs() const override; virtual void set_rxgain(double rxgain) override; - virtual double get_rxgain(void) const override; + virtual double get_rxgain() const override; virtual size_t receive_frame( complexf *buf, size_t num_samples, @@ -75,10 +77,10 @@ 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(void) const override; - virtual const char* device_name(void) const override; + virtual bool is_clk_source_ok() const override; + virtual const char* device_name() const override; - virtual double get_temperature(void) const override; + virtual double get_temperature() const override; private: SDRDeviceConfig& m_conf; @@ -90,10 +92,19 @@ class Dexter : public Output::SDRDevice struct iio_channel* m_tx_channel = nullptr; struct iio_buffer *m_buffer = nullptr; + /* Underflows are counted in a separate thread */ + std::atomic m_running = ATOMIC_VAR_INIT(false); + std::thread m_underflow_read_thread; + void underflow_read_process(); + mutable std::mutex m_underflows_mutex; size_t underflows = 0; + + size_t prev_underflows = 0; size_t num_late = 0; size_t num_frames_modulated = 0; + size_t num_buffers_pushed = 0; + uint64_t m_utc_seconds_at_startup; uint64_t m_clock_count_at_startup = 0; uint64_t m_clock_count_frame = 0; diff --git a/src/output/SDR.cpp b/src/output/SDR.cpp index ae09acd..2b6700f 100644 --- a/src/output/SDR.cpp +++ b/src/output/SDR.cpp @@ -106,7 +106,6 @@ SDR::~SDR() void SDR::set_sample_size(size_t size) { - etiLog.level(debug) << "Setting sample size to " << size; m_size = size; } -- cgit v1.2.3 From 4f1f002ffad12144237352ad096353b8872171be Mon Sep 17 00:00:00 2001 From: "Matthias P. Braendli" Date: Wed, 1 Feb 2023 13:48:11 +0100 Subject: Remove dexter timestamp_state_t --- src/output/Dexter.cpp | 131 ++++++++++++++++++++++++++------------------------ src/output/Dexter.h | 12 ++--- 2 files changed, 72 insertions(+), 71 deletions(-) diff --git a/src/output/Dexter.cpp b/src/output/Dexter.cpp index 5904824..cc10c57 100644 --- a/src/output/Dexter.cpp +++ b/src/output/Dexter.cpp @@ -175,7 +175,16 @@ Dexter::Dexter(SDRDeviceConfig& config) : etiLog.level(warn) << "Failed to set dexter_dsp_tx.stream0_start_clks = " << 0 << " : " << get_iio_error(r); } - // Prepare streams +#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() +{ + etiLog.level(debug) << "DEXTER CHANNEL_UP"; constexpr int CHANNEL_INDEX = 0; m_tx_channel = iio_device_get_channel(m_ad9957_tx0, CHANNEL_INDEX); if (m_tx_channel == nullptr) { @@ -185,17 +194,23 @@ Dexter::Dexter(SDRDeviceConfig& config) : iio_channel_enable(m_tx_channel); m_buffer = iio_device_create_buffer(m_ad9957_tx0, IIO_BUFFER_LEN/sizeof(int16_t), 0); - if (!m_buffer) { + if (not m_buffer) { throw std::runtime_error("Dexter: Cannot create IIO buffer."); } +} -#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_down() +{ + iio_channel_disable(m_tx_channel); + + etiLog.level(debug) << "DEXTER CHANNEL_DOWN"; + if (m_buffer) { + iio_buffer_destroy(m_buffer); + m_buffer = nullptr; + } } + Dexter::~Dexter() { m_running = false; @@ -208,10 +223,6 @@ Dexter::~Dexter() iio_device_attr_write_longlong(m_dexter_dsp_tx, "gain0", 0); } - if (m_buffer) { - iio_buffer_destroy(m_buffer); - } - iio_context_destroy(m_ctx); m_ctx = nullptr; } @@ -363,67 +374,66 @@ void Dexter::transmit_frame(const struct FrameData& frame) const bool require_timestamped_tx = (m_conf.enableSync and frame.ts.timestamp_valid); - if (not require_timestamped_tx) { - etiLog.level(debug) << "TIMESTAMP_STATE STREAMING 1"; - timestamp_state = timestamp_state_t::STREAMING; - } - else if (require_timestamped_tx and timestamp_state == timestamp_state_t::REQUIRES_SET) { - /* - 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; - */ - - // 10 because timestamp_pps is represented in 16.384 MHz clocks - constexpr uint64_t TIMESTAMP_PPS_PER_DSP_CLOCKS = DSP_CLOCK / 16384000; - 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); - } + if (m_buffer == nullptr) { + 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; + */ + + // 10 because timestamp_pps is represented in 16.384 MHz clocks + constexpr uint64_t TIMESTAMP_PPS_PER_DSP_CLOCKS = DSP_CLOCK / 16384000; + 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 = (double)((int64_t)frame_ts_clocks - pps_clks) / DSP_CLOCK; + const double margin = (double)((int64_t)frame_ts_clocks - pps_clks) / DSP_CLOCK; - etiLog.level(debug) << "Dexter: 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; + etiLog.level(debug) << "Dexter: 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; - // 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; - num_late++; - return; - } + // 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; + 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; + 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; + } } - timestamp_state = timestamp_state_t::STREAMING; - etiLog.level(debug) << "TIMESTAMP_STATE STREAMING 2"; + + channel_up(); } if (m_require_timestamp_refresh) { - etiLog.level(debug) << "TIMESTAMP_STATE WAIT_FOR_UNDERRUN"; - timestamp_state = timestamp_state_t::WAIT_FOR_UNDERRUN; + etiLog.level(debug) << "DEXTER REQUIRE REFRESH"; + channel_down(); + m_require_timestamp_refresh = false; } // DabMod::launch_modulator ensures we get int16_t IQ here //const size_t num_samples = frame.buf.size() / (2*sizeof(int16_t)); //const int16_t *buf = reinterpret_cast(frame.buf.data()); - if (timestamp_state == timestamp_state_t::STREAMING) { + if (m_buffer) { for (size_t i = 0; i < IIO_BUFFERS; i++) { constexpr size_t buflen = TRANSMISSION_FRAME_LEN / IIO_BUFFERS; @@ -433,8 +443,7 @@ void Dexter::transmit_frame(const struct FrameData& frame) etiLog.level(error) << "Dexter: failed to push buffer " << get_iio_error(pushed) << " after " << num_buffers_pushed << " bufs"; num_buffers_pushed = 0; - etiLog.level(debug) << "TIMESTAMP_STATE REQUIRES_SET"; - timestamp_state = timestamp_state_t::REQUIRES_SET; + channel_down(); break; } num_buffers_pushed++; @@ -449,10 +458,6 @@ void Dexter::transmit_frame(const struct FrameData& frame) if (u != 0 and u != prev_underflows) { etiLog.level(warn) << "Dexter: underflow! " << prev_underflows << " -> " << u; - if (timestamp_state == timestamp_state_t::WAIT_FOR_UNDERRUN) { - etiLog.level(debug) << "TIMESTAMP_STATE REQUIRES_SET"; - timestamp_state = timestamp_state_t::REQUIRES_SET; - } } prev_underflows = u; diff --git a/src/output/Dexter.h b/src/output/Dexter.h index a3c827b..36d0ef7 100644 --- a/src/output/Dexter.h +++ b/src/output/Dexter.h @@ -83,6 +83,10 @@ class Dexter : public Output::SDRDevice virtual double get_temperature() const override; private: + + void channel_up(); + void channel_down(); + SDRDeviceConfig& m_conf; struct iio_context* m_ctx = nullptr; @@ -108,14 +112,6 @@ class Dexter : public Output::SDRDevice uint64_t m_utc_seconds_at_startup; uint64_t m_clock_count_at_startup = 0; uint64_t m_clock_count_frame = 0; - - enum class timestamp_state_t { - REQUIRES_SET, - STREAMING, - WAIT_FOR_UNDERRUN, - }; - - timestamp_state_t timestamp_state = timestamp_state_t::REQUIRES_SET; }; } // namespace Output -- cgit v1.2.3 From bf6e05a427e050ec54b9da91da8ac04f52fa006c Mon Sep 17 00:00:00 2001 From: "Matthias P. Braendli" Date: Wed, 1 Feb 2023 13:53:55 +0100 Subject: Remove easydabv3 support --- .gitignore | 3 +++ Makefile.am | 9 ++------- configure.ac | 19 +++---------------- doc/easydabv3.ini | 37 ------------------------------------- src/DabModulator.cpp | 47 +++++++++++++++++++---------------------------- src/Utils.cpp | 16 ++-------------- 6 files changed, 29 insertions(+), 102 deletions(-) delete mode 100644 doc/easydabv3.ini diff --git a/.gitignore b/.gitignore index 3ac59f6..2a6b136 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,9 @@ config.h config.h.in config.status odr-dabmod +*~ + +*.iq __pycache__/ *.py[cod] diff --git a/Makefile.am b/Makefile.am index 46f2c21..64884e0 100644 --- a/Makefile.am +++ b/Makefile.am @@ -39,7 +39,7 @@ odr_dabmod_CFLAGS = -Wall -Isrc -Ilib \ $(GITVERSION_FLAGS) odr_dabmod_CXXFLAGS = -Wall -Isrc -Ilib -std=c++11 \ $(GITVERSION_FLAGS) $(BOOST_CPPFLAGS) -odr_dabmod_LDADD = $(BOOST_LDFLAGS) $(BOOST_THREAD_LIB) +odr_dabmod_LDADD = $(BOOST_LDFLAGS) $(BOOST_THREAD_LIB) $(UHD_LIBS) $(LIMESDR_LIBS) $(ADDITIONAL_UHD_LIBS) odr_dabmod_SOURCES = src/DabMod.cpp \ src/PcDebug.h \ src/DabModulator.cpp \ @@ -123,10 +123,7 @@ odr_dabmod_SOURCES = src/DabMod.cpp \ lib/edi/ETIDecoder.hpp \ lib/edi/ETIDecoder.cpp \ lib/edi/PFT.hpp \ - lib/edi/PFT.cpp - -if !COMPILE_FOR_EASYDABV3 -odr_dabmod_SOURCES += \ + lib/edi/PFT.cpp \ src/FIRFilter.cpp \ src/FIRFilter.h \ src/MemlessPoly.cpp \ @@ -173,7 +170,5 @@ odr_dabmod_SOURCES += \ src/TII.cpp \ src/TII.h -odr_dabmod_LDADD += $(UHD_LIBS) $(LIMESDR_LIBS) $(ADDITIONAL_UHD_LIBS) -endif man_MANS = man/odr-dabmod.1 diff --git a/configure.ac b/configure.ac index 9b45a89..59ee4e5 100644 --- a/configure.ac +++ b/configure.ac @@ -52,9 +52,6 @@ AC_ARG_ENABLE([zeromq], AC_ARG_ENABLE([native], [AS_HELP_STRING([--disable-native], [Do not compile with -march=native])], [], [enable_native=yes]) -AC_ARG_ENABLE([easydabv3], - [AS_HELP_STRING([--enable-easydabv3], [Build for EasyDABv3 board])], - [], [enable_easydabv3=no]) AC_ARG_ENABLE([dexter], [AS_HELP_STRING([--enable-dexter], [Build for PrecisionWave Dexter board])], [], [enable_dexter=no]) @@ -80,9 +77,7 @@ AX_CHECK_COMPILE_FLAG([-Wdouble-promotion], [CXXFLAGS="$CXXFLAGS -Wdouble-promot AX_CHECK_COMPILE_FLAG(["-Wformat=2"], [CXXFLAGS="$CXXFLAGS -Wformat=2"], [], ["-Werror"]) AC_LANG_POP([C++]) - -AS_IF([test "x$enable_easydabv3" = "xno"], - [PKG_CHECK_MODULES([FFTW], [fftw3f], [], [AC_MSG_ERROR([FFTW is required])])]) +PKG_CHECK_MODULES([FFTW], [fftw3f], [], [AC_MSG_ERROR([FFTW is required])]) echo "Checking zeromq" @@ -104,13 +99,11 @@ AS_IF([test "x$enable_trace" != "xno"], # Define conditionals for Makefile.am AM_CONDITIONAL([IS_GIT_REPO], [test -d '.git']) -AM_CONDITIONAL([COMPILE_FOR_EASYDABV3], [test "x$enable_easydabv3" = "xyes"]) # Defines for config.h AX_PTHREAD([], AC_MSG_ERROR([requires pthread])) -AS_IF([test "x$enable_easydabv3" = "xno"], - [PKG_CHECK_MODULES([SOAPYSDR], [SoapySDR], enable_soapysdr=yes, enable_soapysdr=no)]) +PKG_CHECK_MODULES([SOAPYSDR], [SoapySDR], enable_soapysdr=yes, enable_soapysdr=no) AS_IF([test "x$enable_limesdr" = "xyes"], [AC_CHECK_LIB([LimeSuite], [LMS_Init], [LIMESDR_LIBS="-lLimeSuite"], @@ -128,9 +121,6 @@ AC_SUBST([CFLAGS], ["$CFLAGS $EXTRA $FFTW_CFLAGS $SOAPYSDR_CFLAGS $PTHREAD_CFLAG AC_SUBST([CXXFLAGS], ["$CXXFLAGS $EXTRA $FFTW_CFLAGS $SOAPYSDR_CFLAGS $PTHREAD_CFLAGS"]) AC_SUBST([LIBS], ["$FFTW_LIBS $SOAPYSDR_LIBS $PTHREAD_LIBS $ZMQ_LIBS $LIMESDR_LIBS $IIO_LIBS $BLADERF_LIBS"]) -AS_IF([test "x$enable_easydabv3" = "xyes" && test "x$enable_output_uhd" == "xyes"], - AC_MSG_ERROR([Cannot enable both EasyDABv3 and UHD output])) - # Checks for UHD. AS_IF([test "x$enable_output_uhd" = "xyes"], [ PKG_CHECK_MODULES([UHD], [uhd], [], [AC_MSG_ERROR([UHD is required])]) @@ -157,9 +147,6 @@ AS_IF([test "x$enable_dexter" = "xyes"], AS_IF([test "x$enable_bladerf" = "xyes"], [AC_DEFINE(HAVE_BLADERF, [1], [Define if BladeRF output is enabled]) ]) -AS_IF([test "x$enable_easydabv3" = "xyes"], - AC_DEFINE(BUILD_FOR_EASYDABV3, [1], [Define if we are building for EasyDABv3])) - # Checks for header files. AC_CHECK_HEADERS([fcntl.h limits.h memory.h netinet/in.h stdint.h stdlib.h string.h sys/time.h sys/timeb.h unistd.h]) @@ -227,7 +214,7 @@ echo "***********************************************" echo enabled="" disabled="" -for feat in prof trace output_uhd zeromq soapysdr easydabv3 limesdr bladerf dexter +for feat in prof trace output_uhd zeromq soapysdr limesdr bladerf dexter do eval var=\$enable_$feat AS_IF([test "x$var" = "xyes"], diff --git a/doc/easydabv3.ini b/doc/easydabv3.ini deleted file mode 100644 index 5f0103f..0000000 --- a/doc/easydabv3.ini +++ /dev/null @@ -1,37 +0,0 @@ -; This sample configuration is useful if ODR-DabMod is compiled -; with --enable-easydabv3 - -[remotecontrol] -zmqctrl=1 -zmqctrlendpoint=tcp://127.0.0.1:9400 -; There is no telnet RC available in this build - - -[log] -syslog=0 -filelog=0 -filename=odr-dabmod.log - -[input] -transport=zeromq -source=tcp://localhost:9100 -max_frames_queued=400 - -; There are no [modulator], [cfr], [firfilter], [poly] nor [tii] sections - -[output] -output=file - -[fileoutput] -; to be confirmed -format=complexf - -filename=/dev/csdiof1 - -show_metadata=0 -; TODO add option for writing out timestamps to csdiof1 - -[delaymanagement] -synchronous=0 -mutenotimestamps=0 -offset=1.002 diff --git a/src/DabModulator.cpp b/src/DabModulator.cpp index aa4f2a8..1f16d1d 100644 --- a/src/DabModulator.cpp +++ b/src/DabModulator.cpp @@ -3,7 +3,7 @@ Her Majesty the Queen in Right of Canada (Communications Research Center Canada) - Copyright (C) 2019 + Copyright (C) 2023 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org @@ -31,32 +31,29 @@ #include "DabModulator.h" #include "PcDebug.h" -#if !defined(BUILD_FOR_EASYDABV3) -# include "QpskSymbolMapper.h" -# include "FrequencyInterleaver.h" -# include "PhaseReference.h" -# include "DifferentialModulator.h" -# include "NullSymbol.h" -# include "CicEqualizer.h" -# include "OfdmGenerator.h" -# include "GainControl.h" -# include "GuardIntervalInserter.h" -# include "Resampler.h" -# include "FIRFilter.h" -# include "MemlessPoly.h" -# include "TII.h" -#endif - -#include "FrameMultiplexer.h" -#include "PrbsGenerator.h" #include "BlockPartitioner.h" -#include "SignalMultiplexer.h" +#include "CicEqualizer.h" #include "ConvEncoder.h" +#include "DifferentialModulator.h" +#include "FIRFilter.h" +#include "FrameMultiplexer.h" +#include "FrequencyInterleaver.h" +#include "GainControl.h" +#include "GuardIntervalInserter.h" +#include "Log.h" +#include "MemlessPoly.h" +#include "NullSymbol.h" +#include "OfdmGenerator.h" +#include "PhaseReference.h" +#include "PrbsGenerator.h" #include "PuncturingEncoder.h" +#include "QpskSymbolMapper.h" +#include "RemoteControl.h" +#include "Resampler.h" +#include "SignalMultiplexer.h" +#include "TII.h" #include "TimeInterleaver.h" #include "TimestampDecoder.h" -#include "RemoteControl.h" -#include "Log.h" using namespace std; @@ -140,7 +137,6 @@ int DabModulator::process(Buffer* dataOut) auto cifMux = make_shared(myEtiSource); auto cifPart = make_shared(mode); -#if !defined(BUILD_FOR_EASYDABV3) auto cifMap = make_shared(myNbCarriers); auto cifRef = make_shared(mode); auto cifFreq = make_shared(mode); @@ -231,7 +227,6 @@ int DabModulator::process(Buffer* dataOut) m_settings.outputRate, mySpacing); } -#endif myOutput = make_shared(dataOut); @@ -340,9 +335,6 @@ int DabModulator::process(Buffer* dataOut) } myFlowgraph->connect(cifMux, cifPart); -#if defined(BUILD_FOR_EASYDABV3) - myFlowgraph->connect(cifPart, myOutput); -#else myFlowgraph->connect(cifPart, cifMap); myFlowgraph->connect(cifMap, cifFreq); myFlowgraph->connect(cifRef, cifDiff); @@ -372,7 +364,6 @@ int DabModulator::process(Buffer* dataOut) prev_plugin = p; } } -#endif } //////////////////////////////////////////////////////////////////// diff --git a/src/Utils.cpp b/src/Utils.cpp index f39c4c9..350838e 100644 --- a/src/Utils.cpp +++ b/src/Utils.cpp @@ -3,7 +3,7 @@ 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 http://opendigitalradio.org @@ -62,10 +62,6 @@ static void printHeader() "SSE " << #endif "\n"; - -#if defined(BUILD_FOR_EASYDABV3) - std::cerr << " This is a build for the EasyDABv3 board" << std::endl; -#endif } void printUsage(const char* progName) @@ -77,13 +73,8 @@ void printUsage(const char* progName) fprintf(out, "Usage with command line options:\n"); fprintf(out, "\t%s" " input" -#if defined(BUILD_FOR_EASYDABV3) - " -f filename -F format" -#else " (-f filename -F format | -u uhddevice -F frequency)" -#endif " [-o offset]" -#if !defined(BUILD_FOR_EASYDABV3) "\n\t" " [-G txgain]" " [-T filter_taps_file]" @@ -93,7 +84,6 @@ void printUsage(const char* progName) " [-g gainMode]" " [-m dabMode]" " [-r samplingRate]" -#endif " [-l]" " [-h]" "\n", progName); @@ -108,7 +98,6 @@ void printUsage(const char* progName) fprintf(out, " Specifying this option has two implications: It enables synchronous transmission,\n" " requiring an external REFCLK and PPS signal and frames that do not contain a valid timestamp\n" " get muted.\n\n"); -#if !defined(BUILD_FOR_EASYDABV3) fprintf(out, "-u device: Use UHD output with given device string. (use "" for default device)\n"); fprintf(out, "-F frequency: Set the transmit frequency when using UHD output. (mandatory option when using UHD)\n"); fprintf(out, "-G txgain: Set the transmit gain for the UHD driver (default: 0)\n"); @@ -119,7 +108,6 @@ void printUsage(const char* progName) fprintf(out, "-g gainmode: Set computation gain mode: fix, max or var\n"); fprintf(out, "-m mode: Set DAB mode: (0: auto, 1-4: force).\n"); fprintf(out, "-r rate: Set output sampling rate (default: 2048000).\n\n"); -#endif fprintf(out, "-l: Loop file when reach end of file.\n"); fprintf(out, "-h: Print this help.\n"); } @@ -132,7 +120,7 @@ void printVersion(void) " ODR-DabMod is copyright (C) Her Majesty the Queen in Right of Canada,\n" " 2005 -- 2012 Communications Research Centre (CRC),\n" " and\n" - " Copyright (C) 2018 Matthias P. Braendli, matthias.braendli@mpb.li\n" + " Copyright (C) 2023 Matthias P. Braendli, matthias.braendli@mpb.li\n" "\n" " http://opendigitalradio.org\n" "\n" -- cgit v1.2.3 From 0aec6da11b4add62ac473e3f4ea813bb4a8a556d Mon Sep 17 00:00:00 2001 From: "Matthias P. Braendli" Date: Wed, 1 Feb 2023 14:03:03 +0100 Subject: Remove ZeroMQ input --- Makefile.am | 1 - README.md | 2 +- doc/example.ini | 7 - src/ConfigParser.cpp | 7 +- src/ConfigParser.h | 5 +- src/DabMod.cpp | 71 +--------- src/InputFileReader.cpp | 3 +- src/InputReader.h | 61 --------- src/InputZeroMQReader.cpp | 323 ---------------------------------------------- src/Utils.cpp | 1 - 10 files changed, 7 insertions(+), 474 deletions(-) delete mode 100644 src/InputZeroMQReader.cpp diff --git a/Makefile.am b/Makefile.am index 64884e0..fe566bb 100644 --- a/Makefile.am +++ b/Makefile.am @@ -75,7 +75,6 @@ odr_dabmod_SOURCES = src/DabMod.cpp \ src/InputMemory.h \ src/InputReader.h \ src/InputTcpReader.cpp \ - src/InputZeroMQReader.cpp \ src/OutputFile.cpp \ src/OutputFile.h \ src/FrameMultiplexer.cpp \ diff --git a/README.md b/README.md index a23de3d..23e5c36 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ Features - TII insertion - Logging: log to file, to syslog - EDI sources: TCP and UDP, both with and without Protection and Fragmentation Layer. -- ETI sources: ETI-over-TCP, file (Raw, Framed and Streamed) and ZeroMQ +- ETI sources: ETI-over-TCP, file (Raw, Framed and Streamed) - A Telnet and ZeroMQ remote-control that can be used to change some parameters during runtime and retrieve statistics. See `doc/README-RC.md` for more information diff --git a/doc/example.ini b/doc/example.ini index 2105535..cd48ef4 100644 --- a/doc/example.ini +++ b/doc/example.ini @@ -69,13 +69,6 @@ loop=0 ;transport=tcp ;source=localhost:9200 -; When recieving data using ZeroMQ, the source is the URI to be used -;transport=zeromq -;source=tcp://localhost:9100 -; The option max_frames_queued defines the maximum number of ETI frames -; (frame duration: 24ms) that can be in the input queue -;max_frames_queued=100 - [modulator] ; Mode 'fix' uses a fixed factor and is really not recommended. It is more ; useful on an academic perspective for people trying to understand the DAB diff --git a/src/ConfigParser.cpp b/src/ConfigParser.cpp index 9190c60..3e223c3 100644 --- a/src/ConfigParser.cpp +++ b/src/ConfigParser.cpp @@ -3,7 +3,7 @@ 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 http://opendigitalradio.org @@ -113,8 +113,6 @@ static void parse_configfile( } mod_settings.inputTransport = pt.Get("input.transport", "file"); - mod_settings.inputMaxFramesQueued = pt.GetInteger("input.max_frames_queued", - ZMQ_INPUT_MAX_FRAME_QUEUE); mod_settings.edi_max_delay_ms = pt.GetReal("input.edi_max_delay", 0.0f); @@ -574,8 +572,7 @@ void parse_args(int argc, char **argv, mod_settings_t& mod_settings) if (mod_settings.inputName.substr(0, 4) == "zmq+" && mod_settings.inputName.find("://") != std::string::npos) { - // if the name starts with zmq+XYZ://somewhere:port - mod_settings.inputTransport = "zeromq"; + throw std::runtime_error("Support for ZeroMQ input transport has been removed."); } else if (mod_settings.inputName.substr(0, 6) == "tcp://") { mod_settings.inputTransport = "tcp"; diff --git a/src/ConfigParser.h b/src/ConfigParser.h index 8f2a1d2..8681175 100644 --- a/src/ConfigParser.h +++ b/src/ConfigParser.h @@ -3,7 +3,7 @@ Her Majesty the Queen in Right of Canada (Communications Research Center Canada) - Copyright (C) 2017 + Copyright (C) 2023 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org @@ -40,8 +40,6 @@ #include "output/Lime.h" #include "output/BladeRF.h" -#define ZMQ_INPUT_MAX_FRAME_QUEUE 500 - struct mod_settings_t { std::string outputName; bool useZeroMQOutput = false; @@ -69,7 +67,6 @@ struct mod_settings_t { bool loop = false; std::string inputName = ""; std::string inputTransport = "file"; - unsigned inputMaxFramesQueued = ZMQ_INPUT_MAX_FRAME_QUEUE; float edi_max_delay_ms = 0.0f; tii_config_t tiiConfig; diff --git a/src/DabMod.cpp b/src/DabMod.cpp index 45f4d0a..57e6e32 100644 --- a/src/DabMod.cpp +++ b/src/DabMod.cpp @@ -381,17 +381,6 @@ int launch_modulator(int argc, char* argv[]) inputReader = inputFileReader; } - else if (mod_settings.inputTransport == "zeromq") { -#if !defined(HAVE_ZEROMQ) - throw std::runtime_error("Unable to open input: " - "ZeroMQ input transport selected, but not compiled in!"); -#else - auto inputZeroMQReader = make_shared(); - inputZeroMQReader->Open(mod_settings.inputName, mod_settings.inputMaxFramesQueued); - rcs.enrol(inputZeroMQReader.get()); - inputReader = inputZeroMQReader; -#endif - } else if (mod_settings.inputTransport == "tcp") { auto inputTcpReader = make_shared(); inputTcpReader->Open(mod_settings.inputName); @@ -460,17 +449,6 @@ int launch_modulator(int argc, char* argv[]) run_again = true; } } -#if defined(HAVE_ZEROMQ) - else if (auto in_zmq = dynamic_pointer_cast(inputReader)) { - run_again = true; - // Create a new input reader - rcs.remove_controllable(in_zmq.get()); - auto inputZeroMQReader = make_shared(); - inputZeroMQReader->Open(mod_settings.inputName, mod_settings.inputMaxFramesQueued); - rcs.enrol(inputZeroMQReader.get()); - inputReader = inputZeroMQReader; - } -#endif else if (dynamic_pointer_cast(inputReader)) { // Keep the same inputReader, as there is no input buffer overflow run_again = true; @@ -500,14 +478,6 @@ int launch_modulator(int argc, char* argv[]) return ret; } -struct zmq_input_timeout : public std::exception -{ - const char* what() const throw() - { - return "InputZMQ timeout"; - } -}; - static run_modulator_state_t run_modulator(const mod_settings_t& mod_settings, modulator_data& m) { auto ret = run_modulator_state_t::failure; @@ -535,36 +505,9 @@ static run_modulator_state_t run_modulator(const mod_settings_t& mod_settings, m ret = run_modulator_state_t::normal_end; break; } -#if defined(HAVE_ZEROMQ) - else if (dynamic_pointer_cast(m.inputReader)) { - /* An empty frame marks a timeout. We ignore it, but we are - * now able to handle SIGINT properly. - * - * Also, we reconnect zmq every 10 seconds to avoid some - * issues, discussed in - * https://stackoverflow.com/questions/26112992/zeromq-pub-sub-on-unreliable-connection - * - * > It is possible that the PUB socket sees the error - * > while the SUB socket does not. - * > - * > The ZMTP RFC has a proposal for heartbeating that would - * > solve this problem. The current best solution is for - * > PUB sockets to send heartbeats (e.g. 1 per second) when - * > traffic is low, and for SUB sockets to disconnect / - * > reconnect if they stop getting these. - * - * We don't need a heartbeat, because our application is constant frame rate, - * the frames themselves can act as heartbeats. - */ - - const auto now = chrono::steady_clock::now(); - if (last_frame_received + chrono::seconds(10) < now) { - throw zmq_input_timeout(); - } - } -#endif // defined(HAVE_ZEROMQ) else if (dynamic_pointer_cast(m.inputReader)) { - /* Same as for ZeroMQ */ + /* An empty frame marks a timeout. We ignore it, but we are + * now able to handle SIGINT properly. */ } else { throw logic_error("Unhandled framesize==0!"); @@ -681,16 +624,6 @@ static run_modulator_state_t run_modulator(const mod_settings_t& mod_settings, m } } } - catch (const zmq_input_timeout&) { - // The ZeroMQ input timeout - etiLog.level(warn) << "Timeout"; - ret = run_modulator_state_t::again; - } - catch (const zmq_input_overflow& e) { - // The ZeroMQ input has overflowed its buffer - etiLog.level(warn) << e.what(); - ret = run_modulator_state_t::again; - } catch (const FrameMultiplexerError& e) { // The FrameMultiplexer saw an error or a change in the size of a // subchannel. This can be due to a multiplex reconfiguration. diff --git a/src/InputFileReader.cpp b/src/InputFileReader.cpp index 5a9780b..a6b482e 100644 --- a/src/InputFileReader.cpp +++ b/src/InputFileReader.cpp @@ -6,8 +6,7 @@ Copyrigth (C) 2018 Matthias P. Braendli, matthias.braendli@mpb.li - - Input module for reading the ETI data from file or pipe, or ZeroMQ. + Input module for reading the ETI data from file or pipe. Supported file formats: RAW, FRAMED, STREAMED Supports re-sync to RAW ETI file diff --git a/src/InputReader.h b/src/InputReader.h index ab45d4f..2484948 100644 --- a/src/InputReader.h +++ b/src/InputReader.h @@ -38,11 +38,6 @@ #include #include #include -#if defined(HAVE_ZEROMQ) -# include "zmq.hpp" -# include "ThreadsafeQueue.h" -# include "RemoteControl.h" -#endif #include "Log.h" #include "Socket.h" #define INVALID_SOCKET -1 @@ -148,60 +143,4 @@ class InputTcpReader : public InputReader std::string m_uri; }; -struct zmq_input_overflow : public std::exception -{ - const char* what () const throw () - { - return "InputZMQ buffer overflow"; - } -}; - -#if defined(HAVE_ZEROMQ) -/* A ZeroMQ input. See www.zeromq.org for more info */ - -class InputZeroMQReader : public InputReader, public RemoteControllable -{ - public: - InputZeroMQReader(); - InputZeroMQReader(const InputZeroMQReader& other) = delete; - InputZeroMQReader& operator=(const InputZeroMQReader& other) = delete; - ~InputZeroMQReader(); - - int Open(const std::string& uri, size_t max_queued_frames); - virtual int GetNextFrame(void* buffer) override; - virtual std::string GetPrintableInfo() const override; - - /* Base function to set parameters. */ - virtual void set_parameter( - const std::string& parameter, - const std::string& value) override; - - /* Getting a parameter always returns a string. */ - virtual const std::string get_parameter( - const std::string& parameter) const override; - - private: - std::atomic m_running = ATOMIC_VAR_INIT(false); - std::string m_uri; - size_t m_max_queued_frames = 0; - - // Either must contain a full ETI frame, or one flag must be set - struct message_t { - std::vector eti_frame; - bool overflow = false; - bool timeout = false; - bool fault = false; - }; - ThreadsafeQueue m_in_messages; - - mutable std::mutex m_last_in_messages_size_mutex; - size_t m_last_in_messages_size = 0; - - void RecvProcess(void); - - zmq::context_t m_zmqcontext; // is thread-safe - std::thread m_recv_thread; -}; - -#endif diff --git a/src/InputZeroMQReader.cpp b/src/InputZeroMQReader.cpp deleted file mode 100644 index 40a07d4..0000000 --- a/src/InputZeroMQReader.cpp +++ /dev/null @@ -1,323 +0,0 @@ -/* - Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012 - Her Majesty the Queen in Right of Canada (Communications Research - Center Canada) - - Copyright (C) 2018 - Matthias P. Braendli, matthias.braendli@mpb.li - - http://opendigitalradio.org - */ -/* - This file is part of ODR-DabMod. - - ODR-DabMod 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. - - ODR-DabMod 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 ODR-DabMod. If not, see . - */ - -#ifdef HAVE_CONFIG_H -# include "config.h" -#endif - -#if defined(HAVE_ZEROMQ) - -#include -#include -#include -#include -#include "zmq.hpp" -#include "InputReader.h" -#include "PcDebug.h" -#include "Utils.h" - -using namespace std; - -constexpr int ZMQ_TIMEOUT_MS = 100; - -#define NUM_FRAMES_PER_ZMQ_MESSAGE 4 -/* A concatenation of four ETI frames, - * whose maximal size is 6144. - * - * Four frames in one zmq message are sent, so that - * we do not risk breaking ETI vs. transmission frame - * phase. - * - * The header is followed by the four ETI frames. - */ -struct zmq_msg_header_t -{ - uint32_t version; - uint16_t buflen[NUM_FRAMES_PER_ZMQ_MESSAGE]; -}; - -#define ZMQ_DAB_MESSAGE_T_HEADERSIZE \ - (sizeof(uint32_t) + NUM_FRAMES_PER_ZMQ_MESSAGE*sizeof(uint16_t)) - -InputZeroMQReader::InputZeroMQReader() : - InputReader(), - RemoteControllable("inputzmq") -{ - RC_ADD_PARAMETER(buffer, "Size of input buffer [us] (read-only)"); -} - -InputZeroMQReader::~InputZeroMQReader() -{ - m_running = false; - // This avoids the ugly "context was terminated" error because it lets - // poll do its thing first - this_thread::sleep_for(chrono::milliseconds(2 * ZMQ_TIMEOUT_MS)); - m_zmqcontext.close(); - if (m_recv_thread.joinable()) { - m_recv_thread.join(); - } -} - -int InputZeroMQReader::Open(const string& uri, size_t max_queued_frames) -{ - // The URL might start with zmq+tcp:// - if (uri.substr(0, 4) == "zmq+") { - m_uri = uri.substr(4); - } - else { - m_uri = uri; - } - - m_max_queued_frames = max_queued_frames; - - m_running = true; - m_recv_thread = std::thread(&InputZeroMQReader::RecvProcess, this); - - return 0; -} - -int InputZeroMQReader::GetNextFrame(void* buffer) -{ - if (not m_running) { - throw runtime_error("ZMQ input is not ready yet"); - } - - message_t incoming; - - /* Do some prebuffering because reads will happen in bursts - * (4 ETI frames in TM1) and we should make sure that - * we can serve the data required for a full transmission frame. - */ - if (m_in_messages.size() < 4) { - const size_t prebuffering = 10; - etiLog.log(trace, "ZMQ,wait1"); - m_in_messages.wait_and_pop(incoming, prebuffering); - } - else { - etiLog.log(trace, "ZMQ,wait2"); - m_in_messages.wait_and_pop(incoming); - } - etiLog.log(trace, "ZMQ,pop"); - - constexpr size_t framesize = 6144; - - if (incoming.timeout) { - return 0; - } - else if (incoming.fault) { - throw runtime_error("ZMQ input has terminated"); - } - else if (incoming.overflow) { - throw zmq_input_overflow(); - } - else if (incoming.eti_frame.size() == framesize) { - unique_lock lock(m_last_in_messages_size_mutex); - m_last_in_messages_size--; - lock.unlock(); - - memcpy(buffer, &incoming.eti_frame.front(), framesize); - - return framesize; - } - else { - throw logic_error("ZMQ ETI not 6144"); - } -} - -std::string InputZeroMQReader::GetPrintableInfo() const -{ - return "Input ZeroMQ: Receiving from " + m_uri; -} - -void InputZeroMQReader::RecvProcess() -{ - set_thread_name("zmqinput"); - - size_t queue_size = 0; - - zmq::socket_t subscriber(m_zmqcontext, ZMQ_SUB); - // zmq sockets are not thread safe. That's why - // we create it here, and not at object creation. - - bool success = true; - - try { - subscriber.connect(m_uri.c_str()); - } - catch (const zmq::error_t& err) { - etiLog.level(error) << "Failed to connect ZeroMQ socket to '" << - m_uri << "': '" << err.what() << "'"; - success = false; - } - - if (success) try { - // subscribe to all messages - subscriber.setsockopt(ZMQ_SUBSCRIBE, NULL, 0); - } - catch (const zmq::error_t& err) { - etiLog.level(error) << "Failed to subscribe ZeroMQ socket to messages: '" << - err.what() << "'"; - success = false; - } - - if (success) try { - while (m_running) { - zmq::message_t incoming; - zmq::pollitem_t items[1]; - items[0].socket = subscriber; - items[0].events = ZMQ_POLLIN; - const int num_events = zmq::poll(items, 1, ZMQ_TIMEOUT_MS); - if (num_events == 0) { - message_t msg; - msg.timeout = true; - m_in_messages.push(move(msg)); - continue; - } - - subscriber.recv(incoming); - - if (queue_size < m_max_queued_frames) { - if (incoming.size() < ZMQ_DAB_MESSAGE_T_HEADERSIZE) { - throw runtime_error("ZeroMQ packet too small for header"); - } - else { - zmq_msg_header_t dab_msg; - memcpy(&dab_msg, incoming.data(), sizeof(zmq_msg_header_t)); - - if (dab_msg.version != 1) { - etiLog.level(error) << - "ZeroMQ wrong packet version " << - dab_msg.version; - } - - int offset = sizeof(dab_msg.version) + - NUM_FRAMES_PER_ZMQ_MESSAGE * sizeof(*dab_msg.buflen); - - for (int i = 0; i < NUM_FRAMES_PER_ZMQ_MESSAGE; i++) { - if (dab_msg.buflen[i] > 6144) { - stringstream ss; - ss << "ZeroMQ buffer " << i << - " has invalid buflen " << dab_msg.buflen[i]; - throw runtime_error(ss.str()); - } - else { - vector buf(6144, 0x55); - - const int framesize = dab_msg.buflen[i]; - - if ((ssize_t)incoming.size() < offset + framesize) { - throw runtime_error("ZeroMQ packet too small"); - } - - memcpy(&buf.front(), - ((uint8_t*)incoming.data()) + offset, - framesize); - - offset += framesize; - - message_t msg; - msg.eti_frame = move(buf); - queue_size = m_in_messages.push(move(msg)); - etiLog.log(trace, "ZMQ,push %zu", queue_size); - - unique_lock lock(m_last_in_messages_size_mutex); - m_last_in_messages_size++; - } - } - } - } - else { - message_t msg; - msg.overflow = true; - queue_size = m_in_messages.push(move(msg)); - etiLog.level(warn) << "ZeroMQ buffer overfull !"; - throw runtime_error("ZMQ input full"); - } - - if (queue_size < 5) { - etiLog.level(warn) << "ZeroMQ buffer low: " << queue_size << " elements !"; - } - } - } - catch (const zmq::error_t& err) { - etiLog.level(error) << "ZeroMQ error during receive: '" << err.what() << "'"; - } - catch (const std::exception& err) { - etiLog.level(error) << "Exception during receive: '" << err.what() << "'"; - } - - m_running = false; - - etiLog.level(info) << "ZeroMQ input worker terminated"; - - subscriber.close(); - - message_t msg; - msg.fault = true; - queue_size = m_in_messages.push(move(msg)); -} - -// ======================================= -// Remote Control -// ======================================= -void InputZeroMQReader::set_parameter(const string& parameter, const string& value) -{ - stringstream ss(value); - ss.exceptions ( stringstream::failbit | stringstream::badbit ); - - if (parameter == "buffer") { - throw ParameterError("Parameter " + parameter + " is read-only."); - } - else { - stringstream ss_err; - ss_err << "Parameter '" << parameter - << "' is not exported by controllable " << get_rc_name(); - throw ParameterError(ss_err.str()); - } -} - -const string InputZeroMQReader::get_parameter(const string& parameter) const -{ - stringstream ss; - ss << std::fixed; - if (parameter == "buffer") { - // Do not use size of the queue, as it will contain empty - // frames to signal timeouts - unique_lock lock(m_last_in_messages_size_mutex); - const long time_in_buffer_us = 24000 * m_last_in_messages_size; - ss << time_in_buffer_us; - } - else { - ss << "Parameter '" << parameter << - "' is not exported by controllable " << get_rc_name(); - throw ParameterError(ss.str()); - } - return ss.str(); -} - -#endif - diff --git a/src/Utils.cpp b/src/Utils.cpp index 350838e..3f378a7 100644 --- a/src/Utils.cpp +++ b/src/Utils.cpp @@ -90,7 +90,6 @@ void printUsage(const char* progName) fprintf(out, "Where:\n"); fprintf(out, "input: ETI input filename (default: stdin), or\n"); fprintf(out, " tcp://source:port for ETI-over-TCP input, or\n"); - fprintf(out, " zmq+tcp://source:port for ZMQ input.\n"); fprintf(out, " udp://:port for EDI input.\n"); fprintf(out, "-f name: Use file output with given filename. (use /dev/stdout for standard output)\n"); fprintf(out, "-F format: Set the output format (see doc/example.ini for formats) for the file output.\n"); -- cgit v1.2.3 From ba0c32703ee1af770deabdf864c806d974ea8206 Mon Sep 17 00:00:00 2001 From: "Matthias P. Braendli" Date: Wed, 1 Feb 2023 15:14:34 +0100 Subject: Dexter timestamps: add 1s margin in modulator --- src/DabMod.cpp | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/src/DabMod.cpp b/src/DabMod.cpp index 57e6e32..4b4fda0 100644 --- a/src/DabMod.cpp +++ b/src/DabMod.cpp @@ -531,7 +531,7 @@ static run_modulator_state_t run_modulator(const mod_settings_t& mod_settings, m fct = m.etiReader->getFct(); fp = m.etiReader->getFp(); - ts = m.ediInput->ediReader.getTimestamp(); + ts = m.etiReader->getTimestamp(); } else if (m.ediInput) { while (running and not m.ediInput->ediReader.isFrameReady()) { @@ -592,9 +592,9 @@ static run_modulator_state_t run_modulator(const mod_settings_t& mod_settings, m } } - // timestamp is good if we run unsynchronised, or if it's in the future + // 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() > 0); + (ts.timestamp_valid and ts.offset_to_system_time() > 1); if (fct_good and ts_good) { last_eti_fct = fct; @@ -602,15 +602,9 @@ static run_modulator_state_t run_modulator(const mod_settings_t& mod_settings, m m.flowgraph->run(); } else { - etiLog.level(warn) << "Skipping frame " << fct << " FCT " << - (fct_good ? "good" : "bad") << " TS " << - (ts_good ? "good, " : "bad, ") << - (ts.timestamp_valid ? (ts.offset_to_system_time() > 0 ? "in the future" : "in the past") : "invalid"); - - if (m.ediInput) { - m.ediInput->ediReader.clearFrame(); - } - return run_modulator_state_t::again; + etiLog.level(warn) << "Skipping frame " << + " TS " << (ts.timestamp_valid ? "valid" : "invalid") << + " offset " << (ts.timestamp_valid ? ts.offset_to_system_time() : 0); } if (m.ediInput) { -- 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(-) 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(-) 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 4b3465e0a9e79f18398819f3bd0957efc9952928 Mon Sep 17 00:00:00 2001 From: "Matthias P. Braendli" Date: Fri, 3 Mar 2023 14:50:57 +0100 Subject: Remove duplicate stream0_start_clks = 0 on init --- src/output/Dexter.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/output/Dexter.cpp b/src/output/Dexter.cpp index b6e6700..aa0b7ad 100644 --- a/src/output/Dexter.cpp +++ b/src/output/Dexter.cpp @@ -101,12 +101,8 @@ Dexter::Dexter(SDRDeviceConfig& config) : etiLog.level(warn) << "Failed to set dexter_dsp_tx.dc1 = false: " << get_iio_error(r); } - if ((r = iio_device_attr_write_longlong(m_dexter_dsp_tx, "stream0_start_clks", 0)) != 0) { - 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(error) << "Failed to set dexter_dsp_tx.stream0_start_clks = 0: " << get_iio_error(r); + etiLog.level(error) << "Failed to set dexter_dsp_tx.gain0 = 0: " << get_iio_error(r); } if (m_conf.sampleRate != 2048000) { @@ -447,6 +443,9 @@ void Dexter::transmit_frame(struct FrameData&& frame) channel_down(); break; } + else { + fprintf(stderr, "p"); + } num_buffers_pushed++; } num_frames_modulated++; -- cgit v1.2.3 From ed8e8bed00d5ad47a9d4798059fd0e3a8c3f7b8c Mon Sep 17 00:00:00 2001 From: "Matthias P. Braendli" Date: Fri, 24 Mar 2023 10:31:24 +0100 Subject: Fix startup hiccup due to iio buffer allocation taking several seconds --- src/output/Dexter.cpp | 105 ++++++++++++++++++++++++++++++++++---------------- src/output/Dexter.h | 3 +- src/output/SDR.cpp | 2 +- 3 files changed, 75 insertions(+), 35 deletions(-) diff --git a/src/output/Dexter.cpp b/src/output/Dexter.cpp index aa0b7ad..93f1bb6 100644 --- a/src/output/Dexter.cpp +++ b/src/output/Dexter.cpp @@ -48,9 +48,9 @@ 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; +static constexpr size_t TRANSMISSION_FRAME_LEN_SAMPS = (2656 + 76 * 2552) * /* I+Q */ 2; +static constexpr size_t IIO_BUFFERS = 2; +static constexpr size_t IIO_BUFFER_LEN_SAMPS = TRANSMISSION_FRAME_LEN_SAMPS / IIO_BUFFERS; static string get_iio_error(int err) { @@ -73,7 +73,7 @@ Dexter::Dexter(SDRDeviceConfig& config) : etiLog.level(info) << "Dexter:Creating the device"; m_ctx = iio_create_local_context(); - if (!m_ctx) { + if (not m_ctx) { throw std::runtime_error("Dexter: Unable to create iio scan context"); } @@ -83,12 +83,12 @@ Dexter::Dexter(SDRDeviceConfig& config) : } m_dexter_dsp_tx = iio_context_find_device(m_ctx, "dexter_dsp_tx"); - if (!m_dexter_dsp_tx) { + if (not m_dexter_dsp_tx) { throw std::runtime_error("Dexter: Unable to find dexter_dsp_tx iio device"); } m_ad9957_tx0 = iio_context_find_device(m_ctx, "ad9957_tx0"); - if (!m_ad9957_tx0) { + if (not m_ad9957_tx0) { throw std::runtime_error("Dexter: Unable to find ad9957_tx0 iio device"); } @@ -101,10 +101,6 @@ Dexter::Dexter(SDRDeviceConfig& config) : etiLog.level(warn) << "Failed to set dexter_dsp_tx.dc1 = false: " << get_iio_error(r); } - if ((r = iio_device_attr_write_longlong(m_dexter_dsp_tx, "gain0", m_conf.txgain)) != 0) { - etiLog.level(error) << "Failed to set dexter_dsp_tx.gain0 = 0: " << get_iio_error(r); - } - if (m_conf.sampleRate != 2048000) { throw std::runtime_error("Dexter: Only 2048000 samplerate supported"); } @@ -176,16 +172,11 @@ Dexter::Dexter(SDRDeviceConfig& config) : etiLog.level(warn) << "Failed to set dexter_dsp_tx.stream0_start_clks = " << 0 << " : " << 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); - */ -} + 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); + } -void Dexter::channel_up() -{ - etiLog.level(debug) << "DEXTER CHANNEL_UP"; constexpr int CHANNEL_INDEX = 0; m_tx_channel = iio_device_get_channel(m_ad9957_tx0, CHANNEL_INDEX); if (m_tx_channel == nullptr) { @@ -194,21 +185,62 @@ void Dexter::channel_up() iio_channel_enable(m_tx_channel); - m_buffer = iio_device_create_buffer(m_ad9957_tx0, IIO_BUFFER_LEN/sizeof(int16_t), 0); + m_buffer = iio_device_create_buffer(m_ad9957_tx0, IIO_BUFFER_LEN_SAMPS, 0); if (not m_buffer) { throw std::runtime_error("Dexter: Cannot create IIO buffer."); } + + // Flush the FPGA FIFO + { + constexpr size_t buflen_samps = TRANSMISSION_FRAME_LEN_SAMPS / IIO_BUFFERS; + constexpr size_t buflen = buflen_samps * sizeof(int16_t); + + memset(iio_buffer_start(m_buffer), 0, buflen); + ssize_t pushed = iio_buffer_push(m_buffer); + if (pushed < 0) { + etiLog.level(error) << "Dexter: init push buffer " << get_iio_error(pushed); + } + this_thread::sleep_for(chrono::milliseconds(200)); + } + + if ((r = iio_device_attr_write_longlong(m_dexter_dsp_tx, "gain0", m_conf.txgain)) != 0) { + etiLog.level(error) << "Failed to set dexter_dsp_tx.gain0 = " << m_conf.txgain << + " : " << 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() +{ + int r; + if ((r = iio_device_attr_write_longlong(m_dexter_dsp_tx, "gain0", m_conf.txgain)) != 0) { + etiLog.level(error) << "Failed to set dexter_dsp_tx.gain0 = " << m_conf.txgain << + " : " << get_iio_error(r); + } + + m_channel_is_up = true; + etiLog.level(debug) << "DEXTER CHANNEL_UP"; } void Dexter::channel_down() { - iio_channel_disable(m_tx_channel); + int 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); + } - etiLog.level(debug) << "DEXTER CHANNEL_DOWN"; - if (m_buffer) { - iio_buffer_destroy(m_buffer); - m_buffer = nullptr; + // This will flush out the FIFO + 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); } + + m_channel_is_up = false; + etiLog.level(debug) << "DEXTER CHANNEL_DOWN"; } @@ -224,6 +256,11 @@ Dexter::~Dexter() iio_device_attr_write_longlong(m_dexter_dsp_tx, "gain0", 0); } + if (m_buffer) { + iio_buffer_destroy(m_buffer); + m_buffer = nullptr; + } + iio_context_destroy(m_ctx); m_ctx = nullptr; } @@ -259,12 +296,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.stream0_start_clks = 0: " << get_iio_error(r); + etiLog.level(warn) << "Failed to set dexter_dsp_tx.gain0 = 0: " << 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.stream0_start_clks = 0: " << get_iio_error(r); + etiLog.level(warn) << "Failed to set dexter_dsp_tx.gain0 = 0: " << get_iio_error(r); } else { m_conf.txgain = txgain_readback; @@ -276,7 +313,7 @@ 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.stream0_start_clks = 0: " << get_iio_error(r); + etiLog.level(warn) << "Failed to set dexter_dsp_tx.gain0 = 0: " << get_iio_error(r); } return txgain_readback; } @@ -367,9 +404,10 @@ double Dexter::get_temperature(void) const void Dexter::transmit_frame(struct FrameData&& frame) { - if (frame.buf.size() != TRANSMISSION_FRAME_LEN) { + constexpr size_t frame_len_bytes = TRANSMISSION_FRAME_LEN_SAMPS * sizeof(int16_t); + if (frame.buf.size() != frame_len_bytes) { etiLog.level(debug) << "Dexter::transmit_frame Expected " << - TRANSMISSION_FRAME_LEN << " got " << frame.buf.size(); + frame_len_bytes << " got " << frame.buf.size(); throw std::runtime_error("Dexter: invalid buffer size"); } @@ -377,7 +415,7 @@ void Dexter::transmit_frame(struct FrameData&& frame) const double margin_s = frame.ts.offset_to_system_time(); - if (m_buffer == nullptr) { + if (not m_channel_is_up) { if (require_timestamped_tx) { /* uint64_t timeS = frame.ts.timestamp_sec; @@ -430,9 +468,10 @@ void Dexter::transmit_frame(struct FrameData&& frame) //const size_t num_samples = frame.buf.size() / (2*sizeof(int16_t)); //const int16_t *buf = reinterpret_cast(frame.buf.data()); - if (m_buffer) { + if (m_channel_is_up) { for (size_t i = 0; i < IIO_BUFFERS; i++) { - constexpr size_t buflen = TRANSMISSION_FRAME_LEN / IIO_BUFFERS; + constexpr size_t buflen_samps = TRANSMISSION_FRAME_LEN_SAMPS / IIO_BUFFERS; + constexpr size_t buflen = buflen_samps * sizeof(int16_t); memcpy(iio_buffer_start(m_buffer), frame.buf.data() + (i * buflen), buflen); ssize_t pushed = iio_buffer_push(m_buffer); diff --git a/src/output/Dexter.h b/src/output/Dexter.h index 2bd63b1..2f0524e 100644 --- a/src/output/Dexter.h +++ b/src/output/Dexter.h @@ -83,10 +83,11 @@ class Dexter : public Output::SDRDevice virtual double get_temperature() const override; private: - void channel_up(); void channel_down(); + bool m_channel_is_up = false; + SDRDeviceConfig& m_conf; struct iio_context* m_ctx = nullptr; diff --git a/src/output/SDR.cpp b/src/output/SDR.cpp index e22617e..f53197e 100644 --- a/src/output/SDR.cpp +++ b/src/output/SDR.cpp @@ -165,7 +165,7 @@ meta_vec_t SDR::process_metadata(const meta_vec_t& metadataIn) const auto max_size = m_config.enableSync ? - (frame.ts.timestamp_offset * 4.0) / frame_duration_s + (frame.ts.timestamp_offset * 400.0) / frame_duration_s : FRAMES_MAX_SIZE; auto r = m_queue.push_overflow(std::move(frame), max_size); -- cgit v1.2.3 From d7fe3627a5f5ac1376af09a851d59c5c7919f29e Mon Sep 17 00:00:00 2001 From: "Matthias P. Braendli" Date: Fri, 24 Mar 2023 10:50:09 +0100 Subject: Remove debugging printf --- src/output/Dexter.cpp | 5 ----- src/output/SDR.cpp | 16 ---------------- 2 files changed, 21 deletions(-) diff --git a/src/output/Dexter.cpp b/src/output/Dexter.cpp index 93f1bb6..d892486 100644 --- a/src/output/Dexter.cpp +++ b/src/output/Dexter.cpp @@ -456,8 +456,6 @@ void Dexter::transmit_frame(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(); @@ -482,9 +480,6 @@ void Dexter::transmit_frame(struct FrameData&& frame) channel_down(); break; } - else { - fprintf(stderr, "p"); - } num_buffers_pushed++; } num_frames_modulated++; diff --git a/src/output/SDR.cpp b/src/output/SDR.cpp index f53197e..726fb94 100644 --- a/src/output/SDR.cpp +++ b/src/output/SDR.cpp @@ -171,13 +171,6 @@ meta_vec_t SDR::process_metadata(const meta_vec_t& metadataIn) 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; } } @@ -356,15 +349,6 @@ void SDR::handle_frame(struct FrameData&& frame) 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: " << -- cgit v1.2.3 From efea1c485f178cdbadf5babdf7951e6d1a1ed801 Mon Sep 17 00:00:00 2001 From: "Matthias P. Braendli" Date: Wed, 29 Mar 2023 13:44:17 +0200 Subject: Move to C++17 --- Makefile.am | 4 ++-- configure.ac | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Makefile.am b/Makefile.am index fe566bb..0e09236 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,7 +1,7 @@ # Copyright (C) 2007, 2008, 2009, 2010 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 # # http://opendigitalradio.org @@ -37,7 +37,7 @@ bin_PROGRAMS = odr-dabmod odr_dabmod_CFLAGS = -Wall -Isrc -Ilib \ $(GITVERSION_FLAGS) -odr_dabmod_CXXFLAGS = -Wall -Isrc -Ilib -std=c++11 \ +odr_dabmod_CXXFLAGS = -Wall -Isrc -Ilib \ $(GITVERSION_FLAGS) $(BOOST_CPPFLAGS) odr_dabmod_LDADD = $(BOOST_LDFLAGS) $(BOOST_THREAD_LIB) $(UHD_LIBS) $(LIMESDR_LIBS) $(ADDITIONAL_UHD_LIBS) odr_dabmod_SOURCES = src/DabMod.cpp \ diff --git a/configure.ac b/configure.ac index 59ee4e5..ba5d65a 100644 --- a/configure.ac +++ b/configure.ac @@ -1,7 +1,7 @@ # Copyright (C) 2007, 2008, 2009, 2010, 2011, 2012 Her Majesty the # Queen in Right of Canada (Communications Research Center Canada) -# Copyright (C) 2022 Matthias P. Braendli, http://opendigitalradio.org +# Copyright (C) 2023 Matthias P. Braendli, http://opendigitalradio.org # This file is part of ODR-DabMod. # @@ -34,7 +34,7 @@ AC_PROG_CC AM_PROG_CC_C_O AC_PROG_INSTALL -AX_CXX_COMPILE_STDCXX(11,noext,mandatory) +AX_CXX_COMPILE_STDCXX(17,noext,mandatory) EXTRA="" AC_ARG_ENABLE([prof], -- 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(-) 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 6e5fcb1054c14275ac132d9c68d5259adc3444c9 Mon Sep 17 00:00:00 2001 From: "Matthias P. Braendli" Date: Thu, 30 Mar 2023 12:14:49 +0200 Subject: SDR: add queued_frames_ms and fix max queue calculation --- src/output/SDR.cpp | 33 +++++++++++++-------------------- 1 file changed, 13 insertions(+), 20 deletions(-) diff --git a/src/output/SDR.cpp b/src/output/SDR.cpp index a4f5873..99ced7d 100644 --- a/src/output/SDR.cpp +++ b/src/output/SDR.cpp @@ -46,8 +46,11 @@ using namespace std; namespace Output { -// Maximum number of frames that can wait in frames, when not using synchronised transmission -static constexpr size_t FRAMES_MAX_SIZE = 8; +// Maximum number of frames that can wait in frames. +// Keep it low when not using synchronised transmission, in order to reduce delay. +// When using synchronised transmission, use a 6s buffer to give us enough margin. +static constexpr size_t FRAMES_MAX_SIZE_UNSYNC = 8; +static constexpr size_t FRAMES_MAX_SIZE_SYNC = 250; // If the timestamp is further in the future than // 100 seconds, abort @@ -81,6 +84,7 @@ SDR::SDR(SDRDeviceConfig& config, std::shared_ptr device) : RC_ADD_PARAMETER(frames, "Counter of number of frames modulated"); RC_ADD_PARAMETER(gpsdo_num_sv, "Number of Satellite Vehicles tracked by GPSDO"); RC_ADD_PARAMETER(gpsdo_holdover, "1 if the GPSDO is in holdover, 0 if it is using gnss"); + RC_ADD_PARAMETER(queued_frames_ms, "Number of frames queued, represented in milliseconds"); #ifdef HAVE_LIMESDR if (std::dynamic_pointer_cast(device)) { @@ -128,10 +132,6 @@ 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); @@ -169,10 +169,7 @@ meta_vec_t SDR::process_metadata(const meta_vec_t& metadataIn) } - const auto max_size = m_config.enableSync ? - (frame.ts.timestamp_offset * 400.0) / frame_duration_s - : FRAMES_MAX_SIZE; - + const auto max_size = m_config.enableSync ? FRAMES_MAX_SIZE_SYNC : FRAMES_MAX_SIZE_UNSYNC; auto r = m_queue.push_overflow(std::move(frame), max_size); etiLog.log(trace, "SDR,push %d %zu", r.overflowed, r.new_size); @@ -406,19 +403,10 @@ void SDR::set_parameter(const string& parameter, const string& value) else if (parameter == "muting") { ss >> m_config.muting; } - else if (parameter == "underruns" or - parameter == "overruns" or - parameter == "latepackets" or - parameter == "frames" or - parameter == "gpsdo_num_sv" or - parameter == "gpsdo_holdover" or - parameter == "fifo_fill") { - throw ParameterError("Parameter " + parameter + " is read-only."); - } else { stringstream ss_err; ss_err << "Parameter '" << parameter - << "' is not exported by controllable " << get_rc_name(); + << "' is read-only or not exported by controllable " << get_rc_name(); throw ParameterError(ss_err.str()); } } @@ -454,6 +442,11 @@ const string SDR::get_parameter(const string& parameter) const throw ParameterError("Temperature not available"); } } + else if (parameter == "queued_frames_ms") { + ss << m_queue.size() * + chrono::duration_cast(transmission_frame_duration(m_config.dabMode)) + .count(); + } else { if (m_device) { const auto stat = m_device->get_run_statistics(); -- cgit v1.2.3 From fe19871f8ee362f65d194b864eb989618b994e58 Mon Sep 17 00:00:00 2001 From: "Matthias P. Braendli" Date: Fri, 31 Mar 2023 11:30:47 +0200 Subject: Add more DEXTER metrics to RC --- src/output/Dexter.cpp | 172 +++++++++++++++++++++++++++++++++++++++++++------ src/output/SDR.cpp | 35 ++++++++-- src/output/SDRDevice.h | 2 +- 3 files changed, 186 insertions(+), 23 deletions(-) diff --git a/src/output/Dexter.cpp b/src/output/Dexter.cpp index dd882f2..515d810 100644 --- a/src/output/Dexter.cpp +++ b/src/output/Dexter.cpp @@ -344,30 +344,160 @@ SDRDevice::run_statistics_t Dexter::get_run_statistics(void) const std::unique_lock lock(m_attr_thread_mutex); rs["underruns"] = underflows; } - rs["overruns"] = 0; - rs["late_packets"] = num_late; + rs["latepackets"] = 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; + 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("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"); + + constexpr double VMINFACT = 0.85; + constexpr double VMAXFACT = 1.15; + bool voltage_ok = true; + bool temp_ok = true; + + { + std::ifstream in("/sys/bus/i2c/devices/1-002f/hwmon/hwmon0/in2_input", std::ios::in | std::ios::binary); + if (in) { + double vcc3v3; + in >> vcc3v3; + rs["vcc3v3"] = vcc3v3 * (18+36)/36.0/1000.0; + voltage_ok = (vcc3v3 > VMINFACT * 3.3) and (vcc3v3 < VMAXFACT * 3.3); + } + else { + rs["vcc3v3"] = -1; + voltage_ok = false; + } } - else { - rs["clks"] = (ssize_t)-1; - etiLog.level(error) << "Failed to get dexter_dsp_tx.clks: " << get_iio_error(r); + { + std::ifstream in("/sys/bus/i2c/devices/1-002f/hwmon/hwmon0/in1_input", std::ios::in | std::ios::binary); + if (in) { + double vcc5v4; + in >> vcc5v4; + rs["vcc5v4"] = vcc5v4 * (51+36)/36.0/1000.0; + voltage_ok = (vcc5v4 > VMINFACT * 5.4) and (vcc5v4 < VMAXFACT * 5.4); + } + else { + rs["vcc5v4"] = -1; + voltage_ok = false; + } + } + { + std::ifstream in("/sys/bus/i2c/devices/1-002f/hwmon/hwmon0/in3_input", std::ios::in | std::ios::binary); + if (in) { + double vfan; + in >> vfan; + rs["vfan"] = vfan * (560+22)/22.0/1000.0; + } + else { + rs["vfan"] = -1; + voltage_ok = false; + } + } + { + std::ifstream in("/sys/bus/i2c/devices/1-002f/hwmon/hwmon0/in0_input", std::ios::in | std::ios::binary); + if (in) { + double vccmainin; + in >> vccmainin; + rs["vcc_main_in"] = vccmainin * (560+22)/22.0/1000.0; + voltage_ok |= vccmainin > 10.0; + } + else { + rs["vcc_main_in"] = -1; + voltage_ok = false; + } } + { + std::ifstream in("/sys/bus/i2c/devices/1-002f/hwmon/hwmon0/in4_input", std::ios::in | std::ios::binary); + if (in) { + double vcc3v3pll; + in >> vcc3v3pll; + rs["vcc3v3pll"] = vcc3v3pll * (18+36)/36.0/1000.0; + voltage_ok = (vcc3v3pll > VMINFACT * 3.3) and (vcc3v3pll < VMAXFACT * 3.3); + } + else { + rs["vcc3v3pll"] = -1; + voltage_ok = false; + } + } + { + std::ifstream in("/sys/bus/i2c/devices/1-002f/hwmon/hwmon0/in5_input", std::ios::in | std::ios::binary); + if (in) { + double vcc2v5io; + in >> vcc2v5io; + rs["vcc2v5io"] = vcc2v5io * (4.7+36)/36.0/1000.0; + voltage_ok = (vcc2v5io > VMINFACT * 2.5) and (vcc2v5io < VMAXFACT * 2.5); + } + else { + rs["vcc2v5io"] = -1; + voltage_ok = false; + } + } + { + std::ifstream in("/sys/bus/i2c/devices/1-002f/hwmon/hwmon0/in6_input", std::ios::in | std::ios::binary); + if (in) { + double vccocxo; + in >> vccocxo; + rs["vccocxo"] = vccocxo * (51+36)/36.0/1000.0; + } + else { + rs["vccocxo"] = -1; + voltage_ok = false; + } + } + + optional tfpga; + for (int i = 0; i < 100; i++) { + std::string path = "/sys/bus/iio/devices/iio:device"; + path += to_string(i); - long long fifo_not_empty_clks = 0; + std::ifstream iio_name(path + "/name", std::ios::in | std::ios::binary); + std::ostringstream sstr; + sstr << iio_name.rdbuf(); + if (sstr.str() == "xadc\n") { + std::ifstream in_scale(path + "/in_temp0_scale", std::ios::in | std::ios::binary); + std::ifstream in_offset(path + "/in_temp0_offset", std::ios::in | std::ios::binary); + std::ifstream in_temp0_raw(path + "/in_temp0_raw", std::ios::in | std::ios::binary); - 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; + if (in_scale and in_offset and in_temp0_raw) { + double scale, offset, temp0_raw ; + in_scale >> scale; + in_offset >> offset; + in_temp0_raw >> temp0_raw; + + tfpga = (temp0_raw + offset) * scale / 1000.0; + } + break; + } + } + + if (tfpga) { + rs["tempfpga"] = *tfpga; + temp_ok |= *tfpga <= 85; } 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["tempfpga"] = -1; + temp_ok = false; } + + rs["voltage_alarm"] = not voltage_ok; + rs["temp_alarm"] = not temp_ok; + return rs; } @@ -408,7 +538,6 @@ size_t Dexter::receive_frame( bool Dexter::is_clk_source_ok() const { - // TODO return true; } @@ -419,8 +548,15 @@ const char* Dexter::device_name(void) const std::optional Dexter::get_temperature(void) const { - // TODO XADC contains temperature, but value is weird - return std::nullopt; + std::ifstream in("/sys/bus/i2c/devices/1-002f/hwmon/hwmon0/temp1_input", std::ios::in | std::ios::binary); + if (in) { + double tbaseboard; + in >> tbaseboard; + return tbaseboard / 1000.0; + } + else { + return {}; + } } void Dexter::transmit_frame(struct FrameData&& frame) diff --git a/src/output/SDR.cpp b/src/output/SDR.cpp index 99ced7d..cc080dc 100644 --- a/src/output/SDR.cpp +++ b/src/output/SDR.cpp @@ -25,7 +25,9 @@ */ #include "output/SDR.h" +#include "output/UHD.h" #include "output/Lime.h" +#include "output/Dexter.h" #include "PcDebug.h" #include "Log.h" @@ -82,8 +84,14 @@ SDR::SDR(SDRDeviceConfig& config, std::shared_ptr device) : RC_ADD_PARAMETER(underruns, "Counter of number of underruns"); RC_ADD_PARAMETER(latepackets, "Counter of number of late packets"); RC_ADD_PARAMETER(frames, "Counter of number of frames modulated"); - RC_ADD_PARAMETER(gpsdo_num_sv, "Number of Satellite Vehicles tracked by GPSDO"); - RC_ADD_PARAMETER(gpsdo_holdover, "1 if the GPSDO is in holdover, 0 if it is using gnss"); + +#ifdef HAVE_OUTPUT_UHD + if (std::dynamic_pointer_cast(device)) { + RC_ADD_PARAMETER(gpsdo_num_sv, "Number of Satellite Vehicles tracked by GPSDO"); + RC_ADD_PARAMETER(gpsdo_holdover, "1 if the GPSDO is in holdover, 0 if it is using gnss"); + } +#endif // HAVE_OUTPUT_UHD + RC_ADD_PARAMETER(queued_frames_ms, "Number of frames queued, represented in milliseconds"); #ifdef HAVE_LIMESDR @@ -93,8 +101,24 @@ SDR::SDR(SDRDeviceConfig& config, std::shared_ptr device) : #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"); + if (std::dynamic_pointer_cast(device)) { + 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"); + RC_ADD_PARAMETER(gpsdo_locked, "1 if GPSDO is locked"); + RC_ADD_PARAMETER(pps_clk_error_hz, "Estimated error in Hz of clock"); + RC_ADD_PARAMETER(pps_cnt, "Number of 1PPS pulses seen from GPS"); + RC_ADD_PARAMETER(dsp_version, "Version of FPGA DSP"); + RC_ADD_PARAMETER(vcc3v3, "Voltage of VCC 3V3"); + RC_ADD_PARAMETER(vcc5v4, "Voltage of VCC 5V4"); + RC_ADD_PARAMETER(vfan, "Fan voltage"); + RC_ADD_PARAMETER(vcc_main_in, "Main input voltage"); + RC_ADD_PARAMETER(vcc3v3pll, "Voltage of VCC 3V3 PLL"); + RC_ADD_PARAMETER(vcc2v5io, "Voltage of VCC 2V5 IO"); + RC_ADD_PARAMETER(vccocxo, "OCXO voltage"); + RC_ADD_PARAMETER(tempfpga, "FPGA temperature [celsius]"); + RC_ADD_PARAMETER(voltage_alarm, "Voltage out of bounds"); + RC_ADD_PARAMETER(temp_alarm, "Temperature out of bounds"); + } #endif // HAVE_DEXTER } @@ -455,6 +479,9 @@ const string SDR::get_parameter(const string& parameter) const 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); } diff --git a/src/output/SDRDevice.h b/src/output/SDRDevice.h index 337d501..a4f551c 100644 --- a/src/output/SDRDevice.h +++ b/src/output/SDRDevice.h @@ -112,7 +112,7 @@ struct FrameData { // All SDR Devices must implement the SDRDevice interface class SDRDevice { public: - using run_statistic_t = std::variant; + using run_statistic_t = std::variant; using run_statistics_t = std::unordered_map; virtual void tune(double lo_offset, double frequency) = 0; -- cgit v1.2.3 From ae70341365e27d766c8530924209fc4826036aea Mon Sep 17 00:00:00 2001 From: "Matthias P. Braendli" Date: Fri, 31 Mar 2023 14:31:32 +0200 Subject: Add JSON output to RC --- lib/RemoteControl.cpp | 94 ++++++++++++++++++++++++++++++++++++++++--- lib/RemoteControl.h | 14 +++++-- src/DabModulator.cpp | 7 ++++ src/DabModulator.h | 10 ++--- src/FIRFilter.cpp | 9 ++++- src/FIRFilter.h | 10 ++--- src/GainControl.cpp | 20 ++++++++- src/GainControl.h | 12 ++---- src/GuardIntervalInserter.cpp | 9 ++++- src/GuardIntervalInserter.h | 14 +++---- src/MemlessPoly.cpp | 10 ++++- src/MemlessPoly.h | 14 +++---- src/OfdmGenerator.cpp | 9 ++++- src/OfdmGenerator.h | 13 ++---- src/TII.cpp | 11 ++++- src/TII.h | 14 +++---- src/TimestampDecoder.cpp | 21 +++++++++- src/TimestampDecoder.h | 12 ++---- src/output/SDR.cpp | 26 ++++++++++++ src/output/SDR.h | 2 + src/output/SDRDevice.h | 3 +- src/output/UHD.h | 8 ---- 22 files changed, 255 insertions(+), 87 deletions(-) diff --git a/lib/RemoteControl.cpp b/lib/RemoteControl.cpp index 30dcb60..6e9af20 100644 --- a/lib/RemoteControl.cpp +++ b/lib/RemoteControl.cpp @@ -25,6 +25,8 @@ #include #include #include +#include +#include #include #include @@ -102,6 +104,74 @@ std::list< std::vector > RemoteControllers::get_param_list_values(c return allparams; } + +static std::string escape_json(const std::string &s) { + std::ostringstream o; + for (auto c = s.cbegin(); c != s.cend(); c++) { + switch (*c) { + case '"': o << "\\\""; break; + case '\\': o << "\\\\"; break; + case '\b': o << "\\b"; break; + case '\f': o << "\\f"; break; + case '\n': o << "\\n"; break; + case '\r': o << "\\r"; break; + case '\t': o << "\\t"; break; + default: + if ('\x00' <= *c && *c <= '\x1f') { + o << "\\u" + << std::hex << std::setw(4) << std::setfill('0') << static_cast(*c); + } else { + o << *c; + } + } + } + return o.str(); +} + +std::string RemoteControllers::get_params_json(const std::string& name) { + RemoteControllable* controllable = get_controllable_(name); + const auto& values = controllable->get_all_values(); + + std::ostringstream ss; + ss << "{ "; + size_t ix = 0; + for (const auto& element : values) { + if (ix > 0) { + ss << ","; + } + + ss << "\"" << escape_json(element.first) << "\": "; + + const auto& value = element.second; + if (std::holds_alternative(value)) { + ss << "\"" << escape_json(std::get(value)) << "\""; + } + else if (std::holds_alternative(value)) { + ss << std::defaultfloat << 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) ? "true" : "false"); + } + else if (std::holds_alternative(value)) { + ss << "null"; + } + else { + throw std::logic_error("variant alternative not handled"); + } + + ix++; + } + ss << " }"; + + return ss.str(); +} + std::string RemoteControllers::get_param(const std::string& name, const std::string& param) { RemoteControllable* controllable = get_controllable_(name); return controllable->get_parameter(param); @@ -427,10 +497,15 @@ void RemoteControllerZmq::recv_all(zmq::socket_t& pSocket, std::vector +#include +#include #include #include #include @@ -113,13 +115,16 @@ class RemoteControllable { } /* Base function to set parameters. */ - virtual void set_parameter( - const std::string& parameter, - const std::string& value) = 0; + virtual void set_parameter(const std::string& parameter, const std::string& value) = 0; /* Getting a parameter always returns a string. */ virtual const std::string get_parameter(const std::string& parameter) const = 0; + using value_t = std::variant; + using map_t = std::unordered_map; + + virtual const map_t get_all_values() const = 0; + protected: std::string m_rc_name; std::list< std::vector > m_parameters; @@ -135,6 +140,7 @@ class RemoteControllers { void check_faults(); std::list< std::vector > get_param_list_values(const std::string& name); std::string get_param(const std::string& name, const std::string& param); + std::string get_params_json(const std::string& name); void set_param( const std::string& name, diff --git a/src/DabModulator.cpp b/src/DabModulator.cpp index 3d8bd46..67764ba 100644 --- a/src/DabModulator.cpp +++ b/src/DabModulator.cpp @@ -410,3 +410,10 @@ const string DabModulator::get_parameter(const string& parameter) const } return ss.str(); } + +const RemoteControllable::map_t DabModulator::get_all_values() const +{ + map_t map; + map["rate"] = m_settings.outputRate; + return map; +} diff --git a/src/DabModulator.h b/src/DabModulator.h index 00d71f5..0d46e9f 100644 --- a/src/DabModulator.h +++ b/src/DabModulator.h @@ -3,7 +3,7 @@ Her Majesty the Queen in Right of Canada (Communications Research Center Canada) - Copyright (C) 2019 + Copyright (C) 2023 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org @@ -60,11 +60,9 @@ public: EtiSource* getEtiSource() { return &myEtiSource; } /******* REMOTE CONTROL ********/ - virtual void set_parameter(const std::string& parameter, - const std::string& value) override; - - virtual const std::string get_parameter( - const std::string& parameter) const override; + virtual void set_parameter(const std::string& parameter, const std::string& value) override; + virtual const std::string get_parameter(const std::string& parameter) const override; + virtual const RemoteControllable::map_t get_all_values() const override; protected: void setMode(unsigned mode); diff --git a/src/FIRFilter.cpp b/src/FIRFilter.cpp index 89cf0da..523d405 100644 --- a/src/FIRFilter.cpp +++ b/src/FIRFilter.cpp @@ -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) 2017 + Copyright (C) 2023 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org @@ -347,3 +347,10 @@ const string FIRFilter::get_parameter(const string& parameter) const return ss.str(); } +const RemoteControllable::map_t FIRFilter::get_all_values() const +{ + map_t map; + map["ntaps"] = m_taps.size(); + map["tapsfile"] = m_taps_file; + return map; +} diff --git a/src/FIRFilter.h b/src/FIRFilter.h index 8d2e707..2a469aa 100644 --- a/src/FIRFilter.h +++ b/src/FIRFilter.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) 2017 + Copyright (C) 2023 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org @@ -59,11 +59,9 @@ public: const char* name() override { return "FIRFilter"; } /******* REMOTE CONTROL ********/ - virtual void set_parameter(const std::string& parameter, - const std::string& value) override; - - virtual const std::string get_parameter( - const std::string& parameter) const override; + virtual void set_parameter(const std::string& parameter, const std::string& value) override; + virtual const std::string get_parameter(const std::string& parameter) const override; + virtual const RemoteControllable::map_t get_all_values() const override; protected: virtual int internal_process(Buffer* const dataIn, Buffer* dataOut) override; diff --git a/src/GainControl.cpp b/src/GainControl.cpp index b781640..c111de3 100644 --- a/src/GainControl.cpp +++ b/src/GainControl.cpp @@ -3,7 +3,7 @@ Her Majesty the Queen in Right of Canada (Communications Research Center Canada) - Copyright (C) 2017 + Copyright (C) 2023 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org @@ -574,3 +574,21 @@ const string GainControl::get_parameter(const string& parameter) const return ss.str(); } +const RemoteControllable::map_t GainControl::get_all_values() const +{ + map_t map; + map["digital"] = m_digGain; + switch (m_gainmode) { + case GainMode::GAIN_FIX: + map["mode"] = "fix"; + break; + case GainMode::GAIN_MAX: + map["mode"] = "max"; + break; + case GainMode::GAIN_VAR: + map["mode"] = "var"; + break; + } + map["var"] = m_var_variance_rc; + return map; +} diff --git a/src/GainControl.h b/src/GainControl.h index 4c9a2bc..f0fd6b6 100644 --- a/src/GainControl.h +++ b/src/GainControl.h @@ -3,7 +3,7 @@ Her Majesty the Queen in Right of Canada (Communications Research Center Canada) - Copyright (C) 2017 + Copyright (C) 2023 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org @@ -63,13 +63,9 @@ class GainControl : public PipelinedModCodec, public RemoteControllable const char* name() override { return "GainControl"; } /* Functions for the remote control */ - /* Base function to set parameters. */ - virtual void set_parameter(const std::string& parameter, - const std::string& value) override; - - /* Getting a parameter always returns a string. */ - virtual const std::string get_parameter( - const std::string& parameter) const override; + virtual void set_parameter(const std::string& parameter, const std::string& value) override; + virtual const std::string get_parameter(const std::string& parameter) const override; + virtual const RemoteControllable::map_t get_all_values() const override; protected: virtual int internal_process( diff --git a/src/GuardIntervalInserter.cpp b/src/GuardIntervalInserter.cpp index 0cd5bd5..d5c71fb 100644 --- a/src/GuardIntervalInserter.cpp +++ b/src/GuardIntervalInserter.cpp @@ -2,7 +2,7 @@ Copyright (C) 2005, 2206, 2007, 2008, 2009, 2010, 2011 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) - Copyright (C) 2017 + Copyright (C) 2023 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org @@ -302,3 +302,10 @@ const std::string GuardIntervalInserter::get_parameter(const std::string& parame } return ss.str(); } + +const RemoteControllable::map_t GuardIntervalInserter::get_all_values() const +{ + map_t map; + map["windowlen"] = d_windowOverlap; + return map; +} diff --git a/src/GuardIntervalInserter.h b/src/GuardIntervalInserter.h index 02ba72c..f88bdac 100644 --- a/src/GuardIntervalInserter.h +++ b/src/GuardIntervalInserter.h @@ -2,7 +2,7 @@ Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) - Copyright (C) 2017 + Copyright (C) 2023 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org @@ -52,15 +52,13 @@ class GuardIntervalInserter : public ModCodec, public RemoteControllable size_t symSize, size_t& windowOverlap); - int process(Buffer* const dataIn, Buffer* dataOut); - const char* name() { return "GuardIntervalInserter"; } + int process(Buffer* const dataIn, Buffer* dataOut) override; + const char* name() override { return "GuardIntervalInserter"; } /******* REMOTE CONTROL ********/ - virtual void set_parameter(const std::string& parameter, - const std::string& value); - - virtual const std::string get_parameter( - const std::string& parameter) const; + virtual void set_parameter(const std::string& parameter, const std::string& value) override; + virtual const std::string get_parameter(const std::string& parameter) const override; + virtual const RemoteControllable::map_t get_all_values() const override; protected: void update_window(size_t new_window_overlap); diff --git a/src/MemlessPoly.cpp b/src/MemlessPoly.cpp index 905ca67..4801ba0 100644 --- a/src/MemlessPoly.cpp +++ b/src/MemlessPoly.cpp @@ -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 Andreas Steger, andreas.steger@digris.ch @@ -467,3 +467,11 @@ const string MemlessPoly::get_parameter(const string& parameter) const return ss.str(); } +const RemoteControllable::map_t MemlessPoly::get_all_values() const +{ + map_t map; + map["ncoefs"] = m_coefs_am.size(); + map["coefs"] = serialise_coefficients(); + map["coeffile"] = m_coefs_file; + return map; +} diff --git a/src/MemlessPoly.h b/src/MemlessPoly.h index 4642596..09adc13 100644 --- a/src/MemlessPoly.h +++ b/src/MemlessPoly.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 http://opendigitalradio.org @@ -63,17 +63,15 @@ public: MemlessPoly& operator=(const MemlessPoly& other) = delete; virtual ~MemlessPoly(); - virtual const char* name() { return "MemlessPoly"; } + virtual const char* name() override { return "MemlessPoly"; } /******* REMOTE CONTROL ********/ - virtual void set_parameter(const std::string& parameter, - const std::string& value); - - virtual const std::string get_parameter( - const std::string& parameter) const; + virtual void set_parameter(const std::string& parameter, const std::string& value) override; + virtual const std::string get_parameter(const std::string& parameter) const override; + virtual const RemoteControllable::map_t get_all_values() const override; private: - int internal_process(Buffer* const dataIn, Buffer* dataOut); + int internal_process(Buffer* const dataIn, Buffer* dataOut) override; void load_coefficients(std::istream& coefData); std::string serialise_coefficients() const; diff --git a/src/OfdmGenerator.cpp b/src/OfdmGenerator.cpp index 2e68df0..d161861 100644 --- a/src/OfdmGenerator.cpp +++ b/src/OfdmGenerator.cpp @@ -2,7 +2,7 @@ Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) - Copyright (C) 2017 + Copyright (C) 2023 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org @@ -457,3 +457,10 @@ const std::string OfdmGenerator::get_parameter(const std::string& parameter) con } return ss.str(); } + +const RemoteControllable::map_t OfdmGenerator::get_all_values() const +{ + map_t map; + // TODO needs rework of the values + return map; +} diff --git a/src/OfdmGenerator.h b/src/OfdmGenerator.h index 30fdff4..90e562a 100644 --- a/src/OfdmGenerator.h +++ b/src/OfdmGenerator.h @@ -2,7 +2,7 @@ Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) - Copyright (C) 2017 + Copyright (C) 2023 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org @@ -59,14 +59,9 @@ class OfdmGenerator : public ModCodec, public RemoteControllable const char* name() override { return "OfdmGenerator"; } /* Functions for the remote control */ - /* Base function to set parameters. */ - virtual void set_parameter( - const std::string& parameter, - const std::string& value) override; - - /* Getting a parameter always returns a string. */ - virtual const std::string get_parameter( - const std::string& parameter) const override; + virtual void set_parameter(const std::string& parameter, const std::string& value) override; + virtual const std::string get_parameter(const std::string& parameter) const override; + virtual const RemoteControllable::map_t get_all_values() const override; protected: struct cfr_iter_stat_t { diff --git a/src/TII.cpp b/src/TII.cpp index 904f3ff..b329cdb 100644 --- a/src/TII.cpp +++ b/src/TII.cpp @@ -2,7 +2,7 @@ Copyright (C) 2005, 2006, 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 http://opendigitalradio.org @@ -385,3 +385,12 @@ const std::string TII::get_parameter(const std::string& parameter) const return ss.str(); } +const RemoteControllable::map_t TII::get_all_values() const +{ + 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; + return map; +} diff --git a/src/TII.h b/src/TII.h index d8c785d..b0ba646 100644 --- a/src/TII.h +++ b/src/TII.h @@ -2,7 +2,7 @@ Copyright (C) 2005, 2006, 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 http://opendigitalradio.org @@ -83,15 +83,13 @@ class TII : public ModCodec, public RemoteControllable public: TII(unsigned int dabmode, tii_config_t& tii_config); - int process(Buffer* dataIn, Buffer* dataOut); - const char* name(); + int process(Buffer* dataIn, Buffer* dataOut) override; + const char* name() override; /******* REMOTE CONTROL ********/ - virtual void set_parameter(const std::string& parameter, - const std::string& value); - - virtual const std::string get_parameter( - const std::string& parameter) const; + virtual void set_parameter(const std::string& parameter, const std::string& value) override; + virtual const std::string get_parameter(const std::string& parameter) const override; + virtual const RemoteControllable::map_t get_all_values() const override; protected: // Fill m_Acp with the correct carriers for the pattern/comb diff --git a/src/TimestampDecoder.cpp b/src/TimestampDecoder.cpp index 6e97af6..149cd50 100644 --- a/src/TimestampDecoder.cpp +++ b/src/TimestampDecoder.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) 2018 + Copyright (C) 2023 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org @@ -301,3 +301,22 @@ const std::string TimestampDecoder::get_parameter( return ss.str(); } +const RemoteControllable::map_t TimestampDecoder::get_all_values() const +{ + map_t map; + map["offset"] = timestamp_offset; + if (full_timestamp_received) { + map["timestamp"] = time_secs + ((double)time_pps / 16384000.0); + } + else { + map["timestamp"] = std::nullopt; + } + + if (full_timestamp_received) { + map["timestamp0"] = time_secs_of_frame0 + ((double)time_pps_of_frame0 / 16384000.0); + } + else { + map["timestamp0"] = std::nullopt; + } + return map; +} diff --git a/src/TimestampDecoder.h b/src/TimestampDecoder.h index 597b777..dc5aa78 100644 --- a/src/TimestampDecoder.h +++ b/src/TimestampDecoder.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 @@ -118,16 +118,12 @@ class TimestampDecoder : public RemoteControllable /*********** REMOTE CONTROL ***************/ /* Base function to set parameters. */ - virtual void set_parameter(const std::string& parameter, - const std::string& value); - - /* Getting a parameter always returns a string. */ - virtual const std::string get_parameter( - const std::string& parameter) const; + virtual void set_parameter(const std::string& parameter, const std::string& value) override; + virtual const std::string get_parameter(const std::string& parameter) const override; + virtual const RemoteControllable::map_t get_all_values() const override; const char* name() { return "TS"; } - protected: /* Push a new MNSC field into the decoder */ void pushMNSCData(uint8_t framephase, uint16_t mnsc); diff --git a/src/output/SDR.cpp b/src/output/SDR.cpp index cc080dc..860d8ed 100644 --- a/src/output/SDR.cpp +++ b/src/output/SDR.cpp @@ -491,6 +491,9 @@ const string SDR::get_parameter(const string& parameter) const else if (std::holds_alternative(value)) { ss << (std::get(value) ? 1 : 0); } + else if (std::holds_alternative(value)) { + ss << ""; + } else { throw std::logic_error("variant alternative not handled"); } @@ -507,4 +510,27 @@ const string SDR::get_parameter(const string& parameter) const return ss.str(); } +const RemoteControllable::map_t SDR::get_all_values() const +{ + 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; + + if (m_device) { + const std::optional temp = m_device->get_temperature(); + if (temp) { + stat["temp"] = *temp; + } + } + stat["queued_frames_ms"] = m_queue.size() * + (size_t)chrono::duration_cast(transmission_frame_duration(m_config.dabMode)) + .count(); + + return stat; +} + } // namespace Output diff --git a/src/output/SDR.h b/src/output/SDR.h index eb0ed9d..9f08348 100644 --- a/src/output/SDR.h +++ b/src/output/SDR.h @@ -67,6 +67,8 @@ class SDR : public ModOutput, public ModMetadata, public RemoteControllable { virtual const std::string get_parameter( const std::string& parameter) const override; + virtual const RemoteControllable::map_t get_all_values() const override; + private: void process_thread_entry(void); void handle_frame(struct FrameData&& frame); diff --git a/src/output/SDRDevice.h b/src/output/SDRDevice.h index a4f551c..628372a 100644 --- a/src/output/SDRDevice.h +++ b/src/output/SDRDevice.h @@ -112,8 +112,7 @@ struct FrameData { // All SDR Devices must implement the SDRDevice interface class SDRDevice { public: - using run_statistic_t = std::variant; - using run_statistics_t = std::unordered_map; + using run_statistics_t = RemoteControllable::map_t; virtual void tune(double lo_offset, double frequency) = 0; virtual double get_tx_freq(void) const = 0; diff --git a/src/output/UHD.h b/src/output/UHD.h index 5823c0e..97a821e 100644 --- a/src/output/UHD.h +++ b/src/output/UHD.h @@ -55,14 +55,6 @@ DESCRIPTION: #include #include -// If the timestamp is further in the future than -// 100 seconds, abort -#define TIMESTAMP_ABORT_FUTURE 100 - -// Add a delay to increase buffers when -// frames are too far in the future -#define TIMESTAMP_MARGIN_FUTURE 0.5 - namespace Output { class UHD : public Output::SDRDevice -- cgit v1.2.3 From a65370dbbcafecedcedb4619a6a07a02ec2ff721 Mon Sep 17 00:00:00 2001 From: "Matthias P. Braendli" Date: Tue, 4 Apr 2023 14:43:41 +0200 Subject: Add json remote HTTP server --- doc/zmq-ctrl/json_remote_server.py | 131 +++++++++++++++++++++++++++++++++++++ 1 file changed, 131 insertions(+) create mode 100755 doc/zmq-ctrl/json_remote_server.py diff --git a/doc/zmq-ctrl/json_remote_server.py b/doc/zmq-ctrl/json_remote_server.py new file mode 100755 index 0000000..728cf7c --- /dev/null +++ b/doc/zmq-ctrl/json_remote_server.py @@ -0,0 +1,131 @@ +#!/usr/bin/env python +# +# This is an example program that illustrates +# how to interact with the zeromq remote control +# using JSON. +# +# LICENSE: see bottom of file + +import sys +import zmq +from pprint import pprint +import json +import re +from http.server import BaseHTTPRequestHandler, HTTPServer +import time + +re_url = re.compile(r"/([a-zA-Z0-9]+).json") + +ZMQ_REMOTE = "tcp://localhost:9400" +HTTP_HOSTNAME = "localhost" +HTTP_PORT = 8080 + +class DabMuxServer(BaseHTTPRequestHandler): + def err500(self, message): + self.send_response(500) + self.send_header("Content-type", "application/json") + self.end_headers() + self.wfile.write(json.dumps({"error": message}).encode()) + + def do_GET(self): + m = re_url.match(self.path) + if m: + sock = context.socket(zmq.REQ) + poller = zmq.Poller() + poller.register(sock, zmq.POLLIN) + sock.connect(ZMQ_REMOTE) + + sock.send(b"ping") + socks = dict(poller.poll(1000)) + if socks: + if socks.get(sock) == zmq.POLLIN: + data = sock.recv() + if data != b"ok": + print(f"Received {data} to ping!", file=sys.stderr) + self.err500("ping failure") + return + else: + print("ZMQ error: ping timeout", file=sys.stderr) + self.err500("ping timeout") + return + + sock.send(b"showjson", flags=zmq.SNDMORE) + sock.send(m.group(1).encode()) + + socks = dict(poller.poll(1000)) + if socks: + if socks.get(sock) == zmq.POLLIN: + data = sock.recv_multipart() + print("Received: {}".format(len(data)), file=sys.stderr) + parts = [] + for i, part_data in enumerate(data): + part = part_data.decode() + print(" RX {}: {}".format(i, part.replace('\n',' ')), file=sys.stderr) + + if i == 0 and part != "fail": + self.send_response(200) + self.send_header("Content-type", "application/json") + self.end_headers() + self.wfile.write(part_data) + return + parts.append(part) + self.err500("data error " + " ".join(parts)) + return + + else: + print("ZMQ error: timeout", file=sys.stderr) + self.err500("timeout") + return + else: + self.send_response(200) + self.send_header("Content-type", "text/html") + self.end_headers() + self.wfile.write("""ODR-DabMod RC HTTP server\n""".encode()) + self.wfile.write("""\n""".encode()) + for mod in ("sdr", "tist", "modulator", "tii", "ofdm", "gain", "guardinterval"): + self.wfile.write(f"""

{mod}.json

\n""".encode()) + self.wfile.write("""\n""".encode()) + + +if __name__ == "__main__": + context = zmq.Context() + + webServer = HTTPServer((HTTP_HOSTNAME, HTTP_PORT), DabMuxServer) + print("Server started http://%s:%s" % (HTTP_HOSTNAME, HTTP_PORT)) + + try: + webServer.serve_forever() + except KeyboardInterrupt: + pass + + webServer.server_close() + + context.destroy(linger=5) + + +# 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 + + -- cgit v1.2.3 From 0958aa73aab5d71c73771cf989c66d3b8d7bb6b8 Mon Sep 17 00:00:00 2001 From: "Matthias P. Braendli" Date: Wed, 26 Apr 2023 14:37:43 +0200 Subject: Move include unistd.h from Buffer.h to cpp --- src/Buffer.cpp | 1 + src/Buffer.h | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Buffer.cpp b/src/Buffer.cpp index 20f71f9..2c4b171 100644 --- a/src/Buffer.cpp +++ b/src/Buffer.cpp @@ -28,6 +28,7 @@ #include "Buffer.h" #include "PcDebug.h" +#include #include #include #include diff --git a/src/Buffer.h b/src/Buffer.h index d181a46..af52e93 100644 --- a/src/Buffer.h +++ b/src/Buffer.h @@ -31,7 +31,6 @@ # include #endif -#include #include #include -- cgit v1.2.3 From c7961c2e8688d6db2a87b5079c60a04b8fe37e74 Mon Sep 17 00:00:00 2001 From: "Matthias P. Braendli" Date: Wed, 26 Apr 2023 14:37:57 +0200 Subject: Normalise TII to 1/sqrt(48) --- src/TII.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/TII.cpp b/src/TII.cpp index b329cdb..2d7429f 100644 --- a/src/TII.cpp +++ b/src/TII.cpp @@ -189,6 +189,8 @@ int TII::process(Buffer* dataIn, Buffer* dataOut) complexf* in = reinterpret_cast(dataIn->getData()); complexf* out = reinterpret_cast(dataOut->getData()); + constexpr float normalisation = 0.144337f; // 1/sqrt(48); + /* Normalise the TII carrier power according to ETSI TR 101 496-3 * Clause 5.4.2.2 Paragraph 7: * @@ -196,8 +198,7 @@ int TII::process(Buffer* dataIn, Buffer* dataOut) * > is 1:48 for all Modes, so that the signal power in a TII symbol is * > 16 dB below the signal power of the other symbols. * - * This is because we only enable 32 out of 1536 carriers, not because - * every carrier is lower power. + * Divide by sqrt(48) because I and Q are separately normalised. */ for (size_t i = 0; i < m_Acp.size(); i++) { /* See header file for an explanation of the old variant. @@ -218,8 +219,8 @@ int TII::process(Buffer* dataIn, Buffer* dataOut) * and fuse the two conditionals together: */ if (m_Acp[i]) { - out[i] = in[i]; - out[i+1] = (m_conf.old_variant ? in[i+1] : in[i]); + out[i] = in[i] * normalisation; + out[i+1] = (m_conf.old_variant ? in[i+1] : in[i]) * normalisation; } } } -- cgit v1.2.3 From fd05ca739d94b23609e85e074fb659b5e279d358 Mon Sep 17 00:00:00 2001 From: "Matthias P. Braendli" Date: Wed, 26 Apr 2023 14:57:37 +0200 Subject: dexter: set frequency to ad9957, lo_offset to dsp --- src/output/Dexter.cpp | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/output/Dexter.cpp b/src/output/Dexter.cpp index 515d810..2d053aa 100644 --- a/src/output/Dexter.cpp +++ b/src/output/Dexter.cpp @@ -277,12 +277,18 @@ Dexter::~Dexter() void Dexter::tune(double lo_offset, double frequency) { - // TODO lo_offset - long long freq = m_conf.frequency - 204800000; + // lo_offset is applied to the DSP, and frequency is given to the ad9957 + + long long freq = frequency; int r = 0; + if ((r = iio_device_attr_write_longlong(m_ad9957_tx0, "center_frequency", freq)) != 0) { + etiLog.level(warn) << "Failed to set ad9957_tx0.center_frequency = " << freq << " : " << get_iio_error(r); + } + + long long lo_offs = lo_offset; - if ((r = iio_device_attr_write_longlong(m_dexter_dsp_tx, "frequency0", freq)) != 0) { - etiLog.level(warn) << "Failed to set dexter_dsp_tx.frequency0 = " << freq << " : " << get_iio_error(r); + if ((r = iio_device_attr_write_longlong(m_dexter_dsp_tx, "frequency0", lo_offs)) != 0) { + etiLog.level(warn) << "Failed to set dexter_dsp_tx.frequency0 = " << lo_offs << " : " << get_iio_error(r); } } -- cgit v1.2.3 From b17001011204a47432a1deb970cd15466c16f0bf Mon Sep 17 00:00:00 2001 From: "Matthias P. Braendli" Date: Wed, 26 Apr 2023 17:03:14 +0200 Subject: Properly set TX frequency --- src/output/Dexter.cpp | 25 +++++++++++++++++-------- src/output/Dexter.h | 1 + 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/src/output/Dexter.cpp b/src/output/Dexter.cpp index 2d053aa..4cb9cd8 100644 --- a/src/output/Dexter.cpp +++ b/src/output/Dexter.cpp @@ -87,6 +87,11 @@ Dexter::Dexter(SDRDeviceConfig& config) : throw std::runtime_error("Dexter: Unable to find dexter_dsp_tx iio device"); } + m_ad9957 = iio_context_find_device(m_ctx, "ad9957"); + if (not m_ad9957) { + throw std::runtime_error("Dexter: Unable to find ad9957 iio device"); + } + m_ad9957_tx0 = iio_context_find_device(m_ctx, "ad9957_tx0"); if (not m_ad9957_tx0) { throw std::runtime_error("Dexter: Unable to find ad9957_tx0 iio device"); @@ -281,8 +286,8 @@ void Dexter::tune(double lo_offset, double frequency) long long freq = frequency; int r = 0; - if ((r = iio_device_attr_write_longlong(m_ad9957_tx0, "center_frequency", freq)) != 0) { - etiLog.level(warn) << "Failed to set ad9957_tx0.center_frequency = " << freq << " : " << get_iio_error(r); + if ((r = iio_device_attr_write_longlong(m_ad9957, "center_frequency", freq)) != 0) { + etiLog.level(warn) << "Failed to set ad9957.center_frequency = " << freq << " : " << get_iio_error(r); } long long lo_offs = lo_offset; @@ -294,17 +299,21 @@ void Dexter::tune(double lo_offset, double frequency) double Dexter::get_tx_freq(void) const { - long long frequency = 0; + long long lo_offset = 0; int r = 0; - if ((r = iio_device_attr_read_longlong(m_dexter_dsp_tx, "frequency0", &frequency)) != 0) { - etiLog.level(warn) << "Failed to read dexter_dsp_tx.frequency0 = " << - frequency << " : " << get_iio_error(r); + if ((r = iio_device_attr_read_longlong(m_dexter_dsp_tx, "frequency0", &lo_offset)) != 0) { + etiLog.level(warn) << "Failed to read dexter_dsp_tx.frequency0: " << get_iio_error(r); return 0; } - else { - return frequency + 204800000; + + long long frequency = 0; + if ((r = iio_device_attr_read_longlong(m_ad9957, "center_frequency", &frequency)) != 0) { + etiLog.level(warn) << "Failed to read ad9957.center_frequency: " << get_iio_error(r); + return 0; } + + return frequency + lo_offset; } void Dexter::set_txgain(double txgain) diff --git a/src/output/Dexter.h b/src/output/Dexter.h index 7925de7..3d47f87 100644 --- a/src/output/Dexter.h +++ b/src/output/Dexter.h @@ -93,6 +93,7 @@ class Dexter : public Output::SDRDevice struct iio_context* m_ctx = nullptr; struct iio_device* m_dexter_dsp_tx = nullptr; + struct iio_device* m_ad9957 = nullptr; struct iio_device* m_ad9957_tx0 = nullptr; struct iio_channel* m_tx_channel = nullptr; struct iio_buffer *m_buffer = nullptr; -- cgit v1.2.3 From 830fb3ab0a8631055b2341b8dac50b937b6e99bb Mon Sep 17 00:00:00 2001 From: "Matthias P. Braendli" Date: Wed, 26 Apr 2023 18:08:54 +0200 Subject: Dexter: Add three clock states and handling --- src/ConfigParser.cpp | 14 ++- src/output/Dexter.cpp | 310 +++++++++++++++++++++++++++++++------------------- src/output/Dexter.h | 18 ++- 3 files changed, 217 insertions(+), 125 deletions(-) diff --git a/src/ConfigParser.cpp b/src/ConfigParser.cpp index 3e223c3..cb4dc24 100644 --- a/src/ConfigParser.cpp +++ b/src/ConfigParser.cpp @@ -278,7 +278,8 @@ static void parse_configfile( mod_settings.sdr_device_config = sdr_device_config; mod_settings.useUHDOutput = true; } -#endif +#endif // defined(HAVE_OUTPUT_UHD) + #if defined(HAVE_SOAPYSDR) else if (output_selected == "soapysdr") { auto& outputsoapy_conf = mod_settings.sdr_device_config; @@ -309,7 +310,8 @@ static void parse_configfile( mod_settings.useSoapyOutput = true; } -#endif +#endif // defined(HAVE_SOAPYSDR) + #if defined(HAVE_DEXTER) else if (output_selected == "dexter") { auto& outputdexter_conf = mod_settings.sdr_device_config; @@ -318,6 +320,7 @@ static void parse_configfile( outputdexter_conf.frequency = pt.GetReal("dexteroutput.frequency", 0); std::string chan = pt.Get("dexteroutput.channel", ""); outputdexter_conf.dabMode = mod_settings.dabMode; + outputdexter_conf.maxGPSHoldoverTime = pt.GetInteger("dexteroutput.max_gps_holdover_time", 0); if (outputdexter_conf.frequency == 0 && chan == "") { std::cerr << " dexter output enabled, but neither frequency nor channel defined.\n"; @@ -333,7 +336,8 @@ static void parse_configfile( mod_settings.useDexterOutput = true; } -#endif +#endif // defined(HAVE_DEXTER) + #if defined(HAVE_LIMESDR) else if (output_selected == "limesdr") { auto& outputlime_conf = mod_settings.sdr_device_config; @@ -363,7 +367,7 @@ static void parse_configfile( mod_settings.useLimeOutput = true; } -#endif +#endif // defined(HAVE_LIMESDR) #if defined(HAVE_BLADERF) else if (output_selected == "bladerf") { @@ -392,7 +396,7 @@ static void parse_configfile( mod_settings.useBladeRFOutput = true; } -#endif +#endif // defined(HAVE_BLADERF) #if defined(HAVE_ZEROMQ) else if (output_selected == "zmq") { diff --git a/src/output/Dexter.cpp b/src/output/Dexter.cpp index 4cb9cd8..59baf7e 100644 --- a/src/output/Dexter.cpp +++ b/src/output/Dexter.cpp @@ -120,58 +120,6 @@ Dexter::Dexter(SDRDeviceConfig& config) : // skip: antenna - // get H/W time - /* Procedure: - * Wait 200ms after second change, fetch pps_clks attribute - * idem at the next second, and check that pps_clks incremented by DSP_CLOCK - * If ok, store the correspondence between current second change (measured in UTC clock time) - * and the counter value at pps rising edge. */ - - etiLog.level(info) << "Dexter: Waiting for second change..."; - - struct timespec time_at_startup; - fill_time(&time_at_startup); - time_at_startup.tv_nsec = 0; - - struct timespec time_now; - do { - fill_time(&time_now); - this_thread::sleep_for(chrono::milliseconds(1)); - } while (time_at_startup.tv_sec == time_now.tv_sec); - this_thread::sleep_for(chrono::milliseconds(200)); - - long long pps_clks = 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); - throw std::runtime_error("Dexter: Cannot read IIO attribute"); - } - - time_t tnow = time_now.tv_sec; - etiLog.level(info) << "Dexter: pps_clks " << pps_clks << " at UTC " << - put_time(std::gmtime(&tnow), "%Y-%m-%d %H:%M:%S"); - - time_at_startup.tv_sec = time_now.tv_sec; - do { - fill_time(&time_now); - this_thread::sleep_for(chrono::milliseconds(1)); - } while (time_at_startup.tv_sec == time_now.tv_sec); - this_thread::sleep_for(chrono::milliseconds(200)); - - long long pps_clks2 = 0; - if ((r = iio_device_attr_read_longlong(m_dexter_dsp_tx, "pps_clks", &pps_clks2)) != 0) { - etiLog.level(error) << "Failed to get dexter_dsp_tx.pps_clks: " << get_iio_error(r); - throw std::runtime_error("Dexter: Cannot read IIO attribute"); - } - tnow = time_now.tv_sec; - etiLog.level(info) << "Dexter: pps_clks increased by " << pps_clks2 - pps_clks << " at UTC " << - put_time(std::gmtime(&tnow), "%Y-%m-%d %H:%M:%S"); - - if ((uint64_t)pps_clks + DSP_CLOCK != (uint64_t)pps_clks2) { - throw std::runtime_error("Dexter: Wrong increase of pps_clks, expected " + to_string(DSP_CLOCK)); - } - m_utc_seconds_at_startup = time_now.tv_sec; - m_clock_count_at_startup = pps_clks2; - // 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)); @@ -248,35 +196,118 @@ void Dexter::channel_down() etiLog.level(debug) << "DEXTER CHANNEL_DOWN"; } - -Dexter::~Dexter() +void Dexter::handle_hw_time() { - m_running = false; - if (m_underflow_read_thread.joinable()) { - m_underflow_read_thread.join(); - } - - if (m_ctx) { - if (m_dexter_dsp_tx) { - iio_device_attr_write_longlong(m_dexter_dsp_tx, "gain0", 0); - } - - if (m_buffer) { - iio_buffer_destroy(m_buffer); - m_buffer = nullptr; - } - - if (m_tx_channel) { - iio_channel_disable(m_tx_channel); - } - - iio_context_destroy(m_ctx); - m_ctx = nullptr; - } + /* + * On startup, wait until `gpsdo_locked==1` and `pps_loss_of_signal==0`, + * then do the clocks alignment and go to normal state. + * + * In normal state, if `pps_loss_of_signal==1`, go to holdover state. + * + * If we've been in holdover state for longer than the configured time, or + * if `pps_loss_of_signal==0` stop the mod and restart. + */ + int r; - if (m_underflow_ctx) { - iio_context_destroy(m_underflow_ctx); - m_underflow_ctx = nullptr; + switch (m_clock_state) { + case DexterClockState::Startup: + { + long long gpsdo_locked = 0; + if ((r = iio_device_attr_read_longlong(m_dexter_dsp_tx, "gpsdo_locked", &gpsdo_locked)) != 0) { + etiLog.level(error) << "Failed to get dexter_dsp_tx.gpsdo_locked: " << get_iio_error(r); + throw std::runtime_error("Dexter: Cannot read IIO attribute"); + } + + 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"); + } + + if (gpsdo_locked == 1 and pps_loss_of_signal == 0) { + /* Procedure: + * Wait 200ms after second change, fetch pps_clks attribute + * idem at the next second, and check that pps_clks incremented by DSP_CLOCK + * If ok, store the correspondence between current second change (measured in UTC clock time) + * and the counter value at pps rising edge. */ + + etiLog.level(info) << "Dexter: Waiting for second change..."; + + struct timespec time_at_startup; + fill_time(&time_at_startup); + time_at_startup.tv_nsec = 0; + + struct timespec time_now; + do { + fill_time(&time_now); + this_thread::sleep_for(chrono::milliseconds(1)); + } while (time_at_startup.tv_sec == time_now.tv_sec); + this_thread::sleep_for(chrono::milliseconds(200)); + + long long pps_clks = 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); + throw std::runtime_error("Dexter: Cannot read IIO attribute"); + } + + time_t tnow = time_now.tv_sec; + etiLog.level(info) << "Dexter: pps_clks " << pps_clks << " at UTC " << + put_time(std::gmtime(&tnow), "%Y-%m-%d %H:%M:%S"); + + time_at_startup.tv_sec = time_now.tv_sec; + do { + fill_time(&time_now); + this_thread::sleep_for(chrono::milliseconds(1)); + } while (time_at_startup.tv_sec == time_now.tv_sec); + this_thread::sleep_for(chrono::milliseconds(200)); + + long long pps_clks2 = 0; + if ((r = iio_device_attr_read_longlong(m_dexter_dsp_tx, "pps_clks", &pps_clks2)) != 0) { + etiLog.level(error) << "Failed to get dexter_dsp_tx.pps_clks: " << get_iio_error(r); + throw std::runtime_error("Dexter: Cannot read IIO attribute"); + } + tnow = time_now.tv_sec; + etiLog.level(info) << "Dexter: pps_clks increased by " << pps_clks2 - pps_clks << " at UTC " << + put_time(std::gmtime(&tnow), "%Y-%m-%d %H:%M:%S"); + + if ((uint64_t)pps_clks + DSP_CLOCK != (uint64_t)pps_clks2) { + throw std::runtime_error("Dexter: Wrong increase of pps_clks, expected " + to_string(DSP_CLOCK)); + } + + 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_clock_state = DexterClockState::Normal; + } + } + break; + case DexterClockState::Normal: + { + 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"); + } + + if (pps_loss_of_signal == 1) { + m_holdover_since = chrono::steady_clock::now(); + m_clock_state = DexterClockState::Holdover; + } + } + break; + case DexterClockState::Holdover: + { + using namespace chrono; + const duration d = steady_clock::now() - m_holdover_since; + const auto max_holdover_duration = seconds(m_conf.maxGPSHoldoverTime); + if (d > max_holdover_duration) { + 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(); + } + } + break; } } @@ -526,7 +557,14 @@ double Dexter::get_real_secs(void) const throw std::runtime_error("Dexter: Cannot read IIO attribute"); } - return (double)m_utc_seconds_at_startup + (double)(clks - m_clock_count_at_startup) / (double)DSP_CLOCK; + switch (m_clock_state) { + case DexterClockState::Startup: + return 0; + case DexterClockState::Normal: + case DexterClockState::Holdover: + return (double)m_utc_seconds_at_startup + (double)(clks - m_clock_count_at_startup) / (double)DSP_CLOCK; + } + throw std::logic_error("Unhandled switch"); } void Dexter::set_rxgain(double rxgain) @@ -585,46 +623,53 @@ 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) { - 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_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"); + if (m_clock_state == DexterClockState::Startup) { + return; // not ready } - - 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_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) { - 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_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; + else { + 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_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_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) { + 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_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; + } + m_require_timestamp_refresh = false; } - m_require_timestamp_refresh = false; } channel_up(); @@ -704,8 +749,37 @@ void Dexter::underflow_read_process() m_running = false; } -} // namespace Output +Dexter::~Dexter() +{ + m_running = false; + if (m_underflow_read_thread.joinable()) { + m_underflow_read_thread.join(); + } -#endif // HAVE_DEXTER + if (m_ctx) { + if (m_dexter_dsp_tx) { + iio_device_attr_write_longlong(m_dexter_dsp_tx, "gain0", 0); + } + if (m_buffer) { + iio_buffer_destroy(m_buffer); + 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; + } +} + +} // namespace Output + +#endif // HAVE_DEXTER diff --git a/src/output/Dexter.h b/src/output/Dexter.h index 3d47f87..f70bb14 100644 --- a/src/output/Dexter.h +++ b/src/output/Dexter.h @@ -42,6 +42,7 @@ DESCRIPTION: #include #include #include +#include #include "output/SDR.h" #include "ModPlugin.h" @@ -50,6 +51,12 @@ DESCRIPTION: namespace Output { +enum class DexterClockState { + Startup, + Normal, + Holdover +}; + class Dexter : public Output::SDRDevice { public: @@ -85,6 +92,7 @@ class Dexter : public Output::SDRDevice private: void channel_up(); void channel_down(); + void handle_hw_time(); bool m_channel_is_up = false; @@ -112,9 +120,15 @@ class Dexter : public Output::SDRDevice size_t num_buffers_pushed = 0; - uint64_t m_utc_seconds_at_startup; + DexterClockState m_clock_state = DexterClockState::Startup; + + // Only valid when m_clock_state is not Startup + uint64_t m_utc_seconds_at_startup = 0; uint64_t m_clock_count_at_startup = 0; - uint64_t m_clock_count_frame = 0; + + // Only valid when m_clock_state Holdover + std::chrono::steady_clock::time_point m_holdover_since = + std::chrono::steady_clock::time_point::min(); }; } // namespace Output -- 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(-) 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 8bb7afbdc7cab7010c5bfb5a0b750a62d953aedf Mon Sep 17 00:00:00 2001 From: "Matthias P. Braendli" Date: Thu, 4 May 2023 13:27:10 +0200 Subject: Revert "Normalise TII to 1/sqrt(48)" and fix TII level in GainControl This reverts commit c7961c2e8688d6db2a87b5079c60a04b8fe37e74. --- src/GainControl.cpp | 23 ++++++++++++++++------- src/GainControl.h | 1 + src/TII.cpp | 9 ++++----- 3 files changed, 21 insertions(+), 12 deletions(-) diff --git a/src/GainControl.cpp b/src/GainControl.cpp index c111de3..d90da45 100644 --- a/src/GainControl.cpp +++ b/src/GainControl.cpp @@ -127,21 +127,25 @@ int GainControl::internal_process(Buffer* const dataIn, Buffer* dataOut) if ((sizeIn % m_frameSize) != 0) { PDEBUG("%zu != %zu\n", sizeIn, m_frameSize); - throw std::runtime_error( - "GainControl::process input size not valid!"); + throw std::runtime_error("GainControl::process input size not valid!"); } const auto constantGain4 = _mm_set1_ps(constantGain); for (size_t i = 0; i < sizeIn; i += m_frameSize) { - gain128.m = computeGain(in, m_frameSize); + // Do not apply gain computation to the NULL symbol, which either + // is blank or contains TII. Apply the gain calculation from the next + // symbol on the NULL symbol to get consistent TII power. + if (i > 0) { + gain128.m = computeGain(in, m_frameSize); + } + else { + gain128.m = computeGain(in + m_frameSize, m_frameSize); + } gain128.m = _mm_mul_ps(gain128.m, constantGain4); PDEBUG("********** Gain: %10f **********\n", gain128.f[0]); - //////////////////////////////////////////////////////////////////////// - // Applying gain to output data - //////////////////////////////////////////////////////////////////////// for (size_t sample = 0; sample < m_frameSize; ++sample) { out[sample] = _mm_mul_ps(in[sample], gain128.m); } @@ -163,7 +167,12 @@ int GainControl::internal_process(Buffer* const dataIn, Buffer* dataOut) } for (size_t i = 0; i < sizeIn; i += m_frameSize) { - gain = constantGain * computeGain(in, m_frameSize); + // Do not apply gain computation to the NULL symbol, which either + // is blank or contains TII. Apply the gain calculation from the next + // symbol on the NULL symbol to get consistent TII power. + gain = constantGain * (i > 0 ? + computeGain(in, m_frameSize) : + computeGain(in + m_frameSize, m_frameSize)); PDEBUG("********** Gain: %10f **********\n", gain); diff --git a/src/GainControl.h b/src/GainControl.h index f0fd6b6..f024fa2 100644 --- a/src/GainControl.h +++ b/src/GainControl.h @@ -38,6 +38,7 @@ #include #include #include + #ifdef __SSE__ # include #endif diff --git a/src/TII.cpp b/src/TII.cpp index 2d7429f..b329cdb 100644 --- a/src/TII.cpp +++ b/src/TII.cpp @@ -189,8 +189,6 @@ int TII::process(Buffer* dataIn, Buffer* dataOut) complexf* in = reinterpret_cast(dataIn->getData()); complexf* out = reinterpret_cast(dataOut->getData()); - constexpr float normalisation = 0.144337f; // 1/sqrt(48); - /* Normalise the TII carrier power according to ETSI TR 101 496-3 * Clause 5.4.2.2 Paragraph 7: * @@ -198,7 +196,8 @@ int TII::process(Buffer* dataIn, Buffer* dataOut) * > is 1:48 for all Modes, so that the signal power in a TII symbol is * > 16 dB below the signal power of the other symbols. * - * Divide by sqrt(48) because I and Q are separately normalised. + * This is because we only enable 32 out of 1536 carriers, not because + * every carrier is lower power. */ for (size_t i = 0; i < m_Acp.size(); i++) { /* See header file for an explanation of the old variant. @@ -219,8 +218,8 @@ int TII::process(Buffer* dataIn, Buffer* dataOut) * and fuse the two conditionals together: */ if (m_Acp[i]) { - out[i] = in[i] * normalisation; - out[i+1] = (m_conf.old_variant ? in[i+1] : in[i]) * normalisation; + out[i] = in[i]; + out[i+1] = (m_conf.old_variant ? in[i+1] : in[i]); } } } -- cgit v1.2.3 From 829863ab82ca3743c64c76310af5eebcaab1fc84 Mon Sep 17 00:00:00 2001 From: "Matthias P. Braendli" Date: Fri, 5 May 2023 14:26:16 +0200 Subject: Dexter: Only do handle_hw_time when synchronous is enabled --- src/output/Dexter.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/output/Dexter.cpp b/src/output/Dexter.cpp index 25de030..c1d8d2f 100644 --- a/src/output/Dexter.cpp +++ b/src/output/Dexter.cpp @@ -620,9 +620,13 @@ size_t Dexter::receive_frame( bool Dexter::is_clk_source_ok() { - handle_hw_time(); - - return m_clock_state != DexterClockState::Startup; + if (m_conf.enableSync) { + handle_hw_time(); + return m_clock_state != DexterClockState::Startup; + } + else { + return true; + } } const char* Dexter::device_name(void) const -- cgit v1.2.3 From 1d07999d373b0fb0b67de576aa4d7b10308b4150 Mon Sep 17 00:00:00 2001 From: "Matthias P. Braendli" Date: Tue, 16 May 2023 22:04:21 +0200 Subject: Avoid undefined format conversion and measure clipping --- src/DabMod.cpp | 28 ++++---- src/DabModulator.cpp | 170 +++++++++++++++++++++++++++--------------------- src/DabModulator.h | 26 ++++---- src/FormatConverter.cpp | 60 ++++++++++++++--- src/FormatConverter.h | 7 +- 5 files changed, 181 insertions(+), 110 deletions(-) diff --git a/src/DabMod.cpp b/src/DabMod.cpp index e5436ad..805fab5 100644 --- a/src/DabMod.cpp +++ b/src/DabMod.cpp @@ -364,19 +364,25 @@ int launch_modulator(int argc, char* argv[]) etiLog.level(debug) << "FFTW planning done."; } - shared_ptr format_converter; + std::string output_format; if (mod_settings.useFileOutput and (mod_settings.fileOutputFormat == "s8" or mod_settings.fileOutputFormat == "u8" or mod_settings.fileOutputFormat == "s16")) { - format_converter = make_shared(mod_settings.fileOutputFormat); + output_format = mod_settings.fileOutputFormat; } else if (mod_settings.useBladeRFOutput or mod_settings.useDexterOutput) { - format_converter = make_shared("s16"); + output_format = "s16"; } auto output = prepare_output(mod_settings); + if (not output_format.empty()) { + if (auto o = dynamic_pointer_cast(output)) { + o->set_sample_size(FormatConverter::get_format_size(output_format)); + } + } + // Set thread priority to realtime if (int r = set_realtime_prio(1)) { etiLog.level(error) << "Could not set priority for modulator:" << r; @@ -426,25 +432,15 @@ int launch_modulator(int argc, char* argv[]) shared_ptr modulator; if (inputReader) { m.etiReader = make_shared(mod_settings.tist_offset_s); - modulator = make_shared(*m.etiReader, mod_settings); + modulator = make_shared(*m.etiReader, mod_settings, output_format); } else if (ediInput) { - modulator = make_shared(ediInput->ediReader, mod_settings); + modulator = make_shared(ediInput->ediReader, mod_settings, output_format); } rcs.enrol(modulator.get()); - if (format_converter) { - flowgraph.connect(modulator, format_converter); - flowgraph.connect(format_converter, output); - - if (auto o = dynamic_pointer_cast(output)) { - o->set_sample_size(format_converter->get_format_size()); - } - } - else { - flowgraph.connect(modulator, output); - } + flowgraph.connect(modulator, output); if (inputReader) { etiLog.level(info) << inputReader->GetPrintableInfo(); diff --git a/src/DabModulator.cpp b/src/DabModulator.cpp index 64ebf03..5213d8d 100644 --- a/src/DabModulator.cpp +++ b/src/DabModulator.cpp @@ -59,19 +59,22 @@ using namespace std; DabModulator::DabModulator(EtiSource& etiSource, - mod_settings_t& settings) : + mod_settings_t& settings, + const std::string& format) : ModInput(), RemoteControllable("modulator"), m_settings(settings), - myEtiSource(etiSource), - myFlowgraph() + m_format(format), + m_etiSource(etiSource), + m_flowgraph() { PDEBUG("DabModulator::DabModulator() @ %p\n", this); RC_ADD_PARAMETER(rate, "(Read-only) IQ output samplerate"); + RC_ADD_PARAMETER(num_clipped_samples, "(Read-only) Number of samples clipped in last frame during format conversion"); if (m_settings.dabMode == 0) { - setMode(2); + setMode(1); } else { setMode(m_settings.dabMode); @@ -83,36 +86,36 @@ void DabModulator::setMode(unsigned mode) { switch (mode) { case 1: - myNbSymbols = 76; - myNbCarriers = 1536; - mySpacing = 2048; - myNullSize = 2656; - mySymSize = 2552; - myFicSizeOut = 288; + m_nbSymbols = 76; + m_nbCarriers = 1536; + m_spacing = 2048; + m_nullSize = 2656; + m_symSize = 2552; + m_ficSizeOut = 288; break; case 2: - myNbSymbols = 76; - myNbCarriers = 384; - mySpacing = 512; - myNullSize = 664; - mySymSize = 638; - myFicSizeOut = 288; + m_nbSymbols = 76; + m_nbCarriers = 384; + m_spacing = 512; + m_nullSize = 664; + m_symSize = 638; + m_ficSizeOut = 288; break; case 3: - myNbSymbols = 153; - myNbCarriers = 192; - mySpacing = 256; - myNullSize = 345; - mySymSize = 319; - myFicSizeOut = 384; + m_nbSymbols = 153; + m_nbCarriers = 192; + m_spacing = 256; + m_nullSize = 345; + m_symSize = 319; + m_ficSizeOut = 384; break; case 4: - myNbSymbols = 76; - myNbCarriers = 768; - mySpacing = 1024; - myNullSize = 1328; - mySymSize = 1276; - myFicSizeOut = 288; + m_nbSymbols = 76; + m_nbCarriers = 768; + m_spacing = 1024; + m_nullSize = 1328; + m_symSize = 1276; + m_ficSizeOut = 288; break; default: throw std::runtime_error("DabModulator::setMode invalid mode size"); @@ -126,27 +129,27 @@ int DabModulator::process(Buffer* dataOut) PDEBUG("DabModulator::process(dataOut: %p)\n", dataOut); - if (not myFlowgraph) { + if (not m_flowgraph) { etiLog.level(debug) << "Setting up DabModulator..."; const unsigned mode = m_settings.dabMode; setMode(mode); - myFlowgraph = make_shared(m_settings.showProcessTime); + m_flowgraph = make_shared(m_settings.showProcessTime); //////////////////////////////////////////////////////////////// // CIF data initialisation //////////////////////////////////////////////////////////////// auto cifPrbs = make_shared(864 * 8, 0x110); - auto cifMux = make_shared(myEtiSource); + auto cifMux = make_shared(m_etiSource); auto cifPart = make_shared(mode); - auto cifMap = make_shared(myNbCarriers); + auto cifMap = make_shared(m_nbCarriers); auto cifRef = make_shared(mode); auto cifFreq = make_shared(mode); - auto cifDiff = make_shared(myNbCarriers); + auto cifDiff = make_shared(m_nbCarriers); - auto cifNull = make_shared(myNbCarriers); + auto cifNull = make_shared(m_nbCarriers); auto cifSig = make_shared( - (1 + myNbSymbols) * myNbCarriers * sizeof(complexf)); + (1 + m_nbSymbols) * m_nbCarriers * sizeof(complexf)); // TODO this needs a review bool useCicEq = false; @@ -167,8 +170,8 @@ int DabModulator::process(Buffer* dataOut) shared_ptr cifCicEq; if (useCicEq) { cifCicEq = make_shared( - myNbCarriers, - (float)mySpacing * (float)m_settings.outputRate / 2048000.0f, + m_nbCarriers, + (float)m_spacing * (float)m_settings.outputRate / 2048000.0f, cic_ratio); } @@ -186,9 +189,9 @@ int DabModulator::process(Buffer* dataOut) } auto cifOfdm = make_shared( - (1 + myNbSymbols), - myNbCarriers, - mySpacing, + (1 + m_nbSymbols), + m_nbCarriers, + m_spacing, m_settings.enableCfr, m_settings.cfrClip, m_settings.cfrErrorClip); @@ -196,7 +199,7 @@ int DabModulator::process(Buffer* dataOut) rcs.enrol(cifOfdm.get()); auto cifGain = make_shared( - mySpacing, + m_spacing, m_settings.gainMode, m_settings.digitalgain, m_settings.normalise, @@ -205,7 +208,7 @@ int DabModulator::process(Buffer* dataOut) rcs.enrol(cifGain.get()); auto cifGuard = make_shared( - myNbSymbols, mySpacing, myNullSize, mySymSize, + m_nbSymbols, m_spacing, m_nullSize, m_symSize, m_settings.ofdmWindowOverlap); rcs.enrol(cifGuard.get()); @@ -227,17 +230,21 @@ int DabModulator::process(Buffer* dataOut) cifRes = make_shared( 2048000, m_settings.outputRate, - mySpacing); + m_spacing); } - myOutput = make_shared(dataOut); + if (not m_format.empty()) { + m_formatConverter = make_shared(m_format); + } + + m_output = make_shared(dataOut); - myFlowgraph->connect(cifPrbs, cifMux); + m_flowgraph->connect(cifPrbs, cifMux); //////////////////////////////////////////////////////////////// // Processing FIC //////////////////////////////////////////////////////////////// - shared_ptr fic(myEtiSource.getFic()); + shared_ptr fic(m_etiSource.getFic()); //////////////////////////////////////////////////////////////// // Data initialisation //////////////////////////////////////////////////////////////// @@ -269,15 +276,15 @@ int DabModulator::process(Buffer* dataOut) PDEBUG(" Adding tail\n"); ficPunc->append_tail_rule(PuncturingRule(3, 0xcccccc)); - myFlowgraph->connect(fic, ficPrbs); - myFlowgraph->connect(ficPrbs, ficConv); - myFlowgraph->connect(ficConv, ficPunc); - myFlowgraph->connect(ficPunc, cifPart); + m_flowgraph->connect(fic, ficPrbs); + m_flowgraph->connect(ficPrbs, ficConv); + m_flowgraph->connect(ficConv, ficPunc); + m_flowgraph->connect(ficPunc, cifPart); //////////////////////////////////////////////////////////////// // Configuring subchannels //////////////////////////////////////////////////////////////// - for (const auto& subchannel : myEtiSource.getSubchannels()) { + for (const auto& subchannel : m_etiSource.getSubchannels()) { //////////////////////////////////////////////////////////// // Data initialisation @@ -329,23 +336,23 @@ int DabModulator::process(Buffer* dataOut) // Configuring time interleaver auto subchInterleaver = make_shared(subchSizeOut); - myFlowgraph->connect(subchannel, subchPrbs); - myFlowgraph->connect(subchPrbs, subchConv); - myFlowgraph->connect(subchConv, subchPunc); - myFlowgraph->connect(subchPunc, subchInterleaver); - myFlowgraph->connect(subchInterleaver, cifMux); + m_flowgraph->connect(subchannel, subchPrbs); + m_flowgraph->connect(subchPrbs, subchConv); + m_flowgraph->connect(subchConv, subchPunc); + m_flowgraph->connect(subchPunc, subchInterleaver); + m_flowgraph->connect(subchInterleaver, cifMux); } - myFlowgraph->connect(cifMux, cifPart); - myFlowgraph->connect(cifPart, cifMap); - myFlowgraph->connect(cifMap, cifFreq); - myFlowgraph->connect(cifRef, cifDiff); - myFlowgraph->connect(cifFreq, cifDiff); - myFlowgraph->connect(cifNull, cifSig); - myFlowgraph->connect(cifDiff, cifSig); + m_flowgraph->connect(cifMux, cifPart); + m_flowgraph->connect(cifPart, cifMap); + m_flowgraph->connect(cifMap, cifFreq); + m_flowgraph->connect(cifRef, cifDiff); + m_flowgraph->connect(cifFreq, cifDiff); + m_flowgraph->connect(cifNull, cifSig); + m_flowgraph->connect(cifDiff, cifSig); if (tii) { - myFlowgraph->connect(tiiRef, tii); - myFlowgraph->connect(tii, cifSig); + m_flowgraph->connect(tiiRef, tii); + m_flowgraph->connect(tii, cifSig); } shared_ptr prev_plugin = static_pointer_cast(cifSig); @@ -354,15 +361,18 @@ int DabModulator::process(Buffer* dataOut) static_pointer_cast(cifOfdm), static_pointer_cast(cifGain), static_pointer_cast(cifGuard), - static_pointer_cast(cifFilter), // optional block - static_pointer_cast(cifRes), // optional block - static_pointer_cast(cifPoly), // optional block - static_pointer_cast(myOutput), + // optional blocks + static_pointer_cast(cifFilter), + static_pointer_cast(cifRes), + static_pointer_cast(cifPoly), + static_pointer_cast(m_formatConverter), + // mandatory block + static_pointer_cast(m_output), }); for (auto& p : plugins) { if (p) { - myFlowgraph->connect(prev_plugin, p); + m_flowgraph->connect(prev_plugin, p); prev_plugin = p; } } @@ -372,13 +382,13 @@ int DabModulator::process(Buffer* dataOut) //////////////////////////////////////////////////////////////////// // Processing data //////////////////////////////////////////////////////////////////// - return myFlowgraph->run(); + return m_flowgraph->run(); } meta_vec_t DabModulator::process_metadata(const meta_vec_t& metadataIn) { - if (myOutput) { - return myOutput->get_latest_metadata(); + if (m_output) { + return m_output->get_latest_metadata(); } return {}; @@ -390,6 +400,9 @@ void DabModulator::set_parameter(const string& parameter, const string& value) if (parameter == "rate") { throw ParameterError("Parameter 'rate' is read-only"); } + else if (parameter == "num_clipped_samples") { + throw ParameterError("Parameter 'num_clipped_samples' is read-only"); + } else { stringstream ss; ss << "Parameter '" << parameter << @@ -404,6 +417,16 @@ const string DabModulator::get_parameter(const string& parameter) const if (parameter == "rate") { ss << m_settings.outputRate; } + else if (parameter == "num_clipped_samples") { + if (m_formatConverter) { + ss << m_formatConverter->get_num_clipped_samples(); + } + else { + ss << "Parameter '" << parameter << + "' is not available when no format conversion is done."; + throw ParameterError(ss.str()); + } + } else { ss << "Parameter '" << parameter << "' is not exported by controllable " << get_rc_name(); @@ -416,5 +439,6 @@ const RemoteControllable::map_t DabModulator::get_all_values() const { map_t map; map["rate"] = m_settings.outputRate; + map["num_clipped_samples"] = m_formatConverter ? m_formatConverter->get_num_clipped_samples() : 0; return map; } diff --git a/src/DabModulator.h b/src/DabModulator.h index 0d46e9f..6381252 100644 --- a/src/DabModulator.h +++ b/src/DabModulator.h @@ -39,6 +39,7 @@ #include "ConfigParser.h" #include "EtiReader.h" #include "Flowgraph.h" +#include "FormatConverter.h" #include "GainControl.h" #include "OutputMemory.h" #include "RemoteControl.h" @@ -49,7 +50,8 @@ class DabModulator : public ModInput, public ModMetadata, public RemoteControllable { public: - DabModulator(EtiSource& etiSource, mod_settings_t& settings); + DabModulator(EtiSource& etiSource, mod_settings_t& settings, const std::string& format); + // Allowed formats: s8, u8 and s16. Empty string means no conversion int process(Buffer* dataOut) override; const char* name() override { return "DabModulator"; } @@ -57,7 +59,7 @@ public: virtual meta_vec_t process_metadata(const meta_vec_t& metadataIn) override; /* Required to get the timestamp */ - EtiSource* getEtiSource() { return &myEtiSource; } + EtiSource* getEtiSource() { return &m_etiSource; } /******* REMOTE CONTROL ********/ virtual void set_parameter(const std::string& parameter, const std::string& value) override; @@ -68,17 +70,19 @@ protected: void setMode(unsigned mode); mod_settings_t& m_settings; + std::string m_format; - EtiSource& myEtiSource; - std::shared_ptr myFlowgraph; + EtiSource& m_etiSource; + std::shared_ptr m_flowgraph; - size_t myNbSymbols; - size_t myNbCarriers; - size_t mySpacing; - size_t myNullSize; - size_t mySymSize; - size_t myFicSizeOut; + size_t m_nbSymbols; + size_t m_nbCarriers; + size_t m_spacing; + size_t m_nullSize; + size_t m_symSize; + size_t m_ficSizeOut; - std::shared_ptr myOutput; + std::shared_ptr m_formatConverter; + std::shared_ptr m_output; }; diff --git a/src/FormatConverter.cpp b/src/FormatConverter.cpp index cda8a4d..fc4cc2f 100644 --- a/src/FormatConverter.cpp +++ b/src/FormatConverter.cpp @@ -2,7 +2,7 @@ Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011 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 @@ -45,6 +45,8 @@ int FormatConverter::process(Buffer* const dataIn, Buffer* dataOut) PDEBUG("FormatConverter::process(dataIn: %p, dataOut: %p)\n", dataIn, dataOut); + size_t num_clipped_samples = 0; + size_t sizeIn = dataIn->getLength() / sizeof(float); float* in = reinterpret_cast(dataIn->getData()); @@ -53,7 +55,17 @@ int FormatConverter::process(Buffer* const dataIn, Buffer* dataOut) int16_t* out = reinterpret_cast(dataOut->getData()); for (size_t i = 0; i < sizeIn; i++) { - out[i] = in[i]; + if (in[i] < INT16_MIN) { + out[i] = INT16_MIN; + num_clipped_samples++; + } + else if (in[i] > INT16_MAX) { + out[i] = INT16_MAX; + num_clipped_samples++; + } + else { + out[i] = in[i]; + } } } else if (m_format == "u8") { @@ -61,7 +73,19 @@ int FormatConverter::process(Buffer* const dataIn, Buffer* dataOut) uint8_t* out = reinterpret_cast(dataOut->getData()); for (size_t i = 0; i < sizeIn; i++) { - out[i] = in[i] + 128; + const auto samp = in[i] + 128.0f; + if (samp < 0) { + out[i] = 0; + num_clipped_samples++; + } + else if (samp > INT8_MAX) { + out[i] = INT8_MAX; + num_clipped_samples++; + } + else { + out[i] = samp; + } + } } else if (m_format == "s8") { @@ -69,13 +93,25 @@ int FormatConverter::process(Buffer* const dataIn, Buffer* dataOut) int8_t* out = reinterpret_cast(dataOut->getData()); for (size_t i = 0; i < sizeIn; i++) { - out[i] = in[i]; + if (in[i] < INT8_MIN) { + out[i] = INT8_MIN; + num_clipped_samples++; + } + else if (in[i] > INT8_MAX) { + out[i] = INT8_MAX; + num_clipped_samples++; + } + else { + out[i] = in[i]; + } } } else { throw std::runtime_error("FormatConverter: Invalid format " + m_format); } + m_num_clipped_samples.store(num_clipped_samples); + return dataOut->getLength(); } @@ -84,19 +120,25 @@ const char* FormatConverter::name() return "FormatConverter"; } -size_t FormatConverter::get_format_size() const +size_t FormatConverter::get_num_clipped_samples() const +{ + return m_num_clipped_samples.load(); +} + + +size_t FormatConverter::get_format_size(const std::string& format) { // Returns 2*sizeof(SAMPLE_TYPE) because we have I + Q - if (m_format == "s16") { + if (format == "s16") { return 4; } - else if (m_format == "u8") { + else if (format == "u8") { return 2; } - else if (m_format == "s8") { + else if (format == "s8") { return 2; } else { - throw std::runtime_error("FormatConverter: Invalid format " + m_format); + throw std::runtime_error("FormatConverter: Invalid format " + format); } } diff --git a/src/FormatConverter.h b/src/FormatConverter.h index ceb2e17..05511c0 100644 --- a/src/FormatConverter.h +++ b/src/FormatConverter.h @@ -34,22 +34,27 @@ #include "ModPlugin.h" #include +#include #include #include class FormatConverter : public ModCodec { public: + static size_t get_format_size(const std::string& format); + // Allowed formats: s8, u8 and s16 FormatConverter(const std::string& format); int process(Buffer* const dataIn, Buffer* dataOut); const char* name(); - size_t get_format_size() const; + size_t get_num_clipped_samples() const; private: std::string m_format; + + std::atomic m_num_clipped_samples = 0; }; -- cgit v1.2.3 From 150b75b244602c789934f1a5094f33aa7da3c09a Mon Sep 17 00:00:00 2001 From: "Matthias P. Braendli" Date: Tue, 6 Jun 2023 12:10:39 +0200 Subject: DEXTER: Add pacontrol interface --- doc/example.ini | 6 +++++ src/MemlessPoly.cpp | 4 +-- src/output/Dexter.cpp | 71 +++++++++++++++++++++++++++++++++++++++++++++++++- src/output/Dexter.h | 14 +++++++++- src/output/SDRDevice.h | 4 +++ 5 files changed, 95 insertions(+), 4 deletions(-) diff --git a/doc/example.ini b/doc/example.ini index cd48ef4..4cc6d26 100644 --- a/doc/example.ini +++ b/doc/example.ini @@ -320,6 +320,12 @@ txgain=32768 ;frequency=234208000 channel=13C +pacontrol_config_endpoint=tcp://localhost:5558 + +; TargetPower in dBm +; Runtime-changes to this setting are directly sent to pacontrol +pacontrol_targetpower=33 + [limeoutput] ; Lime output directly runs against the LMS device driver. It does not support SFN nor predistortion. device= diff --git a/src/MemlessPoly.cpp b/src/MemlessPoly.cpp index 4801ba0..a2b0082 100644 --- a/src/MemlessPoly.cpp +++ b/src/MemlessPoly.cpp @@ -314,7 +314,7 @@ void MemlessPoly::worker_thread(MemlessPoly::worker_t *workerdata) set_thread_name("MemlessPoly"); while (true) { - worker_t::input_data_t in_data; + worker_t::input_data_t in_data = {}; try { workerdata->in_queue.wait_and_pop(in_data); } @@ -386,7 +386,7 @@ int MemlessPoly::internal_process(Buffer* const dataIn, Buffer* dataOut) // Wait for completion of the tasks for (auto& worker : m_workers) { - int ret; + int ret = 0; worker.out_queue.wait_and_pop(ret); } } diff --git a/src/output/Dexter.cpp b/src/output/Dexter.cpp index c1d8d2f..dd51517 100644 --- a/src/output/Dexter.cpp +++ b/src/output/Dexter.cpp @@ -66,9 +66,35 @@ static void fill_time(struct timespec *t) } } +static void pacontrol_set_targetpower(zmq::socket_t& sock, int targetpower_dBm) +{ + stringstream message_builder; + message_builder << "{'PA0' : {'TargetPower': "; + message_builder << targetpower_dBm; + message_builder << "}}"; + const auto message = message_builder.str(); + + try { + const auto r = sock.send(zmq::const_buffer{message.data(), message.size()}); + if (r.has_value()) { + etiLog.level(debug) << "Sent TargetPower=" << targetpower_dBm << " to pacontrol"; + } + else { + // zmq_send returned EAGAIN + etiLog.level(info) << "Send TargetPower=" << targetpower_dBm << " failed"; + } + } + catch (const zmq::error_t& err) { + etiLog.level(warn) << "Failed to send TargetPower=" << targetpower_dBm << ": " << err.what(); + } +} + + Dexter::Dexter(SDRDeviceConfig& config) : SDRDevice(), - m_conf(config) + m_conf(config), + m_zmq_context(1), + m_zmq_sock(m_zmq_context, ZMQ_PUSH) { etiLog.level(info) << "Dexter:Creating the device"; @@ -166,6 +192,41 @@ Dexter::Dexter(SDRDeviceConfig& config) : m_running = true; m_underflow_read_thread = std::thread(&Dexter::underflow_read_process, this); + + m_zmq_sock.setsockopt(ZMQ_SNDTIMEO, 0); + if (not m_conf.pacontrol_config_endpoint.empty()) { + etiLog.level(debug) << "Creating pacontrol connection to " << + m_conf.pacontrol_config_endpoint; + m_zmq_sock.connect(m_conf.pacontrol_config_endpoint.c_str()); + } + + if (m_conf.pacontrol_targetpower.has_value()) { + pacontrol_set_targetpower(m_zmq_sock, *m_conf.pacontrol_targetpower); + } + else { + etiLog.level(warn) << "Config does not defined PA target power"; + } +} + +static void pacontrol_set_mute(zmq::socket_t& sock, bool mute) +{ + string message = "{'PA0' : {'Mute': "; + message += (mute ? "True" : "False"); + message += "}}"; + + try { + const auto r = sock.send(zmq::const_buffer{message.data(), message.size()}); + if (r.has_value()) { + etiLog.level(debug) << "Sent mute=" << mute << " to pacontrol"; + } + else { + // zmq_send returned EAGAIN + etiLog.level(info) << "Send mute=" << mute << " failed"; + } + } + catch (const zmq::error_t& err) { + etiLog.level(warn) << "Failed to send mute=" << mute << ": " << err.what(); + } } void Dexter::channel_up() @@ -178,6 +239,10 @@ void Dexter::channel_up() m_channel_is_up = true; etiLog.level(debug) << "DEXTER CHANNEL_UP"; + + if (m_zmq_sock.connected()) { + pacontrol_set_mute(m_zmq_sock, false); + } } void Dexter::channel_down() @@ -194,6 +259,10 @@ void Dexter::channel_down() m_channel_is_up = false; etiLog.level(debug) << "DEXTER CHANNEL_DOWN"; + + if (m_zmq_sock.connected()) { + pacontrol_set_mute(m_zmq_sock, true); + } } void Dexter::handle_hw_time() diff --git a/src/output/Dexter.h b/src/output/Dexter.h index d4f425f..57b9798 100644 --- a/src/output/Dexter.h +++ b/src/output/Dexter.h @@ -34,8 +34,14 @@ DESCRIPTION: # include #endif -#ifdef HAVE_DEXTER +#if defined(HAVE_DEXTER) + +#if !defined(HAVE_ZEROMQ) +#error "ZeroMQ is mandatory for DEXTER" +#endif + #include "iio.h" +#include "zmq.hpp" #include #include @@ -120,6 +126,12 @@ class Dexter : public Output::SDRDevice size_t num_buffers_pushed = 0; + /* Communication with pacontrol */ + zmq::context_t m_zmq_context; + zmq::socket_t m_zmq_sock; + std::string m_pacontrol_endpoint; + + /* Clock State */ DexterClockState m_clock_state = DexterClockState::Startup; // Only valid when m_clock_state is not Startup diff --git a/src/output/SDRDevice.h b/src/output/SDRDevice.h index 26272be..f84b340 100644 --- a/src/output/SDRDevice.h +++ b/src/output/SDRDevice.h @@ -94,6 +94,10 @@ struct SDRDeviceConfig { // TCP port on which to serve TX and RX samples for the // digital pre distortion learning tool uint16_t dpdFeedbackServerPort = 0; + + // DEXTER-specific + std::string pacontrol_config_endpoint; + std::optional pacontrol_targetpower; // dBm }; // Each frame contains one OFDM frame, and its -- cgit v1.2.3 From 71740c7ea14706986507159f8a0e101940f99d4d Mon Sep 17 00:00:00 2001 From: "Matthias P. Braendli" Date: Tue, 27 Jun 2023 11:43:25 +0200 Subject: Add a few cosmetic changes --- lib/RemoteControl.cpp | 2 +- src/output/Dexter.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/RemoteControl.cpp b/lib/RemoteControl.cpp index 6e9af20..16359ad 100644 --- a/lib/RemoteControl.cpp +++ b/lib/RemoteControl.cpp @@ -193,7 +193,7 @@ RemoteControllable* RemoteControllers::get_controllable_(const std::string& name [&](RemoteControllable* r) { return r->get_rc_name() == name; }); if (rc == controllables.end()) { - throw ParameterError("Module name unknown"); + throw ParameterError(string{"Module name '"} + name + "' unknown"); } else { return *rc; diff --git a/src/output/Dexter.cpp b/src/output/Dexter.cpp index dd51517..9c16ea7 100644 --- a/src/output/Dexter.cpp +++ b/src/output/Dexter.cpp @@ -204,7 +204,7 @@ Dexter::Dexter(SDRDeviceConfig& config) : pacontrol_set_targetpower(m_zmq_sock, *m_conf.pacontrol_targetpower); } else { - etiLog.level(warn) << "Config does not defined PA target power"; + etiLog.level(warn) << "Config does not define PA target power"; } } -- cgit v1.2.3 From 1d46beadab0396ff75bd1fbad32fb7924ed11aa7 Mon Sep 17 00:00:00 2001 From: "Matthias P. Braendli" Date: Tue, 27 Jun 2023 16:13:58 +0200 Subject: RC: handle nonexistent module in showjson --- lib/RemoteControl.cpp | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/lib/RemoteControl.cpp b/lib/RemoteControl.cpp index 16359ad..dcae682 100644 --- a/lib/RemoteControl.cpp +++ b/lib/RemoteControl.cpp @@ -590,7 +590,7 @@ void RemoteControllerZmq::process() } } else if (msg.size() == 2 && command == "show") { - std::string module((char*) msg[1].data(), msg[1].size()); + const std::string module((char*) msg[1].data(), msg[1].size()); try { list< vector > r = rcs.get_param_list_values(module); size_t r_size = r.size(); @@ -608,17 +608,22 @@ void RemoteControllerZmq::process() } } else if (msg.size() == 2 && command == "showjson") { - std::string module((char*) msg[1].data(), msg[1].size()); - std::string json = rcs.get_params_json(module); + const std::string module((char*) msg[1].data(), msg[1].size()); + try { + std::string json = rcs.get_params_json(module); - zmq::message_t zmsg(json.size()); - memcpy(zmsg.data(), json.data(), json.size()); + zmq::message_t zmsg(json.size()); + memcpy(zmsg.data(), json.data(), json.size()); - repSocket.send(zmsg, zmq::send_flags::none); + repSocket.send(zmsg, zmq::send_flags::none); + } + catch (const ParameterError &err) { + send_fail_reply(repSocket, err.what()); + } } else if (msg.size() == 3 && command == "get") { - std::string module((char*) msg[1].data(), msg[1].size()); - std::string parameter((char*) msg[2].data(), msg[2].size()); + const std::string module((char*) msg[1].data(), msg[1].size()); + const std::string parameter((char*) msg[2].data(), msg[2].size()); try { std::string value = rcs.get_param(module, parameter); @@ -631,9 +636,9 @@ void RemoteControllerZmq::process() } } else if (msg.size() == 4 && command == "set") { - std::string module((char*) msg[1].data(), msg[1].size()); - std::string parameter((char*) msg[2].data(), msg[2].size()); - std::string value((char*) msg[3].data(), msg[3].size()); + const std::string module((char*) msg[1].data(), msg[1].size()); + const std::string parameter((char*) msg[2].data(), msg[2].size()); + const std::string value((char*) msg[3].data(), msg[3].size()); try { rcs.set_param(module, parameter, value); -- cgit v1.2.3 From 43df82ccb4dbef42e511b381d9dd86e5c6a64644 Mon Sep 17 00:00:00 2001 From: "Matthias P. Braendli" Date: Tue, 27 Jun 2023 16:24:14 +0200 Subject: RC: re-enable m_active after ZMQ RC restart --- lib/RemoteControl.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/RemoteControl.cpp b/lib/RemoteControl.cpp index dcae682..fd0ea77 100644 --- a/lib/RemoteControl.cpp +++ b/lib/RemoteControl.cpp @@ -532,6 +532,7 @@ void RemoteControllerZmq::send_fail_reply(zmq::socket_t &pSocket, const std::str void RemoteControllerZmq::process() { m_fault = false; + m_active = true; // create zmq reply socket for receiving ctrl parameters try { -- cgit v1.2.3 From 8a547d0c0a84ea5a3464c7bc82a45f78aaae81c0 Mon Sep 17 00:00:00 2001 From: "Matthias P. Braendli" Date: Tue, 18 Jul 2023 17:08:04 +0200 Subject: Dexter: remove dexter_dsp_tx attributes from metrics --- src/output/Dexter.cpp | 156 -------------------------------------------------- 1 file changed, 156 deletions(-) diff --git a/src/output/Dexter.cpp b/src/output/Dexter.cpp index 9c16ea7..132636c 100644 --- a/src/output/Dexter.cpp +++ b/src/output/Dexter.cpp @@ -465,21 +465,6 @@ 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; @@ -490,14 +475,6 @@ SDRDevice::run_statistics_t Dexter::get_run_statistics(void) const rs["latepackets"] = num_late; rs["frames"] = num_frames_modulated; - 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"); - rs["in_holdover_since"] = 0; switch (m_clock_state) { case DexterClockState::Startup: @@ -510,142 +487,9 @@ SDRDevice::run_statistics_t Dexter::get_run_statistics(void) const break; } - constexpr double VMINFACT = 0.85; - constexpr double VMAXFACT = 1.15; - bool voltage_ok = true; - bool temp_ok = true; - - { - std::ifstream in("/sys/bus/i2c/devices/1-002f/hwmon/hwmon0/in2_input", std::ios::in | std::ios::binary); - if (in) { - double vcc3v3; - in >> vcc3v3; - rs["vcc3v3"] = vcc3v3 * (18+36)/36.0/1000.0; - voltage_ok = (vcc3v3 > VMINFACT * 3.3) and (vcc3v3 < VMAXFACT * 3.3); - } - else { - rs["vcc3v3"] = -1; - voltage_ok = false; - } - } - { - std::ifstream in("/sys/bus/i2c/devices/1-002f/hwmon/hwmon0/in1_input", std::ios::in | std::ios::binary); - if (in) { - double vcc5v4; - in >> vcc5v4; - rs["vcc5v4"] = vcc5v4 * (51+36)/36.0/1000.0; - voltage_ok = (vcc5v4 > VMINFACT * 5.4) and (vcc5v4 < VMAXFACT * 5.4); - } - else { - rs["vcc5v4"] = -1; - voltage_ok = false; - } - } - { - std::ifstream in("/sys/bus/i2c/devices/1-002f/hwmon/hwmon0/in3_input", std::ios::in | std::ios::binary); - if (in) { - double vfan; - in >> vfan; - rs["vfan"] = vfan * (560+22)/22.0/1000.0; - } - else { - rs["vfan"] = -1; - voltage_ok = false; - } - } - { - std::ifstream in("/sys/bus/i2c/devices/1-002f/hwmon/hwmon0/in0_input", std::ios::in | std::ios::binary); - if (in) { - double vccmainin; - in >> vccmainin; - rs["vcc_main_in"] = vccmainin * (560+22)/22.0/1000.0; - voltage_ok |= vccmainin > 10.0; - } - else { - rs["vcc_main_in"] = -1; - voltage_ok = false; - } - } - { - std::ifstream in("/sys/bus/i2c/devices/1-002f/hwmon/hwmon0/in4_input", std::ios::in | std::ios::binary); - if (in) { - double vcc3v3pll; - in >> vcc3v3pll; - rs["vcc3v3pll"] = vcc3v3pll * (18+36)/36.0/1000.0; - voltage_ok = (vcc3v3pll > VMINFACT * 3.3) and (vcc3v3pll < VMAXFACT * 3.3); - } - else { - rs["vcc3v3pll"] = -1; - voltage_ok = false; - } - } - { - std::ifstream in("/sys/bus/i2c/devices/1-002f/hwmon/hwmon0/in5_input", std::ios::in | std::ios::binary); - if (in) { - double vcc2v5io; - in >> vcc2v5io; - rs["vcc2v5io"] = vcc2v5io * (4.7+36)/36.0/1000.0; - voltage_ok = (vcc2v5io > VMINFACT * 2.5) and (vcc2v5io < VMAXFACT * 2.5); - } - else { - rs["vcc2v5io"] = -1; - voltage_ok = false; - } - } - { - std::ifstream in("/sys/bus/i2c/devices/1-002f/hwmon/hwmon0/in6_input", std::ios::in | std::ios::binary); - if (in) { - double vccocxo; - in >> vccocxo; - rs["vccocxo"] = vccocxo * (51+36)/36.0/1000.0; - } - else { - rs["vccocxo"] = -1; - voltage_ok = false; - } - } - - optional tfpga; - for (int i = 0; i < 100; i++) { - std::string path = "/sys/bus/iio/devices/iio:device"; - path += to_string(i); - - std::ifstream iio_name(path + "/name", std::ios::in | std::ios::binary); - std::ostringstream sstr; - sstr << iio_name.rdbuf(); - if (sstr.str() == "xadc\n") { - std::ifstream in_scale(path + "/in_temp0_scale", std::ios::in | std::ios::binary); - std::ifstream in_offset(path + "/in_temp0_offset", std::ios::in | std::ios::binary); - std::ifstream in_temp0_raw(path + "/in_temp0_raw", std::ios::in | std::ios::binary); - - if (in_scale and in_offset and in_temp0_raw) { - double scale, offset, temp0_raw ; - in_scale >> scale; - in_offset >> offset; - in_temp0_raw >> temp0_raw; - - tfpga = (temp0_raw + offset) * scale / 1000.0; - } - break; - } - } - - if (tfpga) { - rs["tempfpga"] = *tfpga; - temp_ok |= *tfpga <= 85; - } - else { - rs["tempfpga"] = -1; - temp_ok = false; - } - - rs["voltage_alarm"] = not voltage_ok; - rs["temp_alarm"] = not temp_ok; - return rs; } - double Dexter::get_real_secs(void) const { long long clks = 0; -- cgit v1.2.3 From d521d4f0c5ad3b663a322453c5798626081cb1f3 Mon Sep 17 00:00:00 2001 From: "Matthias P. Braendli" Date: Wed, 19 Jul 2023 20:26:36 +0200 Subject: Change RC showjson command --- Makefile.am | 2 + lib/Json.cpp | 102 ++++++++++++++++++++++++++++++++++++++++++ lib/Json.h | 66 +++++++++++++++++++++++++++ lib/RemoteControl.cpp | 94 ++++++++------------------------------ lib/RemoteControl.h | 8 ++-- src/DabModulator.cpp | 4 +- src/DabModulator.h | 2 +- src/FIRFilter.cpp | 4 +- src/FIRFilter.h | 2 +- src/GainControl.cpp | 4 +- src/GainControl.h | 2 +- src/GuardIntervalInserter.cpp | 4 +- src/GuardIntervalInserter.h | 2 +- src/MemlessPoly.cpp | 4 +- src/MemlessPoly.h | 2 +- src/OfdmGenerator.cpp | 4 +- src/OfdmGenerator.h | 2 +- src/TII.cpp | 4 +- src/TII.h | 2 +- src/TimestampDecoder.cpp | 4 +- src/TimestampDecoder.h | 2 +- src/output/SDR.cpp | 6 +-- src/output/SDR.h | 2 +- src/output/SDRDevice.h | 2 +- 24 files changed, 220 insertions(+), 110 deletions(-) create mode 100644 lib/Json.cpp create mode 100644 lib/Json.h diff --git a/Makefile.am b/Makefile.am index 0e09236..6e7c9ce 100644 --- a/Makefile.am +++ b/Makefile.am @@ -98,6 +98,8 @@ odr_dabmod_SOURCES = src/DabMod.cpp \ lib/RemoteControl.h \ lib/Log.cpp \ lib/Log.h \ + lib/Json.h \ + lib/Json.cpp \ lib/Globals.cpp \ lib/INIReader.h \ lib/crc.h \ diff --git a/lib/Json.cpp b/lib/Json.cpp new file mode 100644 index 0000000..9bda8c3 --- /dev/null +++ b/lib/Json.cpp @@ -0,0 +1,102 @@ +/* + 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 "Json.h" + +namespace json { + static std::string escape_json(const std::string &s) { + std::ostringstream o; + for (auto c = s.cbegin(); c != s.cend(); c++) { + switch (*c) { + case '"': o << "\\\""; break; + case '\\': o << "\\\\"; break; + case '\b': o << "\\b"; break; + case '\f': o << "\\f"; break; + case '\n': o << "\\n"; break; + case '\r': o << "\\r"; break; + case '\t': o << "\\t"; break; + default: + if ('\x00' <= *c && *c <= '\x1f') { + o << "\\u" + << std::hex << std::setw(4) << std::setfill('0') << static_cast(*c); + } else { + o << *c; + } + } + } + return o.str(); + } + + std::string map_to_json(const map_t& values) { + std::ostringstream ss; + ss << "{ "; + size_t ix = 0; + for (const auto& element : values) { + if (ix > 0) { + ss << ","; + } + + ss << "\"" << escape_json(element.first) << "\": "; + + const auto& value = element.second.data; + if (std::holds_alternative(value)) { + ss << "\"" << escape_json(std::get(value)) << "\""; + } + else if (std::holds_alternative(value)) { + ss << std::defaultfloat << 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) ? "true" : "false"); + } + else if (std::holds_alternative(value)) { + ss << "null"; + } + else if (std::holds_alternative(value)) { + ss << map_to_json(std::get(value)); + } + else { + throw std::logic_error("variant alternative not handled"); + } + + ix++; + } + ss << " }"; + + return ss.str(); + } +} diff --git a/lib/Json.h b/lib/Json.h new file mode 100644 index 0000000..26da9a8 --- /dev/null +++ b/lib/Json.h @@ -0,0 +1,66 @@ +/* + 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 + +#include +#include +#include +#include +#include +#include +#include + +namespace json { + + using map_t = std::unordered_map; + + struct value_t { + std::variant< + map_t, + std::string, + double, + 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::string map_to_json(const map_t& values); +} diff --git a/lib/RemoteControl.cpp b/lib/RemoteControl.cpp index fd0ea77..b544461 100644 --- a/lib/RemoteControl.cpp +++ b/lib/RemoteControl.cpp @@ -105,71 +105,14 @@ std::list< std::vector > RemoteControllers::get_param_list_values(c } -static std::string escape_json(const std::string &s) { - std::ostringstream o; - for (auto c = s.cbegin(); c != s.cend(); c++) { - switch (*c) { - case '"': o << "\\\""; break; - case '\\': o << "\\\\"; break; - case '\b': o << "\\b"; break; - case '\f': o << "\\f"; break; - case '\n': o << "\\n"; break; - case '\r': o << "\\r"; break; - case '\t': o << "\\t"; break; - default: - if ('\x00' <= *c && *c <= '\x1f') { - o << "\\u" - << std::hex << std::setw(4) << std::setfill('0') << static_cast(*c); - } else { - o << *c; - } - } - } - return o.str(); -} - -std::string RemoteControllers::get_params_json(const std::string& name) { - RemoteControllable* controllable = get_controllable_(name); - const auto& values = controllable->get_all_values(); - - std::ostringstream ss; - ss << "{ "; - size_t ix = 0; - for (const auto& element : values) { - if (ix > 0) { - ss << ","; - } - - ss << "\"" << escape_json(element.first) << "\": "; - - const auto& value = element.second; - if (std::holds_alternative(value)) { - ss << "\"" << escape_json(std::get(value)) << "\""; - } - else if (std::holds_alternative(value)) { - ss << std::defaultfloat << 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) ? "true" : "false"); - } - else if (std::holds_alternative(value)) { - ss << "null"; - } - else { - throw std::logic_error("variant alternative not handled"); - } - ix++; +std::string RemoteControllers::get_showjson() { + json::map_t root; + for (auto &controllable : rcs.controllables) { + root[controllable->get_rc_name()].data = controllable->get_all_values(); } - ss << " }"; - return ss.str(); + return json::map_to_json(root); } std::string RemoteControllers::get_param(const std::string& name, const std::string& param) { @@ -590,6 +533,19 @@ void RemoteControllerZmq::process() repSocket.send(zmsg, (--cohort_size > 0) ? zmq::send_flags::sndmore : zmq::send_flags::none); } } + else if (msg.size() == 1 && command == "showjson") { + try { + std::string json = rcs.get_showjson(); + + zmq::message_t zmsg(json.size()); + memcpy(zmsg.data(), json.data(), json.size()); + + repSocket.send(zmsg, zmq::send_flags::none); + } + catch (const ParameterError &err) { + send_fail_reply(repSocket, err.what()); + } + } else if (msg.size() == 2 && command == "show") { const std::string module((char*) msg[1].data(), msg[1].size()); try { @@ -608,20 +564,6 @@ void RemoteControllerZmq::process() send_fail_reply(repSocket, err.what()); } } - else if (msg.size() == 2 && command == "showjson") { - const std::string module((char*) msg[1].data(), msg[1].size()); - try { - std::string json = rcs.get_params_json(module); - - zmq::message_t zmsg(json.size()); - memcpy(zmsg.data(), json.data(), json.size()); - - repSocket.send(zmsg, zmq::send_flags::none); - } - catch (const ParameterError &err) { - send_fail_reply(repSocket, err.what()); - } - } else if (msg.size() == 3 && command == "get") { const std::string module((char*) msg[1].data(), msg[1].size()); const std::string parameter((char*) msg[2].data(), msg[2].size()); diff --git a/lib/RemoteControl.h b/lib/RemoteControl.h index 4bc3b68..26f30d9 100644 --- a/lib/RemoteControl.h +++ b/lib/RemoteControl.h @@ -48,6 +48,7 @@ #include "Log.h" #include "Socket.h" +#include "Json.h" #define RC_ADD_PARAMETER(p, desc) { \ std::vector p; \ @@ -120,10 +121,7 @@ class RemoteControllable { /* Getting a parameter always returns a string. */ virtual const std::string get_parameter(const std::string& parameter) const = 0; - using value_t = std::variant; - using map_t = std::unordered_map; - - virtual const map_t get_all_values() const = 0; + virtual const json::map_t get_all_values() const = 0; protected: std::string m_rc_name; @@ -140,7 +138,7 @@ class RemoteControllers { void check_faults(); std::list< std::vector > get_param_list_values(const std::string& name); std::string get_param(const std::string& name, const std::string& param); - std::string get_params_json(const std::string& name); + std::string get_showjson(); void set_param( const std::string& name, diff --git a/src/DabModulator.cpp b/src/DabModulator.cpp index 5213d8d..0fe9c6d 100644 --- a/src/DabModulator.cpp +++ b/src/DabModulator.cpp @@ -435,9 +435,9 @@ const string DabModulator::get_parameter(const string& parameter) const return ss.str(); } -const RemoteControllable::map_t DabModulator::get_all_values() const +const json::map_t DabModulator::get_all_values() const { - map_t map; + json::map_t map; map["rate"] = m_settings.outputRate; map["num_clipped_samples"] = m_formatConverter ? m_formatConverter->get_num_clipped_samples() : 0; return map; diff --git a/src/DabModulator.h b/src/DabModulator.h index 6381252..140f313 100644 --- a/src/DabModulator.h +++ b/src/DabModulator.h @@ -64,7 +64,7 @@ public: /******* REMOTE CONTROL ********/ virtual void set_parameter(const std::string& parameter, const std::string& value) override; virtual const std::string get_parameter(const std::string& parameter) const override; - virtual const RemoteControllable::map_t get_all_values() const override; + virtual const json::map_t get_all_values() const override; protected: void setMode(unsigned mode); diff --git a/src/FIRFilter.cpp b/src/FIRFilter.cpp index 523d405..d2a6121 100644 --- a/src/FIRFilter.cpp +++ b/src/FIRFilter.cpp @@ -347,9 +347,9 @@ const string FIRFilter::get_parameter(const string& parameter) const return ss.str(); } -const RemoteControllable::map_t FIRFilter::get_all_values() const +const json::map_t FIRFilter::get_all_values() const { - map_t map; + json::map_t map; map["ntaps"] = m_taps.size(); map["tapsfile"] = m_taps_file; return map; diff --git a/src/FIRFilter.h b/src/FIRFilter.h index 2a469aa..a4effa1 100644 --- a/src/FIRFilter.h +++ b/src/FIRFilter.h @@ -61,7 +61,7 @@ public: /******* REMOTE CONTROL ********/ virtual void set_parameter(const std::string& parameter, const std::string& value) override; virtual const std::string get_parameter(const std::string& parameter) const override; - virtual const RemoteControllable::map_t get_all_values() const override; + virtual const json::map_t get_all_values() const override; protected: virtual int internal_process(Buffer* const dataIn, Buffer* dataOut) override; diff --git a/src/GainControl.cpp b/src/GainControl.cpp index d90da45..beb93f6 100644 --- a/src/GainControl.cpp +++ b/src/GainControl.cpp @@ -583,9 +583,9 @@ const string GainControl::get_parameter(const string& parameter) const return ss.str(); } -const RemoteControllable::map_t GainControl::get_all_values() const +const json::map_t GainControl::get_all_values() const { - map_t map; + json::map_t map; map["digital"] = m_digGain; switch (m_gainmode) { case GainMode::GAIN_FIX: diff --git a/src/GainControl.h b/src/GainControl.h index f024fa2..04f6b58 100644 --- a/src/GainControl.h +++ b/src/GainControl.h @@ -66,7 +66,7 @@ class GainControl : public PipelinedModCodec, public RemoteControllable /* Functions for the remote control */ virtual void set_parameter(const std::string& parameter, const std::string& value) override; virtual const std::string get_parameter(const std::string& parameter) const override; - virtual const RemoteControllable::map_t get_all_values() const override; + virtual const json::map_t get_all_values() const override; protected: virtual int internal_process( diff --git a/src/GuardIntervalInserter.cpp b/src/GuardIntervalInserter.cpp index d5c71fb..80394b7 100644 --- a/src/GuardIntervalInserter.cpp +++ b/src/GuardIntervalInserter.cpp @@ -303,9 +303,9 @@ const std::string GuardIntervalInserter::get_parameter(const std::string& parame return ss.str(); } -const RemoteControllable::map_t GuardIntervalInserter::get_all_values() const +const json::map_t GuardIntervalInserter::get_all_values() const { - map_t map; + json::map_t map; map["windowlen"] = d_windowOverlap; return map; } diff --git a/src/GuardIntervalInserter.h b/src/GuardIntervalInserter.h index f88bdac..5aaad2b 100644 --- a/src/GuardIntervalInserter.h +++ b/src/GuardIntervalInserter.h @@ -58,7 +58,7 @@ class GuardIntervalInserter : public ModCodec, public RemoteControllable /******* REMOTE CONTROL ********/ virtual void set_parameter(const std::string& parameter, const std::string& value) override; virtual const std::string get_parameter(const std::string& parameter) const override; - virtual const RemoteControllable::map_t get_all_values() const override; + virtual const json::map_t get_all_values() const override; protected: void update_window(size_t new_window_overlap); diff --git a/src/MemlessPoly.cpp b/src/MemlessPoly.cpp index a2b0082..30d4ce9 100644 --- a/src/MemlessPoly.cpp +++ b/src/MemlessPoly.cpp @@ -467,9 +467,9 @@ const string MemlessPoly::get_parameter(const string& parameter) const return ss.str(); } -const RemoteControllable::map_t MemlessPoly::get_all_values() const +const json::map_t MemlessPoly::get_all_values() const { - map_t map; + json::map_t map; map["ncoefs"] = m_coefs_am.size(); map["coefs"] = serialise_coefficients(); map["coeffile"] = m_coefs_file; diff --git a/src/MemlessPoly.h b/src/MemlessPoly.h index 09adc13..91e6860 100644 --- a/src/MemlessPoly.h +++ b/src/MemlessPoly.h @@ -68,7 +68,7 @@ public: /******* REMOTE CONTROL ********/ virtual void set_parameter(const std::string& parameter, const std::string& value) override; virtual const std::string get_parameter(const std::string& parameter) const override; - virtual const RemoteControllable::map_t get_all_values() const override; + virtual const json::map_t get_all_values() const override; private: int internal_process(Buffer* const dataIn, Buffer* dataOut) override; diff --git a/src/OfdmGenerator.cpp b/src/OfdmGenerator.cpp index d161861..cb799d3 100644 --- a/src/OfdmGenerator.cpp +++ b/src/OfdmGenerator.cpp @@ -458,9 +458,9 @@ const std::string OfdmGenerator::get_parameter(const std::string& parameter) con return ss.str(); } -const RemoteControllable::map_t OfdmGenerator::get_all_values() const +const json::map_t OfdmGenerator::get_all_values() const { - map_t map; + json::map_t map; // TODO needs rework of the values return map; } diff --git a/src/OfdmGenerator.h b/src/OfdmGenerator.h index 90e562a..dc1ad46 100644 --- a/src/OfdmGenerator.h +++ b/src/OfdmGenerator.h @@ -61,7 +61,7 @@ class OfdmGenerator : public ModCodec, public RemoteControllable /* Functions for the remote control */ virtual void set_parameter(const std::string& parameter, const std::string& value) override; virtual const std::string get_parameter(const std::string& parameter) const override; - virtual const RemoteControllable::map_t get_all_values() const override; + virtual const json::map_t get_all_values() const override; protected: struct cfr_iter_stat_t { diff --git a/src/TII.cpp b/src/TII.cpp index b329cdb..9068630 100644 --- a/src/TII.cpp +++ b/src/TII.cpp @@ -385,9 +385,9 @@ const std::string TII::get_parameter(const std::string& parameter) const return ss.str(); } -const RemoteControllable::map_t TII::get_all_values() const +const json::map_t TII::get_all_values() const { - map_t map; + json::map_t map; map["enable"] = m_conf.enable; map["pattern"] = m_conf.pattern; map["comb"] = m_conf.comb; diff --git a/src/TII.h b/src/TII.h index b0ba646..a8d0ca9 100644 --- a/src/TII.h +++ b/src/TII.h @@ -89,7 +89,7 @@ class TII : public ModCodec, public RemoteControllable /******* REMOTE CONTROL ********/ virtual void set_parameter(const std::string& parameter, const std::string& value) override; virtual const std::string get_parameter(const std::string& parameter) const override; - virtual const RemoteControllable::map_t get_all_values() const override; + virtual const json::map_t get_all_values() const override; protected: // Fill m_Acp with the correct carriers for the pattern/comb diff --git a/src/TimestampDecoder.cpp b/src/TimestampDecoder.cpp index 149cd50..4277e55 100644 --- a/src/TimestampDecoder.cpp +++ b/src/TimestampDecoder.cpp @@ -301,9 +301,9 @@ const std::string TimestampDecoder::get_parameter( return ss.str(); } -const RemoteControllable::map_t TimestampDecoder::get_all_values() const +const json::map_t TimestampDecoder::get_all_values() const { - map_t map; + json::map_t map; map["offset"] = timestamp_offset; if (full_timestamp_received) { map["timestamp"] = time_secs + ((double)time_pps / 16384000.0); diff --git a/src/TimestampDecoder.h b/src/TimestampDecoder.h index dc5aa78..b90c328 100644 --- a/src/TimestampDecoder.h +++ b/src/TimestampDecoder.h @@ -120,7 +120,7 @@ class TimestampDecoder : public RemoteControllable /* Base function to set parameters. */ virtual void set_parameter(const std::string& parameter, const std::string& value) override; virtual const std::string get_parameter(const std::string& parameter) const override; - virtual const RemoteControllable::map_t get_all_values() const override; + virtual const json::map_t get_all_values() const override; const char* name() { return "TS"; } diff --git a/src/output/SDR.cpp b/src/output/SDR.cpp index 11321f2..4fc3277 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); + const auto& value = stat.at(parameter).data; if (std::holds_alternative(value)) { ss << std::get(value); } @@ -485,9 +485,9 @@ const string SDR::get_parameter(const string& parameter) const return ss.str(); } -const RemoteControllable::map_t SDR::get_all_values() const +const json::map_t SDR::get_all_values() const { - map_t stat = m_device->get_run_statistics(); + json::map_t stat = m_device->get_run_statistics(); stat["txgain"] = m_config.txgain; stat["rxgain"] = m_config.rxgain; diff --git a/src/output/SDR.h b/src/output/SDR.h index 94c972b..960de0c 100644 --- a/src/output/SDR.h +++ b/src/output/SDR.h @@ -67,7 +67,7 @@ class SDR : public ModOutput, public ModMetadata, public RemoteControllable { virtual const std::string get_parameter( const std::string& parameter) const override; - virtual const RemoteControllable::map_t get_all_values() const override; + virtual const json::map_t get_all_values() const override; private: void process_thread_entry(void); diff --git a/src/output/SDRDevice.h b/src/output/SDRDevice.h index f84b340..f728d8b 100644 --- a/src/output/SDRDevice.h +++ b/src/output/SDRDevice.h @@ -116,7 +116,7 @@ struct FrameData { // All SDR Devices must implement the SDRDevice interface class SDRDevice { public: - using run_statistics_t = RemoteControllable::map_t; + using run_statistics_t = json::map_t; virtual void tune(double lo_offset, double frequency) = 0; virtual double get_tx_freq(void) const = 0; -- 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 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 From c8e4c6851b0975200b5a34b8003e7c08389e6b33 Mon Sep 17 00:00:00 2001 From: "Matthias P. Braendli" Date: Wed, 19 Jul 2023 22:20:29 +0200 Subject: Improve zmq_remote.py --- doc/zmq-ctrl/zmq_remote.py | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/doc/zmq-ctrl/zmq_remote.py b/doc/zmq-ctrl/zmq_remote.py index 56465d3..7581575 100755 --- a/doc/zmq-ctrl/zmq_remote.py +++ b/doc/zmq-ctrl/zmq_remote.py @@ -16,7 +16,7 @@ poller = zmq.Poller() poller.register(sock, zmq.POLLIN) if len(sys.argv) < 2: - print("Usage: program url cmd [args...]") + print("Usage: program url cmd [args...]", file=sys.stderr) sys.exit(1) sock.connect(sys.argv[1]) @@ -25,7 +25,7 @@ message_parts = sys.argv[2:] # first do a ping test -print("ping") +print("ping", file=sys.stderr) sock.send(b"ping") socks = dict(poller.poll(1000)) @@ -33,9 +33,9 @@ if socks: if socks.get(sock) == zmq.POLLIN: data = sock.recv_multipart() - print("Received: {}".format(len(data))) + print("Received: {}".format(len(data)), file=sys.stderr) for i,part in enumerate(data): - print(" {}".format(part)) + print(" {}".format(part), file=sys.stderr) for i, part in enumerate(message_parts): if i == len(message_parts) - 1: @@ -43,18 +43,22 @@ if socks: else: f = zmq.SNDMORE - print("Send {}({}): '{}'".format(i, f, part)) + print("Send {}({}): '{}'".format(i, f, part), file=sys.stderr) sock.send(part.encode(), flags=f) data = sock.recv_multipart() - print("Received: {}".format(len(data))) - for i,part in enumerate(data): - print(" RX {}: {}".format(i, part.decode().replace('\n',' '))) + print("Received: {}".format(len(data)), file=sys.stderr) + for i, part in enumerate(data): + if message_parts[0] == 'showjson': + # This allows you to pipe the JSON into another tool + print(part.decode()) + else: + print(" RX {}: {}".format(i, part.decode().replace('\n',' ')), file=sys.stderr) else: - print("ZMQ error: timeout") + print("ZMQ error: timeout", file=sys.stderr) context.destroy(linger=5) # This is free and unencumbered software released into the public domain. -- cgit v1.2.3 From 2de7bab3cf1c7757078a113b35e927ecbccc5e3d Mon Sep 17 00:00:00 2001 From: "Matthias P. Braendli" Date: Thu, 20 Jul 2023 09:23:17 +0200 Subject: Handle event socket bind failure --- doc/receive_events.py | 2 +- src/Events.cpp | 12 +++++++++++- src/Events.h | 1 + 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/doc/receive_events.py b/doc/receive_events.py index dca27cd..bfd6f86 100755 --- a/doc/receive_events.py +++ b/doc/receive_events.py @@ -13,7 +13,7 @@ from pprint import pprint context = zmq.Context() sock = context.socket(zmq.SUB) -ep = "tcp://127.0.0.1:5557" +ep = "tcp://127.0.0.1:5556" print(f"Receive from {ep}") sock.connect(ep) diff --git a/src/Events.cpp b/src/Events.cpp index d65b73a..3171cda 100644 --- a/src/Events.cpp +++ b/src/Events.cpp @@ -47,11 +47,21 @@ EventSender::~EventSender() void EventSender::bind(const std::string& bind_endpoint) { - m_socket.bind(bind_endpoint); + try { + m_socket.bind(bind_endpoint); + m_socket_valid = true; + } + catch (const zmq::error_t& err) { + fprintf(stderr, "Cannot bind event socket: %s", err.what()); + } } void EventSender::send(const std::string& event_name, const json::map_t& detail) { + if (not m_socket_valid) { + return; + } + 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()); diff --git a/src/Events.h b/src/Events.h index 215c5a8..9f838e5 100644 --- a/src/Events.h +++ b/src/Events.h @@ -61,6 +61,7 @@ class EventSender { private: zmq::context_t m_zmq_context; zmq::socket_t m_socket; + bool m_socket_valid = false; }; class LogToEventSender: public LogBackend { -- cgit v1.2.3 From 595606ca4ed3011af19c6d8a9f95519d84320120 Mon Sep 17 00:00:00 2001 From: "Matthias P. Braendli" Date: Thu, 20 Jul 2023 13:06:22 +0200 Subject: Rework Json so that it works with map that doesn't support incomplete types --- lib/Json.cpp | 5 +++-- lib/Json.h | 7 +++++-- lib/RemoteControl.cpp | 3 ++- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/lib/Json.cpp b/lib/Json.cpp index da5b078..121c2de 100644 --- a/lib/Json.cpp +++ b/lib/Json.cpp @@ -86,8 +86,9 @@ namespace json { else if (std::holds_alternative(value)) { ss << "null"; } - else if (std::holds_alternative(value)) { - ss << map_to_json(std::get(value)); + else if (std::holds_alternative >(value)) { + const map_t& v = *std::get >(value); + ss << map_to_json(v); } else { throw std::logic_error("variant alternative not handled"); diff --git a/lib/Json.h b/lib/Json.h index ee67f35..706394f 100644 --- a/lib/Json.h +++ b/lib/Json.h @@ -41,11 +41,12 @@ namespace json { - using map_t = std::unordered_map; + // STL containers are not required to support incomplete types, + // hence the shared_ptr struct value_t { std::variant< - map_t, + std::shared_ptr>, std::string, double, size_t, @@ -54,5 +55,7 @@ namespace json { std::nullopt_t> v; }; + using map_t = std::unordered_map; + std::string map_to_json(const map_t& values); } diff --git a/lib/RemoteControl.cpp b/lib/RemoteControl.cpp index fbe0662..dca3373 100644 --- a/lib/RemoteControl.cpp +++ b/lib/RemoteControl.cpp @@ -109,7 +109,8 @@ 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()].v = controllable->get_all_values(); + root[controllable->get_rc_name()].v = + std::make_shared(controllable->get_all_values()); } return json::map_to_json(root); -- cgit v1.2.3 From 0887d7e859605fad9617681695e70e3ef738a19c Mon Sep 17 00:00:00 2001 From: "Matthias P. Braendli" Date: Thu, 20 Jul 2023 18:14:51 +0200 Subject: Add main loop RC metrics --- lib/Socket.cpp | 2 +- src/DabMod.cpp | 165 ++++++++++++++++++-------------------------- src/DabModulator.h | 2 + src/EtiReader.cpp | 2 +- src/GuardIntervalInserter.h | 2 + src/InputTcpReader.cpp | 3 + src/TII.h | 1 + src/TimestampDecoder.h | 1 + src/Utils.cpp | 82 ++++++++++++++++++++++ src/Utils.h | 13 ++-- 10 files changed, 169 insertions(+), 104 deletions(-) diff --git a/lib/Socket.cpp b/lib/Socket.cpp index 10ec1ca..b71c01e 100644 --- a/lib/Socket.cpp +++ b/lib/Socket.cpp @@ -893,7 +893,7 @@ ssize_t TCPClient::recv(void *buffer, size_t length, int flags, int timeout_ms) return 0; } - return 0; + throw std::logic_error("unreachable"); } void TCPClient::reconnect() diff --git a/src/DabMod.cpp b/src/DabMod.cpp index fdd9e93..7daa72a 100644 --- a/src/DabMod.cpp +++ b/src/DabMod.cpp @@ -3,7 +3,7 @@ Her Majesty the Queen in Right of Canada (Communications Research Center Canada) - Copyright (C) 2019 + Copyright (C) 2023 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org @@ -96,18 +96,58 @@ void signalHandler(int signalNb) running = 0; } -struct modulator_data -{ - // For ETI - std::shared_ptr inputReader; - std::shared_ptr etiReader; +class ModulatorData : public RemoteControllable { + public: + // For ETI + std::shared_ptr inputReader; + std::shared_ptr etiReader; + + // For EDI + std::shared_ptr ediInput; + + // Common to both EDI and EDI + uint64_t framecount = 0; + Flowgraph *flowgraph = nullptr; + + + // RC-related + ModulatorData() : RemoteControllable("mainloop") { + RC_ADD_PARAMETER(num_modulator_restarts, "(Read-only) Number of mod restarts"); + RC_ADD_PARAMETER(most_recent_edi_decoded, "(Read-only) UNIX Timestamp of most recently decoded EDI frame"); + } + + virtual ~ModulatorData() {} + + virtual void set_parameter(const std::string& parameter, const std::string& value) { + throw ParameterError("Parameter " + parameter + " is read-only"); + } + + virtual const std::string get_parameter(const std::string& parameter) const { + stringstream ss; + if (parameter == "num_modulator_restarts") { + ss << num_modulator_restarts; + } + else if (parameter == "most_recent_edi_decoded") { + ss << most_recent_edi_decoded; + } + else { + ss << "Parameter '" << parameter << + "' is not exported by controllable " << get_rc_name(); + throw ParameterError(ss.str()); + } + return ss.str(); + } - // For EDI - std::shared_ptr ediInput; + virtual const json::map_t get_all_values() const + { + json::map_t map; + map["num_modulator_restarts"].v = num_modulator_restarts; + map["most_recent_edi_decoded"].v = most_recent_edi_decoded; + return map; + } - // Common to both EDI and EDI - uint64_t framecount = 0; - Flowgraph *flowgraph = nullptr; + size_t num_modulator_restarts = 0; + time_t most_recent_edi_decoded = 0; }; enum class run_modulator_state_t { @@ -117,88 +157,8 @@ enum class run_modulator_state_t { reconfigure // Some sort of change of configuration we cannot handle happened }; -static run_modulator_state_t run_modulator(const mod_settings_t& mod_settings, modulator_data& m); - -static void printModSettings(const mod_settings_t& mod_settings) -{ - stringstream ss; - // Print settings - ss << "Input\n"; - ss << " Type: " << mod_settings.inputTransport << "\n"; - ss << " Source: " << mod_settings.inputName << "\n"; - - ss << "Output\n"; - - if (mod_settings.useFileOutput) { - ss << " Name: " << mod_settings.outputName << "\n"; - } -#if defined(HAVE_OUTPUT_UHD) - else if (mod_settings.useUHDOutput) { - ss << " UHD\n" << - " Device: " << mod_settings.sdr_device_config.device << "\n" << - " Subdevice: " << - mod_settings.sdr_device_config.subDevice << "\n" << - " master_clock_rate: " << - mod_settings.sdr_device_config.masterClockRate << "\n" << - " refclk: " << - mod_settings.sdr_device_config.refclk_src << "\n" << - " pps source: " << - mod_settings.sdr_device_config.pps_src << "\n"; - } -#endif -#if defined(HAVE_SOAPYSDR) - else if (mod_settings.useSoapyOutput) { - ss << " SoapySDR\n" - " Device: " << mod_settings.sdr_device_config.device << "\n" << - " master_clock_rate: " << - mod_settings.sdr_device_config.masterClockRate << "\n"; - } -#endif -#if defined(HAVE_DEXTER) - else if (mod_settings.useDexterOutput) { - ss << " PrecisionWave DEXTER\n"; - } -#endif -#if defined(HAVE_LIMESDR) - else if (mod_settings.useLimeOutput) { - ss << " LimeSDR\n" - " Device: " << mod_settings.sdr_device_config.device << "\n" << - " master_clock_rate: " << - mod_settings.sdr_device_config.masterClockRate << "\n"; - } -#endif -#if defined(HAVE_BLADERF) - else if (mod_settings.useBladeRFOutput) { - ss << " BladeRF\n" - " Device: " << mod_settings.sdr_device_config.device << "\n" << - " refclk: " << mod_settings.sdr_device_config.refclk_src << "\n"; - } -#endif - else if (mod_settings.useZeroMQOutput) { - ss << " ZeroMQ\n" << - " Listening on: " << mod_settings.outputName << "\n" << - " Socket type : " << mod_settings.zmqOutputSocketType << "\n"; - } +static run_modulator_state_t run_modulator(const mod_settings_t& mod_settings, ModulatorData& m); - ss << " Sampling rate: "; - if (mod_settings.outputRate > 1000) { - if (mod_settings.outputRate > 1000000) { - ss << std::fixed << std::setprecision(4) << - mod_settings.outputRate / 1000000.0 << - " MHz\n"; - } - else { - ss << std::fixed << std::setprecision(4) << - mod_settings.outputRate / 1000.0 << - " kHz\n"; - } - } - else { - ss << std::fixed << std::setprecision(4) << - mod_settings.outputRate << " Hz\n"; - } - fprintf(stderr, "%s", ss.str().c_str()); -} static shared_ptr prepare_output(mod_settings_t& s) { @@ -346,6 +306,9 @@ int launch_modulator(int argc, char* argv[]) printModSettings(mod_settings); + ModulatorData m; + rcs.enrol(&m); + { // 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 @@ -422,14 +385,15 @@ int launch_modulator(int argc, char* argv[]) "invalid input transport " + mod_settings.inputTransport + " selected!"); } + m.ediInput = ediInput; + m.inputReader = inputReader; + bool run_again = true; while (run_again) { Flowgraph flowgraph(mod_settings.showProcessTime); - modulator_data m; - m.ediInput = ediInput; - m.inputReader = inputReader; + m.framecount = 0; m.flowgraph = &flowgraph; shared_ptr modulator; @@ -493,13 +457,14 @@ int launch_modulator(int argc, char* argv[]) } etiLog.level(info) << m.framecount << " DAB frames, " << ((float)m.framecount * 0.024f) << " seconds encoded"; + m.num_modulator_restarts++; } etiLog.level(info) << "Terminating"; return ret; } -static run_modulator_state_t run_modulator(const mod_settings_t& mod_settings, modulator_data& m) +static run_modulator_state_t run_modulator(const mod_settings_t& mod_settings, ModulatorData& m) { auto ret = run_modulator_state_t::failure; try { @@ -579,6 +544,12 @@ static run_modulator_state_t run_modulator(const mod_settings_t& mod_settings, m break; } + struct timespec t; + if (clock_gettime(CLOCK_REALTIME, &t) != 0) { + throw std::runtime_error(std::string("Failed to retrieve CLOCK_REALTIME") + strerror(errno)); + } + + m.most_recent_edi_decoded = t.tv_sec; fct = m.ediInput->ediReader.getFct(); fp = m.ediInput->ediReader.getFp(); ts = m.ediInput->ediReader.getTimestamp(); @@ -611,7 +582,7 @@ static run_modulator_state_t run_modulator(const mod_settings_t& mod_settings, m last_eti_fct = fct; } else { - etiLog.level(info) << "ETI FCT discontinuity, expected " << + etiLog.level(warn) << "ETI FCT discontinuity, expected " << expected_fct << " received " << fct; if (m.ediInput) { m.ediInput->ediReader.clearFrame(); diff --git a/src/DabModulator.h b/src/DabModulator.h index 140f313..093a782 100644 --- a/src/DabModulator.h +++ b/src/DabModulator.h @@ -53,6 +53,8 @@ public: DabModulator(EtiSource& etiSource, mod_settings_t& settings, const std::string& format); // Allowed formats: s8, u8 and s16. Empty string means no conversion + virtual ~DabModulator() {} + int process(Buffer* dataOut) override; const char* name() override { return "DabModulator"; } diff --git a/src/EtiReader.cpp b/src/EtiReader.cpp index e992e62..580088b 100644 --- a/src/EtiReader.cpp +++ b/src/EtiReader.cpp @@ -646,7 +646,7 @@ bool EdiTransport::rxPacket() const int timeout_ms = 1000; try { ssize_t ret = m_tcpclient.recv(m_tcpbuffer.data(), m_tcpbuffer.size(), 0, timeout_ms); - if (ret == 0 or ret == -1) { + if (ret <= 0) { return false; } else if (ret > (ssize_t)m_tcpbuffer.size()) { diff --git a/src/GuardIntervalInserter.h b/src/GuardIntervalInserter.h index 5aaad2b..f78ac91 100644 --- a/src/GuardIntervalInserter.h +++ b/src/GuardIntervalInserter.h @@ -52,6 +52,8 @@ class GuardIntervalInserter : public ModCodec, public RemoteControllable size_t symSize, size_t& windowOverlap); + virtual ~GuardIntervalInserter() {} + int process(Buffer* const dataIn, Buffer* dataOut) override; const char* name() override { return "GuardIntervalInserter"; } diff --git a/src/InputTcpReader.cpp b/src/InputTcpReader.cpp index 21f8496..8ba4d74 100644 --- a/src/InputTcpReader.cpp +++ b/src/InputTcpReader.cpp @@ -79,6 +79,9 @@ int InputTcpReader::GetNextFrame(void* buffer) etiLog.level(debug) << "TCP input auto reconnect"; std::this_thread::sleep_for(std::chrono::seconds(1)); } + else if (ret == -2) { + etiLog.level(debug) << "TCP input timeout"; + } return ret; } diff --git a/src/TII.h b/src/TII.h index a8d0ca9..f6de70b 100644 --- a/src/TII.h +++ b/src/TII.h @@ -82,6 +82,7 @@ class TII : public ModCodec, public RemoteControllable { public: TII(unsigned int dabmode, tii_config_t& tii_config); + virtual ~TII() {} int process(Buffer* dataIn, Buffer* dataOut) override; const char* name() override; diff --git a/src/TimestampDecoder.h b/src/TimestampDecoder.h index b90c328..25796ca 100644 --- a/src/TimestampDecoder.h +++ b/src/TimestampDecoder.h @@ -98,6 +98,7 @@ class TimestampDecoder : public RemoteControllable * frame transmission */ TimestampDecoder(double& offset_s); + virtual ~TimestampDecoder() {} frame_timestamp getTimestamp(void); diff --git a/src/Utils.cpp b/src/Utils.cpp index 3f378a7..94f198c 100644 --- a/src/Utils.cpp +++ b/src/Utils.cpp @@ -25,6 +25,7 @@ along with ODR-DabMod. If not, see . */ +#include "sstream" #include "Utils.h" #include "GainControl.h" #if defined(HAVE_PRCTL) @@ -144,6 +145,87 @@ void printStartupInfo() printHeader(); } +void printModSettings(const mod_settings_t& mod_settings) +{ + std::stringstream ss; + // Print settings + ss << "Input\n"; + ss << " Type: " << mod_settings.inputTransport << "\n"; + ss << " Source: " << mod_settings.inputName << "\n"; + + ss << "Output\n"; + + if (mod_settings.useFileOutput) { + ss << " Name: " << mod_settings.outputName << "\n"; + } +#if defined(HAVE_OUTPUT_UHD) + else if (mod_settings.useUHDOutput) { + ss << " UHD\n" << + " Device: " << mod_settings.sdr_device_config.device << "\n" << + " Subdevice: " << + mod_settings.sdr_device_config.subDevice << "\n" << + " master_clock_rate: " << + mod_settings.sdr_device_config.masterClockRate << "\n" << + " refclk: " << + mod_settings.sdr_device_config.refclk_src << "\n" << + " pps source: " << + mod_settings.sdr_device_config.pps_src << "\n"; + } +#endif +#if defined(HAVE_SOAPYSDR) + else if (mod_settings.useSoapyOutput) { + ss << " SoapySDR\n" + " Device: " << mod_settings.sdr_device_config.device << "\n" << + " master_clock_rate: " << + mod_settings.sdr_device_config.masterClockRate << "\n"; + } +#endif +#if defined(HAVE_DEXTER) + else if (mod_settings.useDexterOutput) { + ss << " PrecisionWave DEXTER\n"; + } +#endif +#if defined(HAVE_LIMESDR) + else if (mod_settings.useLimeOutput) { + ss << " LimeSDR\n" + " Device: " << mod_settings.sdr_device_config.device << "\n" << + " master_clock_rate: " << + mod_settings.sdr_device_config.masterClockRate << "\n"; + } +#endif +#if defined(HAVE_BLADERF) + else if (mod_settings.useBladeRFOutput) { + ss << " BladeRF\n" + " Device: " << mod_settings.sdr_device_config.device << "\n" << + " refclk: " << mod_settings.sdr_device_config.refclk_src << "\n"; + } +#endif + else if (mod_settings.useZeroMQOutput) { + ss << " ZeroMQ\n" << + " Listening on: " << mod_settings.outputName << "\n" << + " Socket type : " << mod_settings.zmqOutputSocketType << "\n"; + } + + ss << " Sampling rate: "; + if (mod_settings.outputRate > 1000) { + if (mod_settings.outputRate > 1000000) { + ss << std::fixed << std::setprecision(4) << + mod_settings.outputRate / 1000000.0 << + " MHz\n"; + } + else { + ss << std::fixed << std::setprecision(4) << + mod_settings.outputRate / 1000.0 << + " kHz\n"; + } + } + else { + ss << std::fixed << std::setprecision(4) << + mod_settings.outputRate << " Hz\n"; + } + fprintf(stderr, "%s", ss.str().c_str()); +} + int set_realtime_prio(int prio) { // Set thread priority to realtime diff --git a/src/Utils.h b/src/Utils.h index 9e88488..82728e9 100644 --- a/src/Utils.h +++ b/src/Utils.h @@ -3,7 +3,7 @@ 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 http://opendigitalradio.org @@ -31,12 +31,13 @@ # include "config.h" #endif -#include -#include -#include -#include #include #include +#include +#include +#include +#include +#include "ConfigParser.h" void printUsage(const char* progName); @@ -44,6 +45,8 @@ void printVersion(void); void printStartupInfo(void); +void printModSettings(const mod_settings_t& mod_settings); + // Set SCHED_RR with priority prio (0=lowest) int set_realtime_prio(int prio); -- cgit v1.2.3 From 738a1e859737458d3ee9c5a8e92655bdd107a07e Mon Sep 17 00:00:00 2001 From: "Matthias P. Braendli" Date: Tue, 15 Aug 2023 10:05:09 +0200 Subject: Revert "DEXTER: Add pacontrol interface" This reverts commit 150b75b244602c789934f1a5094f33aa7da3c09a. --- doc/example.ini | 6 ----- src/MemlessPoly.cpp | 4 +-- src/output/Dexter.cpp | 71 +------------------------------------------------- src/output/Dexter.h | 14 +--------- src/output/SDRDevice.h | 4 --- 5 files changed, 4 insertions(+), 95 deletions(-) diff --git a/doc/example.ini b/doc/example.ini index 4cc6d26..cd48ef4 100644 --- a/doc/example.ini +++ b/doc/example.ini @@ -320,12 +320,6 @@ txgain=32768 ;frequency=234208000 channel=13C -pacontrol_config_endpoint=tcp://localhost:5558 - -; TargetPower in dBm -; Runtime-changes to this setting are directly sent to pacontrol -pacontrol_targetpower=33 - [limeoutput] ; Lime output directly runs against the LMS device driver. It does not support SFN nor predistortion. device= diff --git a/src/MemlessPoly.cpp b/src/MemlessPoly.cpp index 184b5bd..17a7f57 100644 --- a/src/MemlessPoly.cpp +++ b/src/MemlessPoly.cpp @@ -314,7 +314,7 @@ void MemlessPoly::worker_thread(MemlessPoly::worker_t *workerdata) set_thread_name("MemlessPoly"); while (true) { - worker_t::input_data_t in_data = {}; + worker_t::input_data_t in_data; try { workerdata->in_queue.wait_and_pop(in_data); } @@ -386,7 +386,7 @@ int MemlessPoly::internal_process(Buffer* const dataIn, Buffer* dataOut) // Wait for completion of the tasks for (auto& worker : m_workers) { - int ret = 0; + int ret; worker.out_queue.wait_and_pop(ret); } } diff --git a/src/output/Dexter.cpp b/src/output/Dexter.cpp index e52f774..14edace 100644 --- a/src/output/Dexter.cpp +++ b/src/output/Dexter.cpp @@ -66,35 +66,9 @@ static void fill_time(struct timespec *t) } } -static void pacontrol_set_targetpower(zmq::socket_t& sock, int targetpower_dBm) -{ - stringstream message_builder; - message_builder << "{'PA0' : {'TargetPower': "; - message_builder << targetpower_dBm; - message_builder << "}}"; - const auto message = message_builder.str(); - - try { - const auto r = sock.send(zmq::const_buffer{message.data(), message.size()}); - if (r.has_value()) { - etiLog.level(debug) << "Sent TargetPower=" << targetpower_dBm << " to pacontrol"; - } - else { - // zmq_send returned EAGAIN - etiLog.level(info) << "Send TargetPower=" << targetpower_dBm << " failed"; - } - } - catch (const zmq::error_t& err) { - etiLog.level(warn) << "Failed to send TargetPower=" << targetpower_dBm << ": " << err.what(); - } -} - - Dexter::Dexter(SDRDeviceConfig& config) : SDRDevice(), - m_conf(config), - m_zmq_context(1), - m_zmq_sock(m_zmq_context, ZMQ_PUSH) + m_conf(config) { etiLog.level(info) << "Dexter:Creating the device"; @@ -192,41 +166,6 @@ Dexter::Dexter(SDRDeviceConfig& config) : m_running = true; m_underflow_read_thread = std::thread(&Dexter::underflow_read_process, this); - - m_zmq_sock.setsockopt(ZMQ_SNDTIMEO, 0); - if (not m_conf.pacontrol_config_endpoint.empty()) { - etiLog.level(debug) << "Creating pacontrol connection to " << - m_conf.pacontrol_config_endpoint; - m_zmq_sock.connect(m_conf.pacontrol_config_endpoint.c_str()); - } - - if (m_conf.pacontrol_targetpower.has_value()) { - pacontrol_set_targetpower(m_zmq_sock, *m_conf.pacontrol_targetpower); - } - else { - etiLog.level(warn) << "Config does not define PA target power"; - } -} - -static void pacontrol_set_mute(zmq::socket_t& sock, bool mute) -{ - string message = "{'PA0' : {'Mute': "; - message += (mute ? "True" : "False"); - message += "}}"; - - try { - const auto r = sock.send(zmq::const_buffer{message.data(), message.size()}); - if (r.has_value()) { - etiLog.level(debug) << "Sent mute=" << mute << " to pacontrol"; - } - else { - // zmq_send returned EAGAIN - etiLog.level(info) << "Send mute=" << mute << " failed"; - } - } - catch (const zmq::error_t& err) { - etiLog.level(warn) << "Failed to send mute=" << mute << ": " << err.what(); - } } void Dexter::channel_up() @@ -239,10 +178,6 @@ void Dexter::channel_up() m_channel_is_up = true; etiLog.level(debug) << "DEXTER CHANNEL_UP"; - - if (m_zmq_sock.connected()) { - pacontrol_set_mute(m_zmq_sock, false); - } } void Dexter::channel_down() @@ -259,10 +194,6 @@ void Dexter::channel_down() m_channel_is_up = false; etiLog.level(debug) << "DEXTER CHANNEL_DOWN"; - - if (m_zmq_sock.connected()) { - pacontrol_set_mute(m_zmq_sock, true); - } } void Dexter::handle_hw_time() diff --git a/src/output/Dexter.h b/src/output/Dexter.h index 57b9798..d4f425f 100644 --- a/src/output/Dexter.h +++ b/src/output/Dexter.h @@ -34,14 +34,8 @@ DESCRIPTION: # include #endif -#if defined(HAVE_DEXTER) - -#if !defined(HAVE_ZEROMQ) -#error "ZeroMQ is mandatory for DEXTER" -#endif - +#ifdef HAVE_DEXTER #include "iio.h" -#include "zmq.hpp" #include #include @@ -126,12 +120,6 @@ class Dexter : public Output::SDRDevice size_t num_buffers_pushed = 0; - /* Communication with pacontrol */ - zmq::context_t m_zmq_context; - zmq::socket_t m_zmq_sock; - std::string m_pacontrol_endpoint; - - /* Clock State */ DexterClockState m_clock_state = DexterClockState::Startup; // Only valid when m_clock_state is not Startup diff --git a/src/output/SDRDevice.h b/src/output/SDRDevice.h index f728d8b..378829c 100644 --- a/src/output/SDRDevice.h +++ b/src/output/SDRDevice.h @@ -94,10 +94,6 @@ struct SDRDeviceConfig { // TCP port on which to serve TX and RX samples for the // digital pre distortion learning tool uint16_t dpdFeedbackServerPort = 0; - - // DEXTER-specific - std::string pacontrol_config_endpoint; - std::optional pacontrol_targetpower; // dBm }; // Each frame contains one OFDM frame, and its -- cgit v1.2.3 From 6b1fa1637b03a9e4ddbac6aa99d936056c3d473f Mon Sep 17 00:00:00 2001 From: "Matthias P. Braendli" Date: Tue, 15 Aug 2023 10:06:14 +0200 Subject: MemlessPoly: avoid uninitialised variables --- src/MemlessPoly.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/MemlessPoly.cpp b/src/MemlessPoly.cpp index 17a7f57..184b5bd 100644 --- a/src/MemlessPoly.cpp +++ b/src/MemlessPoly.cpp @@ -314,7 +314,7 @@ void MemlessPoly::worker_thread(MemlessPoly::worker_t *workerdata) set_thread_name("MemlessPoly"); while (true) { - worker_t::input_data_t in_data; + worker_t::input_data_t in_data = {}; try { workerdata->in_queue.wait_and_pop(in_data); } @@ -386,7 +386,7 @@ int MemlessPoly::internal_process(Buffer* const dataIn, Buffer* dataOut) // Wait for completion of the tasks for (auto& worker : m_workers) { - int ret; + int ret = 0; worker.out_queue.wait_and_pop(ret); } } -- cgit v1.2.3 From 6c0732dcfc2af963e2b8577e9fc38f71307a1033 Mon Sep 17 00:00:00 2001 From: "Matthias P. Braendli" Date: Tue, 15 Aug 2023 10:11:06 +0200 Subject: Dexter: remove old metrics from SDR.cpp --- doc/example.ini | 9 ++++++++- src/output/SDR.cpp | 21 --------------------- 2 files changed, 8 insertions(+), 22 deletions(-) diff --git a/doc/example.ini b/doc/example.ini index cd48ef4..f009fbe 100644 --- a/doc/example.ini +++ b/doc/example.ini @@ -316,10 +316,17 @@ channel=13C ;dpd_port=50055 [dexteroutput] -txgain=32768 +txgain=65535 + +; channel/frequency is applied to ad9957.center_frequency ;frequency=234208000 channel=13C +; lo offset is applied to dexter_dsp_tx.frequency0 +lo_offset=0 + +max_gps_holdover_time=3600 + [limeoutput] ; Lime output directly runs against the LMS device driver. It does not support SFN nor predistortion. device= diff --git a/src/output/SDR.cpp b/src/output/SDR.cpp index 6c03b53..d2956a2 100644 --- a/src/output/SDR.cpp +++ b/src/output/SDR.cpp @@ -99,27 +99,6 @@ 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 - if (std::dynamic_pointer_cast(device)) { - 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"); - RC_ADD_PARAMETER(gpsdo_locked, "1 if GPSDO is locked"); - RC_ADD_PARAMETER(pps_clk_error_hz, "Estimated error in Hz of clock"); - RC_ADD_PARAMETER(pps_cnt, "Number of 1PPS pulses seen from GPS"); - RC_ADD_PARAMETER(dsp_version, "Version of FPGA DSP"); - RC_ADD_PARAMETER(vcc3v3, "Voltage of VCC 3V3"); - RC_ADD_PARAMETER(vcc5v4, "Voltage of VCC 5V4"); - RC_ADD_PARAMETER(vfan, "Fan voltage"); - RC_ADD_PARAMETER(vcc_main_in, "Main input voltage"); - RC_ADD_PARAMETER(vcc3v3pll, "Voltage of VCC 3V3 PLL"); - RC_ADD_PARAMETER(vcc2v5io, "Voltage of VCC 2V5 IO"); - RC_ADD_PARAMETER(vccocxo, "OCXO voltage"); - RC_ADD_PARAMETER(tempfpga, "FPGA temperature [celsius]"); - RC_ADD_PARAMETER(voltage_alarm, "Voltage out of bounds"); - RC_ADD_PARAMETER(temp_alarm, "Temperature out of bounds"); - } -#endif // HAVE_DEXTER } SDR::~SDR() -- cgit v1.2.3 From 2980f73251c631960fcbe048eda861afa7a1be89 Mon Sep 17 00:00:00 2001 From: "Matthias P. Braendli" Date: Tue, 15 Aug 2023 10:37:45 +0200 Subject: Dexter: Add in_holdover_since metric --- src/output/Dexter.cpp | 7 +++++++ src/output/SDR.cpp | 10 ++++++++++ 2 files changed, 17 insertions(+) diff --git a/src/output/Dexter.cpp b/src/output/Dexter.cpp index 14edace..457ec78 100644 --- a/src/output/Dexter.cpp +++ b/src/output/Dexter.cpp @@ -407,6 +407,7 @@ SDRDevice::run_statistics_t Dexter::get_run_statistics(void) const rs["frames"].v = num_frames_modulated; rs["in_holdover_since"].v = 0; + rs["remaining_holdover_s"].v = nullopt; switch (m_clock_state) { case DexterClockState::Startup: rs["clock_state"].v = "startup"; break; @@ -415,6 +416,12 @@ SDRDevice::run_statistics_t Dexter::get_run_statistics(void) const case DexterClockState::Holdover: rs["clock_state"].v = "holdover"; rs["in_holdover_since"].v = m_holdover_since_t; + { + using namespace std::chrono; + const auto max_holdover_duration = seconds(m_conf.maxGPSHoldoverTime); + const duration remaining = max_holdover_duration - (steady_clock::now() - m_holdover_since); + rs["remaining_holdover_s"].v = duration_cast(remaining).count(); + } break; } diff --git a/src/output/SDR.cpp b/src/output/SDR.cpp index d2956a2..d57e4d6 100644 --- a/src/output/SDR.cpp +++ b/src/output/SDR.cpp @@ -99,6 +99,16 @@ 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 + if (std::dynamic_pointer_cast(device)) { + RC_ADD_PARAMETER(in_holdover_since, "DEXTER timestamp when holdover began"); + RC_ADD_PARAMETER(remaining_holdover_s, "DEXTER remaining number of seconds in holdover"); + RC_ADD_PARAMETER(clock_state, "DEXTER clock state: startup/normal/holdover"); + } +#endif // HAVE_DEXTER + + } SDR::~SDR() -- cgit v1.2.3 From a759d1fae861e7f0836283dae5dce49dae6528fc Mon Sep 17 00:00:00 2001 From: "Matthias P. Braendli" Date: Tue, 15 Aug 2023 10:58:11 +0200 Subject: Add parameters sdr.synchronous and mainloop.running_since --- src/DabMod.cpp | 15 +++++++++------ src/Utils.cpp | 13 ++++++++++++- src/Utils.h | 3 +++ src/output/SDR.cpp | 6 ++++++ 4 files changed, 30 insertions(+), 7 deletions(-) diff --git a/src/DabMod.cpp b/src/DabMod.cpp index 7daa72a..d43ebd5 100644 --- a/src/DabMod.cpp +++ b/src/DabMod.cpp @@ -114,6 +114,7 @@ class ModulatorData : public RemoteControllable { ModulatorData() : RemoteControllable("mainloop") { RC_ADD_PARAMETER(num_modulator_restarts, "(Read-only) Number of mod restarts"); RC_ADD_PARAMETER(most_recent_edi_decoded, "(Read-only) UNIX Timestamp of most recently decoded EDI frame"); + RC_ADD_PARAMETER(running_since, "(Read-only) UNIX Timestamp of most recent modulator restart"); } virtual ~ModulatorData() {} @@ -127,6 +128,9 @@ class ModulatorData : public RemoteControllable { if (parameter == "num_modulator_restarts") { ss << num_modulator_restarts; } + if (parameter == "running_since") { + ss << running_since; + } else if (parameter == "most_recent_edi_decoded") { ss << most_recent_edi_decoded; } @@ -142,12 +146,14 @@ class ModulatorData : public RemoteControllable { { json::map_t map; map["num_modulator_restarts"].v = num_modulator_restarts; + map["running_since"].v = running_since; map["most_recent_edi_decoded"].v = most_recent_edi_decoded; return map; } size_t num_modulator_restarts = 0; time_t most_recent_edi_decoded = 0; + time_t running_since = 0; }; enum class run_modulator_state_t { @@ -391,6 +397,8 @@ int launch_modulator(int argc, char* argv[]) bool run_again = true; while (run_again) { + m.running_since = get_clock_realtime_seconds(); + Flowgraph flowgraph(mod_settings.showProcessTime); m.framecount = 0; @@ -544,12 +552,7 @@ static run_modulator_state_t run_modulator(const mod_settings_t& mod_settings, M break; } - struct timespec t; - if (clock_gettime(CLOCK_REALTIME, &t) != 0) { - throw std::runtime_error(std::string("Failed to retrieve CLOCK_REALTIME") + strerror(errno)); - } - - m.most_recent_edi_decoded = t.tv_sec; + m.most_recent_edi_decoded = get_clock_realtime_seconds(); fct = m.ediInput->ediReader.getFct(); fp = m.ediInput->ediReader.getFp(); ts = m.ediInput->ediReader.getTimestamp(); diff --git a/src/Utils.cpp b/src/Utils.cpp index 94f198c..20297ea 100644 --- a/src/Utils.cpp +++ b/src/Utils.cpp @@ -25,7 +25,8 @@ along with ODR-DabMod. If not, see . */ -#include "sstream" +#include +#include #include "Utils.h" #include "GainControl.h" #if defined(HAVE_PRCTL) @@ -304,3 +305,13 @@ std::chrono::milliseconds transmission_frame_duration(unsigned int dabmode) } } + +time_t get_clock_realtime_seconds() +{ + struct timespec t; + if (clock_gettime(CLOCK_REALTIME, &t) != 0) { + throw std::runtime_error(std::string("Failed to retrieve CLOCK_REALTIME") + strerror(errno)); + } + + return t.tv_sec; +} diff --git a/src/Utils.h b/src/Utils.h index 82728e9..367dd48 100644 --- a/src/Utils.h +++ b/src/Utils.h @@ -36,6 +36,7 @@ #include #include #include +#include #include #include "ConfigParser.h" @@ -59,3 +60,5 @@ double parseChannel(const std::string& chan); // dabMode is either 1, 2, 3, 4, corresponding to TM I, TM II, TM III and TM IV. // throws a runtime_error if dabMode is not one of these values. std::chrono::milliseconds transmission_frame_duration(unsigned int dabmode); + +time_t get_clock_realtime_seconds(); diff --git a/src/output/SDR.cpp b/src/output/SDR.cpp index d57e4d6..e466287 100644 --- a/src/output/SDR.cpp +++ b/src/output/SDR.cpp @@ -84,6 +84,7 @@ SDR::SDR(SDRDeviceConfig& config, std::shared_ptr device) : RC_ADD_PARAMETER(underruns, "Counter of number of underruns"); RC_ADD_PARAMETER(latepackets, "Counter of number of late packets"); RC_ADD_PARAMETER(frames, "Counter of number of frames modulated"); + RC_ADD_PARAMETER(synchronous, "1 if configured for synchronous transmission"); #ifdef HAVE_OUTPUT_UHD if (std::dynamic_pointer_cast(device)) { @@ -435,6 +436,9 @@ const string SDR::get_parameter(const string& parameter) const chrono::duration_cast(transmission_frame_duration(m_config.dabMode)) .count(); } + else if (parameter == "synchronous") { + ss << m_config.enableSync; + } else { if (m_device) { const auto stat = m_device->get_run_statistics(); @@ -494,6 +498,8 @@ const json::map_t SDR::get_all_values() const (size_t)chrono::duration_cast(transmission_frame_duration(m_config.dabMode)) .count(); + stat["synchronous"].v = m_config.enableSync; + return stat; } -- cgit v1.2.3 From 61b3fa742e24acc6b1b7747b3e863dff43670d51 Mon Sep 17 00:00:00 2001 From: "Matthias P. Braendli" Date: Tue, 15 Aug 2023 15:18:04 +0200 Subject: Fix compliation --- src/output/Dexter.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/output/Dexter.cpp b/src/output/Dexter.cpp index 457ec78..8e2a6c5 100644 --- a/src/output/Dexter.cpp +++ b/src/output/Dexter.cpp @@ -420,7 +420,7 @@ SDRDevice::run_statistics_t Dexter::get_run_statistics(void) const using namespace std::chrono; const auto max_holdover_duration = seconds(m_conf.maxGPSHoldoverTime); const duration remaining = max_holdover_duration - (steady_clock::now() - m_holdover_since); - rs["remaining_holdover_s"].v = duration_cast(remaining).count(); + rs["remaining_holdover_s"].v = (ssize_t)duration_cast(remaining).count(); } break; } -- cgit v1.2.3 From b102ff50555518606b8356bbc1dd70e233d0466c Mon Sep 17 00:00:00 2001 From: "Matthias P. Braendli" Date: Thu, 24 Aug 2023 15:35:30 +0200 Subject: Add channel to SDR RC --- src/ConfigParser.cpp | 10 +++++----- src/Utils.cpp | 53 +++++++++++++++++++++++++++++++++++++++++++++++++--- src/Utils.h | 8 ++++++-- src/output/SDR.cpp | 34 ++++++++++++++++++++++++++++++++- 4 files changed, 94 insertions(+), 11 deletions(-) diff --git a/src/ConfigParser.cpp b/src/ConfigParser.cpp index 68ee74b..1219ae7 100644 --- a/src/ConfigParser.cpp +++ b/src/ConfigParser.cpp @@ -251,7 +251,7 @@ static void parse_configfile( throw std::runtime_error("Configuration error"); } else if (sdr_device_config.frequency == 0) { - sdr_device_config.frequency = parseChannel(chan); + sdr_device_config.frequency = parse_channel(chan); } else if (sdr_device_config.frequency != 0 && chan != "") { std::cerr << " UHD output: cannot define both frequency and channel.\n"; @@ -305,7 +305,7 @@ static void parse_configfile( throw std::runtime_error("Configuration error"); } else if (outputsoapy_conf.frequency == 0) { - outputsoapy_conf.frequency = parseChannel(chan); + outputsoapy_conf.frequency = parse_channel(chan); } else if (outputsoapy_conf.frequency != 0 && chan != "") { std::cerr << " soapy output: cannot define both frequency and channel.\n"; @@ -333,7 +333,7 @@ static void parse_configfile( throw std::runtime_error("Configuration error"); } else if (outputdexter_conf.frequency == 0) { - outputdexter_conf.frequency = parseChannel(chan); + outputdexter_conf.frequency = parse_channel(chan); } else if (outputdexter_conf.frequency != 0 && chan != "") { std::cerr << " dexter output: cannot define both frequency and channel.\n"; @@ -362,7 +362,7 @@ static void parse_configfile( throw std::runtime_error("Configuration error"); } else if (outputlime_conf.frequency == 0) { - outputlime_conf.frequency = parseChannel(chan); + outputlime_conf.frequency = parse_channel(chan); } else if (outputlime_conf.frequency != 0 && chan != "") { std::cerr << " Lime output: cannot define both frequency and channel.\n"; @@ -391,7 +391,7 @@ static void parse_configfile( throw std::runtime_error("Configuration error"); } else if (outputbladerf_conf.frequency == 0) { - outputbladerf_conf.frequency = parseChannel(chan); + outputbladerf_conf.frequency = parse_channel(chan); } else if (outputbladerf_conf.frequency != 0 && chan != "") { std::cerr << " BladeRF output: cannot define both frequency and channel.\n"; diff --git a/src/Utils.cpp b/src/Utils.cpp index 20297ea..788d125 100644 --- a/src/Utils.cpp +++ b/src/Utils.cpp @@ -244,7 +244,7 @@ void set_thread_name(const char *name) #endif } -double parseChannel(const std::string& chan) +double parse_channel(const std::string& chan) { double freq; if (chan == "5A") freq = 174928000; @@ -286,12 +286,59 @@ double parseChannel(const std::string& chan) else if (chan == "13E") freq = 237488000; else if (chan == "13F") freq = 239200000; else { - std::cerr << " soapy output: channel " << chan << " does not exist in table\n"; - throw std::out_of_range("soapy channel selection error"); + std::cerr << "Channel " << chan << " does not exist in table\n"; + throw std::out_of_range("channel out of range"); } return freq; } +std::optional convert_frequency_to_channel(double frequency) +{ + const int freq = round(frequency); + std::string chan; + if (freq == 174928000) chan = "5A"; + else if (freq == 176640000) chan = "5B"; + else if (freq == 178352000) chan = "5C"; + else if (freq == 180064000) chan = "5D"; + else if (freq == 181936000) chan = "6A"; + else if (freq == 183648000) chan = "6B"; + else if (freq == 185360000) chan = "6C"; + else if (freq == 187072000) chan = "6D"; + else if (freq == 188928000) chan = "7A"; + else if (freq == 190640000) chan = "7B"; + else if (freq == 192352000) chan = "7C"; + else if (freq == 194064000) chan = "7D"; + else if (freq == 195936000) chan = "8A"; + else if (freq == 197648000) chan = "8B"; + else if (freq == 199360000) chan = "8C"; + else if (freq == 201072000) chan = "8D"; + else if (freq == 202928000) chan = "9A"; + else if (freq == 204640000) chan = "9B"; + else if (freq == 206352000) chan = "9C"; + else if (freq == 208064000) chan = "9D"; + else if (freq == 209936000) chan = "10A"; + else if (freq == 211648000) chan = "10B"; + else if (freq == 213360000) chan = "10C"; + else if (freq == 215072000) chan = "10D"; + else if (freq == 216928000) chan = "11A"; + else if (freq == 218640000) chan = "11B"; + else if (freq == 220352000) chan = "11C"; + else if (freq == 222064000) chan = "11D"; + else if (freq == 223936000) chan = "12A"; + else if (freq == 225648000) chan = "12B"; + else if (freq == 227360000) chan = "12C"; + else if (freq == 229072000) chan = "12D"; + else if (freq == 230784000) chan = "13A"; + else if (freq == 232496000) chan = "13B"; + else if (freq == 234208000) chan = "13C"; + else if (freq == 235776000) chan = "13D"; + else if (freq == 237488000) chan = "13E"; + else if (freq == 239200000) chan = "13F"; + else { return std::nullopt; } + + return chan; +} + std::chrono::milliseconds transmission_frame_duration(unsigned int dabmode) { using namespace std::chrono; diff --git a/src/Utils.h b/src/Utils.h index 367dd48..584a756 100644 --- a/src/Utils.h +++ b/src/Utils.h @@ -31,6 +31,7 @@ # include "config.h" #endif +#include #include #include #include @@ -54,8 +55,11 @@ int set_realtime_prio(int prio); // Set the name of the thread void set_thread_name(const char *name); -// Convert a channel like 10A to a frequency -double parseChannel(const std::string& chan); +// Convert a channel like 10A to a frequency in Hz +double parse_channel(const std::string& chan); + +// Convert a frequency in Hz to a channel. +std::optional convert_frequency_to_channel(double frequency); // dabMode is either 1, 2, 3, 4, corresponding to TM I, TM II, TM III and TM IV. // throws a runtime_error if dabMode is not one of these values. diff --git a/src/output/SDR.cpp b/src/output/SDR.cpp index e466287..91c31f0 100644 --- a/src/output/SDR.cpp +++ b/src/output/SDR.cpp @@ -78,7 +78,8 @@ SDR::SDR(SDRDeviceConfig& config, std::shared_ptr device) : RC_ADD_PARAMETER(txgain, "TX gain"); RC_ADD_PARAMETER(rxgain, "RX gain for DPD feedback"); RC_ADD_PARAMETER(bandwidth, "Analog front-end bandwidth"); - RC_ADD_PARAMETER(freq, "Transmission frequency"); + RC_ADD_PARAMETER(freq, "Transmission frequency in Hz"); + RC_ADD_PARAMETER(channel, "Transmission frequency as channel"); RC_ADD_PARAMETER(muting, "Mute the output by stopping the transmitter"); RC_ADD_PARAMETER(temp, "Temperature in degrees C of the device"); RC_ADD_PARAMETER(underruns, "Counter of number of underruns"); @@ -389,6 +390,18 @@ void SDR::set_parameter(const string& parameter, const string& value) m_device->tune(m_config.lo_offset, m_config.frequency); m_config.frequency = m_device->get_tx_freq(); } + else if (parameter == "channel") { + try { + const double frequency = parse_channel(value); + + m_config.frequency = frequency; + m_device->tune(m_config.lo_offset, m_config.frequency); + m_config.frequency = m_device->get_tx_freq(); + } + catch (const std::out_of_range& e) { + throw ParameterError("Cannot parse channel"); + } + } else if (parameter == "muting") { ss >> m_config.muting; } @@ -416,6 +429,16 @@ const string SDR::get_parameter(const string& parameter) const else if (parameter == "freq") { ss << m_config.frequency; } + else if (parameter == "channel") { + const auto maybe_freq = convert_frequency_to_channel(m_config.frequency); + + if (maybe_freq.has_value()) { + ss << *maybe_freq; + } + else { + throw ParameterError("Frequency is outside list of channels"); + } + } else if (parameter == "muting") { ss << m_config.muting; } @@ -488,6 +511,15 @@ const json::map_t SDR::get_all_values() const stat["muting"].v = m_config.muting; stat["temp"].v = std::nullopt; + const auto maybe_freq = convert_frequency_to_channel(m_config.frequency); + + if (maybe_freq.has_value()) { + stat["channel"].v = *maybe_freq; + } + else { + stat["channel"].v = std::nullopt; + } + if (m_device) { const std::optional temp = m_device->get_temperature(); if (temp) { -- cgit v1.2.3 From 4a1ef9edbf8c32d87292beac6cf96606442bf302 Mon Sep 17 00:00:00 2001 From: "Matthias P. Braendli" Date: Fri, 25 Aug 2023 14:01:24 +0200 Subject: Add max_gps_holdover_time to RC, allow modifying synchronous too --- src/output/SDR.cpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/output/SDR.cpp b/src/output/SDR.cpp index 91c31f0..44af875 100644 --- a/src/output/SDR.cpp +++ b/src/output/SDR.cpp @@ -86,6 +86,7 @@ SDR::SDR(SDRDeviceConfig& config, std::shared_ptr device) : RC_ADD_PARAMETER(latepackets, "Counter of number of late packets"); RC_ADD_PARAMETER(frames, "Counter of number of frames modulated"); RC_ADD_PARAMETER(synchronous, "1 if configured for synchronous transmission"); + RC_ADD_PARAMETER(max_gps_holdover_time, "Max holdover duration in seconds"); #ifdef HAVE_OUTPUT_UHD if (std::dynamic_pointer_cast(device)) { @@ -405,6 +406,14 @@ void SDR::set_parameter(const string& parameter, const string& value) else if (parameter == "muting") { ss >> m_config.muting; } + else if (parameter == "synchronous") { + uint32_t enableSync = 0; + ss >> enableSync; + m_config.enableSync = enableSync > 0; + } + else if (parameter == "max_gps_holdover_time") { + ss >> m_config.maxGPSHoldoverTime; + } else { stringstream ss_err; ss_err << "Parameter '" << parameter @@ -462,6 +471,9 @@ const string SDR::get_parameter(const string& parameter) const else if (parameter == "synchronous") { ss << m_config.enableSync; } + else if (parameter == "max_gps_holdover_time") { + ss << m_config.maxGPSHoldoverTime; + } else { if (m_device) { const auto stat = m_device->get_run_statistics(); @@ -531,6 +543,7 @@ const json::map_t SDR::get_all_values() const .count(); stat["synchronous"].v = m_config.enableSync; + stat["max_gps_holdover_time"].v = (size_t)m_config.maxGPSHoldoverTime; return stat; } -- cgit v1.2.3 From 0f11a2bc9b22a1362245459ae45ae4f86cb9374e Mon Sep 17 00:00:00 2001 From: "Matthias P. Braendli" Date: Tue, 5 Sep 2023 10:11:39 +0200 Subject: Dexter: make channel_down wait for underflow to occur --- src/output/Dexter.cpp | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/src/output/Dexter.cpp b/src/output/Dexter.cpp index 8e2a6c5..26472e8 100644 --- a/src/output/Dexter.cpp +++ b/src/output/Dexter.cpp @@ -187,11 +187,33 @@ void Dexter::channel_down() etiLog.level(error) << "Failed to set dexter_dsp_tx.gain0 = 0: " << get_iio_error(r); } - // This will flush out the FIFO + // Setting stream0_start_clocks to 0 will flush out the FIFO, but we need to wait a bit before + // we "up" the channel again 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); } + long long underflows_old = 0; + + if ((r = iio_device_attr_read_longlong(m_dexter_dsp_tx, "buffer_underflows0", &underflows_old)) != 0) { + etiLog.level(warn) << "Failed to read dexter_dsp_tx.buffer_underflows0 : " << get_iio_error(r); + } + + long long underflows = underflows_old; + + // Limiting to 10*96ms is just a safety to avoid running into an infinite loop + for (size_t i = 0; underflows == underflows_old && i < 10; i++) { + if ((r = iio_device_attr_read_longlong(m_dexter_dsp_tx, "buffer_underflows0", &underflows)) != 0) { + etiLog.level(warn) << "Failed to read dexter_dsp_tx.buffer_underflows0 : " << get_iio_error(r); + } + + this_thread::sleep_for(chrono::milliseconds(96)); + } + + if (underflows == underflows_old) { + etiLog.level(warn) << "DEXTER CHANNEL_DOWN, no underflow detected! " << underflows; + } + m_channel_is_up = false; etiLog.level(debug) << "DEXTER CHANNEL_DOWN"; } -- cgit v1.2.3 From 9fec0b8c25a4faa18c4b05e7754bb2973db9870d Mon Sep 17 00:00:00 2001 From: "Matthias P. Braendli" Date: Tue, 12 Sep 2023 16:30:50 +0200 Subject: Fix timestamp0 and timestamp in RC showjson --- lib/Json.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Json.cpp b/lib/Json.cpp index 121c2de..b2315b6 100644 --- a/lib/Json.cpp +++ b/lib/Json.cpp @@ -72,7 +72,7 @@ namespace json { ss << "\"" << escape_json(std::get(value)) << "\""; } else if (std::holds_alternative(value)) { - ss << std::defaultfloat << std::get(value); + ss << std::fixed << std::get(value); } else if (std::holds_alternative(value)) { ss << std::get(value); -- cgit v1.2.3 From 6728ddc82936d8d6223a885bf7fbcec9a00c18a6 Mon Sep 17 00:00:00 2001 From: "Matthias P. Braendli" Date: Tue, 10 Oct 2023 14:43:23 +0200 Subject: Don't clutter log output every 6s --- src/output/SDR.cpp | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/output/SDR.cpp b/src/output/SDR.cpp index 44af875..75a7ee3 100644 --- a/src/output/SDR.cpp +++ b/src/output/SDR.cpp @@ -357,12 +357,6 @@ void SDR::handle_frame(struct FrameData&& frame) return; } - if (frame.ts.fct == 0) { - etiLog.level(debug) << - "OutputSDR: TX FCT=" << frame.ts.fct << - " TS " << frame.ts.to_string(); - } - m_device->transmit_frame(std::move(frame)); } -- cgit v1.2.3 From c2467d222ec08ddc4c6f79ea01773496090f809f Mon Sep 17 00:00:00 2001 From: "Matthias P. Braendli" Date: Tue, 31 Oct 2023 23:02:10 +0100 Subject: Add FIC decoder, present ensemble info for monitoring --- Makefile.am | 4 + lib/Json.cpp | 75 ++-- lib/Json.h | 4 +- src/CharsetTools.cpp | 143 +++++++ src/CharsetTools.h | 58 +++ src/DabMod.cpp | 69 ++++ src/EtiReader.cpp | 17 +- src/EtiReader.h | 11 + src/FigParser.cpp | 1047 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/FigParser.h | 385 +++++++++++++++++++ 10 files changed, 1777 insertions(+), 36 deletions(-) create mode 100644 src/CharsetTools.cpp create mode 100644 src/CharsetTools.h create mode 100644 src/FigParser.cpp create mode 100644 src/FigParser.h diff --git a/Makefile.am b/Makefile.am index 5c75c62..d29b530 100644 --- a/Makefile.am +++ b/Makefile.am @@ -46,6 +46,8 @@ odr_dabmod_SOURCES = src/DabMod.cpp \ src/DabModulator.h \ src/Buffer.cpp \ src/Buffer.h \ + src/CharsetTools.cpp \ + src/CharsetTools.h \ src/ConfigParser.cpp \ src/ConfigParser.h \ src/ModPlugin.cpp \ @@ -56,6 +58,8 @@ odr_dabmod_SOURCES = src/DabMod.cpp \ src/Eti.h \ src/Events.cpp \ src/Events.h \ + src/FigParser.cpp \ + src/FigParser.h \ src/FicSource.cpp \ src/FicSource.h \ src/PuncturingRule.cpp \ diff --git a/lib/Json.cpp b/lib/Json.cpp index b2315b6..4dc2f25 100644 --- a/lib/Json.cpp +++ b/lib/Json.cpp @@ -22,7 +22,6 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ -#include #include #include #include @@ -66,33 +65,7 @@ namespace json { } ss << "\"" << escape_json(element.first) << "\": "; - - const auto& value = element.second.v; - if (std::holds_alternative(value)) { - ss << "\"" << escape_json(std::get(value)) << "\""; - } - else if (std::holds_alternative(value)) { - ss << std::fixed << 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) ? "true" : "false"); - } - else if (std::holds_alternative(value)) { - ss << "null"; - } - else if (std::holds_alternative >(value)) { - const map_t& v = *std::get >(value); - ss << map_to_json(v); - } - else { - throw std::logic_error("variant alternative not handled"); - } + ss << value_to_json(element.second); ix++; } @@ -100,4 +73,50 @@ namespace json { return ss.str(); } + + std::string value_to_json(const value_t& value) + { + std::ostringstream ss; + + if (std::holds_alternative(value.v)) { + ss << "\"" << escape_json(std::get(value.v)) << "\""; + } + else if (std::holds_alternative(value.v)) { + ss << std::fixed << std::get(value.v); + } + else if (std::holds_alternative(value.v)) { + ss << std::get(value.v); + } + else if (std::holds_alternative(value.v)) { + ss << std::get(value.v); + } + else if (std::holds_alternative(value.v)) { + ss << (std::get(value.v) ? "true" : "false"); + } + else if (std::holds_alternative(value.v)) { + ss << "null"; + } + else if (std::holds_alternative >(value.v)) { + const auto& vec = std::get >(value.v); + ss << "[ "; + size_t list_ix = 0; + for (const auto& list_element : vec) { + if (list_ix > 0) { + ss << ","; + } + ss << value_to_json(list_element); + list_ix++; + } + ss << "]"; + } + else if (std::holds_alternative >(value.v)) { + const map_t& v = *std::get >(value.v); + ss << map_to_json(v); + } + else { + throw std::logic_error("variant alternative not handled"); + } + + return ss.str(); + } } diff --git a/lib/Json.h b/lib/Json.h index 706394f..65aa668 100644 --- a/lib/Json.h +++ b/lib/Json.h @@ -31,7 +31,7 @@ # include "config.h" #endif -#include +#include #include #include #include @@ -47,6 +47,7 @@ namespace json { struct value_t { std::variant< std::shared_ptr>, + std::vector, std::string, double, size_t, @@ -58,4 +59,5 @@ namespace json { using map_t = std::unordered_map; std::string map_to_json(const map_t& values); + std::string value_to_json(const value_t& value); } diff --git a/src/CharsetTools.cpp b/src/CharsetTools.cpp new file mode 100644 index 0000000..d35c121 --- /dev/null +++ b/src/CharsetTools.cpp @@ -0,0 +1,143 @@ +/* + Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011 Her Majesty + the Queen in Right of Canada (Communications Research Center Canada) + + Most parts of this file are taken from dablin, + Copyright (C) 2015-2022 Stefan Pöschel + + Copyright (C) 2023 + Matthias P. Braendli, matthias.braendli@mpb.li + + http://opendigitalradio.org + */ +/* + This file is part of ODR-DabMod. + + ODR-DabMod 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. + + ODR-DabMod 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 ODR-DabMod. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "CharsetTools.h" + +// --- CharsetTools ----------------------------------------------------------------- +const char* CharsetTools::no_char = ""; +const char* CharsetTools::ebu_values_0x00_to_0x1F[] = { + no_char , "\u0118", "\u012E", "\u0172", "\u0102", "\u0116", "\u010E", "\u0218", "\u021A", "\u010A", no_char , no_char , "\u0120", "\u0139" , "\u017B", "\u0143", + "\u0105", "\u0119", "\u012F", "\u0173", "\u0103", "\u0117", "\u010F", "\u0219", "\u021B", "\u010B", "\u0147", "\u011A", "\u0121", "\u013A", "\u017C", no_char +}; +const char* CharsetTools::ebu_values_0x7B_to_0xFF[] = { + /* starting some chars earlier than 0x80 -----> */ "\u00AB", "\u016F", "\u00BB", "\u013D", "\u0126", + "\u00E1", "\u00E0", "\u00E9", "\u00E8", "\u00ED", "\u00EC", "\u00F3", "\u00F2", "\u00FA", "\u00F9", "\u00D1", "\u00C7", "\u015E", "\u00DF", "\u00A1", "\u0178", + "\u00E2", "\u00E4", "\u00EA", "\u00EB", "\u00EE", "\u00EF", "\u00F4", "\u00F6", "\u00FB", "\u00FC", "\u00F1", "\u00E7", "\u015F", "\u011F", "\u0131", "\u00FF", + "\u0136", "\u0145", "\u00A9", "\u0122", "\u011E", "\u011B", "\u0148", "\u0151", "\u0150", "\u20AC", "\u00A3", "\u0024", "\u0100", "\u0112", "\u012A", "\u016A", + "\u0137", "\u0146", "\u013B", "\u0123", "\u013C", "\u0130", "\u0144", "\u0171", "\u0170", "\u00BF", "\u013E", "\u00B0", "\u0101", "\u0113", "\u012B", "\u016B", + "\u00C1", "\u00C0", "\u00C9", "\u00C8", "\u00CD", "\u00CC", "\u00D3", "\u00D2", "\u00DA", "\u00D9", "\u0158", "\u010C", "\u0160", "\u017D", "\u00D0", "\u013F", + "\u00C2", "\u00C4", "\u00CA", "\u00CB", "\u00CE", "\u00CF", "\u00D4", "\u00D6", "\u00DB", "\u00DC", "\u0159", "\u010D", "\u0161", "\u017E", "\u0111", "\u0140", + "\u00C3", "\u00C5", "\u00C6", "\u0152", "\u0177", "\u00DD", "\u00D5", "\u00D8", "\u00DE", "\u014A", "\u0154", "\u0106", "\u015A", "\u0179", "\u0164", "\u00F0", + "\u00E3", "\u00E5", "\u00E6", "\u0153", "\u0175", "\u00FD", "\u00F5", "\u00F8", "\u00FE", "\u014B", "\u0155", "\u0107", "\u015B", "\u017A", "\u0165", "\u0127" +}; + +std::string CharsetTools::ConvertCharEBUToUTF8(const uint8_t value) { + // convert via LUT + if(value <= 0x1F) + return ebu_values_0x00_to_0x1F[value]; + if(value >= 0x7B) + return ebu_values_0x7B_to_0xFF[value - 0x7B]; + + // convert by hand (avoiding a LUT with mostly 1:1 mapping) + switch(value) { + case 0x24: + return "\u0142"; + case 0x5C: + return "\u016E"; + case 0x5E: + return "\u0141"; + case 0x60: + return "\u0104"; + } + + // leave untouched + return std::string((char*) &value, 1); +} + + +std::string CharsetTools::ConvertTextToUTF8(const uint8_t *data, size_t len, int charset, std::string* charset_name) { + // remove undesired chars + std::vector cleaned_data; + for(size_t i = 0; i < len; i++) { + switch(data[i]) { + case 0x00: // NULL + case 0x0A: // PLB + case 0x0B: // EoH + case 0x1F: // PWB + continue; + default: + cleaned_data.push_back(data[i]); + } + } + + // convert characters + if(charset == 0b0000) { // EBU Latin based + if(charset_name) + *charset_name = "EBU Latin based"; + + std::string result; + for(const uint8_t& c : cleaned_data) + result += ConvertCharEBUToUTF8(c); + return result; + } + + if(charset == 0b1111) { // UTF-8 + if(charset_name) + *charset_name = "UTF-8"; + + return std::string((char*) &cleaned_data[0], cleaned_data.size()); + } + + // ignore unsupported charset + return ""; +} + + +size_t StringTools::UTF8CharsLen(const std::string &s, size_t chars) { + size_t result; + for(result = 0; result < s.size(); result++) { + // if not a continuation byte, handle counter + if((s[result] & 0xC0) != 0x80) { + if(chars == 0) + break; + chars--; + } + } + return result; +} + +size_t StringTools::UTF8Len(const std::string &s) { + // ignore continuation bytes + return std::count_if(s.cbegin(), s.cend(), [](const char c){return (c & 0xC0) != 0x80;}); +} + +std::string StringTools::UTF8Substr(const std::string &s, size_t pos, size_t count) { + std::string result = s; + result.erase(0, UTF8CharsLen(result, pos)); + result.erase(UTF8CharsLen(result, count)); + return result; +} diff --git a/src/CharsetTools.h b/src/CharsetTools.h new file mode 100644 index 0000000..f86692f --- /dev/null +++ b/src/CharsetTools.h @@ -0,0 +1,58 @@ +/* + Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011 Her Majesty + the Queen in Right of Canada (Communications Research Center Canada) + + Most parts of this file are taken from dablin, + Copyright (C) 2015-2022 Stefan Pöschel + + Copyright (C) 2023 + Matthias P. Braendli, matthias.braendli@mpb.li + + http://opendigitalradio.org + */ +/* + This file is part of ODR-DabMod. + + ODR-DabMod 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. + + ODR-DabMod 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 ODR-DabMod. If not, see . + */ + +#pragma once +#include +#include +#include +#include +#include +#include +#include + +class CharsetTools { + private: + static const char* no_char; + static const char* ebu_values_0x00_to_0x1F[]; + static const char* ebu_values_0x7B_to_0xFF[]; + static std::string ConvertCharEBUToUTF8(const uint8_t value); + public: + static std::string ConvertTextToUTF8(const uint8_t *data, size_t len, int charset, std::string* charset_name); +}; + +typedef std::vector string_vector_t; + +// --- StringTools ----------------------------------------------------------------- +class StringTools { +private: + static size_t UTF8CharsLen(const std::string &s, size_t chars); +public: + static size_t UTF8Len(const std::string &s); + static std::string UTF8Substr(const std::string &s, size_t pos, size_t count); +}; diff --git a/src/DabMod.cpp b/src/DabMod.cpp index d43ebd5..0ab112b 100644 --- a/src/DabMod.cpp +++ b/src/DabMod.cpp @@ -115,6 +115,9 @@ class ModulatorData : public RemoteControllable { RC_ADD_PARAMETER(num_modulator_restarts, "(Read-only) Number of mod restarts"); RC_ADD_PARAMETER(most_recent_edi_decoded, "(Read-only) UNIX Timestamp of most recently decoded EDI frame"); RC_ADD_PARAMETER(running_since, "(Read-only) UNIX Timestamp of most recent modulator restart"); + RC_ADD_PARAMETER(ensemble_label, "(Read-only) Label of the ensemble"); + RC_ADD_PARAMETER(ensemble_eid, "(Read-only) Ensemble ID"); + RC_ADD_PARAMETER(num_services, "(Read-only) Number of services in the ensemble"); } virtual ~ModulatorData() {} @@ -134,6 +137,42 @@ class ModulatorData : public RemoteControllable { else if (parameter == "most_recent_edi_decoded") { ss << most_recent_edi_decoded; } + else if (parameter == "ensemble_label") { + if (ediInput) { + const auto ens = ediInput->ediReader.getEnsembleInfo(); + if (ens) { + ss << FICDecoder::ConvertLabelToUTF8(ens->label, nullptr); + } + else { + throw ParameterError("Not available yet"); + } + } + else { + throw ParameterError("Not available yet"); + } + } + else if (parameter == "ensemble_eid") { + if (ediInput) { + const auto ens = ediInput->ediReader.getEnsembleInfo(); + if (ens) { + ss << ens->eid; + } + else { + throw ParameterError("Not available yet"); + } + } + else { + throw ParameterError("Not available yet"); + } + } + else if (parameter == "num_services") { + if (ediInput) { + ss << ediInput->ediReader.getSubchannels().size(); + } + else { + throw ParameterError("Not available yet"); + } + } else { ss << "Parameter '" << parameter << "' is not exported by controllable " << get_rc_name(); @@ -148,6 +187,36 @@ class ModulatorData : public RemoteControllable { map["num_modulator_restarts"].v = num_modulator_restarts; map["running_since"].v = running_since; map["most_recent_edi_decoded"].v = most_recent_edi_decoded; + + if (ediInput) { + map["num_services"].v = ediInput->ediReader.getSubchannels().size(); + + const auto ens = ediInput->ediReader.getEnsembleInfo(); + if (ens) { + map["ensemble_label"].v = FICDecoder::ConvertLabelToUTF8(ens->label, nullptr); + map["ensemble_eid"].v = ens->eid; + } + else { + map["ensemble_label"].v = nullopt; + map["ensemble_eid"].v = nullopt; + } + + std::vector services; + + for (const auto& s : ediInput->ediReader.getServiceInfo()) { + auto service_map = make_shared(); + (*service_map)["sad"].v = s.second.subchannel.start; + (*service_map)["sid"].v = s.second.sid; + (*service_map)["label"].v = FICDecoder::ConvertLabelToUTF8(s.second.label, nullptr); + (*service_map)["bitrate"].v = s.second.subchannel.bitrate; + json::value_t v; + v.v = service_map; + services.push_back(v); + } + + map["ensemble_services"].v = services; + + } return map; } diff --git a/src/EtiReader.cpp b/src/EtiReader.cpp index 580088b..244cc18 100644 --- a/src/EtiReader.cpp +++ b/src/EtiReader.cpp @@ -228,7 +228,7 @@ int EtiReader::loadEtiData(const Buffer& dataIn) unsigned size = mySources[i]->framesize(); PDEBUG("Writting %i bytes of subchannel data\n", size); Buffer subch(size, in); - mySources[i]->loadSubchannelData(move(subch)); + mySources[i]->loadSubchannelData(std::move(subch)); input_size -= size; framesize -= size; in += size; @@ -295,9 +295,9 @@ uint32_t EtiReader::getPPSOffset() return timestamp; } -EdiReader::EdiReader( - double& tist_offset_s) : - m_timestamp_decoder(tist_offset_s) +EdiReader::EdiReader(double& tist_offset_s) : + m_timestamp_decoder(tist_offset_s), + m_fic_decoder(/*verbose*/ false) { rcs.enrol(&m_timestamp_decoder); } @@ -411,7 +411,10 @@ void EdiReader::update_fic(std::vector&& fic) if (not m_proto_valid) { throw std::logic_error("Cannot update FIC before protocol"); } - m_fic = move(fic); + + m_fic_decoder.Process(fic.data(), fic.size()); + + m_fic = std::move(fic); } void EdiReader::update_edi_time( @@ -463,7 +466,7 @@ void EdiReader::add_subchannel(EdiDecoder::eti_stc_data&& stc) throw std::invalid_argument( "EDI: MST data length inconsistent with FIC"); } - source->loadSubchannelData(move(stc.mst)); + source->loadSubchannelData(std::move(stc.mst)); if (m_sources.size() > 64) { throw std::invalid_argument("Too many subchannels"); @@ -609,7 +612,7 @@ bool EdiTransport::rxPacket() received_from = rp.received_from; EdiDecoder::Packet p; - p.buf = move(rp.packetdata); + p.buf = std::move(rp.packetdata); p.received_on_port = rp.port_received_on; m_decoder.push_packet(p); } diff --git a/src/EtiReader.h b/src/EtiReader.h index fb2c84c..29091bd 100644 --- a/src/EtiReader.h +++ b/src/EtiReader.h @@ -34,6 +34,7 @@ #include "Eti.h" #include "Log.h" #include "FicSource.h" +#include "FigParser.h" #include "Socket.h" #include "SubchannelSource.h" #include "TimestampDecoder.h" @@ -174,6 +175,15 @@ public: // Gets called by the EDI library to tell us that all data for a frame was given to us virtual void assemble(EdiDecoder::ReceivedTagPacket&& tagpacket) override; + + std::optional getEnsembleInfo() const { + return m_fic_decoder.observer.ensemble; + } + + std::map getServiceInfo() const { + return m_fic_decoder.observer.services; + } + private: bool m_proto_valid = false; bool m_frameReady = false; @@ -197,6 +207,7 @@ private: std::map > m_sources; TimestampDecoder m_timestamp_decoder; + FICDecoder m_fic_decoder; }; /* The EDI input does not use the inputs defined in InputReader.h, as they were diff --git a/src/FigParser.cpp b/src/FigParser.cpp new file mode 100644 index 0000000..bda2f83 --- /dev/null +++ b/src/FigParser.cpp @@ -0,0 +1,1047 @@ +/* + Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011 Her Majesty + the Queen in Right of Canada (Communications Research Center Canada) + + Most parts of this file are taken from dablin, + Copyright (C) 2015-2022 Stefan Pöschel + + Copyright (C) 2023 + Matthias P. Braendli, matthias.braendli@mpb.li + + http://opendigitalradio.org + */ +/* + This file is part of ODR-DabMod. + + ODR-DabMod 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. + + ODR-DabMod 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 ODR-DabMod. If not, see . + */ + +#include "FigParser.h" +#include "PcDebug.h" +#include "Log.h" +#include "crc.h" +#include "CharsetTools.h" + +#include +#include +#include +#include +#include + + +template +static uint16_t read_16b(T buf) +{ + uint16_t value = 0; + value = (uint16_t)(buf[0]) << 8; + value |= (uint16_t)(buf[1]); + return value; +} + +static bool checkCRC(const uint8_t *buf, size_t size) +{ + const uint16_t crc_from_packet = read_16b(buf + size - 2); + uint16_t crc_calc = 0xffff; + crc_calc = crc16(crc_calc, buf, size - 2); + crc_calc ^= 0xffff; + return crc_from_packet == crc_calc; +} + +void FICDecoderObserver::FICChangeEnsemble(const FIC_ENSEMBLE& e) +{ + services.clear(); + ensemble = e; +} +void FICDecoderObserver::FICChangeService(const LISTED_SERVICE& ls) +{ + services[ls.sid] = ls; +} +void FICDecoderObserver::FICChangeUTCDateTime(const FIC_DAB_DT& dt) +{ + utc_dt = dt; +} + +// --- FICDecoder ----------------------------------------------------------------- +FICDecoder::FICDecoder(bool verbose) : + verbose(verbose), + utc_dt_long(false) +{ } + + +void FICDecoder::Reset() { + ensemble = FIC_ENSEMBLE(); + services.clear(); + subchannels.clear(); + utc_dt = FIC_DAB_DT(); +} + +void FICDecoder::Process(const uint8_t *data, size_t len) { + // check for integer FIB count + if(len % 32) { + etiLog.log(warn, "FICDecoder: Ignoring non-integer FIB count FIC data with %zu bytes\n", len); + return; + } + + for(size_t i = 0; i < len; i += 32) + ProcessFIB(data + i); +} + +void FICDecoder::ProcessFIB(const uint8_t *data) { + if (not checkCRC(data, 32)) { + observer.FICDiscardedFIB(); + return; + } + + // iterate over all FIGs + for(size_t offset = 0; offset < 30 && data[offset] != 0xFF;) { + int type = data[offset] >> 5; + size_t len = data[offset] & 0x1F; + offset++; + + switch(type) { + case 0: + ProcessFIG0(data + offset, len); + break; + case 1: + ProcessFIG1(data + offset, len); + break; + // default: + // etiLog.log(warn, "FICDecoder: received unsupported FIG %d with %zu bytes\n", type, len); + } + offset += len; + } +} + + +void FICDecoder::ProcessFIG0(const uint8_t *data, size_t len) { + if(len < 1) { + etiLog.log(warn, "FICDecoder: received empty FIG 0\n"); + return; + } + + // read/skip FIG 0 header + FIG0_HEADER header(data[0]); + data++; + len--; + + // ignore next config/other ensembles/data services + if(header.cn || header.oe || header.pd) + return; + + + // handle extension + switch(header.extension) { + case 0: + ProcessFIG0_0(data, len); + break; + case 1: + ProcessFIG0_1(data, len); + break; + case 2: + ProcessFIG0_2(data, len); + break; + case 5: + ProcessFIG0_5(data, len); + break; + case 8: + ProcessFIG0_8(data, len); + break; + case 9: + ProcessFIG0_9(data, len); + break; + case 10: + ProcessFIG0_10(data, len); + break; + case 13: + ProcessFIG0_13(data, len); + break; + case 17: + ProcessFIG0_17(data, len); + break; + case 18: + ProcessFIG0_18(data, len); + break; + case 19: + ProcessFIG0_19(data, len); + break; + // default: + // etiLog.log(warn, "FICDecoder: received unsupported FIG 0/%d with %zu field bytes\n", header.extension, len); + } +} + +void FICDecoder::ProcessFIG0_0(const uint8_t *data, size_t len) { + // FIG 0/0 - Ensemble information + // EId and alarm flag only + + if(len < 4) + return; + + FIC_ENSEMBLE new_ensemble = ensemble; + new_ensemble.eid = data[0] << 8 | data[1]; + new_ensemble.al_flag = data[2] & 0x20; + + if(ensemble != new_ensemble) { + ensemble = new_ensemble; + + if (verbose) + etiLog.log(debug, "FICDecoder: EId 0x%04X: alarm flag: %s\n", + ensemble.eid, ensemble.al_flag ? "true" : "false"); + + UpdateEnsemble(); + } +} + +void FICDecoder::ProcessFIG0_1(const uint8_t *data, size_t len) { + // FIG 0/1 - Basic sub-channel organization + + // iterate through all sub-channels + for(size_t offset = 0; offset < len;) { + int subchid = data[offset] >> 2; + size_t start_address = (data[offset] & 0x03) << 8 | data[offset + 1]; + offset += 2; + + FIC_SUBCHANNEL sc; + sc.start = start_address; + + bool short_long_form = data[offset] & 0x80; + if(short_long_form) { + // long form + int option = (data[offset] & 0x70) >> 4; + int pl = (data[offset] & 0x0C) >> 2; + size_t subch_size = (data[offset] & 0x03) << 8 | data[offset + 1]; + + switch(option) { + case 0b000: + sc.size = subch_size; + sc.pl = "EEP " + std::to_string(pl + 1) + "-A"; + sc.bitrate = subch_size / eep_a_size_factors[pl] * 8; + break; + case 0b001: + sc.size = subch_size; + sc.pl = "EEP " + std::to_string(pl + 1) + "-B"; + sc.bitrate = subch_size / eep_b_size_factors[pl] * 32; + break; + } + offset += 2; + } else { + // short form + + bool table_switch = data[offset] & 0x40; + if(!table_switch) { + int table_index = data[offset] & 0x3F; + sc.size = uep_sizes[table_index]; + sc.pl = "UEP " + std::to_string(uep_pls[table_index]); + sc.bitrate = uep_bitrates[table_index]; + } + offset++; + } + + if(!sc.IsNone()) { + FIC_SUBCHANNEL& current_sc = GetSubchannel(subchid); + sc.language = current_sc.language; // ignored for comparison + if(current_sc != sc) { + current_sc = sc; + + if (verbose) + etiLog.log(debug, "FICDecoder: SubChId %2d: start %3zu CUs, size %3zu CUs, PL %-7s = %3d kBit/s\n", subchid, sc.start, sc.size, sc.pl.c_str(), sc.bitrate); + + UpdateSubchannel(subchid); + } + } + } +} + +void FICDecoder::ProcessFIG0_2(const uint8_t *data, size_t len) { + // FIG 0/2 - Basic service and service component definition + // programme services only + + // iterate through all services + for(size_t offset = 0; offset < len;) { + uint16_t sid = data[offset] << 8 | data[offset + 1]; + offset += 2; + + size_t num_service_comps = data[offset++] & 0x0F; + + // iterate through all service components + for(size_t comp = 0; comp < num_service_comps; comp++) { + int tmid = data[offset] >> 6; + + switch(tmid) { + case 0b00: // MSC stream audio + int ascty = data[offset] & 0x3F; + int subchid = data[offset + 1] >> 2; + bool ps = data[offset + 1] & 0x02; + bool ca = data[offset + 1] & 0x01; + + if(!ca) { + switch(ascty) { + case 0: // DAB + case 63: // DAB+ + bool dab_plus = ascty == 63; + + AUDIO_SERVICE audio_service(subchid, dab_plus); + + FIC_SERVICE& service = GetService(sid); + AUDIO_SERVICE& current_audio_service = service.audio_comps[subchid]; + if(current_audio_service != audio_service || ps != (service.pri_comp_subchid == subchid)) { + current_audio_service = audio_service; + if(ps) + service.pri_comp_subchid = subchid; + + if (verbose) + etiLog.log(debug, "FICDecoder: SId 0x%04X: audio service (SubChId %2d, %-4s, %s)\n", sid, subchid, dab_plus ? "DAB+" : "DAB", ps ? "primary" : "secondary"); + + UpdateService(service); + } + + break; + } + } + } + + offset += 2; + } + } +} + +void FICDecoder::ProcessFIG0_5(const uint8_t *data, size_t len) { + // FIG 0/5 - Service component language + // programme services only + + // iterate through all components + for(size_t offset = 0; offset < len;) { + bool ls_flag = data[offset] & 0x80; + if(ls_flag) { + // long form - skipped, as not relevant + offset += 3; + } else { + // short form + bool msc_fic_flag = data[offset] & 0x40; + + // handle only MSC components + if(!msc_fic_flag) { + int subchid = data[offset] & 0x3F; + int language = data[offset + 1]; + + FIC_SUBCHANNEL& current_sc = GetSubchannel(subchid); + if(current_sc.language != language) { + current_sc.language = language; + + if (verbose) + etiLog.log(debug, "FICDecoder: SubChId %2d: language '%s'\n", subchid, ConvertLanguageToString(language).c_str()); + + UpdateSubchannel(subchid); + } + } + + offset += 2; + } + } +} + +void FICDecoder::ProcessFIG0_8(const uint8_t *data, size_t len) { + // FIG 0/8 - Service component global definition + // programme services only + + // iterate through all service components + for(size_t offset = 0; offset < len;) { + uint16_t sid = data[offset] << 8 | data[offset + 1]; + offset += 2; + + bool ext_flag = data[offset] & 0x80; + int scids = data[offset] & 0x0F; + offset++; + + bool ls_flag = data[offset] & 0x80; + if(ls_flag) { + // long form - skipped, as not relevant + offset += 2; + } else { + // short form + bool msc_fic_flag = data[offset] & 0x40; + + // handle only MSC components + if(!msc_fic_flag) { + int subchid = data[offset] & 0x3F; + + FIC_SERVICE& service = GetService(sid); + bool new_comp = service.comp_defs.find(scids) == service.comp_defs.end(); + int& current_subchid = service.comp_defs[scids]; + if(new_comp || current_subchid != subchid) { + current_subchid = subchid; + + if (verbose) + etiLog.log(debug, "FICDecoder: SId 0x%04X, SCIdS %2d: MSC service component (SubChId %2d)\n", sid, scids, subchid); + + UpdateService(service); + } + } + + offset++; + } + + // skip Rfa field, if needed + if(ext_flag) + offset++; + } +} + +void FICDecoder::ProcessFIG0_9(const uint8_t *data, size_t len) { + // FIG 0/9 - Time and country identifier - Country, LTO and International table + // ensemble ECC/LTO and international table ID only + + if(len < 3) + return; + + FIC_ENSEMBLE new_ensemble = ensemble; + new_ensemble.lto = (data[0] & 0x20 ? -1 : 1) * (data[0] & 0x1F); + new_ensemble.ecc = data[1]; + new_ensemble.inter_table_id = data[2]; + + if(ensemble != new_ensemble) { + ensemble = new_ensemble; + + if (verbose) + etiLog.log(debug, "FICDecoder: ECC: 0x%02X, LTO: %s, international table ID: 0x%02X (%s)\n", + ensemble.ecc, ConvertLTOToString(ensemble.lto).c_str(), ensemble.inter_table_id, ConvertInterTableIDToString(ensemble.inter_table_id).c_str()); + + UpdateEnsemble(); + + // update services that changes may affect + for(const fic_services_t::value_type& service : services) { + const FIC_SERVICE& s = service.second; + if(s.pty_static != FIC_SERVICE::pty_none || s.pty_dynamic != FIC_SERVICE::pty_none) + UpdateService(s); + } + } +} + +void FICDecoder::ProcessFIG0_10(const uint8_t *data, size_t len) { + // FIG 0/10 - Date and time (d&t) + + if(len < 4) + return; + + FIC_DAB_DT new_utc_dt; + + // ignore short form, once long form available + bool utc_flag = data[2] & 0x08; + if(!utc_flag && utc_dt_long) + return; + + // retrieve date + int mjd = (data[0] & 0x7F) << 10 | data[1] << 2 | data[2] >> 6; + + int y0 = floor((mjd - 15078.2) / 365.25); + int m0 = floor((mjd - 14956.1 - floor(y0 * 365.25)) / 30.6001); + int d = mjd - 14956 - floor(y0 * 365.25) - floor(m0 * 30.6001); + int k = (m0 == 14 || m0 == 15) ? 1 : 0; + int y = y0 + k; + int m = m0 - 1 - k * 12; + + new_utc_dt.dt.tm_year = y; // from 1900 + new_utc_dt.dt.tm_mon = m - 1; // 0-based + new_utc_dt.dt.tm_mday = d; + + // retrieve time + new_utc_dt.dt.tm_hour = (data[2] & 0x07) << 2 | data[3] >> 6; + new_utc_dt.dt.tm_min = data[3] & 0x3F; + new_utc_dt.dt.tm_isdst = -1; // ignore DST + if(utc_flag) { + // long form + if(len < 6) + return; + new_utc_dt.dt.tm_sec = data[4] >> 2; + new_utc_dt.ms = (data[4] & 0x03) << 8 | data[5]; + utc_dt_long = true; + } else { + // short form + new_utc_dt.dt.tm_sec = 0; + new_utc_dt.ms = FIC_DAB_DT::ms_none; + } + + if(utc_dt != new_utc_dt) { + // print only once (or once again on precision change) + if(utc_dt.IsNone() || utc_dt.IsMsNone() != new_utc_dt.IsMsNone()) + if (verbose) + etiLog.log(debug, "FICDecoder: UTC date/time: %s\n", ConvertDateTimeToString(new_utc_dt, 0, true).c_str()); + + utc_dt = new_utc_dt; + + observer.FICChangeUTCDateTime(utc_dt); + } +} + +void FICDecoder::ProcessFIG0_13(const uint8_t *data, size_t len) { + // FIG 0/13 - User application information + // programme services only + + // iterate through all service components + for(size_t offset = 0; offset < len;) { + uint16_t sid = data[offset] << 8 | data[offset + 1]; + offset += 2; + + int scids = data[offset] >> 4; + size_t num_scids_uas = data[offset] & 0x0F; + offset++; + + // iterate through all user applications + for(size_t scids_ua = 0; scids_ua < num_scids_uas; scids_ua++) { + int ua_type = data[offset] << 3 | data[offset + 1] >> 5; + size_t ua_data_length = data[offset + 1] & 0x1F; + offset += 2; + + // handle only Slideshow + if(ua_type == 0x002) { + FIC_SERVICE& service = GetService(sid); + if(service.comp_sls_uas.find(scids) == service.comp_sls_uas.end()) { + ua_data_t& sls_ua_data = service.comp_sls_uas[scids]; + + sls_ua_data.resize(ua_data_length); + if(ua_data_length) + memcpy(&sls_ua_data[0], data + offset, ua_data_length); + + if (verbose) + etiLog.log(debug, "FICDecoder: SId 0x%04X, SCIdS %2d: Slideshow (%zu bytes UA data)\n", sid, scids, ua_data_length); + + UpdateService(service); + } + } + + offset += ua_data_length; + } + } +} + +void FICDecoder::ProcessFIG0_17(const uint8_t *data, size_t len) { + // FIG 0/17 - Programme Type + // programme type only + + // iterate through all services + for(size_t offset = 0; offset < len;) { + uint16_t sid = data[offset] << 8 | data[offset + 1]; + bool sd = data[offset + 2] & 0x80; + bool l_flag = data[offset + 2] & 0x20; + bool cc_flag = data[offset + 2] & 0x10; + offset += 3; + + // skip language, if present + if(l_flag) + offset++; + + // programme type (international code) + int pty = data[offset] & 0x1F; + offset++; + + // skip CC part, if present + if(cc_flag) + offset++; + + FIC_SERVICE& service = GetService(sid); + int& current_pty = sd ? service.pty_dynamic : service.pty_static; + if(current_pty != pty) { + // suppress message, if dynamic FIC messages disabled and dynamic PTY not initally be set + bool show_msg = !(sd && current_pty != FIC_SERVICE::pty_none); + + current_pty = pty; + + if(verbose && show_msg) { + // assuming international table ID 0x01 here! + etiLog.log(debug, "FICDecoder: SId 0x%04X: programme type (%s): '%s'\n", + sid, sd ? "dynamic" : "static", ConvertPTYToString(pty, 0x01).c_str()); + } + + UpdateService(service); + } + } +} + +void FICDecoder::ProcessFIG0_18(const uint8_t *data, size_t len) { + // FIG 0/18 - Announcement support + + // iterate through all services + for(size_t offset = 0; offset < len;) { + uint16_t sid = data[offset] << 8 | data[offset + 1]; + uint16_t asu_flags = data[offset + 2] << 8 | data[offset + 3]; + size_t number_of_clusters = data[offset + 4] & 0x1F; + offset += 5; + + cids_t cids; + for(size_t i = 0; i < number_of_clusters; i++) + cids.emplace(data[offset++]); + + FIC_SERVICE& service = GetService(sid); + uint16_t& current_asu_flags = service.asu_flags; + cids_t& current_cids = service.cids; + if(current_asu_flags != asu_flags || current_cids != cids) { + current_asu_flags = asu_flags; + current_cids = cids; + + std::string cids_str; + char cid_string[5]; + for(const cids_t::value_type& cid : cids) { + if(!cids_str.empty()) + cids_str += "/"; + snprintf(cid_string, sizeof(cid_string), "0x%02X", cid); + cids_str += std::string(cid_string); + } + + if (verbose) + etiLog.log(debug, "FICDecoder: SId 0x%04X: ASu flags 0x%04X, cluster(s) %s\n", + sid, asu_flags, cids_str.c_str()); + + UpdateService(service); + } + } +} + +void FICDecoder::ProcessFIG0_19(const uint8_t *data, size_t len) { + // FIG 0/19 - Announcement switching + + // iterate through all announcement clusters + for(size_t offset = 0; offset < len;) { + uint8_t cid = data[offset]; + uint16_t asw_flags = data[offset + 1] << 8 | data[offset + 2]; + bool region_flag = data[offset + 3] & 0x40; + int subchid = data[offset + 3] & 0x3F; + offset += region_flag ? 5 : 4; + + FIC_ASW_CLUSTER ac; + ac.asw_flags = asw_flags; + ac.subchid = subchid; + + FIC_ASW_CLUSTER& current_ac = ensemble.asw_clusters[cid]; + if(current_ac != ac) { + current_ac = ac; + + if (verbose) { + etiLog.log(debug, "FICDecoder: ASw cluster 0x%02X: flags 0x%04X, SubChId %2d\n", + cid, asw_flags, subchid); + } + + UpdateEnsemble(); + + // update services that changes may affect + for(const fic_services_t::value_type& service : services) { + const FIC_SERVICE& s = service.second; + if(s.cids.find(cid) != s.cids.cend()) + UpdateService(s); + } + } + } +} + +void FICDecoder::ProcessFIG1(const uint8_t *data, size_t len) { + if(len < 1) { + etiLog.log(warn, "FICDecoder: received empty FIG 1\n"); + return; + } + + // read/skip FIG 1 header + FIG1_HEADER header(data[0]); + data++; + len--; + + // ignore other ensembles + if(header.oe) + return; + + // check for (un)supported extension + set ID field len + size_t len_id = -1; + switch(header.extension) { + case 0: // ensemble + case 1: // programme service + len_id = 2; + break; + case 4: // service component + // programme services only (P/D = 0) + if(data[0] & 0x80) + return; + len_id = 3; + break; + default: + // etiLog.log(debug, "FICDecoder: received unsupported FIG 1/%d with %zu field bytes\n", header.extension, len); + return; + } + + // check length + size_t len_calced = len_id + 16 + 2; + if(len != len_calced) { + etiLog.log(warn, "FICDecoder: received FIG 1/%d having %zu field bytes (expected: %zu)\n", header.extension, len, len_calced); + return; + } + + // parse actual label data + FIC_LABEL label; + label.charset = header.charset; + memcpy(label.label, data + len_id, 16); + label.short_label_mask = data[len_id + 16] << 8 | data[len_id + 17]; + + + // handle extension + switch(header.extension) { + case 0: { // ensemble + uint16_t eid = data[0] << 8 | data[1]; + ProcessFIG1_0(eid, label); + break; } + case 1: { // programme service + uint16_t sid = data[0] << 8 | data[1]; + ProcessFIG1_1(sid, label); + break; } + case 4: { // service component + int scids = data[0] & 0x0F; + uint16_t sid = data[1] << 8 | data[2]; + ProcessFIG1_4(sid, scids, label); + break; } + } +} + +void FICDecoder::ProcessFIG1_0(uint16_t eid, const FIC_LABEL& label) { + if(ensemble.label != label) { + ensemble.label = label; + + std::string label_str = ConvertLabelToUTF8(label, nullptr); + std::string short_label_str = DeriveShortLabelUTF8(label_str, label.short_label_mask); + if (verbose) + etiLog.log(debug, "FICDecoder: EId 0x%04X: ensemble label '" "\x1B[32m" "%s" "\x1B[0m" "' ('" "\x1B[32m" "%s" "\x1B[0m" "')\n", + eid, label_str.c_str(), short_label_str.c_str()); + + UpdateEnsemble(); + } +} + +void FICDecoder::ProcessFIG1_1(uint16_t sid, const FIC_LABEL& label) { + FIC_SERVICE& service = GetService(sid); + if(service.label != label) { + service.label = label; + + if (verbose) { + std::string label_str = ConvertLabelToUTF8(label, nullptr); + std::string short_label_str = DeriveShortLabelUTF8(label_str, label.short_label_mask); + etiLog.log(debug, "FICDecoder: SId 0x%04X: programme service label '" "\x1B[32m" "%s" "\x1B[0m" "' ('" "\x1B[32m" "%s" "\x1B[0m" "')\n", + sid, label_str.c_str(), short_label_str.c_str()); + } + + UpdateService(service); + } +} + +void FICDecoder::ProcessFIG1_4(uint16_t sid, int scids, const FIC_LABEL& label) { + // programme services only + + FIC_SERVICE& service = GetService(sid); + FIC_LABEL& comp_label = service.comp_labels[scids]; + if(comp_label != label) { + comp_label = label; + + if (verbose) { + std::string label_str = ConvertLabelToUTF8(label, nullptr); + std::string short_label_str = DeriveShortLabelUTF8(label_str, label.short_label_mask); + etiLog.log(debug, "FICDecoder: SId 0x%04X, SCIdS %2d: service component label '" "\x1B[32m" "%s" "\x1B[0m" "' ('" "\x1B[32m" "%s" "\x1B[0m" "')\n", + sid, scids, label_str.c_str(), short_label_str.c_str()); + } + + UpdateService(service); + } +} + +FIC_SUBCHANNEL& FICDecoder::GetSubchannel(int subchid) { + // created automatically, if not yet existing + return subchannels[subchid]; +} + +void FICDecoder::UpdateSubchannel(int subchid) { + // update services that consist of this sub-channel + for(const fic_services_t::value_type& service : services) { + const FIC_SERVICE& s = service.second; + if(s.audio_comps.find(subchid) != s.audio_comps.end()) + UpdateService(s); + } +} + +FIC_SERVICE& FICDecoder::GetService(uint16_t sid) { + FIC_SERVICE& result = services[sid]; // created, if not yet existing + + // if new service, set SID + if(result.IsNone()) + result.sid = sid; + return result; +} + +void FICDecoder::UpdateService(const FIC_SERVICE& service) { + // abort update, if primary component or label not yet present + if(service.HasNoPriCompSubchid() || service.label.IsNone()) + return; + + // secondary components (if both component and definition are present) + bool multi_comps = false; + for(const comp_defs_t::value_type& comp_def : service.comp_defs) { + if(comp_def.second == service.pri_comp_subchid || service.audio_comps.find(comp_def.second) == service.audio_comps.end()) + continue; + UpdateListedService(service, comp_def.first, true); + multi_comps = true; + } + + // primary component + UpdateListedService(service, LISTED_SERVICE::scids_none, multi_comps); +} + +void FICDecoder::UpdateListedService(const FIC_SERVICE& service, int scids, bool multi_comps) { + // assemble listed service + LISTED_SERVICE ls; + ls.sid = service.sid; + ls.scids = scids; + ls.label = service.label; + ls.pty_static = service.pty_static; + ls.pty_dynamic = service.pty_dynamic; + ls.asu_flags = service.asu_flags; + ls.cids = service.cids; + ls.pri_comp_subchid = service.pri_comp_subchid; + ls.multi_comps = multi_comps; + + if(scids == LISTED_SERVICE::scids_none) { // primary component + ls.audio_service = service.audio_comps.at(service.pri_comp_subchid); + } else { // secondary component + ls.audio_service = service.audio_comps.at(service.comp_defs.at(scids)); + + // use component label, if available + comp_labels_t::const_iterator cl_it = service.comp_labels.find(scids); + if(cl_it != service.comp_labels.end()) + ls.label = cl_it->second; + } + + // use sub-channel information, if available + fic_subchannels_t::const_iterator sc_it = subchannels.find(ls.audio_service.subchid); + if(sc_it != subchannels.end()) + ls.subchannel = sc_it->second; + + /* check (for) Slideshow; currently only supported in X-PAD + * - derive the required SCIdS (if not yet known) + * - derive app type from UA data (if present) + */ + int sls_scids = scids; + if(sls_scids == LISTED_SERVICE::scids_none) { + for(const comp_defs_t::value_type& comp_def : service.comp_defs) { + if(comp_def.second == ls.audio_service.subchid) { + sls_scids = comp_def.first; + break; + } + } + } + if(sls_scids != LISTED_SERVICE::scids_none && service.comp_sls_uas.find(sls_scids) != service.comp_sls_uas.end()) + ls.sls_app_type = GetSLSAppType(service.comp_sls_uas.at(sls_scids)); + + // forward to observer + observer.FICChangeService(ls); +} + +int FICDecoder::GetSLSAppType(const ua_data_t& ua_data) { + // default values, if no UA data present + bool ca_flag = false; + int xpad_app_type = 12; + bool dg_flag = false; + int dscty = 60; // MOT + + // if UA data present, parse X-PAD data + if(ua_data.size() >= 2) { + ca_flag = ua_data[0] & 0x80; + xpad_app_type = ua_data[0] & 0x1F; + dg_flag = ua_data[1] & 0x80; + dscty = ua_data[1] & 0x3F; + } + + // if no CA is used, but DGs and MOT, enable Slideshow + if(!ca_flag && !dg_flag && dscty == 60) + return xpad_app_type; + else + return LISTED_SERVICE::sls_app_type_none; +} + +void FICDecoder::UpdateEnsemble() { + // abort update, if EId or label not yet present + if(ensemble.IsNone() || ensemble.label.IsNone()) + return; + + // forward to observer + observer.FICChangeEnsemble(ensemble); +} + +std::string FICDecoder::ConvertLabelToUTF8(const FIC_LABEL& label, std::string* charset_name) { + std::string result = CharsetTools::ConvertTextToUTF8(label.label, sizeof(label.label), label.charset, charset_name); + + // discard trailing spaces + size_t last_pos = result.find_last_not_of(' '); + if(last_pos != std::string::npos) + result.resize(last_pos + 1); + + return result; +} + +const size_t FICDecoder::uep_sizes[] = { + 16, 21, 24, 29, 35, 24, 29, 35, 42, 52, 29, 35, 42, 52, 32, 42, + 48, 58, 70, 40, 52, 58, 70, 84, 48, 58, 70, 84, 104, 58, 70, 84, + 104, 64, 84, 96, 116, 140, 80, 104, 116, 140, 168, 96, 116, 140, 168, 208, + 116, 140, 168, 208, 232, 128, 168, 192, 232, 280, 160, 208, 280, 192, 280, 416 +}; +const int FICDecoder::uep_pls[] = { + 5, 4, 3, 2, 1, 5, 4, 3, 2, 1, 5, 4, 3, 2, 5, 4, + 3, 2, 1, 5, 4, 3, 2, 1, 5, 4, 3, 2, 1, 5, 4, 3, + 2, 5, 4, 3, 2, 1, 5, 4, 3, 2, 1, 5, 4, 3, 2, 1, + 5, 4, 3, 2, 1, 5, 4, 3, 2, 1, 5, 4, 2, 5, 3, 1 +}; +const int FICDecoder::uep_bitrates[] = { + 32, 32, 32, 32, 32, 48, 48, 48, 48, 48, 56, 56, 56, 56, 64, 64, + 64, 64, 64, 80, 80, 80, 80, 80, 96, 96, 96, 96, 96, 112, 112, 112, + 112, 128, 128, 128, 128, 128, 160, 160, 160, 160, 160, 192, 192, 192, 192, 192, + 224, 224, 224, 224, 224, 256, 256, 256, 256, 256, 320, 320, 320, 384, 384, 384 +}; +const int FICDecoder::eep_a_size_factors[] = {12, 8, 6, 4}; +const int FICDecoder::eep_b_size_factors[] = {27, 21, 18, 15}; + +const char* FICDecoder::languages_0x00_to_0x2B[] = { + "unknown/not applicable", "Albanian", "Breton", "Catalan", "Croatian", "Welsh", "Czech", "Danish", + "German", "English", "Spanish", "Esperanto", "Estonian", "Basque", "Faroese", "French", + "Frisian", "Irish", "Gaelic", "Galician", "Icelandic", "Italian", "Sami", "Latin", + "Latvian", "Luxembourgian", "Lithuanian", "Hungarian", "Maltese", "Dutch", "Norwegian", "Occitan", + "Polish", "Portuguese", "Romanian", "Romansh", "Serbian", "Slovak", "Slovene", "Finnish", + "Swedish", "Turkish", "Flemish", "Walloon" +}; +const char* FICDecoder::languages_0x7F_downto_0x45[] = { + "Amharic", "Arabic", "Armenian", "Assamese", "Azerbaijani", "Bambora", "Belorussian", "Bengali", + "Bulgarian", "Burmese", "Chinese", "Chuvash", "Dari", "Fulani", "Georgian", "Greek", + "Gujurati", "Gurani", "Hausa", "Hebrew", "Hindi", "Indonesian", "Japanese", "Kannada", + "Kazakh", "Khmer", "Korean", "Laotian", "Macedonian", "Malagasay", "Malaysian", "Moldavian", + "Marathi", "Ndebele", "Nepali", "Oriya", "Papiamento", "Persian", "Punjabi", "Pushtu", + "Quechua", "Russian", "Rusyn", "Serbo-Croat", "Shona", "Sinhalese", "Somali", "Sranan Tongo", + "Swahili", "Tadzhik", "Tamil", "Tatar", "Telugu", "Thai", "Ukranian", "Urdu", + "Uzbek", "Vietnamese", "Zulu" +}; + +const char* FICDecoder::ptys_rds_0x00_to_0x1D[] = { + "No programme type", "News", "Current Affairs", "Information", + "Sport", "Education", "Drama", "Culture", + "Science", "Varied", "Pop Music", "Rock Music", + "Easy Listening Music", "Light Classical", "Serious Classical", "Other Music", + "Weather/meteorology", "Finance/Business", "Children's programmes", "Social Affairs", + "Religion", "Phone In", "Travel", "Leisure", + "Jazz Music", "Country Music", "National Music", "Oldies Music", + "Folk Music", "Documentary" +}; +const char* FICDecoder::ptys_rbds_0x00_to_0x1D[] = { + "No program type", "News", "Information", "Sports", + "Talk", "Rock", "Classic Rock", "Adult Hits", + "Soft Rock", "Top 40", "Country", "Oldies", + "Soft", "Nostalgia", "Jazz", "Classical", + "Rhythm and Blues", "Soft Rhythm and Blues", "Foreign Language", "Religious Music", + "Religious Talk", "Personality", "Public", "College", + "(rfu)", "(rfu)", "(rfu)", "(rfu)", + "(rfu)", "Weather" +}; + +const char* FICDecoder::asu_types_0_to_10[] = { + "Alarm", "Road Traffic flash", "Transport flash", "Warning/Service", + "News flash", "Area weather flash", "Event announcement", "Special event", + "Programme Information", "Sport report", "Financial report" +}; + +std::string FICDecoder::ConvertLanguageToString(const int value) { + if(value >= 0x00 && value <= 0x2B) + return languages_0x00_to_0x2B[value]; + if(value == 0x40) + return "background sound/clean feed"; + if(value >= 0x45 && value <= 0x7F) + return languages_0x7F_downto_0x45[0x7F - value]; + return "unknown (" + std::to_string(value) + ")"; +} + +std::string FICDecoder::ConvertLTOToString(const int value) { + // just to silence recent GCC's truncation warnings + int lto_value = value % 0x3F; + + char lto_string[7]; + snprintf(lto_string, sizeof(lto_string), "%+03d:%02d", lto_value / 2, (lto_value % 2) ? 30 : 0); + return lto_string; +} + +std::string FICDecoder::ConvertInterTableIDToString(const int value) { + switch(value) { + case 0x01: + return "RDS PTY"; + case 0x02: + return "RBDS PTY"; + default: + return "unknown"; + } +} + +std::string FICDecoder::ConvertDateTimeToString(FIC_DAB_DT utc_dt, const int lto, bool output_ms) { + const char* weekdays[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; + + // if desired, apply LTO + if(lto) + utc_dt.dt.tm_min += lto * 30; + + // normalize time (apply LTO, set day of week) + if(mktime(&utc_dt.dt) == (time_t) -1) + throw std::runtime_error("FICDecoder: error while normalizing date/time"); + + std::string result; + char s[11]; + + strftime(s, sizeof(s), "%F", &utc_dt.dt); + result += std::string(s) + ", " + weekdays[utc_dt.dt.tm_wday] + " - "; + + if(!utc_dt.IsMsNone()) { + // long form + strftime(s, sizeof(s), "%T", &utc_dt.dt); + result += s; + if(output_ms) { + snprintf(s, sizeof(s), ".%03d", utc_dt.ms); + result += s; + } + } else { + // short form + strftime(s, sizeof(s), "%R", &utc_dt.dt); + result += s; + } + + return result; +} + +std::string FICDecoder::ConvertPTYToString(const int value, const int inter_table_id) { + switch(inter_table_id) { + case 0x01: + return value <= 0x1D ? ptys_rds_0x00_to_0x1D[value] : "(not used)"; + case 0x02: + return value <= 0x1D ? ptys_rbds_0x00_to_0x1D[value] : "(not used)"; + default: + return "(unknown)"; + } +} + +std::string FICDecoder::ConvertASuTypeToString(const int value) { + if(value >= 0 && value <= 10) + return asu_types_0_to_10[value]; + return "unknown (" + std::to_string(value) + ")"; +} + +std::string FICDecoder::DeriveShortLabelUTF8(const std::string& long_label, uint16_t short_label_mask) { + std::string short_label; + + for(size_t i = 0; i < long_label.length(); i++) // consider discarded trailing spaces + if(short_label_mask & (0x8000 >> i)) + short_label += StringTools::UTF8Substr(long_label, i, 1); + + return short_label; +} diff --git a/src/FigParser.h b/src/FigParser.h new file mode 100644 index 0000000..b241123 --- /dev/null +++ b/src/FigParser.h @@ -0,0 +1,385 @@ +/* + Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011 Her Majesty + the Queen in Right of Canada (Communications Research Center Canada) + + Most parts of this file are taken from dablin, + Copyright (C) 2015-2022 Stefan Pöschel + + Copyright (C) 2023 + Matthias P. Braendli, matthias.braendli@mpb.li + + http://opendigitalradio.org + */ +/* + This file is part of ODR-DabMod. + + ODR-DabMod 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. + + ODR-DabMod 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 ODR-DabMod. If not, see . + */ + +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct FIG0_HEADER { + bool cn; + bool oe; + bool pd; + int extension; + + FIG0_HEADER(uint8_t data) : cn(data & 0x80), oe(data & 0x40), pd(data & 0x20), extension(data & 0x1F) {} +}; + +struct FIG1_HEADER { + int charset; + bool oe; + int extension; + + FIG1_HEADER(uint8_t data) : charset(data >> 4), oe(data & 0x08), extension(data & 0x07) {} +}; + +struct FIC_LABEL { + int charset; + uint8_t label[16]; + uint16_t short_label_mask; + + static const int charset_none = -1; + bool IsNone() const {return charset == charset_none;} + + FIC_LABEL() : charset(charset_none), short_label_mask(0x0000) { + memset(label, 0x00, sizeof(label)); + } + + bool operator==(const FIC_LABEL & fic_label) const { + return charset == fic_label.charset && !memcmp(label, fic_label.label, sizeof(label)) && short_label_mask == fic_label.short_label_mask; + } + bool operator!=(const FIC_LABEL & fic_label) const { + return !(*this == fic_label); + } +}; + +struct FIC_SUBCHANNEL { + size_t start; + size_t size; + std::string pl; + int bitrate; + int language; + + static const int language_none = -1; + bool IsNone() const {return pl.empty() && language == language_none;} + + FIC_SUBCHANNEL() : start(0), size(0), bitrate(-1), language(language_none) {} + + bool operator==(const FIC_SUBCHANNEL & fic_subchannel) const { + return + start == fic_subchannel.start && + size == fic_subchannel.size && + pl == fic_subchannel.pl && + bitrate == fic_subchannel.bitrate && + language == fic_subchannel.language; + } + bool operator!=(const FIC_SUBCHANNEL & fic_subchannel) const { + return !(*this == fic_subchannel); + } +}; + +struct FIC_ASW_CLUSTER { + uint16_t asw_flags; + int subchid; + + static const int asw_flags_none = 0x0000; + + static const int subchid_none = -1; + bool IsNone() const {return subchid == subchid_none;} + + FIC_ASW_CLUSTER() : asw_flags(asw_flags_none), subchid(subchid_none) {} + + bool operator==(const FIC_ASW_CLUSTER & fic_asw_cluster) const { + return asw_flags == fic_asw_cluster.asw_flags && subchid == fic_asw_cluster.subchid; + } + bool operator!=(const FIC_ASW_CLUSTER & fic_asw_cluster) const { + return !(*this == fic_asw_cluster); + } +}; + +typedef std::map asw_clusters_t; + +struct FIC_DAB_DT { + struct tm dt; + int ms; + + static const int none = -1; + bool IsNone() const {return dt.tm_year == none;} + + static const int ms_none = -1; + bool IsMsNone() const {return ms == ms_none;} + + FIC_DAB_DT() : ms(ms_none) { + dt.tm_year = none; + } + + bool operator==(const FIC_DAB_DT & fic_dab_dt) const { + return + ms == fic_dab_dt.ms && + dt.tm_sec == fic_dab_dt.dt.tm_sec && + dt.tm_min == fic_dab_dt.dt.tm_min && + dt.tm_hour == fic_dab_dt.dt.tm_hour && + dt.tm_mday == fic_dab_dt.dt.tm_mday && + dt.tm_mon == fic_dab_dt.dt.tm_mon && + dt.tm_year == fic_dab_dt.dt.tm_year; + } + bool operator!=(const FIC_DAB_DT & fic_dab_dt) const { + return !(*this == fic_dab_dt); + } +}; + +struct FIC_ENSEMBLE { + int eid; + bool al_flag; + FIC_LABEL label; + int ecc; + int lto; + int inter_table_id; + asw_clusters_t asw_clusters; + + static const int eid_none = -1; + bool IsNone() const {return eid == eid_none;} + + static const int ecc_none = -1; + static const int lto_none = -100; + static const int inter_table_id_none = -1; + + FIC_ENSEMBLE() : + eid(eid_none), + al_flag(false), + ecc(ecc_none), + lto(lto_none), + inter_table_id(inter_table_id_none) + {} + + bool operator==(const FIC_ENSEMBLE & ensemble) const { + return + eid == ensemble.eid && + al_flag == ensemble.al_flag && + label == ensemble.label && + ecc == ensemble.ecc && + lto == ensemble.lto && + inter_table_id == ensemble.inter_table_id && + asw_clusters == ensemble.asw_clusters; + } + bool operator!=(const FIC_ENSEMBLE & ensemble) const { + return !(*this == ensemble); + } +}; + +struct AUDIO_SERVICE { + int subchid; + bool dab_plus; + + static const int subchid_none = -1; + bool IsNone() const {return subchid == subchid_none;} + + AUDIO_SERVICE() : AUDIO_SERVICE(subchid_none, false) {} + AUDIO_SERVICE(int subchid, bool dab_plus) : subchid(subchid), dab_plus(dab_plus) {} + + bool operator==(const AUDIO_SERVICE & audio_service) const { + return subchid == audio_service.subchid && dab_plus == audio_service.dab_plus; + } + bool operator!=(const AUDIO_SERVICE & audio_service) const { + return !(*this == audio_service); + } +}; + +typedef std::map audio_comps_t; +typedef std::map comp_defs_t; +typedef std::map comp_labels_t; +typedef std::vector ua_data_t; +typedef std::map comp_sls_uas_t; +typedef std::set cids_t; + +struct FIC_SERVICE { + int sid; + int pri_comp_subchid; + FIC_LABEL label; + int pty_static; + int pty_dynamic; + uint16_t asu_flags; + cids_t cids; + + // components + audio_comps_t audio_comps; // from FIG 0/2 : SubChId -> AUDIO_SERVICE + comp_defs_t comp_defs; // from FIG 0/8 : SCIdS -> SubChId + comp_labels_t comp_labels; // from FIG 1/4 : SCIdS -> FIC_LABEL + comp_sls_uas_t comp_sls_uas; // from FIG 0/13: SCIdS -> UA data + + static const int sid_none = -1; + bool IsNone() const {return sid == sid_none;} + + static const int pri_comp_subchid_none = -1; + bool HasNoPriCompSubchid() const {return pri_comp_subchid == pri_comp_subchid_none;} + + static const int pty_none = -1; + + static const int asu_flags_none = 0x0000; + + FIC_SERVICE() : sid(sid_none), pri_comp_subchid(pri_comp_subchid_none), pty_static(pty_none), pty_dynamic(pty_none), asu_flags(asu_flags_none) {} +}; + +struct LISTED_SERVICE { + int sid; + int scids; + FIC_SUBCHANNEL subchannel; + AUDIO_SERVICE audio_service; + FIC_LABEL label; + int pty_static; + int pty_dynamic; + int sls_app_type; + uint16_t asu_flags; + cids_t cids; + + int pri_comp_subchid; // only used for sorting + bool multi_comps; + + static const int sid_none = -1; + bool IsNone() const {return sid == sid_none;} + + static const int scids_none = -1; + bool IsPrimary() const {return scids == scids_none;} + + static const int pty_none = -1; + + static const int asu_flags_none = 0x0000; + + static const int sls_app_type_none = -1; + bool HasSLS() const {return sls_app_type != sls_app_type_none;} + + LISTED_SERVICE() : + sid(sid_none), + scids(scids_none), + pty_static(pty_none), + pty_dynamic(pty_none), + sls_app_type(sls_app_type_none), + asu_flags(asu_flags_none), + pri_comp_subchid(AUDIO_SERVICE::subchid_none), + multi_comps(false) + {} + + bool operator<(const LISTED_SERVICE & service) const { + if(pri_comp_subchid != service.pri_comp_subchid) + return pri_comp_subchid < service.pri_comp_subchid; + if(sid != service.sid) + return sid < service.sid; + return scids < service.scids; + } +}; + +typedef std::map fic_services_t; +typedef std::map fic_subchannels_t; + +// --- FICDecoderObserver ----------------------------------------------------------------- +class FICDecoderObserver { + public: + virtual ~FICDecoderObserver() {} + + std::optional ensemble; + std::optional utc_dt; + std::map services; + + virtual void FICChangeEnsemble(const FIC_ENSEMBLE& ensemble); + virtual void FICChangeService(const LISTED_SERVICE& service); + virtual void FICChangeUTCDateTime(const FIC_DAB_DT& utc_dt); + + virtual void FICDiscardedFIB() {} +}; + + +// --- FICDecoder ----------------------------------------------------------------- +class FICDecoder { + private: + bool verbose; + + void ProcessFIB(const uint8_t *data); + + void ProcessFIG0(const uint8_t *data, size_t len); + void ProcessFIG0_0(const uint8_t *data, size_t len); + void ProcessFIG0_1(const uint8_t *data, size_t len); + void ProcessFIG0_2(const uint8_t *data, size_t len); + void ProcessFIG0_5(const uint8_t *data, size_t len); + void ProcessFIG0_8(const uint8_t *data, size_t len); + void ProcessFIG0_9(const uint8_t *data, size_t len); + void ProcessFIG0_10(const uint8_t *data, size_t len); + void ProcessFIG0_13(const uint8_t *data, size_t len); + void ProcessFIG0_17(const uint8_t *data, size_t len); + void ProcessFIG0_18(const uint8_t *data, size_t len); + void ProcessFIG0_19(const uint8_t *data, size_t len); + + void ProcessFIG1(const uint8_t *data, size_t len); + void ProcessFIG1_0(uint16_t eid, const FIC_LABEL& label); + void ProcessFIG1_1(uint16_t sid, const FIC_LABEL& label); + void ProcessFIG1_4(uint16_t sid, int scids, const FIC_LABEL& label); + + FIC_SUBCHANNEL& GetSubchannel(int subchid); + void UpdateSubchannel(int subchid); + FIC_SERVICE& GetService(uint16_t sid); + void UpdateService(const FIC_SERVICE& service); + void UpdateListedService(const FIC_SERVICE& service, int scids, bool multi_comps); + int GetSLSAppType(const ua_data_t& ua_data); + + FIC_ENSEMBLE ensemble; + void UpdateEnsemble(); + + fic_services_t services; + fic_subchannels_t subchannels; // from FIG 0/1: SubChId -> FIC_SUBCHANNEL + + FIC_DAB_DT utc_dt; + bool utc_dt_long; + + static const size_t uep_sizes[]; + static const int uep_pls[]; + static const int uep_bitrates[]; + static const int eep_a_size_factors[]; + static const int eep_b_size_factors[]; + + static const char* languages_0x00_to_0x2B[]; + static const char* languages_0x7F_downto_0x45[]; + + static const char* ptys_rds_0x00_to_0x1D[]; + static const char* ptys_rbds_0x00_to_0x1D[]; + + static const char* asu_types_0_to_10[]; + public: + FICDecoder(bool verbose); + void Process(const uint8_t *data, size_t len); + void Reset(); + + FICDecoderObserver observer; + + static std::string ConvertLabelToUTF8(const FIC_LABEL& label, std::string* charset_name); + static std::string ConvertLanguageToString(const int value); + static std::string ConvertLTOToString(const int value); + static std::string ConvertInterTableIDToString(const int value); + static std::string ConvertDateTimeToString(FIC_DAB_DT utc_dt, const int lto, bool output_ms); + static std::string ConvertPTYToString(const int value, const int inter_table_id); + static std::string ConvertASuTypeToString(const int value); + static std::string DeriveShortLabelUTF8(const std::string& long_label, uint16_t short_label_mask); +}; + -- cgit v1.2.3 From 0d7550715db39408a82d268026b711f454fef2f8 Mon Sep 17 00:00:00 2001 From: "Matthias P. Braendli" Date: Thu, 2 Nov 2023 11:57:40 +0100 Subject: Add edi_source RC param --- src/DabMod.cpp | 14 ++++++++++++++ src/EtiReader.cpp | 9 +++++---- src/EtiReader.h | 2 ++ 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/src/DabMod.cpp b/src/DabMod.cpp index 0ab112b..e0e8a5b 100644 --- a/src/DabMod.cpp +++ b/src/DabMod.cpp @@ -114,9 +114,11 @@ class ModulatorData : public RemoteControllable { ModulatorData() : RemoteControllable("mainloop") { RC_ADD_PARAMETER(num_modulator_restarts, "(Read-only) Number of mod restarts"); RC_ADD_PARAMETER(most_recent_edi_decoded, "(Read-only) UNIX Timestamp of most recently decoded EDI frame"); + RC_ADD_PARAMETER(edi_source, "(Read-only) URL of the EDI/TCP source"); RC_ADD_PARAMETER(running_since, "(Read-only) UNIX Timestamp of most recent modulator restart"); RC_ADD_PARAMETER(ensemble_label, "(Read-only) Label of the ensemble"); RC_ADD_PARAMETER(ensemble_eid, "(Read-only) Ensemble ID"); + RC_ADD_PARAMETER(ensemble_services, "(Read-only, only JSON) Ensemble service information"); RC_ADD_PARAMETER(num_services, "(Read-only) Number of services in the ensemble"); } @@ -165,6 +167,14 @@ class ModulatorData : public RemoteControllable { throw ParameterError("Not available yet"); } } + else if (parameter == "edi_source") { + if (ediInput) { + ss << ediInput->ediTransport.getTcpUri(); + } + else { + throw ParameterError("Not available yet"); + } + } else if (parameter == "num_services") { if (ediInput) { ss << ediInput->ediReader.getSubchannels().size(); @@ -173,6 +183,9 @@ class ModulatorData : public RemoteControllable { throw ParameterError("Not available yet"); } } + else if (parameter == "ensemble_services") { + throw ParameterError("ensemble_services is only available through 'showjson'"); + } else { ss << "Parameter '" << parameter << "' is not exported by controllable " << get_rc_name(); @@ -189,6 +202,7 @@ class ModulatorData : public RemoteControllable { map["most_recent_edi_decoded"].v = most_recent_edi_decoded; if (ediInput) { + map["edi_source"].v = ediInput->ediTransport.getTcpUri(); map["num_services"].v = ediInput->ediReader.getSubchannels().size(); const auto ens = ediInput->ediReader.getEnsembleInfo(); diff --git a/src/EtiReader.cpp b/src/EtiReader.cpp index 244cc18..eda2f23 100644 --- a/src/EtiReader.cpp +++ b/src/EtiReader.cpp @@ -540,8 +540,8 @@ void EdiTransport::Open(const std::string& uri) { etiLog.level(info) << "Opening EDI :" << uri; - const string proto = uri.substr(0, 3); - if (proto == "udp") { + const string proto = uri.substr(0, 6); + if (proto == "udp://") { if (m_proto == Proto::TCP) { throw std::invalid_argument("Cannot specify both TCP and UDP urls"); } @@ -571,7 +571,7 @@ void EdiTransport::Open(const std::string& uri) m_proto = Proto::UDP; m_enabled = true; } - else if (proto == "tcp") { + else if (proto == "tcp://") { if (m_proto != Proto::Unspecified) { throw std::invalid_argument("Cannot call Open several times with TCP"); } @@ -582,10 +582,11 @@ void EdiTransport::Open(const std::string& uri) } m_port = std::stoi(uri.substr(found_port+1)); - const std::string hostname = uri.substr(6, found_port-6);// skip tcp:// + const std::string hostname = uri.substr(6, found_port-6); etiLog.level(info) << "EDI TCP connect to " << hostname << ":" << m_port; + m_tcp_uri = uri; m_tcpclient.connect(hostname, m_port); m_proto = Proto::TCP; m_enabled = true; diff --git a/src/EtiReader.h b/src/EtiReader.h index 29091bd..703e42a 100644 --- a/src/EtiReader.h +++ b/src/EtiReader.h @@ -221,6 +221,7 @@ class EdiTransport { void Open(const std::string& uri); bool isEnabled(void) const { return m_enabled; } + std::string getTcpUri(void) const { return m_tcp_uri; } /* Receive a packet and give it to the decoder. Returns * true if a packet was received, false in case of socket @@ -229,6 +230,7 @@ class EdiTransport { bool rxPacket(void); private: + std::string m_tcp_uri; bool m_enabled; int m_port; std::string m_bindto; -- cgit v1.2.3 From f84065c3cc6fff0edb771f85190f7228f4d740b6 Mon Sep 17 00:00:00 2001 From: "Matthias P. Braendli" Date: Tue, 7 Nov 2023 16:08:31 +0100 Subject: Fix compilation when all SDR outputs disabled --- src/ConfigParser.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/ConfigParser.h b/src/ConfigParser.h index 8681175..d35432b 100644 --- a/src/ConfigParser.h +++ b/src/ConfigParser.h @@ -84,9 +84,7 @@ struct mod_settings_t { // Settings for the OFDM windowing size_t ofdmWindowOverlap = 0; -#if defined(HAVE_OUTPUT_UHD) || defined(HAVE_SOAPYSDR) || defined(HAVE_LIMESDR) || defined(HAVE_BLADERF) || defined(HAVE_DEXTER) Output::SDRDeviceConfig sdr_device_config; -#endif bool showProcessTime = true; }; -- cgit v1.2.3