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 --- src/ConfigParser.cpp | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) (limited to 'src/ConfigParser.cpp') 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; -- 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(-) (limited to 'src/ConfigParser.cpp') 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 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 (limited to 'src/ConfigParser.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 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(-) (limited to 'src/ConfigParser.cpp') 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 343df6eb8792b3efd33f4426766865ae03ccf316 Mon Sep 17 00:00:00 2001 From: "Matthias P. Braendli" Date: Wed, 19 Jul 2023 22:12:18 +0200 Subject: Add events --- Makefile.am | 2 + doc/receive_events.py | 59 +++++++++++++++++++++++++++++ lib/Json.cpp | 2 +- lib/Json.h | 10 +---- lib/RemoteControl.cpp | 2 +- src/ConfigParser.cpp | 8 +++- src/DabMod.cpp | 3 ++ src/DabModulator.cpp | 4 +- src/Events.cpp | 87 +++++++++++++++++++++++++++++++++++++++++++ src/Events.h | 76 +++++++++++++++++++++++++++++++++++++ src/FIRFilter.cpp | 4 +- src/GainControl.cpp | 10 ++--- src/GuardIntervalInserter.cpp | 2 +- src/MemlessPoly.cpp | 6 +-- src/TII.cpp | 8 ++-- src/TimestampDecoder.cpp | 10 ++--- src/output/Dexter.cpp | 16 ++++---- src/output/SDR.cpp | 16 ++++---- src/output/Soapy.cpp | 8 ++-- src/output/UHD.cpp | 16 ++++---- 20 files changed, 287 insertions(+), 62 deletions(-) create mode 100755 doc/receive_events.py create mode 100644 src/Events.cpp create mode 100644 src/Events.h (limited to 'src/ConfigParser.cpp') diff --git a/Makefile.am b/Makefile.am index 6e7c9ce..5c75c62 100644 --- a/Makefile.am +++ b/Makefile.am @@ -54,6 +54,8 @@ odr_dabmod_SOURCES = src/DabMod.cpp \ src/EtiReader.h \ src/Eti.cpp \ src/Eti.h \ + src/Events.cpp \ + src/Events.h \ src/FicSource.cpp \ src/FicSource.h \ src/PuncturingRule.cpp \ diff --git a/doc/receive_events.py b/doc/receive_events.py new file mode 100755 index 0000000..dca27cd --- /dev/null +++ b/doc/receive_events.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python +# +# This is an example program that shows +# how to receive runtime events from ODR-DabMod +# +# LICENSE: see bottom of file + +import sys +import zmq +import json +from pprint import pprint + +context = zmq.Context() +sock = context.socket(zmq.SUB) + +ep = "tcp://127.0.0.1:5557" +print(f"Receive from {ep}") +sock.connect(ep) + +# subscribe to all events +sock.setsockopt(zmq.SUBSCRIBE, bytes([])) + +while True: + parts = sock.recv_multipart() + if len(parts) == 2: + print("Received event '{}'".format(parts[0].decode())) + pprint(json.loads(parts[1].decode())) + + else: + print("Received strange event:") + pprint(parts) + + print() + + +# This is free and unencumbered software released into the public domain. +# +# Anyone is free to copy, modify, publish, use, compile, sell, or +# distribute this software, either in source code form or as a compiled +# binary, for any purpose, commercial or non-commercial, and by any +# means. +# +# In jurisdictions that recognize copyright laws, the author or authors +# of this software dedicate any and all copyright interest in the +# software to the public domain. We make this dedication for the benefit +# of the public at large and to the detriment of our heirs and +# successors. We intend this dedication to be an overt act of +# relinquishment in perpetuity of all present and future rights to this +# software under copyright law. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +# IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. +# +# For more information, please refer to diff --git a/lib/Json.cpp b/lib/Json.cpp index 9bda8c3..da5b078 100644 --- a/lib/Json.cpp +++ b/lib/Json.cpp @@ -67,7 +67,7 @@ namespace json { ss << "\"" << escape_json(element.first) << "\": "; - const auto& value = element.second.data; + const auto& value = element.second.v; if (std::holds_alternative(value)) { ss << "\"" << escape_json(std::get(value)) << "\""; } diff --git a/lib/Json.h b/lib/Json.h index 26da9a8..ee67f35 100644 --- a/lib/Json.h +++ b/lib/Json.h @@ -51,15 +51,7 @@ namespace json { size_t, ssize_t, bool, - std::nullopt_t> data; - - template - value_t operator=(const T& map) { - value_t v; - v.data = map; - return v; - } - + std::nullopt_t> v; }; std::string map_to_json(const map_t& values); diff --git a/lib/RemoteControl.cpp b/lib/RemoteControl.cpp index b544461..fbe0662 100644 --- a/lib/RemoteControl.cpp +++ b/lib/RemoteControl.cpp @@ -109,7 +109,7 @@ std::list< std::vector > RemoteControllers::get_param_list_values(c std::string RemoteControllers::get_showjson() { json::map_t root; for (auto &controllable : rcs.controllables) { - root[controllable->get_rc_name()].data = controllable->get_all_values(); + root[controllable->get_rc_name()].v = controllable->get_all_values(); } return json::map_to_json(root); diff --git a/src/ConfigParser.cpp b/src/ConfigParser.cpp index cb4dc24..68ee74b 100644 --- a/src/ConfigParser.cpp +++ b/src/ConfigParser.cpp @@ -37,6 +37,7 @@ #include "ConfigParser.h" #include "Utils.h" #include "Log.h" +#include "Events.h" #include "DabModulator.h" #include "output/SDR.h" @@ -114,11 +115,16 @@ static void parse_configfile( mod_settings.inputTransport = pt.Get("input.transport", "file"); - mod_settings.edi_max_delay_ms = pt.GetReal("input.edi_max_delay", 0.0f); + mod_settings.edi_max_delay_ms = pt.GetReal("input.edi_max_delay", 0.0); mod_settings.inputName = pt.Get("input.source", "/dev/stdin"); // log parameters: + const string events_endpoint = pt.Get("log.events_endpoint", ""); + if (not events_endpoint.empty()) { + events.bind(events_endpoint); + } + if (pt.GetInteger("log.syslog", 0) == 1) { etiLog.register_backend(make_shared()); } diff --git a/src/DabMod.cpp b/src/DabMod.cpp index 805fab5..fdd9e93 100644 --- a/src/DabMod.cpp +++ b/src/DabMod.cpp @@ -47,6 +47,7 @@ # include #endif +#include "Events.h" #include "Utils.h" #include "Log.h" #include "DabModulator.h" @@ -324,6 +325,8 @@ int launch_modulator(int argc, char* argv[]) mod_settings_t mod_settings; parse_args(argc, argv, mod_settings); + etiLog.register_backend(make_shared()); + etiLog.level(info) << "Configuration parsed. Starting up version " << #if defined(GITVERSION) GITVERSION; diff --git a/src/DabModulator.cpp b/src/DabModulator.cpp index 0fe9c6d..4a29132 100644 --- a/src/DabModulator.cpp +++ b/src/DabModulator.cpp @@ -438,7 +438,7 @@ const string DabModulator::get_parameter(const string& parameter) const const json::map_t DabModulator::get_all_values() const { json::map_t map; - map["rate"] = m_settings.outputRate; - map["num_clipped_samples"] = m_formatConverter ? m_formatConverter->get_num_clipped_samples() : 0; + map["rate"].v = m_settings.outputRate; + map["num_clipped_samples"].v = m_formatConverter ? m_formatConverter->get_num_clipped_samples() : 0; return map; } diff --git a/src/Events.cpp b/src/Events.cpp new file mode 100644 index 0000000..d65b73a --- /dev/null +++ b/src/Events.cpp @@ -0,0 +1,87 @@ +/* + Copyright (C) 2007, 2008, 2009, 2010, 2011, 2012 + Her Majesty the Queen in Right of Canada (Communications Research + Center Canada) + + Copyright (C) 2023 + Matthias P. Braendli, matthias.braendli@mpb.li + + http://www.opendigitalradio.org + */ +/* + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ +#include +#include +#include +#include +#include +#include +#include + +#include "Events.h" + +EventSender events; + +EventSender::EventSender() : + m_zmq_context(1), + m_socket(m_zmq_context, zmq::socket_type::pub) +{ + int linger = 2000; + m_socket.setsockopt(ZMQ_LINGER, &linger, sizeof(linger)); +} + +EventSender::~EventSender() +{ } + +void EventSender::bind(const std::string& bind_endpoint) +{ + m_socket.bind(bind_endpoint); +} + +void EventSender::send(const std::string& event_name, const json::map_t& detail) +{ + zmq::message_t zmsg1(event_name.data(), event_name.size()); + const auto detail_json = json::map_to_json(detail); + zmq::message_t zmsg2(detail_json.data(), detail_json.size()); + + try { + m_socket.send(zmsg1, zmq::send_flags::sndmore); + m_socket.send(zmsg2, zmq::send_flags::none); + } + catch (const zmq::error_t& err) { + fprintf(stderr, "Cannot send event %s: %s", event_name.c_str(), err.what()); + } +} + + +void LogToEventSender::log(log_level_t level, const std::string& message) +{ + std::string event_name; + if (level == log_level_t::warn) { event_name = "warn"; } + else if (level == log_level_t::error) { event_name = "error"; } + else if (level == log_level_t::alert) { event_name = "alert"; } + else if (level == log_level_t::emerg) { event_name = "emerg"; } + + if (not event_name.empty()) { + json::map_t detail; + detail["message"].v = message; + events.send(event_name, detail); + } +} + +std::string LogToEventSender::get_name() const +{ + return "EventSender"; +} diff --git a/src/Events.h b/src/Events.h new file mode 100644 index 0000000..215c5a8 --- /dev/null +++ b/src/Events.h @@ -0,0 +1,76 @@ +/* + Copyright (C) 2007, 2008, 2009, 2010, 2011, 2012 + Her Majesty the Queen in Right of Canada (Communications Research + Center Canada) + + Copyright (C) 2023 + Matthias P. Braendli, matthias.braendli@mpb.li + + http://www.opendigitalradio.org + + This module adds remote-control capability to some of the dabmux/dabmod modules. + */ +/* + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ + +#pragma once + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#if defined(HAVE_ZEROMQ) +# include "zmq.hpp" +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include "Log.h" +#include "Json.h" + +class EventSender { + public: + EventSender(); + EventSender(const EventSender& other) = delete; + const EventSender& operator=(const EventSender& other) = delete; + EventSender(EventSender&& other) = delete; + EventSender& operator=(EventSender&& other) = delete; + ~EventSender(); + + void bind(const std::string& bind_endpoint); + + void send(const std::string& event_name, const json::map_t& detail); + private: + zmq::context_t m_zmq_context; + zmq::socket_t m_socket; +}; + +class LogToEventSender: public LogBackend { + public: + virtual ~LogToEventSender() {}; + virtual void log(log_level_t level, const std::string& message); + virtual std::string get_name() const; +}; + +/* events is a singleton used in all parts of the program to output log messages. + * It is constructed in Events.cpp */ +extern EventSender events; + diff --git a/src/FIRFilter.cpp b/src/FIRFilter.cpp index d2a6121..57e7127 100644 --- a/src/FIRFilter.cpp +++ b/src/FIRFilter.cpp @@ -350,7 +350,7 @@ const string FIRFilter::get_parameter(const string& parameter) const const json::map_t FIRFilter::get_all_values() const { json::map_t map; - map["ntaps"] = m_taps.size(); - map["tapsfile"] = m_taps_file; + map["ntaps"].v = m_taps.size(); + map["tapsfile"].v = m_taps_file; return map; } diff --git a/src/GainControl.cpp b/src/GainControl.cpp index beb93f6..84cf065 100644 --- a/src/GainControl.cpp +++ b/src/GainControl.cpp @@ -586,18 +586,18 @@ const string GainControl::get_parameter(const string& parameter) const const json::map_t GainControl::get_all_values() const { json::map_t map; - map["digital"] = m_digGain; + map["digital"].v = m_digGain; switch (m_gainmode) { case GainMode::GAIN_FIX: - map["mode"] = "fix"; + map["mode"].v = "fix"; break; case GainMode::GAIN_MAX: - map["mode"] = "max"; + map["mode"].v = "max"; break; case GainMode::GAIN_VAR: - map["mode"] = "var"; + map["mode"].v = "var"; break; } - map["var"] = m_var_variance_rc; + map["var"].v = m_var_variance_rc; return map; } diff --git a/src/GuardIntervalInserter.cpp b/src/GuardIntervalInserter.cpp index 80394b7..3c2db14 100644 --- a/src/GuardIntervalInserter.cpp +++ b/src/GuardIntervalInserter.cpp @@ -306,6 +306,6 @@ const std::string GuardIntervalInserter::get_parameter(const std::string& parame const json::map_t GuardIntervalInserter::get_all_values() const { json::map_t map; - map["windowlen"] = d_windowOverlap; + map["windowlen"].v = d_windowOverlap; return map; } diff --git a/src/MemlessPoly.cpp b/src/MemlessPoly.cpp index 30d4ce9..184b5bd 100644 --- a/src/MemlessPoly.cpp +++ b/src/MemlessPoly.cpp @@ -470,8 +470,8 @@ const string MemlessPoly::get_parameter(const string& parameter) const const json::map_t MemlessPoly::get_all_values() const { json::map_t map; - map["ncoefs"] = m_coefs_am.size(); - map["coefs"] = serialise_coefficients(); - map["coeffile"] = m_coefs_file; + map["ncoefs"].v = m_coefs_am.size(); + map["coefs"].v = serialise_coefficients(); + map["coeffile"].v = m_coefs_file; return map; } diff --git a/src/TII.cpp b/src/TII.cpp index 9068630..2656cbf 100644 --- a/src/TII.cpp +++ b/src/TII.cpp @@ -388,9 +388,9 @@ const std::string TII::get_parameter(const std::string& parameter) const const json::map_t TII::get_all_values() const { json::map_t map; - map["enable"] = m_conf.enable; - map["pattern"] = m_conf.pattern; - map["comb"] = m_conf.comb; - map["old_variant"] = m_conf.old_variant; + map["enable"].v = m_conf.enable; + map["pattern"].v = m_conf.pattern; + map["comb"].v = m_conf.comb; + map["old_variant"].v = m_conf.old_variant; return map; } diff --git a/src/TimestampDecoder.cpp b/src/TimestampDecoder.cpp index 4277e55..a7972c9 100644 --- a/src/TimestampDecoder.cpp +++ b/src/TimestampDecoder.cpp @@ -304,19 +304,19 @@ const std::string TimestampDecoder::get_parameter( const json::map_t TimestampDecoder::get_all_values() const { json::map_t map; - map["offset"] = timestamp_offset; + map["offset"].v = timestamp_offset; if (full_timestamp_received) { - map["timestamp"] = time_secs + ((double)time_pps / 16384000.0); + map["timestamp"].v = time_secs + ((double)time_pps / 16384000.0); } else { - map["timestamp"] = std::nullopt; + map["timestamp"].v = std::nullopt; } if (full_timestamp_received) { - map["timestamp0"] = time_secs_of_frame0 + ((double)time_pps_of_frame0 / 16384000.0); + map["timestamp0"].v = time_secs_of_frame0 + ((double)time_pps_of_frame0 / 16384000.0); } else { - map["timestamp0"] = std::nullopt; + map["timestamp0"].v = std::nullopt; } return map; } diff --git a/src/output/Dexter.cpp b/src/output/Dexter.cpp index 132636c..e52f774 100644 --- a/src/output/Dexter.cpp +++ b/src/output/Dexter.cpp @@ -470,20 +470,20 @@ SDRDevice::run_statistics_t Dexter::get_run_statistics(void) const run_statistics_t rs; { std::unique_lock lock(m_attr_thread_mutex); - rs["underruns"] = underflows; + rs["underruns"].v = underflows; } - rs["latepackets"] = num_late; - rs["frames"] = num_frames_modulated; + rs["latepackets"].v = num_late; + rs["frames"].v = num_frames_modulated; - rs["in_holdover_since"] = 0; + rs["in_holdover_since"].v = 0; switch (m_clock_state) { case DexterClockState::Startup: - rs["clock_state"] = "startup"; break; + rs["clock_state"].v = "startup"; break; case DexterClockState::Normal: - rs["clock_state"] = "normal"; break; + rs["clock_state"].v = "normal"; break; case DexterClockState::Holdover: - rs["clock_state"] = "holdover"; - rs["in_holdover_since"] = m_holdover_since_t; + rs["clock_state"].v = "holdover"; + rs["in_holdover_since"].v = m_holdover_since_t; break; } diff --git a/src/output/SDR.cpp b/src/output/SDR.cpp index 4fc3277..6c03b53 100644 --- a/src/output/SDR.cpp +++ b/src/output/SDR.cpp @@ -450,7 +450,7 @@ const string SDR::get_parameter(const string& parameter) const if (m_device) { const auto stat = m_device->get_run_statistics(); try { - const auto& value = stat.at(parameter).data; + const auto& value = stat.at(parameter).v; if (std::holds_alternative(value)) { ss << std::get(value); } @@ -489,19 +489,19 @@ const json::map_t SDR::get_all_values() const { json::map_t stat = m_device->get_run_statistics(); - stat["txgain"] = m_config.txgain; - stat["rxgain"] = m_config.rxgain; - stat["freq"] = m_config.frequency; - stat["muting"] = m_config.muting; - stat["temp"] = std::nullopt; + stat["txgain"].v = m_config.txgain; + stat["rxgain"].v = m_config.rxgain; + stat["freq"].v = m_config.frequency; + stat["muting"].v = m_config.muting; + stat["temp"].v = std::nullopt; if (m_device) { const std::optional temp = m_device->get_temperature(); if (temp) { - stat["temp"] = *temp; + stat["temp"].v = *temp; } } - stat["queued_frames_ms"] = m_queue.size() * + stat["queued_frames_ms"].v = m_queue.size() * (size_t)chrono::duration_cast(transmission_frame_duration(m_config.dabMode)) .count(); diff --git a/src/output/Soapy.cpp b/src/output/Soapy.cpp index 4d33e39..7931860 100644 --- a/src/output/Soapy.cpp +++ b/src/output/Soapy.cpp @@ -183,10 +183,10 @@ double Soapy::get_bandwidth(void) const SDRDevice::run_statistics_t Soapy::get_run_statistics(void) const { run_statistics_t rs; - rs["underruns"] = underflows; - rs["overruns"] = overflows; - rs["timeouts"] = timeouts; - rs["frames"] = num_frames_modulated; + rs["underruns"].v = underflows; + rs["overruns"].v = overflows; + rs["timeouts"].v = timeouts; + rs["frames"].v = num_frames_modulated; return rs; } diff --git a/src/output/UHD.cpp b/src/output/UHD.cpp index 6638b6c..094e021 100644 --- a/src/output/UHD.cpp +++ b/src/output/UHD.cpp @@ -380,19 +380,19 @@ void UHD::transmit_frame(struct FrameData&& frame) SDRDevice::run_statistics_t UHD::get_run_statistics(void) const { run_statistics_t rs; - rs["underruns"] = num_underflows; - rs["overruns"] = num_overflows; - rs["late_packets"] = num_late_packets; - rs["frames"] = num_frames_modulated; + rs["underruns"].v = num_underflows; + rs["overruns"].v = num_overflows; + rs["late_packets"].v = num_late_packets; + rs["frames"].v = num_frames_modulated; if (m_device_time) { const auto gpsdo_stat = m_device_time->get_gnss_stats(); - rs["gpsdo_holdover"] = gpsdo_stat.holdover; - rs["gpsdo_num_sv"] = gpsdo_stat.num_sv; + rs["gpsdo_holdover"].v = gpsdo_stat.holdover; + rs["gpsdo_num_sv"].v = gpsdo_stat.num_sv; } else { - rs["gpsdo_holdover"] = true; - rs["gpsdo_num_sv"] = 0; + rs["gpsdo_holdover"].v = true; + rs["gpsdo_num_sv"].v = 0; } return rs; } -- cgit v1.2.3 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(-) (limited to 'src/ConfigParser.cpp') 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