/*
Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Her Majesty the
Queen in Right of Canada (Communications Research Center Canada)
Copyright (C) 2017
Matthias P. Braendli, matthias.braendli@mpb.li
http://opendigitalradio.org
*/
/*
This file is part of ODR-DabMod.
ODR-DabMod is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
ODR-DabMod is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with ODR-DabMod. If not, see .
*/
#include "output/UHD.h"
#ifdef HAVE_OUTPUT_UHD
#include "PcDebug.h"
#include "Log.h"
#include "RemoteControl.h"
#include "Utils.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
namespace Output {
// Maximum number of frames that can wait in frames
static const size_t FRAMES_MAX_SIZE = 8;
static std::string stringtrim(const std::string &s)
{
auto wsfront = std::find_if_not(s.begin(), s.end(),
[](int c){ return std::isspace(c);} );
return std::string(wsfront,
std::find_if_not(s.rbegin(),
std::string::const_reverse_iterator(wsfront),
[](int c){ return std::isspace(c);} ).base());
}
static void uhd_msg_handler(uhd::msg::type_t type, const std::string &msg)
{
if (type == uhd::msg::warning) {
etiLog.level(warn) << "UHD Warning: " << msg;
}
else if (type == uhd::msg::error) {
etiLog.level(error) << "UHD Error: " << msg;
}
else {
// do not print very short U messages and such
if (stringtrim(msg).size() != 1) {
etiLog.level(debug) << "UHD Message: " << msg;
}
}
}
// Check function for GPS TIMELOCK sensor from the ODR LEA-M8F board GPSDO
static bool check_gps_timelock(uhd::usrp::multi_usrp::sptr usrp)
{
try {
std::string sensor_value(
usrp->get_mboard_sensor("gps_timelock", 0).to_pp_string());
if (sensor_value.find("TIME LOCKED") == std::string::npos) {
etiLog.level(warn) << "OutputUHD: gps_timelock " << sensor_value;
return false;
}
return true;
}
catch (uhd::lookup_error &e) {
etiLog.level(warn) << "OutputUHD: no gps_timelock sensor";
return false;
}
}
// Check function for GPS LOCKED sensor from the Ettus GPSDO
static bool check_gps_locked(uhd::usrp::multi_usrp::sptr usrp)
{
try {
uhd::sensor_value_t sensor_value(
usrp->get_mboard_sensor("gps_locked", 0));
if (not sensor_value.to_bool()) {
etiLog.level(warn) << "OutputUHD: gps_locked " <<
sensor_value.to_pp_string();
return false;
}
return true;
}
catch (uhd::lookup_error &e) {
etiLog.level(warn) << "OutputUHD: no gps_locked sensor";
return false;
}
}
UHD::UHD(
SDRDeviceConfig& config) :
SDRDevice(),
m_conf(config),
m_running(false)
{
// Variables needed for GPS fix check
first_gps_fix_check.tv_sec = 0;
last_gps_fix_check.tv_sec = 0;
time_last_frame.tv_sec = 0;
std::stringstream device;
device << m_conf.device;
if (m_conf.masterClockRate != 0) {
if (device.str() != "") {
device << ",";
}
device << "master_clock_rate=" << m_conf.masterClockRate;
}
MDEBUG("OutputUHD::OutputUHD(device: %s) @ %p\n",
device.str().c_str(), this);
/* TODO
RC_ADD_PARAMETER(rxgain, "UHD analog daughterboard RX gain for DPD feedback");
*/
uhd::msg::register_handler(uhd_msg_handler);
uhd::set_thread_priority_safe();
etiLog.log(info, "OutputUHD:Creating the usrp device with: %s...",
device.str().c_str());
m_usrp = uhd::usrp::multi_usrp::make(device.str());
etiLog.log(info, "OutputUHD:Using device: %s...",
m_usrp->get_pp_string().c_str());
if (m_conf.masterClockRate != 0.0) {
double master_clk_rate = m_usrp->get_master_clock_rate();
etiLog.log(debug, "OutputUHD:Checking master clock rate: %f...",
master_clk_rate);
if (fabs(master_clk_rate - m_conf.masterClockRate) >
(m_conf.masterClockRate * 1e-6)) {
throw std::runtime_error("Cannot set USRP master_clock_rate. Aborted.");
}
}
MDEBUG("OutputUHD:Setting REFCLK and PPS input...\n");
if (m_conf.refclk_src == "gpsdo-ettus") {
m_usrp->set_clock_source("gpsdo");
}
else {
m_usrp->set_clock_source(m_conf.refclk_src);
}
m_usrp->set_time_source(m_conf.pps_src);
if (m_conf.subDevice != "") {
m_usrp->set_tx_subdev_spec(uhd::usrp::subdev_spec_t(m_conf.subDevice),
uhd::usrp::multi_usrp::ALL_MBOARDS);
}
etiLog.level(debug) << "UHD clock source is " << m_usrp->get_clock_source(0);
etiLog.level(debug) << "UHD time source is " << m_usrp->get_time_source(0);
m_usrp->set_tx_rate(m_conf.sampleRate);
etiLog.log(debug, "OutputUHD:Set rate to %d. Actual TX Rate: %f sps...",
m_conf.sampleRate, m_usrp->get_tx_rate());
if (fabs(m_usrp->get_tx_rate() / m_conf.sampleRate) >
m_conf.sampleRate * 1e-6) {
throw std::runtime_error("Cannot set USRP sample rate. Aborted.");
}
tune(m_conf.lo_offset, m_conf.frequency);
m_conf.frequency = m_usrp->get_tx_freq();
etiLog.level(info) << std::fixed << std::setprecision(3) <<
"OutputUHD:Actual TX frequency: " << m_conf.frequency;
etiLog.level(info) << std::fixed << std::setprecision(3) <<
"OutputUHD:Actual RX frequency: " << m_usrp->get_tx_freq();
m_usrp->set_tx_gain(m_conf.txgain);
m_conf.txgain = m_usrp->get_tx_gain();
etiLog.log(debug, "OutputUHD:Actual TX Gain: %f", m_conf.txgain);
etiLog.log(debug, "OutputUHD:Mute on missing timestamps: %s",
m_conf.muteNoTimestamps ? "enabled" : "disabled");
// preparing output thread worker data
// TODO sourceContainsTimestamp = false;
m_usrp->set_rx_rate(m_conf.sampleRate);
etiLog.log(debug, "OutputUHD:Actual RX Rate: %f sps.", m_usrp->get_rx_rate());
m_usrp->set_rx_antenna("RX2");
etiLog.log(debug, "OutputUHD:Set RX Antenna: %s",
m_usrp->get_rx_antenna().c_str());
m_usrp->set_rx_gain(m_conf.rxgain);
etiLog.log(debug, "OutputUHD:Actual RX Gain: %f", m_usrp->get_rx_gain());
/* TODO
uhdFeedback = std::make_shared(
m_usrp, m_conf.dpdFeedbackServerPort, m_conf.sampleRate);
*/
const uhd::stream_args_t stream_args("fc32"); //complex floats
m_rx_stream = m_usrp->get_rx_stream(stream_args);
m_tx_stream = m_usrp->get_tx_stream(stream_args);
m_running.store(true);
m_async_rx_thread = boost::thread(&UHD::print_async_thread, this);
MDEBUG("OutputUHD:UHD ready.\n");
}
UHD::~UHD()
{
stop_threads();
}
void UHD::tune(double lo_offset, double frequency)
{
if (lo_offset != 0.0) {
etiLog.level(info) << std::fixed << std::setprecision(3) <<
"OutputUHD:Setting freq to " << frequency <<
" with LO offset " << lo_offset << "...";
const auto tr = uhd::tune_request_t(frequency, lo_offset);
uhd::tune_result_t result = m_usrp->set_tx_freq(tr);
etiLog.level(debug) << "OutputUHD:" <<
std::fixed << std::setprecision(0) <<
" Target RF: " << result.target_rf_freq <<
" Actual RF: " << result.actual_rf_freq <<
" Target DSP: " << result.target_dsp_freq <<
" Actual DSP: " << result.actual_dsp_freq;
}
else {
//set the centre frequency
etiLog.level(info) << std::fixed << std::setprecision(3) <<
"OutputUHD:Setting freq to " << frequency << "...";
m_usrp->set_tx_freq(frequency);
}
// TODO configure LO offset also for RX
m_usrp->set_rx_freq(frequency);
}
double UHD::get_tx_freq(void)
{
return m_usrp->get_tx_freq();
}
void UHD::set_txgain(double txgain)
{
m_usrp->set_tx_gain(txgain);
m_conf.txgain = m_usrp->get_tx_gain();
}
double UHD::get_txgain(void)
{
return m_usrp->get_tx_gain();
}
void UHD::transmit_frame(const struct FrameData& frame)
{
const double tx_timeout = 20.0;
const size_t sizeIn = frame.buf.size() / sizeof(complexf);
const complexf* in_data = reinterpret_cast(&frame.buf[0]);
size_t usrp_max_num_samps = m_tx_stream->get_max_num_samps();
size_t num_acc_samps = 0; //number of accumulated samples
while (m_running.load() and (not m_conf.muting) and (num_acc_samps < sizeIn)) {
size_t samps_to_send = std::min(sizeIn - num_acc_samps, usrp_max_num_samps);
uhd::tx_metadata_t md_tx = md;
// ensure the the last packet has EOB set if the timestamps has been
// refreshed and need to be reconsidered.
md_tx.end_of_burst = (
frame.ts.timestamp_valid and
frame.ts.timestamp_refresh and
samps_to_send <= usrp_max_num_samps );
//send a single packet
size_t num_tx_samps = m_tx_stream->send(
&in_data[num_acc_samps],
samps_to_send, md_tx, tx_timeout);
etiLog.log(trace, "UHD,sent %zu of %zu", num_tx_samps, samps_to_send);
num_acc_samps += num_tx_samps;
md_tx.time_spec = md.time_spec +
uhd::time_spec_t(0, num_tx_samps/m_conf.sampleRate);
if (num_tx_samps == 0) {
etiLog.log(warn,
"OutputUHD unable to write to device, skipping frame!");
break;
}
}
}
SDRDevice::RunStatistics UHD::get_run_statistics(void)
{
RunStatistics rs;
rs.num_underruns = num_underflows;
rs.num_overruns = num_overflows;
rs.num_late_packets = num_late_packets;
rs.num_frames_modulated = num_frames_modulated;
return rs;
}
double UHD::get_real_secs(void)
{
return m_usrp->get_time_now().get_real_secs();
}
void UHD::set_rxgain(double rxgain)
{
m_usrp->set_rx_gain(m_conf.rxgain);
m_conf.rxgain = m_usrp->get_rx_gain();
}
double UHD::get_rxgain()
{
return m_usrp->get_rx_gain();
}
size_t UHD::receive_frame(
complexf *buf,
size_t num_samples,
struct frame_timestamp& ts,
double timeout_secs)
{
uhd::stream_cmd_t cmd(
uhd::stream_cmd_t::stream_mode_t::STREAM_MODE_NUM_SAMPS_AND_DONE);
cmd.num_samps = num_samples;
cmd.stream_now = false;
cmd.time_spec = uhd::time_spec_t(ts.timestamp_sec, ts.pps_offset());
m_rx_stream->issue_stream_cmd(cmd);
uhd::rx_metadata_t md;
constexpr double timeout = 60;
size_t samples_read = m_rx_stream->recv(buf, num_samples, md, timeout);
// Update the ts with the effective receive TS
ts.timestamp_sec = md.time_spec.get_full_secs();
ts.timestamp_pps = md.time_spec.get_frac_secs() * 16384000.0;
return samples_read;
}
// Return true if GPS and reference clock inputs are ok
bool UHD::is_clk_source_ok(void)
{
//TODO
return true;
}
const char* UHD::device_name(void)
{
return "UHD";
}
bool UHD::refclk_loss_needs_check() const
{
if (suppress_refclk_loss_check) {
return false;
}
return m_conf.refclk_src != "internal";
}
bool UHD::gpsfix_needs_check() const
{
if (m_conf.refclk_src == "internal") {
return false;
}
else if (m_conf.refclk_src == "gpsdo") {
return (m_conf.maxGPSHoldoverTime != 0);
}
else if (m_conf.refclk_src == "gpsdo-ettus") {
return (m_conf.maxGPSHoldoverTime != 0);
}
else {
return false;
}
}
bool UHD::gpsdo_is_ettus() const
{
return (m_conf.refclk_src == "gpsdo-ettus");
}
void UHD::stop_threads()
{
m_running.store(false);
if (m_async_rx_thread.joinable()) {
m_async_rx_thread.join();
}
}
static int transmission_frame_duration_ms(unsigned int dabMode)
{
switch (dabMode) {
// could happen when called from constructor and we take the mode from ETI
case 0: return 0;
case 1: return 96;
case 2: return 24;
case 3: return 24;
case 4: return 48;
default:
throw std::runtime_error("OutputUHD: invalid DAB mode");
}
}
void UHD::set_usrp_time()
{
if (m_conf.enableSync and (m_conf.pps_src == "none")) {
etiLog.level(warn) <<
"OutputUHD: WARNING:"
" you are using synchronous transmission without PPS input!";
struct timespec now;
if (clock_gettime(CLOCK_REALTIME, &now)) {
perror("OutputUHD:Error: could not get time: ");
etiLog.level(error) << "OutputUHD: could not get time";
}
else {
m_usrp->set_time_now(uhd::time_spec_t(now.tv_sec));
etiLog.level(info) << "OutputUHD: Setting USRP time to " <<
std::fixed <<
uhd::time_spec_t(now.tv_sec).get_real_secs();
}
}
if (m_conf.pps_src != "none") {
/* handling time for synchronisation: wait until the next full
* second, and set the USRP time at next PPS */
struct timespec now;
time_t seconds;
if (clock_gettime(CLOCK_REALTIME, &now)) {
etiLog.level(error) << "OutputUHD: could not get time :" <<
strerror(errno);
throw std::runtime_error("OutputUHD: could not get time.");
}
else {
seconds = now.tv_sec;
MDEBUG("OutputUHD:sec+1: %ld ; now: %ld ...\n", seconds+1, now.tv_sec);
while (seconds + 1 > now.tv_sec) {
usleep(1);
if (clock_gettime(CLOCK_REALTIME, &now)) {
etiLog.level(error) << "OutputUHD: could not get time :" <<
strerror(errno);
throw std::runtime_error("OutputUHD: could not get time.");
}
}
MDEBUG("OutputUHD:sec+1: %ld ; now: %ld ...\n", seconds+1, now.tv_sec);
/* We are now shortly after the second change. */
usleep(200000); // 200ms, we want the PPS to be later
m_usrp->set_time_unknown_pps(uhd::time_spec_t(seconds + 2));
etiLog.level(info) << "OutputUHD: Setting USRP time next pps to " <<
std::fixed <<
uhd::time_spec_t(seconds + 2).get_real_secs();
}
usleep(1e6);
etiLog.log(info, "OutputUHD: USRP time %f\n",
m_usrp->get_time_now().get_real_secs());
}
}
void UHD::initial_gps_check()
{
if (first_gps_fix_check.tv_sec == 0) {
etiLog.level(info) << "Waiting for GPS fix";
if (clock_gettime(CLOCK_MONOTONIC, &first_gps_fix_check) != 0) {
stringstream ss;
ss << "clock_gettime failure: " << strerror(errno);
throw std::runtime_error(ss.str());
}
}
check_gps();
if (last_gps_fix_check.tv_sec >
first_gps_fix_check.tv_sec + initial_gps_fix_wait) {
stringstream ss;
ss << "GPS did not show time lock in " <<
initial_gps_fix_wait << " seconds";
throw std::runtime_error(ss.str());
}
if (time_last_frame.tv_sec == 0) {
if (clock_gettime(CLOCK_MONOTONIC, &time_last_frame) != 0) {
stringstream ss;
ss << "clock_gettime failure: " << strerror(errno);
throw std::runtime_error(ss.str());
}
}
struct timespec now;
if (clock_gettime(CLOCK_MONOTONIC, &now) != 0) {
stringstream ss;
ss << "clock_gettime failure: " << strerror(errno);
throw std::runtime_error(ss.str());
}
long delta_us = timespecdiff_us(time_last_frame, now);
long wait_time_us = transmission_frame_duration_ms(m_conf.dabMode);
if (wait_time_us - delta_us > 0) {
usleep(wait_time_us - delta_us);
}
time_last_frame.tv_nsec += wait_time_us * 1000;
if (time_last_frame.tv_nsec >= 1000000000L) {
time_last_frame.tv_nsec -= 1000000000L;
time_last_frame.tv_sec++;
}
}
void UHD::check_gps()
{
struct timespec time_now;
if (clock_gettime(CLOCK_MONOTONIC, &time_now) != 0) {
stringstream ss;
ss << "clock_gettime failure: " << strerror(errno);
throw std::runtime_error(ss.str());
}
// Divide interval by two because we alternate between
// launch and check
if (gpsfix_needs_check() and
last_gps_fix_check.tv_sec + gps_fix_check_interval/2.0 <
time_now.tv_sec) {
last_gps_fix_check = time_now;
// Alternate between launching thread and checking the
// result.
if (gps_fix_task.joinable()) {
if (gps_fix_future.has_value()) {
gps_fix_future.wait();
gps_fix_task.join();
if (not gps_fix_future.get()) {
if (num_checks_without_gps_fix == 0) {
etiLog.level(alert) <<
"OutputUHD: GPS Time Lock lost";
}
num_checks_without_gps_fix++;
}
else {
if (num_checks_without_gps_fix) {
etiLog.level(info) <<
"OutputUHD: GPS Time Lock recovered";
}
num_checks_without_gps_fix = 0;
}
if (gps_fix_check_interval * num_checks_without_gps_fix >
m_conf.maxGPSHoldoverTime) {
std::stringstream ss;
ss << "Lost GPS Time Lock for " << gps_fix_check_interval *
num_checks_without_gps_fix << " seconds";
throw std::runtime_error(ss.str());
}
}
}
else {
// Checking the sensor here takes too much
// time, it has to be done in a separate thread.
if (gpsdo_is_ettus()) {
gps_fix_pt = boost::packaged_task(
boost::bind(check_gps_locked, m_usrp) );
}
else {
gps_fix_pt = boost::packaged_task(
boost::bind(check_gps_timelock, m_usrp) );
}
gps_fix_future = gps_fix_pt.get_future();
gps_fix_task = boost::thread(boost::move(gps_fix_pt));
}
}
}
void UHD::print_async_thread()
{
while (m_running.load()) {
uhd::async_metadata_t async_md;
if (m_usrp->get_device()->recv_async_msg(async_md, 1)) {
const char* uhd_async_message = "";
bool failure = false;
switch (async_md.event_code) {
case uhd::async_metadata_t::EVENT_CODE_BURST_ACK:
break;
case uhd::async_metadata_t::EVENT_CODE_UNDERFLOW:
uhd_async_message = "Underflow";
num_underflows++;
break;
case uhd::async_metadata_t::EVENT_CODE_SEQ_ERROR:
uhd_async_message = "Packet loss between host and device.";
failure = true;
break;
case uhd::async_metadata_t::EVENT_CODE_TIME_ERROR:
uhd_async_message = "Packet had time that was late.";
num_late_packets++;
break;
case uhd::async_metadata_t::EVENT_CODE_UNDERFLOW_IN_PACKET:
uhd_async_message = "Underflow occurred inside a packet.";
failure = true;
break;
case uhd::async_metadata_t::EVENT_CODE_SEQ_ERROR_IN_BURST:
uhd_async_message = "Packet loss within a burst.";
failure = true;
break;
default:
uhd_async_message = "unknown event code";
failure = true;
break;
}
if (failure) {
etiLog.level(alert) <<
"Received Async UHD Message '" <<
uhd_async_message << "' at time " <<
md.time_spec.get_real_secs();
}
}
auto time_now = std::chrono::steady_clock::now();
if (last_print_time + std::chrono::seconds(1) < time_now) {
const double usrp_time =
m_usrp->get_time_now().get_real_secs();
if ( (num_underflows > num_underflows_previous) or
(num_late_packets > num_late_packets_previous)) {
etiLog.log(info,
"OutputUHD status (usrp time: %f): "
"%d underruns and %d late packets since last status.\n",
usrp_time,
num_underflows, num_late_packets);
}
num_underflows_previous = num_underflows;
num_late_packets_previous = num_late_packets;
last_print_time = time_now;
}
}
}
} // namespace Output
#endif // HAVE_OUTPUT_UHD