aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--INSTALL1
-rw-r--r--configure.ac2
-rw-r--r--doc/example.ini13
-rw-r--r--lib/edi/ETIDecoder.cpp12
-rw-r--r--python/dpd/Adapt.py6
-rwxr-xr-xpython/dpdce.py32
-rw-r--r--python/gui-dpdce.ini1
-rwxr-xr-xpython/gui.py22
-rw-r--r--python/gui/templates/modulator.html2
-rw-r--r--src/ConfigParser.cpp2
-rw-r--r--src/DabMod.cpp58
-rw-r--r--src/EtiReader.cpp115
-rw-r--r--src/EtiReader.h12
-rw-r--r--src/InputZeroMQReader.cpp8
-rw-r--r--src/RemoteControl.cpp25
-rw-r--r--src/TimeInterleaver.cpp2
-rw-r--r--src/output/SDR.cpp8
-rw-r--r--src/output/SDRDevice.h4
-rw-r--r--src/output/Soapy.cpp28
-rw-r--r--src/output/Soapy.h4
-rw-r--r--src/output/UHD.cpp26
-rw-r--r--src/output/UHD.h4
22 files changed, 289 insertions, 98 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/configure.ac b/configure.ac
index e7fc50a..c85da3e 100644
--- a/configure.ac
+++ b/configure.ac
@@ -72,7 +72,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++])
diff --git a/doc/example.ini b/doc/example.ini
index 5dd2846..1e802f4 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
@@ -239,6 +246,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 +307,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/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");
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:
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..810ff40 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)
@@ -84,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:
@@ -106,13 +106,15 @@ 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,
- 'tools.staticdir.dir': os.path.join(staticdir, u"dpd/")
+ 'tools.staticdir.dir': os.path.realpath(plot_relative_dir)
},
'/css': {
'tools.staticdir.on': True,
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 @@
</div>
</div>
<div class="panel-body">
- <h3>CFR</h3>
+ <h3>Crest Factor Reduction</h3>
<div class="form-group">
<div class="checkbox">
<label><input type="checkbox" value="1" id="cfrenable">Enable</label>
diff --git a/src/ConfigParser.cpp b/src/ConfigParser.cpp
index 296ecdb..dd8a150 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/DabMod.cpp b/src/DabMod.cpp
index 8267060..d340b30 100644
--- a/src/DabMod.cpp
+++ b/src/DabMod.cpp
@@ -326,11 +326,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;
@@ -349,16 +349,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) {
@@ -512,12 +523,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;
@@ -530,6 +550,8 @@ static run_modulator_state_t run_modulator(modulator_data& m)
break;
}
+ last_frame_received = chrono::steady_clock::now();
+
m.framecount++;
PDEBUG("*****************************************\n");
@@ -581,7 +603,28 @@ static run_modulator_state_t run_modulator(modulator_data& m)
else if (dynamic_pointer_cast<InputZeroMQReader>(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<InputTcpReader>(m.inputReader)) {
@@ -598,6 +641,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();
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<uint8_t> m_tcpbuffer;
+ TCPClient m_tcpclient;
EdiDecoder::ETIDecoder& m_decoder;
};
#endif
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;
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<string> > r = rcs.get_param_list_values(module);
- size_t r_size = r.size();
- for (auto &param_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<string> > r = rcs.get_param_list_values(module);
+ size_t r_size = r.size();
+ for (auto &param_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") {
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<unsigned char*>(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];
diff --git a/src/output/SDR.cpp b/src/output/SDR.cpp
index 95b54f9..01312ff 100644
--- a/src/output/SDR.cpp
+++ b/src/output/SDR.cpp
@@ -76,6 +76,7 @@ SDR::SDR(SDRDeviceConfig& config, std::shared_ptr<SDRDevice> 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");
@@ -381,6 +382,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);
@@ -415,6 +420,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 7ab4bc8..d84ebf9 100644
--- a/src/output/SDRDevice.h
+++ b/src/output/SDRDevice.h
@@ -63,7 +63,9 @@ struct SDRDeviceConfig {
double txgain = 0.0;
double rxgain = 0.0;
bool enableSync = false;
+ double bandwidth = 0.0;
unsigned upsample = 1;
+
// When working with timestamps, mute the frames that
// do not have a timestamp
bool muteNoTimestamps = false;
@@ -124,6 +126,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..3cf5aef 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;
@@ -338,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,
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;