From 8d80d831227cc0d74a90c210e40e17bd6394f4d2 Mon Sep 17 00:00:00 2001 From: "Matthias P. Braendli" Date: Mon, 4 Mar 2019 10:23:56 +0100 Subject: Implement small TimeInterleaver performance improvement --- src/TimeInterleaver.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/TimeInterleaver.cpp b/src/TimeInterleaver.cpp index 7e19af8..1afdefd 100644 --- a/src/TimeInterleaver.cpp +++ b/src/TimeInterleaver.cpp @@ -64,7 +64,7 @@ int TimeInterleaver::process(Buffer* const dataIn, Buffer* dataOut) unsigned char* out = reinterpret_cast(dataOut->getData()); for (size_t i = 0; i < dataOut->getLength();) { - d_history.push_front(d_history.back()); + d_history.push_front(move(d_history.back())); d_history.pop_back(); for (uint_fast16_t j = 0; j < d_framesize;) { d_history[0][j] = in[i]; -- cgit v1.2.3 From e0992467867e4832b3333fb0cb5a638d05250bd3 Mon Sep 17 00:00:00 2001 From: "Matthias P. Braendli" Date: Tue, 12 Mar 2019 16:08:27 +0100 Subject: RC: avoid zmq RC crash on 'show' of non-existing controllable --- src/RemoteControl.cpp | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/src/RemoteControl.cpp b/src/RemoteControl.cpp index 808153a..1065456 100644 --- a/src/RemoteControl.cpp +++ b/src/RemoteControl.cpp @@ -523,16 +523,21 @@ void RemoteControllerZmq::process() } else if (msg.size() == 2 && command == "show") { std::string module((char*) msg[1].data(), msg[1].size()); - list< vector > r = rcs.get_param_list_values(module); - size_t r_size = r.size(); - for (auto ¶m_val : r) { - std::stringstream ss; - ss << param_val[0] << ": " << param_val[1] << endl; - zmq::message_t zmsg(ss.str().size()); - memcpy(zmsg.data(), ss.str().data(), ss.str().size()); - - int flag = (--r_size > 0) ? ZMQ_SNDMORE : 0; - repSocket.send(zmsg, flag); + try { + list< vector > r = rcs.get_param_list_values(module); + size_t r_size = r.size(); + for (auto ¶m_val : r) { + std::stringstream ss; + ss << param_val[0] << ": " << param_val[1] << endl; + zmq::message_t zmsg(ss.str().size()); + memcpy(zmsg.data(), ss.str().data(), ss.str().size()); + + int flag = (--r_size > 0) ? ZMQ_SNDMORE : 0; + repSocket.send(zmsg, flag); + } + } + catch (const ParameterError &err) { + send_fail_reply(repSocket, err.what()); } } else if (msg.size() == 3 && command == "get") { -- cgit v1.2.3 From 35599ee9c8a2822c0f480673b4d8d8e87049094c Mon Sep 17 00:00:00 2001 From: "Matthias P. Braendli" Date: Thu, 14 Mar 2019 08:32:26 +0100 Subject: Add analog bandwidth configuration to UHD and Soapy outputs --- doc/example.ini | 6 ++++++ src/ConfigParser.cpp | 2 ++ src/output/SDR.cpp | 8 ++++++++ src/output/SDRDevice.h | 3 +++ src/output/Soapy.cpp | 28 +++++++++++++++++++++++++--- src/output/Soapy.h | 4 +++- src/output/UHD.cpp | 23 ++++++++++++++++++++++- src/output/UHD.h | 4 +++- 8 files changed, 72 insertions(+), 6 deletions(-) diff --git a/doc/example.ini b/doc/example.ini index b94825c..7f4d3e5 100644 --- a/doc/example.ini +++ b/doc/example.ini @@ -239,6 +239,9 @@ txgain=40 ;frequency=234208000 channel=13C +; Override automatic analog frontend bandwidth calculation. Units: Hz +;bandwidth=2000000 + ; Some USRP boards/frontends support setting an LO offset that has the ; effect of shifting DC out of the signal bandwidth. This should also ; improve IQ imbalance effects, because the mirror will centered on another @@ -297,6 +300,9 @@ txgain=40 channel=13C ;lo_offset=2048000 +; Override automatic analog frontend bandwidth calculation. Units: Hz +;bandwidth=2000000 + ; You can set what TX antenna to use. This will depend on the ; SDR device you are using. ;tx_antenna= diff --git a/src/ConfigParser.cpp b/src/ConfigParser.cpp index 21f2c23..9813663 100644 --- a/src/ConfigParser.cpp +++ b/src/ConfigParser.cpp @@ -235,6 +235,7 @@ static void parse_configfile( sdr_device_config.rx_antenna = pt.Get("uhdoutput.rx_antenna", "RX2"); sdr_device_config.rxgain = pt.GetReal("uhdoutput.rxgain", 0.0); sdr_device_config.frequency = pt.GetReal("uhdoutput.frequency", 0); + sdr_device_config.bandwidth = pt.GetReal("uhdoutput.bandwidth", 0); std::string chan = pt.Get("uhdoutput.channel", ""); sdr_device_config.dabMode = mod_settings.dabMode; @@ -287,6 +288,7 @@ static void parse_configfile( outputsoapy_conf.tx_antenna = pt.Get("soapyoutput.tx_antenna", ""); outputsoapy_conf.lo_offset = pt.GetReal("soapyoutput.lo_offset", 0.0); outputsoapy_conf.frequency = pt.GetReal("soapyoutput.frequency", 0); + outputsoapy_conf.bandwidth = pt.GetReal("soapyoutput.bandwidth", 0); std::string chan = pt.Get("soapyoutput.channel", ""); outputsoapy_conf.dabMode = mod_settings.dabMode; diff --git a/src/output/SDR.cpp b/src/output/SDR.cpp index 23a947b..bacb281 100644 --- a/src/output/SDR.cpp +++ b/src/output/SDR.cpp @@ -75,6 +75,7 @@ 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(muting, "Mute the output by stopping the transmitter"); RC_ADD_PARAMETER(temp, "Temperature in degrees C of the device"); @@ -376,6 +377,10 @@ void SDR::set_parameter(const string& parameter, const string& value) ss >> m_config.rxgain; m_device->set_rxgain(m_config.rxgain); } + else if (parameter == "bandwidth") { + ss >> m_config.bandwidth; + m_device->set_bandwidth(m_config.bandwidth); + } else if (parameter == "freq") { ss >> m_config.frequency; m_device->tune(m_config.lo_offset, m_config.frequency); @@ -409,6 +414,9 @@ const string SDR::get_parameter(const string& parameter) const else if (parameter == "rxgain") { ss << m_config.rxgain; } + else if (parameter == "bandwidth") { + ss << m_config.bandwidth; + } else if (parameter == "freq") { ss << m_config.frequency; } diff --git a/src/output/SDRDevice.h b/src/output/SDRDevice.h index 90a1123..a1a488f 100644 --- a/src/output/SDRDevice.h +++ b/src/output/SDRDevice.h @@ -63,6 +63,7 @@ struct SDRDeviceConfig { double txgain = 0.0; double rxgain = 0.0; bool enableSync = false; + double bandwidth = 0.0; // When working with timestamps, mute the frames that // do not have a timestamp @@ -124,6 +125,8 @@ class SDRDevice { virtual double get_real_secs(void) const = 0; virtual void set_rxgain(double rxgain) = 0; virtual double get_rxgain(void) const = 0; + virtual void set_bandwidth(double bandwidth) = 0; + virtual double get_bandwidth(void) const = 0; virtual size_t receive_frame( complexf *buf, size_t num_samples, diff --git a/src/output/Soapy.cpp b/src/output/Soapy.cpp index 8c84b84..4846279 100644 --- a/src/output/Soapy.cpp +++ b/src/output/Soapy.cpp @@ -2,7 +2,7 @@ Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) - Copyright (C) 2018 + Copyright (C) 2019 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org @@ -90,15 +90,23 @@ Soapy::Soapy(SDRDeviceConfig& config) : std::fixed << std::setprecision(3) << m_conf.frequency / 1000.0 << " kHz."; + if (m_conf.bandwidth > 0) { + m_device->setBandwidth(SOAPY_SDR_TX, 0, m_conf.bandwidth); + m_device->setBandwidth(SOAPY_SDR_RX, 0, m_conf.bandwidth); + etiLog.level(info) << "SoapySDR:Actual TX bandwidth: " << + std::fixed << std::setprecision(2) << + m_device->getBandwidth(SOAPY_SDR_TX, 0); + } + m_device->setGain(SOAPY_SDR_TX, 0, m_conf.txgain); - etiLog.level(info) << "SoapySDR:Actual tx gain: " << + etiLog.level(info) << "SoapySDR:Actual TX gain: " << std::fixed << std::setprecision(2) << m_device->getGain(SOAPY_SDR_TX, 0); if (not m_conf.tx_antenna.empty()) { m_device->setAntenna(SOAPY_SDR_TX, 0, m_conf.tx_antenna); } - etiLog.level(info) << "SoapySDR:Actual tx antenna: " << + etiLog.level(info) << "SoapySDR:Actual TX antenna: " << m_device->getAntenna(SOAPY_SDR_TX, 0); if (m_device->hasHardwareTime()) { @@ -157,6 +165,20 @@ double Soapy::get_txgain(void) const return m_device->getGain(SOAPY_SDR_TX, 0); } +void Soapy::set_bandwidth(double bandwidth) +{ + m_conf.bandwidth = bandwidth; + if (not m_device) throw runtime_error("Soapy device not set up"); + m_device->setBandwidth(SOAPY_SDR_TX, 0, m_conf.bandwidth); + m_device->setBandwidth(SOAPY_SDR_RX, 0, m_conf.bandwidth); +} + +double Soapy::get_bandwidth(void) const +{ + if (not m_device) throw runtime_error("Soapy device not set up"); + return m_device->getBandwidth(SOAPY_SDR_TX, 0); +} + SDRDevice::RunStatistics Soapy::get_run_statistics(void) const { RunStatistics rs; diff --git a/src/output/Soapy.h b/src/output/Soapy.h index 9feb0b8..4ee53ca 100644 --- a/src/output/Soapy.h +++ b/src/output/Soapy.h @@ -2,7 +2,7 @@ Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) - Copyright (C) 2018 + Copyright (C) 2019 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org @@ -63,6 +63,8 @@ class Soapy : public Output::SDRDevice virtual double get_tx_freq(void) const override; virtual void set_txgain(double txgain) override; virtual double get_txgain(void) const override; + virtual void set_bandwidth(double bandwidth) override; + virtual double get_bandwidth(void) const override; virtual void transmit_frame(const struct FrameData& frame) override; virtual RunStatistics get_run_statistics(void) const override; virtual double get_real_secs(void) const override; diff --git a/src/output/UHD.cpp b/src/output/UHD.cpp index c6c500b..e85e66f 100644 --- a/src/output/UHD.cpp +++ b/src/output/UHD.cpp @@ -2,7 +2,7 @@ Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) - Copyright (C) 2018 + Copyright (C) 2019 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org @@ -193,6 +193,15 @@ UHD::UHD(SDRDeviceConfig& config) : throw std::runtime_error("Cannot set USRP sample rate. Aborted."); } + if (m_conf.bandwidth > 0) { + m_usrp->set_tx_bandwidth(m_conf.bandwidth); + m_usrp->set_rx_bandwidth(m_conf.bandwidth); + + etiLog.level(info) << "OutputUHD:Actual TX bandwidth: " << + std::fixed << std::setprecision(2) << + m_usrp->get_tx_bandwidth(); + } + tune(m_conf.lo_offset, m_conf.frequency); m_conf.frequency = m_usrp->get_tx_freq(); @@ -294,6 +303,18 @@ double UHD::get_txgain(void) const return m_usrp->get_tx_gain(); } +void UHD::set_bandwidth(double bandwidth) +{ + m_usrp->set_tx_bandwidth(bandwidth); + m_usrp->set_rx_bandwidth(bandwidth); + m_conf.bandwidth = m_usrp->get_tx_bandwidth(); +} + +double UHD::get_bandwidth(void) const +{ + return m_usrp->get_tx_bandwidth(); +} + void UHD::transmit_frame(const struct FrameData& frame) { const double tx_timeout = 20.0; diff --git a/src/output/UHD.h b/src/output/UHD.h index f42b6e8..29867fb 100644 --- a/src/output/UHD.h +++ b/src/output/UHD.h @@ -2,7 +2,7 @@ Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) - Copyright (C) 2018 + Copyright (C) 2019 Matthias P. Braendli, matthias.braendli@mpb.li http://opendigitalradio.org @@ -77,6 +77,8 @@ class UHD : public Output::SDRDevice virtual double get_tx_freq(void) const override; virtual void set_txgain(double txgain) override; virtual double get_txgain(void) const override; + virtual void set_bandwidth(double bandwidth) override; + virtual double get_bandwidth(void) const override; virtual void transmit_frame(const struct FrameData& frame) override; virtual RunStatistics get_run_statistics(void) const override; virtual double get_real_secs(void) const override; -- cgit v1.2.3 From 643206925cb2bbc4e30c98dc4a0b29326a11846e Mon Sep 17 00:00:00 2001 From: "Matthias P. Braendli" Date: Wed, 10 Apr 2019 10:59:34 +0200 Subject: Suppress ASIO warnings, remove -Wshadow, add -Wno-pragmas --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 0e7d946..a4c539b 100644 --- a/configure.ac +++ b/configure.ac @@ -69,7 +69,7 @@ AX_CHECK_COMPILE_FLAG([-Wduplicated-cond], [CXXFLAGS="$CXXFLAGS -Wduplicated-con AX_CHECK_COMPILE_FLAG([-Wduplicated-branches], [CXXFLAGS="$CXXFLAGS -Wduplicated-branches"], [], ["-Werror"]) AX_CHECK_COMPILE_FLAG([-Wlogical-op], [CXXFLAGS="$CXXFLAGS -Wlogical-op"], [], ["-Werror"]) AX_CHECK_COMPILE_FLAG([-Wrestrict], [CXXFLAGS="$CXXFLAGS -Wrestrict"], [], ["-Werror"]) -AX_CHECK_COMPILE_FLAG([-Wshadow], [CXXFLAGS="$CXXFLAGS -Wshadow"], [], ["-Werror"]) +AX_CHECK_COMPILE_FLAG([-Wno-pragmas], [CXXFLAGS="$CXXFLAGS -Wno-pragmas"], [], ["-Werror"]) AX_CHECK_COMPILE_FLAG([-Wdouble-promotion], [CXXFLAGS="$CXXFLAGS -Wdouble-promotion"], [], ["-Werror"]) AX_CHECK_COMPILE_FLAG(["-Wformat=2"], [CXXFLAGS="$CXXFLAGS -Wformat=2"], [], ["-Werror"]) AC_LANG_POP([C++]) -- cgit v1.2.3 From 5b765b7320c42130c09eab53043da7c0ea9d43ab Mon Sep 17 00:00:00 2001 From: "Matthias P. Braendli" Date: Tue, 16 Apr 2019 11:43:11 +0200 Subject: Avoid ugly 'context terminated' error on zmq restart --- src/InputZeroMQReader.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/InputZeroMQReader.cpp b/src/InputZeroMQReader.cpp index 3661748..1ebc1ca 100644 --- a/src/InputZeroMQReader.cpp +++ b/src/InputZeroMQReader.cpp @@ -42,6 +42,8 @@ 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. @@ -76,6 +78,9 @@ InputZeroMQReader::InputZeroMQReader() : 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(); @@ -190,8 +195,7 @@ void InputZeroMQReader::RecvProcess() zmq::pollitem_t items[1]; items[0].socket = subscriber; items[0].events = ZMQ_POLLIN; - const int zmq_timeout_ms = 100; - const int num_events = zmq::poll(items, 1, zmq_timeout_ms); + const int num_events = zmq::poll(items, 1, ZMQ_TIMEOUT_MS); if (num_events == 0) { message_t msg; msg.timeout = true; -- cgit v1.2.3 From 01cb197301ec5e701a79de29aea7fab4f32fe793 Mon Sep 17 00:00:00 2001 From: "Matthias P. Braendli" Date: Tue, 16 Apr 2019 11:45:10 +0200 Subject: Reset ZMQ input after 10s of absent input data --- src/DabMod.cpp | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/src/DabMod.cpp b/src/DabMod.cpp index ad8101c..7ebde12 100644 --- a/src/DabMod.cpp +++ b/src/DabMod.cpp @@ -492,12 +492,21 @@ 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(modulator_data& m) { auto ret = run_modulator_state_t::failure; try { bool first_frame = true; int last_eti_fct = -1; + auto last_frame_received = chrono::steady_clock::now(); while (running) { int framesize; @@ -510,6 +519,8 @@ static run_modulator_state_t run_modulator(modulator_data& m) break; } + last_frame_received = chrono::steady_clock::now(); + m.framecount++; PDEBUG("*****************************************\n"); @@ -561,7 +572,28 @@ static run_modulator_state_t run_modulator(modulator_data& m) 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)) { @@ -578,6 +610,11 @@ static run_modulator_state_t run_modulator(modulator_data& 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(); -- cgit v1.2.3 From 4556bf9eeb5083935ead5df3a436d18a3016cfcd Mon Sep 17 00:00:00 2001 From: "Matthias P. Braendli" Date: Tue, 30 Apr 2019 16:23:15 +0200 Subject: Do not use py3.6 f-strings for compatibility with py3.5 --- INSTALL | 1 + python/dpd/Adapt.py | 6 +++--- python/dpdce.py | 32 ++++++++++++++++---------------- 3 files changed, 20 insertions(+), 19 deletions(-) diff --git a/INSTALL b/INSTALL index 8a0d80a..0132f1c 100644 --- a/INSTALL +++ b/INSTALL @@ -2,6 +2,7 @@ Required dependencies: ====================== * A C++11 capable compiler + * pkg-config * FFTW 3.x * Optional UHD for USRP * Optional SoapySDR (see below) diff --git a/python/dpd/Adapt.py b/python/dpd/Adapt.py index a30f0c8..745a507 100644 --- a/python/dpd/Adapt.py +++ b/python/dpd/Adapt.py @@ -76,7 +76,7 @@ class Adapt: try: return float(self._mod_rc.get_param_value("sdr", "txgain")) except ValueError as e: - logging.warning(f"Adapt: get_txgain error: {e}") + logging.warning("Adapt: get_txgain error: {}".format(e)) return -1.0 def set_rxgain(self, gain: float) -> None: @@ -90,7 +90,7 @@ class Adapt: try: return float(self._mod_rc.get_param_value("sdr", "rxgain")) except ValueError as e: - logging.warning(f"Adapt: get_rxgain error: {e}") + logging.warning("Adapt: get_rxgain error: {}".format(e)) return -1.0 def set_digital_gain(self, gain: float) -> None: @@ -102,7 +102,7 @@ class Adapt: try: return float(self._mod_rc.get_param_value("gain", "digital")) except ValueError as e: - logging.warning(f"Adapt: get_digital_gain error: {e}") + logging.warning("Adapt: get_digital_gain error: {}".format(e)) return -1.0 def get_predistorter(self): diff --git a/python/dpdce.py b/python/dpdce.py index cf98aa0..94cc7c8 100755 --- a/python/dpdce.py +++ b/python/dpdce.py @@ -335,13 +335,13 @@ def engine_worker(): lr = Heuristics.get_learning_rate(iteration) - summary = [f"Set predistorter:", - f"Signal measurements after iteration {iteration} with learning rate {lr}", - f"TX MER {tx_mer:.2}, RX MER {rx_mer:.2}", - f"Mean-square error: {mse:.3}"] + summary = ["Set predistorter:", + "Signal measurements after iteration {} with learning rate {}".format(iteration, lr), + "TX MER {:.2}, RX MER {:.2}".format(tx_mer, rx_mer), + "Mean-square error: {:.3}".format(mse)] if tx_shoulder_tuple is not None: summary.append("Shoulders: TX {!r}, RX {!r}".format(tx_shoulder_tuple, rx_shoulder_tuple)) - summary.append(f"Running with digital gain {digital_gain}, TX gain {tx_gain} and RX gain {rx_gain}") + summary.append("Running with digital gain {}, TX gain {} and RX gain {}".format(digital_gain, tx_gain, rx_gain)) with lock: results['state'] = 'Update Predistorter' @@ -360,33 +360,33 @@ def engine_worker(): with lock: results['state'] = 'Idle' results['stateprogress'] = 100 - results['summary'] = [f"Restored DPD defaults", - f"Running with digital gain {digital_gain}, TX gain {tx_gain} and RX gain {rx_gain}"] + results['summary'] = ["Restored DPD defaults", + "Running with digital gain {}, TX gain {} and RX gain {}".format(digital_gain, tx_gain, rx_gain)] results['modeldata'] = dpddata_to_str(dpddata) else: - dump_file = os.path.join(plot_path, f"adapt_{dump_id}.pkl") + dump_file = os.path.join(plot_path, "adapt_{}.pkl".format(dump_id)) try: d = adapt.restore(dump_file) - logging.info(f"Restore: {d}") + logging.info("Restore: {}".format(d)) model.set_dpd_data(d['dpddata']) with lock: results['state'] = 'Idle' results['stateprogress'] = 100 - results['summary'] = [f"Restored DPD settings from dumpfile {dump_id}", - f"Running with digital gain {d['digital_gain']}, TX gain {d['txgain']} and RX gain {d['rxgain']}"] + results['summary'] = ["Restored DPD settings from dumpfile {}".format(dump_id), + "Running with digital gain {}, TX gain {} and RX gain {}".format(d['digital_gain'], d['tx_gain'], d['rx_gain'])] results['modeldata'] = dpddata_to_str(d["dpddata"]) except: e = traceback.format_exc() with lock: results['state'] = 'Idle' results['stateprogress'] = 100 - results['summary'] = [f"Failed to restore DPD settings from dumpfile {dump_id}", - f"Error: {e}"] + results['summary'] = ["Failed to restore DPD settings from dumpfile {}".format(dump_id), + "Error: {}".format(e)] except: e = traceback.format_exc() logging.error(e) with lock: - results['summary'] = [f"Exception:"] + e.split("\n") + results['summary'] = ["Exception:"] + e.split("\n") results['state'] = 'Autorestart pending' results['stateprogress'] = 0 @@ -397,7 +397,7 @@ def engine_worker(): time.sleep(2) with lock: dt = datetime.datetime.utcnow().isoformat() - results['summary'] = [f"DPD engine auto-restarted at {dt} UTC", f"After exception {e}"] + results['summary'] = ["DPD engine auto-restarted at {} UTC".format(dt), "After exception {}".format(e)] results['state'] = 'Idle' results['stateprogress'] = 0 @@ -427,7 +427,7 @@ try: cmd_socket.send_success_response(addr, msg_id, None) elif method == 'restore_dump': logging.info('Received RPC request : restore_dump({})'.format(params['dump_id'])) - command_queue.put(f"restore_dump-{params['dump_id']}") + command_queue.put("restore_dump-{}".format(params['dump_id'])) cmd_socket.send_success_response(addr, msg_id, None) elif method == 'get_results': with lock: -- cgit v1.2.3 From a6011d83e6140b8e12e7701f1e479c2fd65ce211 Mon Sep 17 00:00:00 2001 From: "Matthias P. Braendli" Date: Tue, 30 Apr 2019 16:41:38 +0200 Subject: gui: make template folder configurable --- python/gui-dpdce.ini | 1 + python/gui.py | 19 ++++++++++--------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/python/gui-dpdce.ini b/python/gui-dpdce.ini index 4385c80..496adf5 100644 --- a/python/gui-dpdce.ini +++ b/python/gui-dpdce.ini @@ -31,3 +31,4 @@ port=8099 logs_directory=gui/logs static_directory=gui/static +templates_directory=gui/templates diff --git a/python/gui.py b/python/gui.py index a9328ee..7823175 100755 --- a/python/gui.py +++ b/python/gui.py @@ -30,15 +30,14 @@ from jinja2 import Environment, FileSystemLoader from gui.api import API from lib import zmqrc -env = Environment(loader=FileSystemLoader('gui/templates')) - base_js = ["js/odr.js"] base_css = ["css/odr.css"] class Root: - def __init__(self, dpd_port): + def __init__(self, dpd_port, end): self.mod_rc = zmqrc.ModRemoteControl("localhost") self.api = API(self.mod_rc, dpd_port) + self.env = env @cherrypy.expose def index(self): @@ -46,30 +45,30 @@ class Root: @cherrypy.expose def about(self): - tmpl = env.get_template("about.html") + tmpl = self.env.get_template("about.html") return tmpl.render(tab='about', js=base_js, is_login=False) @cherrypy.expose def home(self): - tmpl = env.get_template("home.html") + tmpl = self.env.get_template("home.html") js = base_js + ["js/odr-home.js"] return tmpl.render(tab='home', js=js, css=base_css, is_login=False) @cherrypy.expose def rcvalues(self): - tmpl = env.get_template("rcvalues.html") + tmpl = self.env.get_template("rcvalues.html") js = base_js + ["js/odr-rcvalues.js"] return tmpl.render(tab='rcvalues', js=js, is_login=False) @cherrypy.expose def modulator(self): - tmpl = env.get_template("modulator.html") + tmpl = self.env.get_template("modulator.html") js = base_js + ["js/odr-modulator.js"] return tmpl.render(tab='modulator', js=js, is_login=False) @cherrypy.expose def predistortion(self): - tmpl = env.get_template("predistortion.html") + tmpl = self.env.get_template("predistortion.html") js = base_js + ["js/odr-predistortion.js"] return tmpl.render(tab='predistortion', js=js, is_login=False) @@ -106,9 +105,11 @@ if __name__ == '__main__': }) staticdir = os.path.realpath(config['static_directory']) + templatedir = os.path.realpath(config['templates_directory']) + env = Environment(loader=FileSystemLoader(templatedir)) cherrypy.tree.mount( - Root(dpd_port), config={ + Root(dpd_port, env), config={ '/': { }, '/dpd': { 'tools.staticdir.on': True, -- cgit v1.2.3 From d68dc3858d0c640d1cddd86b81435a513a1073aa Mon Sep 17 00:00:00 2001 From: "Matthias P. Braendli" Date: Tue, 30 Apr 2019 17:23:57 +0200 Subject: GUI: use correct plot directory --- python/gui.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/python/gui.py b/python/gui.py index 7823175..810ff40 100755 --- a/python/gui.py +++ b/python/gui.py @@ -83,6 +83,7 @@ if __name__ == '__main__': allconfig.read(cli_args.config) config = allconfig['gui'] dpd_port = allconfig['dpdce'].getint('control_port') + plot_relative_dir = allconfig['dpdce']['plot_directory'] daemon = False if daemon: @@ -113,7 +114,7 @@ if __name__ == '__main__': '/': { }, '/dpd': { 'tools.staticdir.on': True, - 'tools.staticdir.dir': os.path.join(staticdir, u"dpd/") + 'tools.staticdir.dir': os.path.realpath(plot_relative_dir) }, '/css': { 'tools.staticdir.on': True, -- cgit v1.2.3 From bef9d81a827965cf688bfabba110903d5e306b4f Mon Sep 17 00:00:00 2001 From: "Matthias P. Braendli" Date: Tue, 30 Apr 2019 17:24:10 +0200 Subject: Rename CFR in GUI modulator panel --- python/gui/templates/modulator.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/gui/templates/modulator.html b/python/gui/templates/modulator.html index 016344a..c2b4a24 100644 --- a/python/gui/templates/modulator.html +++ b/python/gui/templates/modulator.html @@ -26,7 +26,7 @@
-

CFR

+

Crest Factor Reduction

-- cgit v1.2.3 From 473232ab177a4811115ff5713bb25893448dafd7 Mon Sep 17 00:00:00 2001 From: "Matthias P. Braendli" Date: Mon, 6 May 2019 16:40:38 +0200 Subject: Fix compilation with UHD 3.14 --- src/output/UHD.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/output/UHD.cpp b/src/output/UHD.cpp index e85e66f..3cf5aef 100644 --- a/src/output/UHD.cpp +++ b/src/output/UHD.cpp @@ -359,8 +359,7 @@ void UHD::transmit_frame(const struct FrameData& frame) num_acc_samps += num_tx_samps; - md_tx.time_spec = md_tx.time_spec + - uhd::time_spec_t(0, num_tx_samps/m_conf.sampleRate); + md_tx.time_spec += uhd::time_spec_t(0, num_tx_samps/m_conf.sampleRate); if (num_tx_samps == 0) { etiLog.log(warn, -- cgit v1.2.3 From 107dca66a069c5e317d040360b8aafd62c8282db Mon Sep 17 00:00:00 2001 From: "Matthias P. Braendli" Date: Mon, 6 May 2019 17:18:25 +0200 Subject: Implement EDI over TCP --- doc/example.ini | 7 ++++ src/DabMod.cpp | 21 +++++++--- src/EtiReader.cpp | 115 +++++++++++++++++++++++++++++++++++++++--------------- src/EtiReader.h | 12 ++++-- 4 files changed, 114 insertions(+), 41 deletions(-) diff --git a/doc/example.ini b/doc/example.ini index 7f4d3e5..b3e2eb3 100644 --- a/doc/example.ini +++ b/doc/example.ini @@ -45,6 +45,13 @@ loop=0 ; Listen for EDI data on a given UDP port, unicast or multicast. ;transport=edi ; +; EDI over TCP: +; +; Connect to TCP server on a given host +;source=tcp://localhost:9201 +; +; EDI over UDP: +; ; Supported syntax for the source setting: ; Bind to default interface and receive data from port 12000 ;source=udp://:12000 diff --git a/src/DabMod.cpp b/src/DabMod.cpp index 7ebde12..1f435bf 100644 --- a/src/DabMod.cpp +++ b/src/DabMod.cpp @@ -306,11 +306,11 @@ int launch_modulator(int argc, char* argv[]) // setMaxDelay wants number of AF packets, which correspond to 24ms ETI frames ediInput.setMaxDelay(lroundf(mod_settings.edi_max_delay_ms / 24.0f)); } - EdiUdpInput ediUdpInput(ediInput); + EdiTransport ediTransport(ediInput); - ediUdpInput.Open(mod_settings.inputName); - if (not ediUdpInput.isEnabled()) { - throw runtime_error("inputTransport is edi, but ediUdpInput is not enabled"); + ediTransport.Open(mod_settings.inputName); + if (not ediTransport.isEnabled()) { + throw runtime_error("inputTransport is edi, but ediTransport is not enabled"); } Flowgraph flowgraph; @@ -329,16 +329,27 @@ int launch_modulator(int argc, char* argv[]) bool first_frame = true; + auto frame_received_tp = chrono::steady_clock::now(); + while (running) { while (running and not ediReader.isFrameReady()) { try { - ediUdpInput.rxPacket(); + bool packet_received = ediTransport.rxPacket(); + if (packet_received) { + frame_received_tp = chrono::steady_clock::now(); + } } catch (const std::runtime_error& e) { etiLog.level(warn) << "EDI input: " << e.what(); running = 0; break; } + + if (frame_received_tp + chrono::seconds(10) < chrono::steady_clock::now()) { + etiLog.level(error) << "No EDI data received in 10 seconds."; + running = 0; + break; + } } if (not running) { diff --git a/src/EtiReader.cpp b/src/EtiReader.cpp index 4c5ad79..94c362a 100644 --- a/src/EtiReader.cpp +++ b/src/EtiReader.cpp @@ -547,7 +547,7 @@ void EdiReader::assemble() m_frameReady = true; } -EdiUdpInput::EdiUdpInput(EdiDecoder::ETIDecoder& decoder) : +EdiTransport::EdiTransport(EdiDecoder::ETIDecoder& decoder) : m_enabled(false), m_port(0), m_bindto("0.0.0.0"), @@ -555,49 +555,100 @@ EdiUdpInput::EdiUdpInput(EdiDecoder::ETIDecoder& decoder) : m_decoder(decoder) { } -void EdiUdpInput::Open(const std::string& uri) +void EdiTransport::Open(const std::string& uri) { etiLog.level(info) << "Opening EDI :" << uri; - size_t found_port = uri.find_first_of(":", 6); - if (found_port == string::npos) { - throw std::invalid_argument("EDI input port must be provided"); - } - m_port = std::stoi(uri.substr(found_port+1)); - std::string host_full = uri.substr(6, found_port-6);// ignore udp:// - size_t found_mcast = host_full.find_first_of("@"); //have multicast address: - if (found_mcast != string::npos) { - if (found_mcast > 0) { - m_bindto = host_full.substr(0, found_mcast); + const string proto = uri.substr(0, 3); + if (proto == "udp") { + size_t found_port = uri.find_first_of(":", 6); + if (found_port == string::npos) { + throw std::invalid_argument("EDI UDP input port must be provided"); } - m_mcastaddr = host_full.substr(found_mcast+1); - } - else if (found_port != 6) { - m_bindto=host_full; + + m_port = std::stoi(uri.substr(found_port+1)); + std::string host_full = uri.substr(6, found_port-6);// skip udp:// + size_t found_mcast = host_full.find_first_of("@"); //have multicast address: + if (found_mcast != string::npos) { + if (found_mcast > 0) { + m_bindto = host_full.substr(0, found_mcast); + } + m_mcastaddr = host_full.substr(found_mcast+1); + } + else if (found_port != 6) { + m_bindto=host_full; + } + + etiLog.level(info) << "EDI UDP input: host:" << m_bindto << + ", source:" << m_mcastaddr << ", port:" << m_port; + + // The max_fragments_queued is only a protection against a runaway + // memory usage. + // Rough calculation: + // 300 seconds, 24ms per frame, up to 20 fragments per frame + const size_t max_fragments_queued = 20 * 300 * 1000 / 24; + + m_udp_rx.start(m_port, m_bindto, m_mcastaddr, max_fragments_queued); + m_proto = Proto::UDP; + m_enabled = true; } + else if (proto == "tcp") { + size_t found_port = uri.find_first_of(":", 6); + if (found_port == string::npos) { + throw std::invalid_argument("EDI TCP input port must be provided"); + } - etiLog.level(info) << "EDI input: host:" << m_bindto << - ", source:" << m_mcastaddr << ", port:" << m_port; + m_port = std::stoi(uri.substr(found_port+1)); + const std::string hostname = uri.substr(6, found_port-6);// skip tcp:// - // The max_fragments_queued is only a protection against a runaway - // memory usage. - // Rough calculation: - // 300 seconds, 24ms per frame, up to 20 fragments per frame - const size_t max_fragments_queued = 20 * 300 * 1000 / 24; + etiLog.level(info) << "EDI TCP connect to " << hostname << ":" << m_port; - m_udp_rx.start(m_port, m_bindto, m_mcastaddr, max_fragments_queued); - m_enabled = true; + m_tcpclient.connect(hostname, m_port); + m_proto = Proto::TCP; + m_enabled = true; + } + else { + throw std::invalid_argument("ETI protocol '" + proto + "' unknown"); + } } -bool EdiUdpInput::rxPacket() +bool EdiTransport::rxPacket() { - auto udp_data = m_udp_rx.get_packet_buffer(); + switch (m_proto) { + case Proto::UDP: + { + auto udp_data = m_udp_rx.get_packet_buffer(); - if (udp_data.empty()) { - return false; - } + if (udp_data.empty()) { + return false; + } - m_decoder.push_packet(udp_data); - return true; + m_decoder.push_packet(udp_data); + return true; + } + case Proto::TCP: + { + m_tcpbuffer.resize(4096); + 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) { + return false; + } + else if (ret > (ssize_t)m_tcpbuffer.size()) { + throw logic_error("EDI TCP: invalid recv() return value"); + } + else { + m_tcpbuffer.resize(ret); + m_decoder.push_bytes(m_tcpbuffer); + return true; + } + } + catch (const TCPSocket::Timeout&) { + return false; + } + } + } + throw logic_error("Incomplete rxPacket implementation!"); } #endif // HAVE_EDI diff --git a/src/EtiReader.h b/src/EtiReader.h index 554231e..38f7903 100644 --- a/src/EtiReader.h +++ b/src/EtiReader.h @@ -34,6 +34,7 @@ #include "Eti.h" #include "Log.h" #include "FicSource.h" +#include "Socket.h" #include "SubchannelSource.h" #include "TimestampDecoder.h" #include "lib/edi/ETIDecoder.hpp" @@ -185,13 +186,12 @@ private: }; /* The EDI input does not use the inputs defined in InputReader.h, as they were - * designed for ETI. It uses the EdiUdpInput which in turn uses a threaded + * designed for ETI. It uses the EdiTransport which in turn uses a threaded * receiver. */ - -class EdiUdpInput { +class EdiTransport { public: - EdiUdpInput(EdiDecoder::ETIDecoder& decoder); + EdiTransport(EdiDecoder::ETIDecoder& decoder); void Open(const std::string& uri); @@ -209,7 +209,11 @@ class EdiUdpInput { std::string m_bindto; std::string m_mcastaddr; + enum class Proto { UDP, TCP }; + Proto m_proto; UdpReceiver m_udp_rx; + std::vector m_tcpbuffer; + TCPClient m_tcpclient; EdiDecoder::ETIDecoder& m_decoder; }; #endif -- cgit v1.2.3 From 3a7202306c6aca5be2dad604f62063d605fd0982 Mon Sep 17 00:00:00 2001 From: "Matthias P. Braendli" Date: Mon, 6 May 2019 17:55:36 +0200 Subject: EDI: Handle sequence counter check for incomplete AF packets --- lib/edi/ETIDecoder.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/lib/edi/ETIDecoder.cpp b/lib/edi/ETIDecoder.cpp index ffc17a4..a5d817e 100644 --- a/lib/edi/ETIDecoder.cpp +++ b/lib/edi/ETIDecoder.cpp @@ -156,8 +156,14 @@ ETIDecoder::decode_state_t ETIDecoder::decode_afpacket( // read length from packet uint32_t taglength = read_32b(input_data.begin() + 2); uint16_t seq = read_16b(input_data.begin() + 6); + + const size_t crclength = 2; + if (input_data.size() < AFPACKET_HEADER_LEN + taglength + crclength) { + return {false, 0}; + } + if (m_last_seq + 1 != seq) { - etiLog.level(warn) << "EDI AF Packet sequence error"; + etiLog.level(warn) << "EDI AF Packet sequence error, " << seq; } m_last_seq = seq; @@ -174,10 +180,6 @@ ETIDecoder::decode_state_t ETIDecoder::decode_afpacket( return {false, 0}; } - const size_t crclength = 2; - if (input_data.size() < AFPACKET_HEADER_LEN + taglength + crclength) { - return {false, 0}; - } if (not has_crc) { throw invalid_argument("AF packet not supported, has no CRC"); -- cgit v1.2.3