From bf6e05a427e050ec54b9da91da8ac04f52fa006c Mon Sep 17 00:00:00 2001 From: "Matthias P. Braendli" Date: Wed, 1 Feb 2023 13:53:55 +0100 Subject: Remove easydabv3 support --- src/Utils.cpp | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) (limited to 'src/Utils.cpp') diff --git a/src/Utils.cpp b/src/Utils.cpp index f39c4c9..350838e 100644 --- a/src/Utils.cpp +++ b/src/Utils.cpp @@ -3,7 +3,7 @@ Her Majesty the Queen in Right of Canada (Communications Research Center Canada) - Copyright (C) 2018 + Copyright (C) 2023 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org @@ -62,10 +62,6 @@ static void printHeader() "SSE " << #endif "\n"; - -#if defined(BUILD_FOR_EASYDABV3) - std::cerr << " This is a build for the EasyDABv3 board" << std::endl; -#endif } void printUsage(const char* progName) @@ -77,13 +73,8 @@ void printUsage(const char* progName) fprintf(out, "Usage with command line options:\n"); fprintf(out, "\t%s" " input" -#if defined(BUILD_FOR_EASYDABV3) - " -f filename -F format" -#else " (-f filename -F format | -u uhddevice -F frequency)" -#endif " [-o offset]" -#if !defined(BUILD_FOR_EASYDABV3) "\n\t" " [-G txgain]" " [-T filter_taps_file]" @@ -93,7 +84,6 @@ void printUsage(const char* progName) " [-g gainMode]" " [-m dabMode]" " [-r samplingRate]" -#endif " [-l]" " [-h]" "\n", progName); @@ -108,7 +98,6 @@ void printUsage(const char* progName) fprintf(out, " Specifying this option has two implications: It enables synchronous transmission,\n" " requiring an external REFCLK and PPS signal and frames that do not contain a valid timestamp\n" " get muted.\n\n"); -#if !defined(BUILD_FOR_EASYDABV3) fprintf(out, "-u device: Use UHD output with given device string. (use "" for default device)\n"); fprintf(out, "-F frequency: Set the transmit frequency when using UHD output. (mandatory option when using UHD)\n"); fprintf(out, "-G txgain: Set the transmit gain for the UHD driver (default: 0)\n"); @@ -119,7 +108,6 @@ void printUsage(const char* progName) fprintf(out, "-g gainmode: Set computation gain mode: fix, max or var\n"); fprintf(out, "-m mode: Set DAB mode: (0: auto, 1-4: force).\n"); fprintf(out, "-r rate: Set output sampling rate (default: 2048000).\n\n"); -#endif fprintf(out, "-l: Loop file when reach end of file.\n"); fprintf(out, "-h: Print this help.\n"); } @@ -132,7 +120,7 @@ void printVersion(void) " ODR-DabMod is copyright (C) Her Majesty the Queen in Right of Canada,\n" " 2005 -- 2012 Communications Research Centre (CRC),\n" " and\n" - " Copyright (C) 2018 Matthias P. Braendli, matthias.braendli@mpb.li\n" + " Copyright (C) 2023 Matthias P. Braendli, matthias.braendli@mpb.li\n" "\n" " http://opendigitalradio.org\n" "\n" -- cgit v1.2.3 From 0aec6da11b4add62ac473e3f4ea813bb4a8a556d Mon Sep 17 00:00:00 2001 From: "Matthias P. Braendli" Date: Wed, 1 Feb 2023 14:03:03 +0100 Subject: Remove ZeroMQ input --- Makefile.am | 1 - README.md | 2 +- doc/example.ini | 7 - src/ConfigParser.cpp | 7 +- src/ConfigParser.h | 5 +- src/DabMod.cpp | 71 +--------- src/InputFileReader.cpp | 3 +- src/InputReader.h | 61 --------- src/InputZeroMQReader.cpp | 323 ---------------------------------------------- src/Utils.cpp | 1 - 10 files changed, 7 insertions(+), 474 deletions(-) delete mode 100644 src/InputZeroMQReader.cpp (limited to 'src/Utils.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 0887d7e859605fad9617681695e70e3ef738a19c Mon Sep 17 00:00:00 2001 From: "Matthias P. Braendli" Date: Thu, 20 Jul 2023 18:14:51 +0200 Subject: Add main loop RC metrics --- lib/Socket.cpp | 2 +- src/DabMod.cpp | 165 ++++++++++++++++++-------------------------- src/DabModulator.h | 2 + src/EtiReader.cpp | 2 +- src/GuardIntervalInserter.h | 2 + src/InputTcpReader.cpp | 3 + src/TII.h | 1 + src/TimestampDecoder.h | 1 + src/Utils.cpp | 82 ++++++++++++++++++++++ src/Utils.h | 13 ++-- 10 files changed, 169 insertions(+), 104 deletions(-) (limited to 'src/Utils.cpp') diff --git a/lib/Socket.cpp b/lib/Socket.cpp index 10ec1ca..b71c01e 100644 --- a/lib/Socket.cpp +++ b/lib/Socket.cpp @@ -893,7 +893,7 @@ ssize_t TCPClient::recv(void *buffer, size_t length, int flags, int timeout_ms) return 0; } - return 0; + throw std::logic_error("unreachable"); } void TCPClient::reconnect() diff --git a/src/DabMod.cpp b/src/DabMod.cpp index fdd9e93..7daa72a 100644 --- a/src/DabMod.cpp +++ b/src/DabMod.cpp @@ -3,7 +3,7 @@ Her Majesty the Queen in Right of Canada (Communications Research Center Canada) - Copyright (C) 2019 + Copyright (C) 2023 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org @@ -96,18 +96,58 @@ void signalHandler(int signalNb) running = 0; } -struct modulator_data -{ - // For ETI - std::shared_ptr inputReader; - std::shared_ptr etiReader; +class ModulatorData : public RemoteControllable { + public: + // For ETI + std::shared_ptr inputReader; + std::shared_ptr etiReader; + + // For EDI + std::shared_ptr ediInput; + + // Common to both EDI and EDI + uint64_t framecount = 0; + Flowgraph *flowgraph = nullptr; + + + // RC-related + ModulatorData() : RemoteControllable("mainloop") { + RC_ADD_PARAMETER(num_modulator_restarts, "(Read-only) Number of mod restarts"); + RC_ADD_PARAMETER(most_recent_edi_decoded, "(Read-only) UNIX Timestamp of most recently decoded EDI frame"); + } + + virtual ~ModulatorData() {} + + virtual void set_parameter(const std::string& parameter, const std::string& value) { + throw ParameterError("Parameter " + parameter + " is read-only"); + } + + virtual const std::string get_parameter(const std::string& parameter) const { + stringstream ss; + if (parameter == "num_modulator_restarts") { + ss << num_modulator_restarts; + } + else if (parameter == "most_recent_edi_decoded") { + ss << most_recent_edi_decoded; + } + else { + ss << "Parameter '" << parameter << + "' is not exported by controllable " << get_rc_name(); + throw ParameterError(ss.str()); + } + return ss.str(); + } - // For EDI - std::shared_ptr ediInput; + virtual const json::map_t get_all_values() const + { + json::map_t map; + map["num_modulator_restarts"].v = num_modulator_restarts; + map["most_recent_edi_decoded"].v = most_recent_edi_decoded; + return map; + } - // Common to both EDI and EDI - uint64_t framecount = 0; - Flowgraph *flowgraph = nullptr; + size_t num_modulator_restarts = 0; + time_t most_recent_edi_decoded = 0; }; enum class run_modulator_state_t { @@ -117,88 +157,8 @@ enum class run_modulator_state_t { reconfigure // Some sort of change of configuration we cannot handle happened }; -static run_modulator_state_t run_modulator(const mod_settings_t& mod_settings, modulator_data& m); - -static void printModSettings(const mod_settings_t& mod_settings) -{ - stringstream ss; - // Print settings - ss << "Input\n"; - ss << " Type: " << mod_settings.inputTransport << "\n"; - ss << " Source: " << mod_settings.inputName << "\n"; - - ss << "Output\n"; - - if (mod_settings.useFileOutput) { - ss << " Name: " << mod_settings.outputName << "\n"; - } -#if defined(HAVE_OUTPUT_UHD) - else if (mod_settings.useUHDOutput) { - ss << " UHD\n" << - " Device: " << mod_settings.sdr_device_config.device << "\n" << - " Subdevice: " << - mod_settings.sdr_device_config.subDevice << "\n" << - " master_clock_rate: " << - mod_settings.sdr_device_config.masterClockRate << "\n" << - " refclk: " << - mod_settings.sdr_device_config.refclk_src << "\n" << - " pps source: " << - mod_settings.sdr_device_config.pps_src << "\n"; - } -#endif -#if defined(HAVE_SOAPYSDR) - else if (mod_settings.useSoapyOutput) { - ss << " SoapySDR\n" - " Device: " << mod_settings.sdr_device_config.device << "\n" << - " master_clock_rate: " << - mod_settings.sdr_device_config.masterClockRate << "\n"; - } -#endif -#if defined(HAVE_DEXTER) - else if (mod_settings.useDexterOutput) { - ss << " PrecisionWave DEXTER\n"; - } -#endif -#if defined(HAVE_LIMESDR) - else if (mod_settings.useLimeOutput) { - ss << " LimeSDR\n" - " Device: " << mod_settings.sdr_device_config.device << "\n" << - " master_clock_rate: " << - mod_settings.sdr_device_config.masterClockRate << "\n"; - } -#endif -#if defined(HAVE_BLADERF) - else if (mod_settings.useBladeRFOutput) { - ss << " BladeRF\n" - " Device: " << mod_settings.sdr_device_config.device << "\n" << - " refclk: " << mod_settings.sdr_device_config.refclk_src << "\n"; - } -#endif - else if (mod_settings.useZeroMQOutput) { - ss << " ZeroMQ\n" << - " Listening on: " << mod_settings.outputName << "\n" << - " Socket type : " << mod_settings.zmqOutputSocketType << "\n"; - } +static run_modulator_state_t run_modulator(const mod_settings_t& mod_settings, ModulatorData& m); - ss << " Sampling rate: "; - if (mod_settings.outputRate > 1000) { - if (mod_settings.outputRate > 1000000) { - ss << std::fixed << std::setprecision(4) << - mod_settings.outputRate / 1000000.0 << - " MHz\n"; - } - else { - ss << std::fixed << std::setprecision(4) << - mod_settings.outputRate / 1000.0 << - " kHz\n"; - } - } - else { - ss << std::fixed << std::setprecision(4) << - mod_settings.outputRate << " Hz\n"; - } - fprintf(stderr, "%s", ss.str().c_str()); -} static shared_ptr prepare_output(mod_settings_t& s) { @@ -346,6 +306,9 @@ int launch_modulator(int argc, char* argv[]) printModSettings(mod_settings); + ModulatorData m; + rcs.enrol(&m); + { // This is mostly useful on ARM systems where FFTW planning takes some time. If we do it here // it will be done before the modulator starts up @@ -422,14 +385,15 @@ int launch_modulator(int argc, char* argv[]) "invalid input transport " + mod_settings.inputTransport + " selected!"); } + m.ediInput = ediInput; + m.inputReader = inputReader; + bool run_again = true; while (run_again) { Flowgraph flowgraph(mod_settings.showProcessTime); - modulator_data m; - m.ediInput = ediInput; - m.inputReader = inputReader; + m.framecount = 0; m.flowgraph = &flowgraph; shared_ptr modulator; @@ -493,13 +457,14 @@ int launch_modulator(int argc, char* argv[]) } etiLog.level(info) << m.framecount << " DAB frames, " << ((float)m.framecount * 0.024f) << " seconds encoded"; + m.num_modulator_restarts++; } etiLog.level(info) << "Terminating"; return ret; } -static run_modulator_state_t run_modulator(const mod_settings_t& mod_settings, modulator_data& m) +static run_modulator_state_t run_modulator(const mod_settings_t& mod_settings, ModulatorData& m) { auto ret = run_modulator_state_t::failure; try { @@ -579,6 +544,12 @@ static run_modulator_state_t run_modulator(const mod_settings_t& mod_settings, m break; } + struct timespec t; + if (clock_gettime(CLOCK_REALTIME, &t) != 0) { + throw std::runtime_error(std::string("Failed to retrieve CLOCK_REALTIME") + strerror(errno)); + } + + m.most_recent_edi_decoded = t.tv_sec; fct = m.ediInput->ediReader.getFct(); fp = m.ediInput->ediReader.getFp(); ts = m.ediInput->ediReader.getTimestamp(); @@ -611,7 +582,7 @@ static run_modulator_state_t run_modulator(const mod_settings_t& mod_settings, m last_eti_fct = fct; } else { - etiLog.level(info) << "ETI FCT discontinuity, expected " << + etiLog.level(warn) << "ETI FCT discontinuity, expected " << expected_fct << " received " << fct; if (m.ediInput) { m.ediInput->ediReader.clearFrame(); diff --git a/src/DabModulator.h b/src/DabModulator.h index 140f313..093a782 100644 --- a/src/DabModulator.h +++ b/src/DabModulator.h @@ -53,6 +53,8 @@ public: DabModulator(EtiSource& etiSource, mod_settings_t& settings, const std::string& format); // Allowed formats: s8, u8 and s16. Empty string means no conversion + virtual ~DabModulator() {} + int process(Buffer* dataOut) override; const char* name() override { return "DabModulator"; } diff --git a/src/EtiReader.cpp b/src/EtiReader.cpp index e992e62..580088b 100644 --- a/src/EtiReader.cpp +++ b/src/EtiReader.cpp @@ -646,7 +646,7 @@ bool EdiTransport::rxPacket() const int timeout_ms = 1000; try { ssize_t ret = m_tcpclient.recv(m_tcpbuffer.data(), m_tcpbuffer.size(), 0, timeout_ms); - if (ret == 0 or ret == -1) { + if (ret <= 0) { return false; } else if (ret > (ssize_t)m_tcpbuffer.size()) { diff --git a/src/GuardIntervalInserter.h b/src/GuardIntervalInserter.h index 5aaad2b..f78ac91 100644 --- a/src/GuardIntervalInserter.h +++ b/src/GuardIntervalInserter.h @@ -52,6 +52,8 @@ class GuardIntervalInserter : public ModCodec, public RemoteControllable size_t symSize, size_t& windowOverlap); + virtual ~GuardIntervalInserter() {} + int process(Buffer* const dataIn, Buffer* dataOut) override; const char* name() override { return "GuardIntervalInserter"; } diff --git a/src/InputTcpReader.cpp b/src/InputTcpReader.cpp index 21f8496..8ba4d74 100644 --- a/src/InputTcpReader.cpp +++ b/src/InputTcpReader.cpp @@ -79,6 +79,9 @@ int InputTcpReader::GetNextFrame(void* buffer) etiLog.level(debug) << "TCP input auto reconnect"; std::this_thread::sleep_for(std::chrono::seconds(1)); } + else if (ret == -2) { + etiLog.level(debug) << "TCP input timeout"; + } return ret; } diff --git a/src/TII.h b/src/TII.h index a8d0ca9..f6de70b 100644 --- a/src/TII.h +++ b/src/TII.h @@ -82,6 +82,7 @@ class TII : public ModCodec, public RemoteControllable { public: TII(unsigned int dabmode, tii_config_t& tii_config); + virtual ~TII() {} int process(Buffer* dataIn, Buffer* dataOut) override; const char* name() override; diff --git a/src/TimestampDecoder.h b/src/TimestampDecoder.h index b90c328..25796ca 100644 --- a/src/TimestampDecoder.h +++ b/src/TimestampDecoder.h @@ -98,6 +98,7 @@ class TimestampDecoder : public RemoteControllable * frame transmission */ TimestampDecoder(double& offset_s); + virtual ~TimestampDecoder() {} frame_timestamp getTimestamp(void); diff --git a/src/Utils.cpp b/src/Utils.cpp index 3f378a7..94f198c 100644 --- a/src/Utils.cpp +++ b/src/Utils.cpp @@ -25,6 +25,7 @@ along with ODR-DabMod. If not, see . */ +#include "sstream" #include "Utils.h" #include "GainControl.h" #if defined(HAVE_PRCTL) @@ -144,6 +145,87 @@ void printStartupInfo() printHeader(); } +void printModSettings(const mod_settings_t& mod_settings) +{ + std::stringstream ss; + // Print settings + ss << "Input\n"; + ss << " Type: " << mod_settings.inputTransport << "\n"; + ss << " Source: " << mod_settings.inputName << "\n"; + + ss << "Output\n"; + + if (mod_settings.useFileOutput) { + ss << " Name: " << mod_settings.outputName << "\n"; + } +#if defined(HAVE_OUTPUT_UHD) + else if (mod_settings.useUHDOutput) { + ss << " UHD\n" << + " Device: " << mod_settings.sdr_device_config.device << "\n" << + " Subdevice: " << + mod_settings.sdr_device_config.subDevice << "\n" << + " master_clock_rate: " << + mod_settings.sdr_device_config.masterClockRate << "\n" << + " refclk: " << + mod_settings.sdr_device_config.refclk_src << "\n" << + " pps source: " << + mod_settings.sdr_device_config.pps_src << "\n"; + } +#endif +#if defined(HAVE_SOAPYSDR) + else if (mod_settings.useSoapyOutput) { + ss << " SoapySDR\n" + " Device: " << mod_settings.sdr_device_config.device << "\n" << + " master_clock_rate: " << + mod_settings.sdr_device_config.masterClockRate << "\n"; + } +#endif +#if defined(HAVE_DEXTER) + else if (mod_settings.useDexterOutput) { + ss << " PrecisionWave DEXTER\n"; + } +#endif +#if defined(HAVE_LIMESDR) + else if (mod_settings.useLimeOutput) { + ss << " LimeSDR\n" + " Device: " << mod_settings.sdr_device_config.device << "\n" << + " master_clock_rate: " << + mod_settings.sdr_device_config.masterClockRate << "\n"; + } +#endif +#if defined(HAVE_BLADERF) + else if (mod_settings.useBladeRFOutput) { + ss << " BladeRF\n" + " Device: " << mod_settings.sdr_device_config.device << "\n" << + " refclk: " << mod_settings.sdr_device_config.refclk_src << "\n"; + } +#endif + else if (mod_settings.useZeroMQOutput) { + ss << " ZeroMQ\n" << + " Listening on: " << mod_settings.outputName << "\n" << + " Socket type : " << mod_settings.zmqOutputSocketType << "\n"; + } + + ss << " Sampling rate: "; + if (mod_settings.outputRate > 1000) { + if (mod_settings.outputRate > 1000000) { + ss << std::fixed << std::setprecision(4) << + mod_settings.outputRate / 1000000.0 << + " MHz\n"; + } + else { + ss << std::fixed << std::setprecision(4) << + mod_settings.outputRate / 1000.0 << + " kHz\n"; + } + } + else { + ss << std::fixed << std::setprecision(4) << + mod_settings.outputRate << " Hz\n"; + } + fprintf(stderr, "%s", ss.str().c_str()); +} + int set_realtime_prio(int prio) { // Set thread priority to realtime diff --git a/src/Utils.h b/src/Utils.h index 9e88488..82728e9 100644 --- a/src/Utils.h +++ b/src/Utils.h @@ -3,7 +3,7 @@ Her Majesty the Queen in Right of Canada (Communications Research Center Canada) - Copyright (C) 2018 + Copyright (C) 2023 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org @@ -31,12 +31,13 @@ # include "config.h" #endif -#include -#include -#include -#include #include #include +#include +#include +#include +#include +#include "ConfigParser.h" void printUsage(const char* progName); @@ -44,6 +45,8 @@ void printVersion(void); void printStartupInfo(void); +void printModSettings(const mod_settings_t& mod_settings); + // Set SCHED_RR with priority prio (0=lowest) int set_realtime_prio(int prio); -- cgit v1.2.3 From a759d1fae861e7f0836283dae5dce49dae6528fc Mon Sep 17 00:00:00 2001 From: "Matthias P. Braendli" Date: Tue, 15 Aug 2023 10:58:11 +0200 Subject: Add parameters sdr.synchronous and mainloop.running_since --- src/DabMod.cpp | 15 +++++++++------ src/Utils.cpp | 13 ++++++++++++- src/Utils.h | 3 +++ src/output/SDR.cpp | 6 ++++++ 4 files changed, 30 insertions(+), 7 deletions(-) (limited to 'src/Utils.cpp') diff --git a/src/DabMod.cpp b/src/DabMod.cpp index 7daa72a..d43ebd5 100644 --- a/src/DabMod.cpp +++ b/src/DabMod.cpp @@ -114,6 +114,7 @@ class ModulatorData : public RemoteControllable { ModulatorData() : RemoteControllable("mainloop") { RC_ADD_PARAMETER(num_modulator_restarts, "(Read-only) Number of mod restarts"); RC_ADD_PARAMETER(most_recent_edi_decoded, "(Read-only) UNIX Timestamp of most recently decoded EDI frame"); + RC_ADD_PARAMETER(running_since, "(Read-only) UNIX Timestamp of most recent modulator restart"); } virtual ~ModulatorData() {} @@ -127,6 +128,9 @@ class ModulatorData : public RemoteControllable { if (parameter == "num_modulator_restarts") { ss << num_modulator_restarts; } + if (parameter == "running_since") { + ss << running_since; + } else if (parameter == "most_recent_edi_decoded") { ss << most_recent_edi_decoded; } @@ -142,12 +146,14 @@ class ModulatorData : public RemoteControllable { { json::map_t map; map["num_modulator_restarts"].v = num_modulator_restarts; + map["running_since"].v = running_since; map["most_recent_edi_decoded"].v = most_recent_edi_decoded; return map; } size_t num_modulator_restarts = 0; time_t most_recent_edi_decoded = 0; + time_t running_since = 0; }; enum class run_modulator_state_t { @@ -391,6 +397,8 @@ int launch_modulator(int argc, char* argv[]) bool run_again = true; while (run_again) { + m.running_since = get_clock_realtime_seconds(); + Flowgraph flowgraph(mod_settings.showProcessTime); m.framecount = 0; @@ -544,12 +552,7 @@ static run_modulator_state_t run_modulator(const mod_settings_t& mod_settings, M break; } - struct timespec t; - if (clock_gettime(CLOCK_REALTIME, &t) != 0) { - throw std::runtime_error(std::string("Failed to retrieve CLOCK_REALTIME") + strerror(errno)); - } - - m.most_recent_edi_decoded = t.tv_sec; + m.most_recent_edi_decoded = get_clock_realtime_seconds(); fct = m.ediInput->ediReader.getFct(); fp = m.ediInput->ediReader.getFp(); ts = m.ediInput->ediReader.getTimestamp(); diff --git a/src/Utils.cpp b/src/Utils.cpp index 94f198c..20297ea 100644 --- a/src/Utils.cpp +++ b/src/Utils.cpp @@ -25,7 +25,8 @@ along with ODR-DabMod. If not, see . */ -#include "sstream" +#include +#include #include "Utils.h" #include "GainControl.h" #if defined(HAVE_PRCTL) @@ -304,3 +305,13 @@ std::chrono::milliseconds transmission_frame_duration(unsigned int dabmode) } } + +time_t get_clock_realtime_seconds() +{ + struct timespec t; + if (clock_gettime(CLOCK_REALTIME, &t) != 0) { + throw std::runtime_error(std::string("Failed to retrieve CLOCK_REALTIME") + strerror(errno)); + } + + return t.tv_sec; +} diff --git a/src/Utils.h b/src/Utils.h index 82728e9..367dd48 100644 --- a/src/Utils.h +++ b/src/Utils.h @@ -36,6 +36,7 @@ #include #include #include +#include #include #include "ConfigParser.h" @@ -59,3 +60,5 @@ double parseChannel(const std::string& chan); // dabMode is either 1, 2, 3, 4, corresponding to TM I, TM II, TM III and TM IV. // throws a runtime_error if dabMode is not one of these values. std::chrono::milliseconds transmission_frame_duration(unsigned int dabmode); + +time_t get_clock_realtime_seconds(); diff --git a/src/output/SDR.cpp b/src/output/SDR.cpp index d57e4d6..e466287 100644 --- a/src/output/SDR.cpp +++ b/src/output/SDR.cpp @@ -84,6 +84,7 @@ SDR::SDR(SDRDeviceConfig& config, std::shared_ptr device) : RC_ADD_PARAMETER(underruns, "Counter of number of underruns"); RC_ADD_PARAMETER(latepackets, "Counter of number of late packets"); RC_ADD_PARAMETER(frames, "Counter of number of frames modulated"); + RC_ADD_PARAMETER(synchronous, "1 if configured for synchronous transmission"); #ifdef HAVE_OUTPUT_UHD if (std::dynamic_pointer_cast(device)) { @@ -435,6 +436,9 @@ const string SDR::get_parameter(const string& parameter) const chrono::duration_cast(transmission_frame_duration(m_config.dabMode)) .count(); } + else if (parameter == "synchronous") { + ss << m_config.enableSync; + } else { if (m_device) { const auto stat = m_device->get_run_statistics(); @@ -494,6 +498,8 @@ const json::map_t SDR::get_all_values() const (size_t)chrono::duration_cast(transmission_frame_duration(m_config.dabMode)) .count(); + stat["synchronous"].v = m_config.enableSync; + return stat; } -- cgit v1.2.3 From 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/Utils.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