diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/ConfigParser.cpp | 87 | ||||
| -rw-r--r-- | src/ConfigParser.h | 17 | ||||
| -rw-r--r-- | src/DabMod.cpp | 63 | ||||
| -rw-r--r-- | src/DabModulator.cpp | 56 | ||||
| -rw-r--r-- | src/OutputSoapy.cpp | 287 | ||||
| -rw-r--r-- | src/OutputSoapy.h | 138 | ||||
| -rw-r--r-- | src/OutputUHD.cpp | 1035 | ||||
| -rw-r--r-- | src/OutputUHD.h | 250 | ||||
| -rw-r--r-- | src/Socket.h | 25 | ||||
| -rw-r--r-- | src/TimestampDecoder.h | 14 | ||||
| -rw-r--r-- | src/Utils.cpp | 14 | ||||
| -rw-r--r-- | src/Utils.h | 4 | ||||
| -rw-r--r-- | src/output/Feedback.cpp (renamed from src/OutputUHDFeedback.cpp) | 80 | ||||
| -rw-r--r-- | src/output/Feedback.h (renamed from src/OutputUHDFeedback.h) | 33 | ||||
| -rw-r--r-- | src/output/SDR.cpp | 433 | ||||
| -rw-r--r-- | src/output/SDR.h | 97 | ||||
| -rw-r--r-- | src/output/SDRDevice.h | 136 | ||||
| -rw-r--r-- | src/output/Soapy.cpp | 264 | ||||
| -rw-r--r-- | src/output/Soapy.h | 98 | ||||
| -rw-r--r-- | src/output/UHD.cpp | 485 | ||||
| -rw-r--r-- | src/output/UHD.h | 127 | ||||
| -rw-r--r-- | src/output/USRPTime.cpp | 280 | ||||
| -rw-r--r-- | src/output/USRPTime.h | 116 | 
23 files changed, 2245 insertions, 1894 deletions
diff --git a/src/ConfigParser.cpp b/src/ConfigParser.cpp index 2c93a57..0e641c0 100644 --- a/src/ConfigParser.cpp +++ b/src/ConfigParser.cpp @@ -29,15 +29,17 @@  #   include "config.h"  #endif +#include <cstdint> +#include <boost/property_tree/ptree.hpp> +#include <boost/property_tree/ini_parser.hpp> +  #include "ConfigParser.h"  #include "porting.h"  #include "Utils.h"  #include "Log.h"  #include "DabModulator.h" +#include "output/SDR.h" -#include <unistd.h> -#include <boost/property_tree/ptree.hpp> -#include <boost/property_tree/ini_parser.hpp>  using namespace std; @@ -214,75 +216,84 @@ static void parse_configfile(      }  #if defined(HAVE_OUTPUT_UHD)      else if (output_selected == "uhd") { -        OutputUHDConfig outputuhd_conf; +        Output::SDRDeviceConfig sdr_device_config; + +        string device = pt.get("uhdoutput.device", ""); +        const auto usrpType = pt.get("uhdoutput.type", ""); +        if (usrpType != "") { +            if (not device.empty()) { +                device += ","; +            } +            device += "type=" + usrpType; +        } +        sdr_device_config.device = device; -        outputuhd_conf.device = pt.get("uhdoutput.device", ""); -        outputuhd_conf.usrpType = pt.get("uhdoutput.type", ""); -        outputuhd_conf.subDevice = pt.get("uhdoutput.subdevice", ""); -        outputuhd_conf.masterClockRate = pt.get<long>("uhdoutput.master_clock_rate", 0); +        sdr_device_config.subDevice = pt.get("uhdoutput.subdevice", ""); +        sdr_device_config.masterClockRate = pt.get<long>("uhdoutput.master_clock_rate", 0); -        if (outputuhd_conf.device.find("master_clock_rate") != std::string::npos) { +        if (sdr_device_config.device.find("master_clock_rate") != std::string::npos) {              std::cerr << "Warning:"                  "setting master_clock_rate in [uhd] device is deprecated !\n";          } -        if (outputuhd_conf.device.find("type=") != std::string::npos) { +        if (sdr_device_config.device.find("type=") != std::string::npos) {              std::cerr << "Warning:"                  "setting type in [uhd] device is deprecated !\n";          } -        outputuhd_conf.txgain = pt.get("uhdoutput.txgain", 0.0); -        outputuhd_conf.rxgain = pt.get("uhdoutput.rxgain", 0.0); -        outputuhd_conf.frequency = pt.get<double>("uhdoutput.frequency", 0); +        sdr_device_config.txgain = pt.get("uhdoutput.txgain", 0.0); +        sdr_device_config.rxgain = pt.get("uhdoutput.rxgain", 0.0); +        sdr_device_config.frequency = pt.get<double>("uhdoutput.frequency", 0);          std::string chan = pt.get<std::string>("uhdoutput.channel", ""); -        outputuhd_conf.dabMode = mod_settings.dabMode; +        sdr_device_config.dabMode = mod_settings.dabMode; -        if (outputuhd_conf.frequency == 0 && chan == "") { +        if (sdr_device_config.frequency == 0 && chan == "") {              std::cerr << "       UHD output enabled, but neither frequency nor channel defined.\n";              throw std::runtime_error("Configuration error");          } -        else if (outputuhd_conf.frequency == 0) { -            outputuhd_conf.frequency = parseChannel(chan); +        else if (sdr_device_config.frequency == 0) { +            sdr_device_config.frequency = parseChannel(chan);          } -        else if (outputuhd_conf.frequency != 0 && chan != "") { +        else if (sdr_device_config.frequency != 0 && chan != "") {              std::cerr << "       UHD output: cannot define both frequency and channel.\n";              throw std::runtime_error("Configuration error");          } -        outputuhd_conf.lo_offset = pt.get<double>("uhdoutput.lo_offset", 0); +        sdr_device_config.lo_offset = pt.get<double>("uhdoutput.lo_offset", 0); -        outputuhd_conf.refclk_src = pt.get("uhdoutput.refclk_source", "internal"); -        outputuhd_conf.pps_src = pt.get("uhdoutput.pps_source", "none"); -        outputuhd_conf.pps_polarity = pt.get("uhdoutput.pps_polarity", "pos"); +        sdr_device_config.refclk_src = pt.get("uhdoutput.refclk_source", "internal"); +        sdr_device_config.pps_src = pt.get("uhdoutput.pps_source", "none"); +        sdr_device_config.pps_polarity = pt.get("uhdoutput.pps_polarity", "pos");          std::string behave = pt.get("uhdoutput.behaviour_refclk_lock_lost", "ignore");          if (behave == "crash") { -            outputuhd_conf.refclk_lock_loss_behaviour = CRASH; +            sdr_device_config.refclk_lock_loss_behaviour = Output::CRASH;          }          else if (behave == "ignore") { -            outputuhd_conf.refclk_lock_loss_behaviour = IGNORE; +            sdr_device_config.refclk_lock_loss_behaviour = Output::IGNORE;          }          else {              std::cerr << "Error: UHD output: behaviour_refclk_lock_lost invalid." << std::endl;              throw std::runtime_error("Configuration error");          } -        outputuhd_conf.maxGPSHoldoverTime = pt.get("uhdoutput.max_gps_holdover_time", 0); +        sdr_device_config.maxGPSHoldoverTime = pt.get("uhdoutput.max_gps_holdover_time", 0); -        outputuhd_conf.dpdFeedbackServerPort = pt.get<long>("uhdoutput.dpd_port", 0); +        sdr_device_config.dpdFeedbackServerPort = pt.get<long>("uhdoutput.dpd_port", 0); -        mod_settings.outputuhd_conf = outputuhd_conf; +        mod_settings.sdr_device_config = sdr_device_config;          mod_settings.useUHDOutput = 1;      }  #endif  #if defined(HAVE_SOAPYSDR)      else if (output_selected == "soapysdr") { -        auto& outputsoapy_conf = mod_settings.outputsoapy_conf; +        auto& outputsoapy_conf = mod_settings.sdr_device_config;          outputsoapy_conf.device = pt.get("soapyoutput.device", "");          outputsoapy_conf.masterClockRate = pt.get<long>("soapyoutput.master_clock_rate", 0);          outputsoapy_conf.txgain = pt.get("soapyoutput.txgain", 0.0); +        outputsoapy_conf.lo_offset = pt.get<double>("soapyoutput.lo_offset", 0.0);          outputsoapy_conf.frequency = pt.get<double>("soapyoutput.frequency", 0);          std::string chan = pt.get<std::string>("soapyoutput.channel", "");          outputsoapy_conf.dabMode = mod_settings.dabMode; @@ -315,9 +326,9 @@ static void parse_configfile(      }  #if defined(HAVE_OUTPUT_UHD) -    mod_settings.outputuhd_conf.enableSync = (pt.get("delaymanagement.synchronous", 0) == 1); -    mod_settings.outputuhd_conf.muteNoTimestamps = (pt.get("delaymanagement.mutenotimestamps", 0) == 1); -    if (mod_settings.outputuhd_conf.enableSync) { +    mod_settings.sdr_device_config.enableSync = (pt.get("delaymanagement.synchronous", 0) == 1); +    mod_settings.sdr_device_config.muteNoTimestamps = (pt.get("delaymanagement.mutenotimestamps", 0) == 1); +    if (mod_settings.sdr_device_config.enableSync) {          std::string delay_mgmt = pt.get<std::string>("delaymanagement.management", "");          std::string fixedoffset = pt.get<std::string>("delaymanagement.fixedoffset", "");          std::string offset_filename = pt.get<std::string>("delaymanagement.dynamicoffsetfile", ""); @@ -392,7 +403,7 @@ void parse_args(int argc, char **argv, mod_settings_t& mod_settings)              break;          case 'F':  #if defined(HAVE_OUTPUT_UHD) -            mod_settings.outputuhd_conf.frequency = strtof(optarg, NULL); +            mod_settings.sdr_device_config.frequency = strtof(optarg, NULL);  #endif              break;          case 'g': @@ -400,7 +411,7 @@ void parse_args(int argc, char **argv, mod_settings_t& mod_settings)              break;          case 'G':  #if defined(HAVE_OUTPUT_UHD) -            mod_settings.outputuhd_conf.txgain = strtod(optarg, NULL); +            mod_settings.sdr_device_config.txgain = strtod(optarg, NULL);  #endif              break;          case 'l': @@ -409,7 +420,7 @@ void parse_args(int argc, char **argv, mod_settings_t& mod_settings)          case 'o':              mod_settings.tist_offset_s = strtod(optarg, NULL);  #if defined(HAVE_OUTPUT_UHD) -            mod_settings.outputuhd_conf.enableSync = true; +            mod_settings.sdr_device_config.enableSync = true;  #endif              break;          case 'm': @@ -427,10 +438,10 @@ void parse_args(int argc, char **argv, mod_settings_t& mod_settings)                  fprintf(stderr, "Options -u and -f are mutually exclusive\n");                  throw std::invalid_argument("Invalid command line options");              } -            mod_settings.outputuhd_conf.device = optarg; -            mod_settings.outputuhd_conf.refclk_src = "internal"; -            mod_settings.outputuhd_conf.pps_src = "none"; -            mod_settings.outputuhd_conf.pps_polarity = "pos"; +            mod_settings.sdr_device_config.device = optarg; +            mod_settings.sdr_device_config.refclk_src = "internal"; +            mod_settings.sdr_device_config.pps_src = "none"; +            mod_settings.sdr_device_config.pps_polarity = "pos";              mod_settings.useUHDOutput = 1;  #endif              break; diff --git a/src/ConfigParser.h b/src/ConfigParser.h index 0be3558..3f64883 100644 --- a/src/ConfigParser.h +++ b/src/ConfigParser.h @@ -34,12 +34,9 @@  #include <string>  #include "GainControl.h"  #include "TII.h" -#if defined(HAVE_OUTPUT_UHD) -#   include "OutputUHD.h" -#endif -#if defined(HAVE_SOAPYSDR) -#   include "OutputSoapy.h" -#endif +#include "output/SDR.h" +#include "output/UHD.h" +#include "output/Soapy.h"  #define ZMQ_INPUT_MAX_FRAME_QUEUE 500 @@ -85,12 +82,8 @@ struct mod_settings_t {      // Settings for the OFDM windowing      unsigned ofdmWindowOverlap = 0; -#if defined(HAVE_OUTPUT_UHD) -    OutputUHDConfig outputuhd_conf; -#endif - -#if defined(HAVE_SOAPYSDR) -    OutputSoapyConfig outputsoapy_conf; +#if defined(HAVE_OUTPUT_UHD) || defined(HAVE_SOAPYSDR) +    Output::SDRDeviceConfig sdr_device_config;  #endif  }; diff --git a/src/DabMod.cpp b/src/DabMod.cpp index 4a4ea82..9880938 100644 --- a/src/DabMod.cpp +++ b/src/DabMod.cpp @@ -37,12 +37,9 @@  #include "OutputFile.h"  #include "FormatConverter.h"  #include "FrameMultiplexer.h" -#if defined(HAVE_OUTPUT_UHD) -#   include "OutputUHD.h" -#endif -#if defined(HAVE_SOAPYSDR) -#   include "OutputSoapy.h" -#endif +#include "output/SDR.h" +#include "output/UHD.h" +#include "output/Soapy.h"  #include "OutputZeroMQ.h"  #include "InputReader.h"  #include "PcDebug.h" @@ -133,15 +130,15 @@ static void printModSettings(const mod_settings_t& mod_settings)      else if (mod_settings.useUHDOutput) {          fprintf(stderr, " UHD\n"                          "  Device: %s\n" -                        "  Type: %s\n" +                        "  Subdevice: %s\n"                          "  master_clock_rate: %ld\n"                          "  refclk: %s\n"                          "  pps source: %s\n", -                mod_settings.outputuhd_conf.device.c_str(), -                mod_settings.outputuhd_conf.usrpType.c_str(), -                mod_settings.outputuhd_conf.masterClockRate, -                mod_settings.outputuhd_conf.refclk_src.c_str(), -                mod_settings.outputuhd_conf.pps_src.c_str()); +                mod_settings.sdr_device_config.device.c_str(), +                mod_settings.sdr_device_config.subDevice.c_str(), +                mod_settings.sdr_device_config.masterClockRate, +                mod_settings.sdr_device_config.refclk_src.c_str(), +                mod_settings.sdr_device_config.pps_src.c_str());      }  #endif  #if defined(HAVE_SOAPYSDR) @@ -149,8 +146,8 @@ static void printModSettings(const mod_settings_t& mod_settings)          fprintf(stderr, " SoapySDR\n"                          "  Device: %s\n"                          "  master_clock_rate: %ld\n", -                mod_settings.outputsoapy_conf.device.c_str(), -                mod_settings.outputsoapy_conf.masterClockRate); +                mod_settings.sdr_device_config.device.c_str(), +                mod_settings.sdr_device_config.masterClockRate);      }  #endif      else if (mod_settings.useZeroMQOutput) { @@ -208,18 +205,20 @@ static shared_ptr<ModOutput> prepare_output(  #if defined(HAVE_OUTPUT_UHD)      else if (s.useUHDOutput) {          s.normalise = 1.0f / normalise_factor; -        s.outputuhd_conf.sampleRate = s.outputRate; -        output = make_shared<OutputUHD>(s.outputuhd_conf); -        rcs.enrol((OutputUHD*)output.get()); +        s.sdr_device_config.sampleRate = s.outputRate; +        auto uhddevice = make_shared<Output::UHD>(s.sdr_device_config); +        output = make_shared<Output::SDR>(s.sdr_device_config, uhddevice); +        rcs.enrol((Output::SDR*)output.get());      }  #endif  #if defined(HAVE_SOAPYSDR)      else if (s.useSoapyOutput) {          /* We normalise the same way as for the UHD output */          s.normalise = 1.0f / normalise_factor; -        s.outputsoapy_conf.sampleRate = s.outputRate; -        output = make_shared<OutputSoapy>(s.outputsoapy_conf); -        rcs.enrol((OutputSoapy*)output.get()); +        s.sdr_device_config.sampleRate = s.outputRate; +        auto soapydevice = make_shared<Output::Soapy>(s.sdr_device_config); +        output = make_shared<Output::SDR>(s.sdr_device_config, soapydevice); +        rcs.enrol((Output::SDR*)output.get());      }  #endif  #if defined(HAVE_ZEROMQ) @@ -321,16 +320,16 @@ int launch_modulator(int argc, char* argv[])              flowgraph.connect(modulator, output);          } +        if (false  #if defined(HAVE_OUTPUT_UHD) -        if (mod_settings.useUHDOutput) { -            ((OutputUHD*)output.get())->setETISource(modulator->getEtiSource()); -        } +                or mod_settings.useUHDOutput  #endif  #if defined(HAVE_SOAPYSDR) -        if (mod_settings.useSoapyOutput) { -            ((OutputSoapy*)output.get())->setETISource(modulator->getEtiSource()); -        } +                or mod_settings.useSoapyOutput  #endif +           ) { +            ((Output::SDR*)output.get())->setETISource(modulator->getEtiSource()); +        }          size_t framecount = 0; @@ -415,16 +414,16 @@ int launch_modulator(int argc, char* argv[])                  flowgraph.connect(modulator, output);              } +            if (false  #if defined(HAVE_OUTPUT_UHD) -            if (mod_settings.useUHDOutput) { -                ((OutputUHD*)output.get())->setETISource(modulator->getEtiSource()); -            } +                    or mod_settings.useUHDOutput  #endif  #if defined(HAVE_SOAPYSDR) -            if (mod_settings.useSoapyOutput) { -                ((OutputSoapy*)output.get())->setETISource(modulator->getEtiSource()); -            } +                    or mod_settings.useSoapyOutput  #endif +               ) { +                ((Output::SDR*)output.get())->setETISource(modulator->getEtiSource()); +            }              // TODO remove              auto output_as_file = dynamic_pointer_cast<OutputFile>(output); diff --git a/src/DabModulator.cpp b/src/DabModulator.cpp index f1ea071..337a595 100644 --- a/src/DabModulator.cpp +++ b/src/DabModulator.cpp @@ -162,10 +162,13 @@ int DabModulator::process(Buffer* dataOut)              }          } -        auto cifCicEq = make_shared<CicEqualizer>( +        shared_ptr<CicEqualizer> cifCicEq; +        if (useCicEq) { +            cifCicEq = make_shared<CicEqualizer>(                  myNbCarriers,                  (float)mySpacing * (float)m_settings.outputRate / 2048000.0f,                  cic_ratio); +        }          shared_ptr<TII> tii;          shared_ptr<PhaseReference> tiiRef; @@ -347,43 +350,24 @@ int DabModulator::process(Buffer* dataOut)              myFlowgraph->connect(tii, cifSig);          } -        if (useCicEq) { -            myFlowgraph->connect(cifSig, cifCicEq); -            myFlowgraph->connect(cifCicEq, cifOfdm); -        } -        else { -            myFlowgraph->connect(cifSig, cifOfdm); -        } -        myFlowgraph->connect(cifOfdm, cifGain); -        myFlowgraph->connect(cifGain, cifGuard); - -        auto cifOut = cifPoly ? -            static_pointer_cast<ModPlugin>(cifPoly) : -            static_pointer_cast<ModPlugin>(myOutput); - -        if (cifFilter) { -            myFlowgraph->connect(cifGuard, cifFilter); -            if (cifRes) { -                myFlowgraph->connect(cifFilter, cifRes); -                myFlowgraph->connect(cifRes, cifOut); -            } -            else { -                myFlowgraph->connect(cifFilter, cifOut); +        shared_ptr<ModPlugin> prev_plugin = static_pointer_cast<ModPlugin>(cifSig); +        const std::list<shared_ptr<ModPlugin> > plugins({ +                static_pointer_cast<ModPlugin>(cifCicEq), +                static_pointer_cast<ModPlugin>(cifOfdm), +                static_pointer_cast<ModPlugin>(cifGain), +                static_pointer_cast<ModPlugin>(cifGuard), +                static_pointer_cast<ModPlugin>(cifFilter), // optional block +                static_pointer_cast<ModPlugin>(cifRes),    // optional block +                static_pointer_cast<ModPlugin>(cifPoly),   // optional block +                static_pointer_cast<ModPlugin>(myOutput), +                }); + +        for (auto& p : plugins) { +            if (p) { +                myFlowgraph->connect(prev_plugin, p); +                prev_plugin = p;              }          } -        else { -            if (cifRes) { -                myFlowgraph->connect(cifGuard, cifRes); -                myFlowgraph->connect(cifRes, cifOut); -            } -            else { -                myFlowgraph->connect(cifGuard, cifOut); -            } -        } - -        if (cifPoly) { -            myFlowgraph->connect(cifPoly, myOutput); -        }      }      //////////////////////////////////////////////////////////////////// diff --git a/src/OutputSoapy.cpp b/src/OutputSoapy.cpp deleted file mode 100644 index 699501c..0000000 --- a/src/OutputSoapy.cpp +++ /dev/null @@ -1,287 +0,0 @@ -/* -   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 - -DESCRIPTION: -   It is an output driver using the SoapySDR library that can output to -   many devices. -*/ - -/* -   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 <http://www.gnu.org/licenses/>. - */ - -#include "OutputSoapy.h" -#ifdef HAVE_SOAPYSDR - -#include <SoapySDR/Errors.hpp> -#include <deque> -#include <chrono> - -#include "Log.h" -#include "Utils.h" - -#include <stdio.h> - -static const size_t FRAMES_MAX_SIZE = 2; - - -using namespace std; - - - -OutputSoapy::OutputSoapy(OutputSoapyConfig& config) : -    ModOutput(), -    RemoteControllable("soapy"), -    m_conf(config), -    m_device(nullptr) -{ -    RC_ADD_PARAMETER(txgain, "SoapySDR analog daughterboard TX gain"); -    RC_ADD_PARAMETER(freq,   "SoapySDR transmission frequency"); -    RC_ADD_PARAMETER(overflows, "SoapySDR overflow count [r/o]"); -    RC_ADD_PARAMETER(underflows, "SoapySDR underflow count [r/o]"); - -    etiLog.level(info) << -        "OutputSoapy:Creating the device with: " << -        config.device; -    try -    { -        m_device = SoapySDR::Device::make(config.device); -        stringstream ss; -        ss << "SoapySDR driver=" << m_device->getDriverKey(); -        ss << " hardware=" << m_device->getHardwareKey(); -        for (const auto &it : m_device->getHardwareInfo()) -        { -            ss << "  " << it.first << "=" << it.second; -        } -    } -    catch (const std::exception &ex) -    { -        etiLog.level(error) << "Error making SoapySDR device: " << -            ex.what(); -        throw std::runtime_error("Cannot create SoapySDR output"); -    } - -    m_device->setMasterClockRate(config.masterClockRate); -    etiLog.level(info) << "SoapySDR master clock rate set to " << -        m_device->getMasterClockRate()/1000.0 << " kHz"; - -    m_device->setSampleRate(SOAPY_SDR_TX, 0, m_conf.sampleRate); -    etiLog.level(info) << "OutputSoapySDR:Actual TX rate: " << -        m_device->getSampleRate(SOAPY_SDR_TX, 0) / 1000.0 << -        " ksps."; - -    m_device->setFrequency(SOAPY_SDR_TX, 0, m_conf.frequency); -    m_conf.frequency = m_device->getFrequency(SOAPY_SDR_TX, 0); -    etiLog.level(info) << "OutputSoapySDR:Actual frequency: " << -        m_conf.frequency / 1000.0 << -        " kHz."; - -    m_device->setGain(SOAPY_SDR_TX, 0, m_conf.txgain); -    etiLog.level(info) << "OutputSoapySDR:Actual tx gain: " << -        m_device->getGain(SOAPY_SDR_TX, 0); - -} - -OutputSoapy::~OutputSoapy() -{ -    m_worker.stop(); -    if (m_device != nullptr) { -        SoapySDR::Device::unmake(m_device); -    } -} - -void SoapyWorker::stop() -{ -    running = false; -    queue.push({}); -    if (m_thread.joinable()) { -        m_thread.join(); -    } -} - -void SoapyWorker::start(SoapySDR::Device *device) -{ -    m_device = device; -    underflows = 0; -    overflows = 0; -    running = true; -    m_thread = std::thread(&SoapyWorker::process_start, this); -} - -void SoapyWorker::process_start() -{ -    // Set thread priority to realtime -    if (int ret = set_realtime_prio(1)) { -        etiLog.level(error) << "Could not set priority for SoapySDR worker:" << ret; -    } - -    set_thread_name("soapyworker"); - -    std::vector<size_t> channels; -    channels.push_back(0); -    auto stream = m_device->setupStream(SOAPY_SDR_TX, "CF32", channels); -    m_device->activateStream(stream); -    process(stream); -    m_device->closeStream(stream); -    running = false; -    etiLog.level(warn) << "SoapySDR worker terminated"; -} - -void SoapyWorker::process(SoapySDR::Stream *stream) -{ -    while (running) { -        struct SoapyWorkerFrameData frame; -        queue.wait_and_pop(frame); - -        // The frame buffer contains bytes representing FC32 samples -        const complexf *buf = reinterpret_cast<complexf*>(frame.buf.data()); -        const size_t numSamples = frame.buf.size() / sizeof(complexf); -        if ((frame.buf.size() % sizeof(complexf)) != 0) { -            throw std::runtime_error("OutputSoapy: invalid buffer size"); -        } - -        // Stream MTU is in samples, not bytes. -        const size_t mtu = m_device->getStreamMTU(stream); - -        size_t num_acc_samps = 0; -        while (running && (num_acc_samps < numSamples)) { -            const void *buffs[1]; -            buffs[0] = buf + num_acc_samps; - -            const size_t samps_to_send = std::min(numSamples - num_acc_samps, mtu); - -            int flags = 0; - -            auto ret = m_device->writeStream(stream, buffs, samps_to_send, flags); - -            if (ret == SOAPY_SDR_TIMEOUT) { -                continue; -            } -            else if (ret == SOAPY_SDR_OVERFLOW) { -                overflows++; -                continue; -            } -            else if (ret == SOAPY_SDR_UNDERFLOW) { -                underflows++; -                continue; -            } - -            if (ret < 0) { -                etiLog.level(error) << "Unexpected stream error " << -                    SoapySDR::errToStr(ret); -                running = false; -            } - -            num_acc_samps += ret; -        } -    } -} - -int OutputSoapy::process(Buffer* dataIn) -{ -    if (first_run) { -        m_worker.start(m_device); -        first_run = false; -    } -    else if (!m_worker.running) { -        etiLog.level(error) << "OutputSoapy: worker thread died"; -        throw std::runtime_error("Fault in OutputSoapy"); -    } - -    SoapyWorkerFrameData frame; -    m_eti_source->calculateTimestamp(frame.ts); - - -    if (frame.ts.fct == -1) { -        etiLog.level(info) << -            "OutputSoapy: dropping one frame with invalid FCT"; -    } -    else { -        const uint8_t* pInData = reinterpret_cast<uint8_t*>(dataIn->getData()); -        frame.buf.resize(dataIn->getLength()); -        std::copy(pInData, pInData + dataIn->getLength(), -                frame.buf.begin()); -        m_worker.queue.push_wait_if_full(frame, FRAMES_MAX_SIZE); -    } - -    return dataIn->getLength(); -} - - -void OutputSoapy::setETISource(EtiSource *etiSource) -{ -    m_eti_source = etiSource; -} - -void OutputSoapy::set_parameter(const string& parameter, const string& value) -{ -    stringstream ss(value); -    ss.exceptions ( stringstream::failbit | stringstream::badbit ); - -    if (parameter == "txgain") { -        ss >> m_conf.txgain; -        m_device->setGain(SOAPY_SDR_TX, 0, m_conf.txgain); -    } -    else if (parameter == "freq") { -        ss >> m_conf.frequency; -        m_device->setFrequency(SOAPY_SDR_TX, 0, m_conf.frequency); -    m_conf.frequency = m_device->getFrequency(SOAPY_SDR_TX, 0); -    } -    else if (parameter == "underflows") { -        throw ParameterError("Parameter 'underflows' is read-only"); -    } -    else if (parameter == "overflows") { -        throw ParameterError("Parameter 'overflows' 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 OutputSoapy::get_parameter(const string& parameter) const -{ -    stringstream ss; -    if (parameter == "txgain") { -        ss << m_conf.txgain; -    } -    else if (parameter == "freq") { -        ss << m_conf.frequency; -    } -    else if (parameter == "underflows") { -        ss << m_worker.underflows; -    } -    else if (parameter == "overflows") { -        ss << m_worker.overflows; -    } -    else { -        ss << "Parameter '" << parameter << -            "' is not exported by controllable " << get_rc_name(); -        throw ParameterError(ss.str()); -    } -    return ss.str(); -} - -#endif // HAVE_SOAPYSDR - diff --git a/src/OutputSoapy.h b/src/OutputSoapy.h deleted file mode 100644 index 230f11b..0000000 --- a/src/OutputSoapy.h +++ /dev/null @@ -1,138 +0,0 @@ -/* -   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 - -DESCRIPTION: -   It is an output driver using the SoapySDR library that can output to -   many devices. -*/ - -/* -   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 <http://www.gnu.org/licenses/>. - */ - -#pragma once - -#ifdef HAVE_CONFIG_H -#   include <config.h> -#endif - -#ifdef HAVE_SOAPYSDR -#include <SoapySDR/Version.hpp> -#include <SoapySDR/Modules.hpp> -#include <SoapySDR/Registry.hpp> -#include <SoapySDR/Device.hpp> - -#include <string> -#include <memory> - -#include "ModPlugin.h" -#include "EtiReader.h" -#include "RemoteControl.h" -#include "ThreadsafeQueue.h" - -typedef std::complex<float> complexf; - -/* This structure is used as initial configuration for the Soapy output. - * It must also contain all remote-controllable settings, otherwise - * they will get lost on a modulator restart. */ -struct OutputSoapyConfig { -    std::string device; - -    long masterClockRate = 32768000; -    unsigned sampleRate = 2048000; -    double frequency = 0.0; -    double txgain = 0.0; -    unsigned dabMode = 0; -}; - -// Each frame contains one OFDM frame, and its -// associated timestamp -struct SoapyWorkerFrameData { -    // Buffer holding frame data -    std::vector<uint8_t> buf; - -    // A full timestamp contains a TIST according to standard -    // and time information within MNSC with tx_second. -    struct frame_timestamp ts; -}; - -class SoapyWorker -{ -    public: -        ThreadsafeQueue<SoapyWorkerFrameData> queue; -        SoapySDR::Device *m_device; -        std::atomic<bool> running; -        size_t underflows; -        size_t overflows; - -        SoapyWorker() {} -        SoapyWorker(const SoapyWorker&) = delete; -        SoapyWorker operator=(const SoapyWorker&) = delete; -        ~SoapyWorker() { stop(); } - -        void start(SoapySDR::Device *device); -        void stop(void); - -    private: -        std::thread m_thread; - -        void process_start(void); -        void process(SoapySDR::Stream *stream); -}; - -class OutputSoapy: public ModOutput, public RemoteControllable -{ -    public: -        OutputSoapy(OutputSoapyConfig& config); -        OutputSoapy(const OutputSoapy& other) = delete; -        OutputSoapy& operator=(const OutputSoapy& other) = delete; -        ~OutputSoapy(); - -        int process(Buffer* dataIn); - -        const char* name() { return "OutputSoapy"; } - -        void setETISource(EtiSource *etiSource); - -        /*********** REMOTE CONTROL ***************/ - -        /* Base function to set parameters. */ -        virtual void set_parameter(const std::string& parameter, -                const std::string& value); - -        /* Getting a parameter always returns a string. */ -        virtual const std::string get_parameter( -                const std::string& parameter) const; - - -    protected: -        SoapyWorker m_worker; -        EtiSource *m_eti_source; -        OutputSoapyConfig& m_conf; - -        SoapySDR::Device *m_device; - -        bool first_run = true; -}; - - -#endif //HAVE_SOAPYSDR diff --git a/src/OutputUHD.cpp b/src/OutputUHD.cpp deleted file mode 100644 index b533075..0000000 --- a/src/OutputUHD.cpp +++ /dev/null @@ -1,1035 +0,0 @@ -/* -   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 <http://www.gnu.org/licenses/>. - */ - -#include "OutputUHD.h" - -#ifdef HAVE_OUTPUT_UHD - -#include "PcDebug.h" -#include "Log.h" -#include "RemoteControl.h" -#include "Utils.h" - -#include <boost/thread/future.hpp> - -#include <uhd/utils/msg.hpp> - -#include <cmath> -#include <iostream> -#include <assert.h> -#include <stdexcept> -#include <stdio.h> -#include <time.h> -#include <errno.h> -#include <unistd.h> -#include <pthread.h> - -using namespace std; - -// Maximum number of frames that can wait in frames -static const size_t FRAMES_MAX_SIZE = 8; - -typedef std::complex<float> complexf; - -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()); -} - -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; -        } -    } -} - -static void tune_usrp_to( -        uhd::usrp::multi_usrp::sptr usrp, -        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 = 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 << "..."; -        usrp->set_tx_freq(frequency); -    } - -    usrp->set_rx_freq(frequency); -} - -// Check function for GPS TIMELOCK sensor from the ODR LEA-M8F board GPSDO -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 -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; -    } -} - - -OutputUHD::OutputUHD( -        OutputUHDConfig& config) : -    ModOutput(), -    RemoteControllable("uhd"), -    myConf(config), -    // Since we don't know the buffer size, we cannot initialise -    // the buffers at object initialisation. -    myDelayBuf(0), -    running(false) -{ -    myConf.muting = true;     // is remote-controllable, and reset by the GPS fix check -    myConf.staticDelayUs = 0; // is remote-controllable - -    // 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 << myConf.device; - -    if (myConf.masterClockRate != 0) { -        if (device.str() != "") { -            device << ","; -        } -        device << "master_clock_rate=" << myConf.masterClockRate; -    } - -    if (myConf.usrpType != "") { -        if (device.str() != "") { -            device << ","; -        } -        device << "type=" << myConf.usrpType; -    } - -    MDEBUG("OutputUHD::OutputUHD(device: %s) @ %p\n", -            device.str().c_str(), this); - -    /* register the parameters that can be remote controlled */ -    RC_ADD_PARAMETER(txgain, "UHD analog daughterboard TX gain"); -    RC_ADD_PARAMETER(rxgain, "UHD analog daughterboard RX gain for DPD feedback"); -    RC_ADD_PARAMETER(freq, "UHD transmission frequency"); -    RC_ADD_PARAMETER(muting, "Mute the output by stopping the transmitter"); -    RC_ADD_PARAMETER(staticdelay, "Set static delay (uS) between 0 and 96000"); -    RC_ADD_PARAMETER(underruns, "Read-only counter of number of underruns"); -    RC_ADD_PARAMETER(latepackets, "Read-only counter of number of late packets"); -    RC_ADD_PARAMETER(frames, "Read-only counter of number of frames modulated"); - -    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()); - -    myUsrp = uhd::usrp::multi_usrp::make(device.str()); - -    etiLog.log(info, "OutputUHD:Using device: %s...", -            myUsrp->get_pp_string().c_str()); - -    if (myConf.masterClockRate != 0.0) { -        double master_clk_rate = myUsrp->get_master_clock_rate(); -        etiLog.log(debug, "OutputUHD:Checking master clock rate: %f...", -                master_clk_rate); - -        if (fabs(master_clk_rate - myConf.masterClockRate) > -                (myConf.masterClockRate * 1e-6)) { -            throw std::runtime_error("Cannot set USRP master_clock_rate. Aborted."); -        } -    } - -    MDEBUG("OutputUHD:Setting REFCLK and PPS input...\n"); - -    if (myConf.refclk_src == "gpsdo-ettus") { -        myUsrp->set_clock_source("gpsdo"); -    } -    else { -        myUsrp->set_clock_source(myConf.refclk_src); -    } -    myUsrp->set_time_source(myConf.pps_src); - -    if (myConf.subDevice != "") { -        myUsrp->set_tx_subdev_spec(uhd::usrp::subdev_spec_t(myConf.subDevice), -                uhd::usrp::multi_usrp::ALL_MBOARDS); -    } - -    etiLog.level(debug) << "UHD clock source is " << myUsrp->get_clock_source(0); - -    etiLog.level(debug) << "UHD time source is " << myUsrp->get_time_source(0); - -    myUsrp->set_tx_rate(myConf.sampleRate); -    etiLog.log(debug, "OutputUHD:Set rate to %d. Actual TX Rate: %f sps...", -            myConf.sampleRate, myUsrp->get_tx_rate()); - -    if (fabs(myUsrp->get_tx_rate() / myConf.sampleRate) > -             myConf.sampleRate * 1e-6) { -        throw std::runtime_error("Cannot set USRP sample rate. Aborted."); -    } - -    tune_usrp_to(myUsrp, myConf.lo_offset, myConf.frequency); - -    myConf.frequency = myUsrp->get_tx_freq(); -    etiLog.level(info) << std::fixed << std::setprecision(3) << -        "OutputUHD:Actual TX frequency: " << myConf.frequency; - -    etiLog.level(info) << std::fixed << std::setprecision(3) << -        "OutputUHD:Actual RX frequency: " << myUsrp->get_tx_freq(); - -    myUsrp->set_tx_gain(myConf.txgain); -    etiLog.log(debug, "OutputUHD:Actual TX Gain: %f", myUsrp->get_tx_gain()); - -    etiLog.log(debug, "OutputUHD:Mute on missing timestamps: %s", -            myConf.muteNoTimestamps ? "enabled" : "disabled"); - -    // preparing output thread worker data -    sync_and_ts_valid = false; - -    SetDelayBuffer(myConf.dabMode); - -    myUsrp->set_rx_rate(myConf.sampleRate); -    etiLog.log(debug, "OutputUHD:Actual RX Rate: %f sps.", myUsrp->get_rx_rate()); - -    myUsrp->set_rx_antenna("RX2"); -    etiLog.log(debug, "OutputUHD:Set RX Antenna: %s", -            myUsrp->get_rx_antenna().c_str()); - -    myUsrp->set_rx_gain(myConf.rxgain); -    etiLog.log(debug, "OutputUHD:Actual RX Gain: %f", myUsrp->get_rx_gain()); - -    uhdFeedback = std::make_shared<OutputUHDFeedback>( -            myUsrp, myConf.dpdFeedbackServerPort, myConf.sampleRate); - -    MDEBUG("OutputUHD:UHD ready.\n"); -} - -bool OutputUHD::refclk_loss_needs_check() const -{ -    if (suppress_refclk_loss_check) { -        return false; -    } -    return myConf.refclk_src != "internal"; -} - -bool OutputUHD::gpsfix_needs_check() const -{ -    if (myConf.refclk_src == "internal") { -        return false; -    } -    else if (myConf.refclk_src == "gpsdo") { -        return (myConf.maxGPSHoldoverTime != 0); -    } -    else if (myConf.refclk_src == "gpsdo-ettus") { -        return (myConf.maxGPSHoldoverTime != 0); -    } -    else { -        return false; -    } -} - -bool OutputUHD::gpsdo_is_ettus() const -{ -    return (myConf.refclk_src == "gpsdo-ettus"); -} - -OutputUHD::~OutputUHD() -{ -    stop_threads(); -} - -void OutputUHD::stop_threads() -{ -    running.store(false); -    uhd_thread.interrupt(); -    uhd_thread.join(); -    async_rx_thread.join(); -} - - -void OutputUHD::setETISource(EtiSource *etiSource) -{ -    myEtiSource = etiSource; -} - -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 OutputUHD::SetDelayBuffer(unsigned int dabMode) -{ -    // find out the duration of the transmission frame (Table 2 in ETSI 300 401) -    myTFDurationMs = transmission_frame_duration_ms(dabMode); - -    // The buffer size equals the number of samples per transmission frame so -    // we calculate it by multiplying the duration of the transmission frame -    // with the samplerate. -    myDelayBuf.resize(myTFDurationMs * myConf.sampleRate / 1000); -} - -int OutputUHD::process(Buffer* dataIn) -{ -    if (not gps_fix_verified) { -        if (gpsfix_needs_check()) { -            initial_gps_check(); - -            if (num_checks_without_gps_fix == 0) { -                set_usrp_time(); -                gps_fix_verified = true; -                myConf.muting = false; -            } -        } -        else { -            set_usrp_time(); -            gps_fix_verified = true; -            myConf.muting = false; -        } -    } -    else { -        if (first_run) { -            etiLog.level(debug) << "OutputUHD: UHD initialising..."; - -            // we only set the delay buffer from the dab mode signaled in ETI if the -            // dab mode was not set in contructor -            if (myTFDurationMs == 0) { -                SetDelayBuffer(myEtiSource->getMode()); -            } - -            running.store(true); -            uhd_thread = boost::thread(&OutputUHD::workerthread, this); -            async_rx_thread = boost::thread( -                    &OutputUHD::print_async_thread, this); - -            lastLen = dataIn->getLength(); -            first_run = false; -            etiLog.level(debug) << "OutputUHD: UHD initialising complete"; -        } - -        if (lastLen != dataIn->getLength()) { -            // I expect that this never happens. -            etiLog.level(emerg) << -                "OutputUHD: Fatal error, input length changed from " << lastLen << -                " to " << dataIn->getLength(); -            throw std::runtime_error("Non-constant input length!"); -        } - -        sync_and_ts_valid = myConf.enableSync and -            myEtiSource->sourceContainsTimestamp(); - -        if (gpsfix_needs_check()) { -            try { -                check_gps(); -            } -            catch (std::runtime_error& e) { -                running.store(false); -                etiLog.level(error) << e.what(); -            } -        } - -        // Prepare the frame for the worker -        UHDWorkerFrameData frame; -        frame.buf.resize(dataIn->getLength()); - -        // calculate delay and fill buffer -        uint32_t noSampleDelay = (myConf.staticDelayUs * (myConf.sampleRate / 1000)) / 1000; -        uint32_t noByteDelay = noSampleDelay * sizeof(complexf); - -        const uint8_t* pInData = (uint8_t*)dataIn->getData(); - -        uint8_t *pTmp = &frame.buf[0]; -        if (noByteDelay) { -            // copy remain from delaybuf -            memcpy(pTmp, &myDelayBuf[0], noByteDelay); -            // copy new data -            memcpy(&pTmp[noByteDelay], pInData, dataIn->getLength() - noByteDelay); -            // copy remaining data to delay buf -            memcpy(&myDelayBuf[0], &pInData[dataIn->getLength() - noByteDelay], noByteDelay); -        } -        else { -            std::copy(pInData, pInData + dataIn->getLength(), -                    frame.buf.begin()); -        } - -        myEtiSource->calculateTimestamp(frame.ts); - -        if (not running.load()) { -            uhd_thread.interrupt(); -            uhd_thread.join(); -            async_rx_thread.join(); -            first_run = true; - -            etiLog.level(error) << "OutputUHD UHD worker failed"; -            throw std::runtime_error("UHD worker failed"); -        } - -        if (frame.ts.fct == -1) { -            etiLog.level(info) << -                "OutputUHD: dropping one frame with invalid FCT"; -        } -        else { -            try { -                uhdFeedback->set_tx_frame(frame.buf, frame.ts); -            } -            catch (const runtime_error& e) { -                etiLog.level(warn) << -                    "OutputUHD: Feedback server failed, restarting..."; - -                uhdFeedback = std::make_shared<OutputUHDFeedback>( -                        myUsrp, myConf.dpdFeedbackServerPort, myConf.sampleRate); -            } - -            size_t num_frames = m_frames.push_wait_if_full(frame, -                    FRAMES_MAX_SIZE); -            etiLog.log(trace, "UHD,push %zu", num_frames); -        } -    } - -    return dataIn->getLength(); -} - - -void OutputUHD::set_usrp_time() -{ -    if (myConf.enableSync and (myConf.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 { -            myUsrp->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 (myConf.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 -            myUsrp->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", -                myUsrp->get_time_now().get_real_secs()); -    } -} - -void OutputUHD::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(myConf.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 OutputUHD::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 > -                        myConf.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<bool>( -                        boost::bind(check_gps_locked, myUsrp) ); -            } -            else { -                gps_fix_pt = boost::packaged_task<bool>( -                        boost::bind(check_gps_timelock, myUsrp) ); -            } -            gps_fix_future = gps_fix_pt.get_future(); - -            gps_fix_task = boost::thread(boost::move(gps_fix_pt)); -        } -    } -} - -void OutputUHD::workerthread() -{ -    // Set thread priority to realtime -    if (int ret = set_realtime_prio(1)) { -        etiLog.level(error) << "Could not set priority for UHD worker:" << ret; -    } - -    set_thread_name("uhdworker"); - -    last_tx_time_initialised = false; - -    uhd::stream_args_t stream_args("fc32"); //complex floats -    myTxStream = myUsrp->get_tx_stream(stream_args); - -    md.start_of_burst = false; -    md.end_of_burst   = false; - -    num_underflows   = 0; -    num_late_packets = 0; - -    size_t last_num_underflows = 0; -    size_t pop_prebuffering = FRAMES_MAX_SIZE; - -    while (running.load()) { -        md.has_time_spec  = false; -        md.time_spec      = uhd::time_spec_t(0.0); - -        struct UHDWorkerFrameData frame; -        etiLog.log(trace, "UHD,wait"); -        m_frames.wait_and_pop(frame, pop_prebuffering); -        etiLog.log(trace, "UHD,pop"); - -        handle_frame(&frame); -        num_frames_modulated++; - -        /* Ensure we fill frames after every underrun and -         * at startup to reduce underrun likelihood. */ -        if (last_num_underflows < num_underflows) { -            pop_prebuffering = FRAMES_MAX_SIZE; -        } -        else { -            pop_prebuffering = 1; -        } -        last_num_underflows = num_underflows; -    } -    running.store(false); -    etiLog.level(warn) << "UHD worker terminated"; -} - -void OutputUHD::handle_frame(const struct UHDWorkerFrameData *frame) -{ -    // Transmit timeout -    static const double tx_timeout = 20.0; - -    // Check for ref_lock -    if (refclk_loss_needs_check()) { -        try { -            if (not myUsrp->get_mboard_sensor("ref_locked", 0).to_bool()) { -                etiLog.log(alert, -                        "OutputUHD: External reference clock lock lost !"); -                if (myConf.refclk_lock_loss_behaviour == CRASH) { -                    throw std::runtime_error( -                            "OutputUHD: External reference clock lock lost."); -                } -            } -        } -        catch (uhd::lookup_error &e) { -            suppress_refclk_loss_check = true; -            etiLog.log(warn, "OutputUHD: This USRP does not have mboard " -                    "sensor for ext clock loss. Check disabled."); -        } -    } - -    double usrp_time = myUsrp->get_time_now().get_real_secs(); -    bool timestamp_discontinuity = false; - -    if (sync_and_ts_valid) { -        // Tx time from MNSC and TIST -        uint32_t tx_second = frame->ts.timestamp_sec; -        uint32_t tx_pps    = frame->ts.timestamp_pps; - -        if (!frame->ts.timestamp_valid) { -            /* We have not received a full timestamp through -             * MNSC. We sleep through the frame. -             */ -            etiLog.level(info) << -                "OutputUHD: Throwing sample " << frame->ts.fct << -                " away: incomplete timestamp " << tx_second << -                " / " << tx_pps; -            usleep(20000); //TODO should this be TM-dependant ? -            return; -        } - -        if (last_tx_time_initialised) { -            const size_t sizeIn = frame->buf.size() / sizeof(complexf); -            uint64_t increment = (uint64_t)sizeIn * 16384000ul / -                                 (uint64_t)myConf.sampleRate; -                                  // samps  * ticks/s  / (samps/s) -                                  // (samps * ticks * s) / (s * samps) -                                  // ticks - -            uint32_t expected_sec = last_tx_second + increment / 16384000ul; -            uint32_t expected_pps = last_tx_pps + increment % 16384000ul; - -            while (expected_pps >= 16384000) { -                expected_sec++; -                expected_pps -= 16384000; -            } - -            if (expected_sec != tx_second or -                    expected_pps != tx_pps) { -                etiLog.level(warn) << "OutputUHD: timestamp irregularity!" << -                    std::fixed << -                    " Expected " << -                    expected_sec << "+" << (double)expected_pps/16384000.0 << -                    "(" << expected_pps << ")" << -                    " Got " << -                    tx_second << "+" << (double)tx_pps/16384000.0 << -                    "(" << tx_pps << ")"; - -                timestamp_discontinuity = true; -            } -        } - -        last_tx_second = tx_second; -        last_tx_pps    = tx_pps; -        last_tx_time_initialised = true; - -        double pps_offset = tx_pps / 16384000.0; - -        md.has_time_spec = true; -        md.time_spec = uhd::time_spec_t(tx_second, pps_offset); -        etiLog.log(trace, "UHD,tist %f", md.time_spec.get_real_secs()); - -        // md is defined, let's do some checks -        if (md.time_spec.get_real_secs() + tx_timeout < usrp_time) { -            etiLog.level(warn) << -                "OutputUHD: Timestamp in the past! offset: " << -                std::fixed << -                md.time_spec.get_real_secs() - usrp_time << -                "  (" << usrp_time << ")" -                " frame " << frame->ts.fct << -                ", tx_second " << tx_second << -                ", pps " << pps_offset; -            return; -        } - -        if (md.time_spec.get_real_secs() > usrp_time + TIMESTAMP_ABORT_FUTURE) { -            etiLog.level(error) << -                "OutputUHD: Timestamp way too far in the future! offset: " << -                std::fixed << -                md.time_spec.get_real_secs() - usrp_time; -            throw std::runtime_error("Timestamp error. Aborted."); -        } -    } -    else { // !sync_and_ts_valid -        if (myConf.muting or myConf.muteNoTimestamps) { -            /* There was some error decoding the timestamp */ -            if (myConf.muting) { -                etiLog.log(info, -                        "OutputUHD: Muting sample %d requested\n", -                        frame->ts.fct); -            } -            else { -                etiLog.log(info, -                        "OutputUHD: Muting sample %d : no timestamp\n", -                        frame->ts.fct); -            } -            usleep(20000); -            return; -        } -    } - -    tx_frame(frame, timestamp_discontinuity); -} - -void OutputUHD::tx_frame(const struct UHDWorkerFrameData *frame, bool ts_update) -{ -    const double tx_timeout = 20.0; -    const size_t sizeIn = frame->buf.size() / sizeof(complexf); -    const complexf* in_data = reinterpret_cast<const complexf*>(&frame->buf[0]); - -    size_t usrp_max_num_samps = myTxStream->get_max_num_samps(); -    size_t num_acc_samps = 0; //number of accumulated samples -    while (running.load() and (not myConf.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 = ( -                sync_and_ts_valid and -                (frame->ts.timestamp_refresh or ts_update) and -                samps_to_send <= usrp_max_num_samps ); - - -        //send a single packet -        size_t num_tx_samps = myTxStream->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/myConf.sampleRate); - -        if (num_tx_samps == 0) { -            etiLog.log(warn, -                    "OutputUHD::workerthread() unable to write to device, skipping frame!\n"); -            break; -        } -    } -} - -void OutputUHD::print_async_thread() -{ -    while (running.load()) { -        uhd::async_metadata_t async_md; -        if (myUsrp->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 = -                myUsrp->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; -        } -    } -} - -// ======================================= -// Remote Control for UHD -// ======================================= -void OutputUHD::set_parameter(const string& parameter, const string& value) -{ -    stringstream ss(value); -    ss.exceptions ( stringstream::failbit | stringstream::badbit ); - -    if (parameter == "txgain") { -        ss >> myConf.txgain; -        myUsrp->set_tx_gain(myConf.txgain); -    } -    else if (parameter == "rxgain") { -        ss >> myConf.rxgain; -        myUsrp->set_rx_gain(myConf.rxgain); -    } -    else if (parameter == "freq") { -        ss >> myConf.frequency; -        tune_usrp_to(myUsrp, myConf.lo_offset, myConf.frequency); -        myConf.frequency = myUsrp->get_tx_freq(); -    } -    else if (parameter == "muting") { -        ss >> myConf.muting; -    } -    else if (parameter == "staticdelay") { -        int64_t adjust; -        ss >> adjust; -        if (adjust > (myTFDurationMs * 1000)) -        { // reset static delay for values outside range -            myConf.staticDelayUs = 0; -        } -        else -        { // the new adjust value is added to the existing delay and the result -            // is wrapped around at TF duration -            int newStaticDelayUs = myConf.staticDelayUs + adjust; -            if (newStaticDelayUs > (myTFDurationMs * 1000)) -                myConf.staticDelayUs = newStaticDelayUs - (myTFDurationMs * 1000); -            else if (newStaticDelayUs < 0) -                myConf.staticDelayUs = newStaticDelayUs + (myTFDurationMs * 1000); -            else -                myConf.staticDelayUs = newStaticDelayUs; -        } -    } -    else if (parameter == "underruns" or -            parameter == "latepackets" or -            parameter == "frames") { -        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 OutputUHD::get_parameter(const string& parameter) const -{ -    stringstream ss; -    if (parameter == "txgain") { -        ss << myConf.txgain; -    } -    else if (parameter == "rxgain") { -        ss << myConf.rxgain; -    } -    else if (parameter == "freq") { -        ss << myConf.frequency; -    } -    else if (parameter == "muting") { -        ss << myConf.muting; -    } -    else if (parameter == "staticdelay") { -        ss << myConf.staticDelayUs; -    } -    else if (parameter == "underruns") { -        ss << num_underflows; -    } -    else if (parameter == "latepackets") { -        ss << num_late_packets; -    } -    else if (parameter == "frames") { -        ss << num_frames_modulated; -    } -    else { -        ss << "Parameter '" << parameter << -            "' is not exported by controllable " << get_rc_name(); -        throw ParameterError(ss.str()); -    } -    return ss.str(); -} - -#endif // HAVE_OUTPUT_UHD - diff --git a/src/OutputUHD.h b/src/OutputUHD.h deleted file mode 100644 index 9213183..0000000 --- a/src/OutputUHD.h +++ /dev/null @@ -1,250 +0,0 @@ -/* -   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 - -DESCRIPTION: -   It is an output driver for the USRP family of devices, and uses the UHD -   library. This version is multi-threaded. A separate thread sends the data to -   the device. - -   Data between the modulator and the UHD thread are exchanged through a -   threadsafe queue. -*/ - -/* -   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 <http://www.gnu.org/licenses/>. - */ - -#pragma once - -#ifdef HAVE_CONFIG_H -#   include <config.h> -#endif - -#ifdef HAVE_OUTPUT_UHD - -#include <uhd/utils/thread_priority.hpp> -#include <uhd/utils/safe_main.hpp> -#include <uhd/usrp/multi_usrp.hpp> -#include <boost/thread.hpp> -#include <deque> -#include <chrono> -#include <memory> -#include <string> -#include <atomic> - -#include "Log.h" -#include "ModPlugin.h" -#include "EtiReader.h" -#include "TimestampDecoder.h" -#include "RemoteControl.h" -#include "ThreadsafeQueue.h" -#include "OutputUHDFeedback.h" - -#include <stdio.h> -#include <sys/types.h> - -//#define MDEBUG(fmt, args...) fprintf(LOG, fmt , ## args) -#define MDEBUG(fmt, args...) - -// If the timestamp is further in the future than -// 100 seconds, abort -#define TIMESTAMP_ABORT_FUTURE 100 - -// Add a delay to increase buffers when -// frames are too far in the future -#define TIMESTAMP_MARGIN_FUTURE 0.5 - -typedef std::complex<float> complexf; - -// Each frame contains one OFDM frame, and its -// associated timestamp -struct UHDWorkerFrameData { -    // Buffer holding frame data -    std::vector<uint8_t> buf; - -    // A full timestamp contains a TIST according to standard -    // and time information within MNSC with tx_second. -    struct frame_timestamp ts; -}; - -enum refclk_lock_loss_behaviour_t { CRASH, IGNORE }; - -/* This structure is used as initial configuration for OutputUHD. - * It must also contain all remote-controllable settings, otherwise - * they will get lost on a modulator restart. */ -struct OutputUHDConfig { -    std::string device; -    std::string usrpType; // e.g. b100, b200, usrp2 - -    // The USRP1 can accept two daughterboards -    std::string subDevice; // e.g. A:0 - -    long masterClockRate = 32768000; -    unsigned sampleRate = 2048000; -    double frequency = 0.0; -    double lo_offset = 0.0; -    double txgain = 0.0; -    double rxgain = 0.0; -    bool enableSync = false; - -    // When working with timestamps, mute the frames that -    // do not have a timestamp -    bool muteNoTimestamps = false; -    unsigned dabMode = 0; -    unsigned maxGPSHoldoverTime = 0; - -    /* allowed values : auto, int, sma, mimo */ -    std::string refclk_src; - -    /* allowed values : int, sma, mimo */ -    std::string pps_src; - -    /* allowed values : pos, neg */ -    std::string pps_polarity; - -    /* What to do when the reference clock PLL loses lock */ -    refclk_lock_loss_behaviour_t refclk_lock_loss_behaviour; - -    // muting can only be changed using the remote control -    bool muting = false; - -    // static delay in microseconds -    int staticDelayUs = 0; - -    // TCP port on which to serve TX and RX samples for the -    // digital pre distortion learning tool -    uint16_t dpdFeedbackServerPort = 0; -}; - -class OutputUHD: public ModOutput, public RemoteControllable { -    public: -        OutputUHD(OutputUHDConfig& config); -        OutputUHD(const OutputUHD& other) = delete; -        OutputUHD operator=(const OutputUHD& other) = delete; -        ~OutputUHD(); - -        int process(Buffer* dataIn); - -        const char* name() { return "OutputUHD"; } - -        void setETISource(EtiSource *etiSource); - -        /*********** REMOTE CONTROL ***************/ - -        /* Base function to set parameters. */ -        virtual void set_parameter(const std::string& parameter, -                const std::string& value); - -        /* Getting a parameter always returns a string. */ -        virtual const std::string get_parameter( -                const std::string& parameter) const; - -    protected: -        EtiSource *myEtiSource = nullptr; -        OutputUHDConfig& myConf; -        uhd::usrp::multi_usrp::sptr myUsrp; -        std::shared_ptr<boost::barrier> mySyncBarrier; -        bool first_run = true; -        bool gps_fix_verified = false; -        std::shared_ptr<OutputUHDFeedback> uhdFeedback; - -    private: -        // Resize the internal delay buffer according to the dabMode and -        // the sample rate. -        void SetDelayBuffer(unsigned int dabMode); - -        // data -        // The remote-controllable static delay is in the OutputUHDConfig -        int myTFDurationMs; // TF duration in milliseconds -        std::vector<complexf> myDelayBuf; -        size_t lastLen = 0; - -        // GPS Fix check variables -        int num_checks_without_gps_fix = 1; -        struct timespec first_gps_fix_check; -        struct timespec last_gps_fix_check; -        struct timespec time_last_frame; -        boost::packaged_task<bool> gps_fix_pt; -        boost::unique_future<bool> gps_fix_future; -        boost::thread gps_fix_task; - -        // Wait time in seconds to get fix -        static const int initial_gps_fix_wait = 180; - -        // Interval for checking the GPS at runtime -        static constexpr double gps_fix_check_interval = 10.0; // seconds - -        // Asynchronous message statistics -        size_t num_underflows = 0; -        size_t num_late_packets = 0; -        size_t num_underflows_previous = 0; -        size_t num_late_packets_previous = 0; - -        size_t num_frames_modulated = 0; - -        uhd::tx_metadata_t md; -        bool     last_tx_time_initialised = false; -        uint32_t last_tx_second = 0; -        uint32_t last_tx_pps = 0; - -        // Used to print statistics once a second -        std::chrono::steady_clock::time_point last_print_time; - -        bool sync_and_ts_valid = false; - -        ThreadsafeQueue<UHDWorkerFrameData> m_frames; - -        // Returns true if we want to verify loss of refclk -        bool refclk_loss_needs_check(void) const; -        bool suppress_refclk_loss_check = false; - -        // Returns true if we want to check for the gps_timelock sensor -        bool gpsfix_needs_check(void) const; - -        // Return true if the gpsdo is from ettus, false if it is the ODR -        // LEA-M8F board is used -        bool gpsdo_is_ettus(void) const; - -        std::atomic<bool> running; -        boost::thread uhd_thread; -        boost::thread async_rx_thread; -        void stop_threads(void); - -        uhd::tx_streamer::sptr myTxStream; - -        // The worker thread decouples the modulator from UHD -        void workerthread(); -        void handle_frame(const struct UHDWorkerFrameData *frame); -        void tx_frame(const struct UHDWorkerFrameData *frame, bool ts_update); - -        // Poll asynchronous metadata from UHD -        void print_async_thread(void); - -        void check_gps(); - -        void set_usrp_time(); - -        void initial_gps_check(); -}; - -#endif // HAVE_OUTPUT_UHD - diff --git a/src/Socket.h b/src/Socket.h index 39554ca..392e758 100644 --- a/src/Socket.h +++ b/src/Socket.h @@ -49,6 +49,14 @@ class TCPSocket {              if ((m_sock = socket(PF_INET, SOCK_STREAM, 0)) < 0) {                  throw std::runtime_error("Can't create TCP socket");              } + +#if defined(HAVE_SO_NOSIGPIPE) +            int val = 1; +            if (setsockopt(m_sock, SOL_SOCKET, SO_NOSIGPIPE, +                        &val, sizeof(val)) < 0) { +                throw std::runtime_error("Can't set SO_NOSIGPIPE"); +            } +#endif          }          ~TCPSocket() { @@ -89,11 +97,12 @@ class TCPSocket {              addr.sin_addr.s_addr = htonl(INADDR_ANY);              const int reuse = 1; -            if (setsockopt(m_sock, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) < 0) { +            if (setsockopt(m_sock, SOL_SOCKET, SO_REUSEADDR, +                        &reuse, sizeof(reuse)) < 0) {                  throw std::runtime_error("Can't reuse address for TCP socket");              } -            if (bind(m_sock, (struct sockaddr*)&addr, sizeof(addr)) < 0) { +            if (::bind(m_sock, (struct sockaddr*)&addr, sizeof(addr)) < 0) {                  close();                  throw std::runtime_error("Can't bind TCP socket");              } @@ -138,8 +147,16 @@ class TCPSocket {          {              uint8_t *buf = (uint8_t*)buffer;              while (buflen > 0) { -                // Set MSG_NOSIGNAL to avoid that this thread gets a SIGPIPE -                ssize_t sent = send(m_sock, buf, buflen, MSG_NOSIGNAL); +                /* On Linux, the MSG_NOSIGNAL flag ensures that the process +                 * would not receive a SIGPIPE and die. +                 * Other systems have SO_NOSIGPIPE set on the socket for the +                 * same effect. */ +#if defined(HAVE_MSG_NOSIGNAL) +                const int flags = MSG_NOSIGNAL; +#else +                const int flags = 0; +#endif +                ssize_t sent = ::send(m_sock, buf, buflen, flags);                  if (sent < 0) {                      return -1;                  } diff --git a/src/TimestampDecoder.h b/src/TimestampDecoder.h index 4bbf5cc..0953f76 100644 --- a/src/TimestampDecoder.h +++ b/src/TimestampDecoder.h @@ -44,7 +44,7 @@ struct frame_timestamp      uint32_t timestamp_sec;      uint32_t timestamp_pps; // In units of 1/16384000 s -    bool timestamp_valid; +    bool timestamp_valid = false;      bool timestamp_refresh;      frame_timestamp() = default; @@ -90,6 +90,18 @@ struct frame_timestamp          return timestamp_pps / 16384000.0;      } +    double get_real_secs() const { +        double t = timestamp_sec; +        t += timestamp_pps; +        return t; +    } + +    long long int get_ns() const { +        long long int ns = timestamp_sec * 1000000000ull; +        ns += llrint((double)timestamp_pps / 0.016384); +        return ns; +    } +      void print(const char* t)      {          fprintf(stderr, diff --git a/src/Utils.cpp b/src/Utils.cpp index f423dc1..f113be3 100644 --- a/src/Utils.cpp +++ b/src/Utils.cpp @@ -214,3 +214,17 @@ double parseChannel(const std::string& chan)      }      return freq;  } + +int transmission_frame_duration_ms(unsigned int dabMode) +{ +    switch (dabMode) { +        case 1: return 96; +        case 2: return 24; +        case 3: return 24; +        case 4: return 48; +        default: +            throw std::runtime_error("invalid DAB mode"); +    } +} + + diff --git a/src/Utils.h b/src/Utils.h index 6a36baf..5d5831b 100644 --- a/src/Utils.h +++ b/src/Utils.h @@ -68,3 +68,7 @@ void set_thread_name(const char *name);  // Convert a channel like 10A to a frequency  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. +int transmission_frame_duration_ms(unsigned int dabMode); + diff --git a/src/OutputUHDFeedback.cpp b/src/output/Feedback.cpp index 68783f2..f0bbd98 100644 --- a/src/OutputUHDFeedback.cpp +++ b/src/output/Feedback.cpp @@ -34,41 +34,41 @@ DESCRIPTION:  #   include <config.h>  #endif -#ifdef HAVE_OUTPUT_UHD -  #include <vector>  #include <complex>  #include <cstring> -#include <uhd/types/stream_cmd.hpp>  #include <sys/socket.h>  #include <errno.h>  #include <poll.h>  #include <boost/date_time/posix_time/posix_time.hpp> -#include "OutputUHDFeedback.h" +#include "output/Feedback.h"  #include "Utils.h"  #include "Socket.h"  using namespace std; -typedef std::complex<float> complexf; -OutputUHDFeedback::OutputUHDFeedback( -        uhd::usrp::multi_usrp::sptr usrp, +namespace Output { + +DPDFeedbackServer::DPDFeedbackServer( +        std::shared_ptr<SDRDevice> device,          uint16_t port, -        uint32_t sampleRate) +        uint32_t sampleRate) : +    m_port(port), +    m_sampleRate(sampleRate), +    m_device(device)  { -    m_port = port; -    m_sampleRate = sampleRate; -    m_usrp = usrp; -      if (m_port) {          m_running.store(true); -        rx_burst_thread = boost::thread(&OutputUHDFeedback::ReceiveBurstThread, this); -        burst_tcp_thread = boost::thread(&OutputUHDFeedback::ServeFeedbackThread, this); +        rx_burst_thread = boost::thread( +                &DPDFeedbackServer::ReceiveBurstThread, this); + +        burst_tcp_thread = boost::thread( +                &DPDFeedbackServer::ServeFeedbackThread, this);      }  } -OutputUHDFeedback::~OutputUHDFeedback() +DPDFeedbackServer::~DPDFeedbackServer()  {      m_running.store(false); @@ -83,12 +83,12 @@ OutputUHDFeedback::~OutputUHDFeedback()      }  } -void OutputUHDFeedback::set_tx_frame( +void DPDFeedbackServer::set_tx_frame(          const std::vector<uint8_t> &buf,          const struct frame_timestamp &buf_ts)  {      if (not m_running) { -        throw runtime_error("OutputUHDFeedback not running"); +        throw runtime_error("DPDFeedbackServer not running");      }      boost::mutex::scoped_lock lock(burstRequest.mutex); @@ -131,13 +131,10 @@ void OutputUHDFeedback::set_tx_frame(      }  } -void OutputUHDFeedback::ReceiveBurstThread() +void DPDFeedbackServer::ReceiveBurstThread()  {      try { -        set_thread_name("uhdreceiveburst"); - -        uhd::stream_args_t stream_args("fc32"); //complex floats -        auto rxStream = m_usrp->get_rx_stream(stream_args); +        set_thread_name("dpdreceiveburst");          while (m_running) {              boost::mutex::scoped_lock lock(burstRequest.mutex); @@ -148,43 +145,40 @@ void OutputUHDFeedback::ReceiveBurstThread()              if (not m_running) break; -            uhd::stream_cmd_t cmd( -                    uhd::stream_cmd_t::stream_mode_t::STREAM_MODE_NUM_SAMPS_AND_DONE); -            cmd.num_samps = burstRequest.num_samples; -            cmd.stream_now = false; +            const size_t num_samps = burstRequest.num_samples; -            double pps = burstRequest.rx_pps / 16384000.0; -            cmd.time_spec = uhd::time_spec_t(burstRequest.rx_second, pps); +            frame_timestamp ts; +            ts.timestamp_sec = burstRequest.rx_second; +            ts.timestamp_pps = burstRequest.rx_pps; +            ts.timestamp_valid = true;              // We need to free the mutex while we recv(), because otherwise we block the              // TX thread              lock.unlock(); -            const double usrp_time = m_usrp->get_time_now().get_real_secs(); -            const double cmd_time = cmd.time_spec.get_real_secs(); - -            rxStream->issue_stream_cmd(cmd); +            const double device_time = m_device->get_real_secs(); +            const double cmd_time = ts.get_real_secs(); -            uhd::rx_metadata_t md; - -            std::vector<uint8_t> buf(cmd.num_samps * sizeof(complexf)); +            std::vector<uint8_t> buf(num_samps * sizeof(complexf));              const double timeout = 60; -            size_t samples_read = rxStream->recv(&buf[0], cmd.num_samps, md, timeout); +            size_t samples_read = m_device->receive_frame( +                    reinterpret_cast<complexf*>(buf.data()), +                    num_samps, ts, timeout);              lock.lock();              burstRequest.rx_samples = std::move(buf);              burstRequest.rx_samples.resize(samples_read * sizeof(complexf));              // The recv might have happened at another time than requested -            burstRequest.rx_second = md.time_spec.get_full_secs(); -            burstRequest.rx_pps = md.time_spec.get_frac_secs() * 16384000.0; +            burstRequest.rx_second = ts.timestamp_sec; +            burstRequest.rx_pps = ts.timestamp_pps;              etiLog.level(debug) << "DPD: acquired " << samples_read <<                  " RX feedback samples " <<                  "at time " << burstRequest.tx_second << " + " <<                  std::fixed << burstRequest.tx_pps / 16384000.0 << -                " Delta=" << cmd_time - usrp_time; +                " Delta=" << cmd_time - device_time;              burstRequest.state = BurstRequestState::Acquired; @@ -205,7 +199,7 @@ void OutputUHDFeedback::ReceiveBurstThread()      m_running.store(false);  } -void OutputUHDFeedback::ServeFeedback() +void DPDFeedbackServer::ServeFeedback()  {      TCPSocket m_server_sock;      m_server_sock.listen(m_port); @@ -335,9 +329,9 @@ void OutputUHDFeedback::ServeFeedback()      }  } -void OutputUHDFeedback::ServeFeedbackThread() +void DPDFeedbackServer::ServeFeedbackThread()  { -    set_thread_name("uhdservefeedback"); +    set_thread_name("dpdfeedbackserver");      while (m_running) {          try { @@ -359,4 +353,4 @@ void OutputUHDFeedback::ServeFeedbackThread()      m_running.store(false);  } -#endif +} // namespace Output diff --git a/src/OutputUHDFeedback.h b/src/output/Feedback.h index 80d287f..2cad508 100644 --- a/src/OutputUHDFeedback.h +++ b/src/output/Feedback.h @@ -36,11 +36,6 @@ DESCRIPTION:  #   include <config.h>  #endif -#ifdef HAVE_OUTPUT_UHD - -#include <uhd/utils/thread_priority.hpp> -#include <uhd/utils/safe_main.hpp> -#include <uhd/usrp/multi_usrp.hpp>  #include <boost/thread.hpp>  #include <memory>  #include <string> @@ -48,6 +43,9 @@ DESCRIPTION:  #include "Log.h"  #include "TimestampDecoder.h" +#include "output/SDRDevice.h" + +namespace Output {  enum class BurstRequestState {      None, // To pending request @@ -56,7 +54,7 @@ enum class BurstRequestState {      Acquired, // Both TX and RX frames are ready  }; -struct UHDReceiveBurstRequest { +struct FeedbackBurstRequest {      // All fields in this struct are protected      mutable boost::mutex mutex;      boost::condition_variable mutex_notification; @@ -83,21 +81,21 @@ struct UHDReceiveBurstRequest {  };  // Serve TX samples and RX feedback samples over a TCP connection -class OutputUHDFeedback { +class DPDFeedbackServer {      public: -        OutputUHDFeedback( -                uhd::usrp::multi_usrp::sptr usrp, -                uint16_t port, +        DPDFeedbackServer( +                std::shared_ptr<SDRDevice> device, +                uint16_t port, // Set to 0 to disable the Feedbackserver                  uint32_t sampleRate); -        OutputUHDFeedback(const OutputUHDFeedback& other) = delete; -        OutputUHDFeedback& operator=(const OutputUHDFeedback& other) = delete; -        ~OutputUHDFeedback(); +        DPDFeedbackServer(const DPDFeedbackServer& other) = delete; +        DPDFeedbackServer& operator=(const DPDFeedbackServer& other) = delete; +        ~DPDFeedbackServer();          void set_tx_frame(const std::vector<uint8_t> &buf,                  const struct frame_timestamp& ts);      private: -        // Thread that reacts to burstRequests and receives from the USRP +        // Thread that reacts to burstRequests and receives from the SDR device          void ReceiveBurstThread(void);          // Thread that listens for requests over TCP to get TX and RX feedback @@ -107,13 +105,12 @@ class OutputUHDFeedback {          boost::thread rx_burst_thread;          boost::thread burst_tcp_thread; -        UHDReceiveBurstRequest burstRequest; +        FeedbackBurstRequest burstRequest;          std::atomic_bool m_running;          uint16_t m_port = 0;          uint32_t m_sampleRate = 0; -        uhd::usrp::multi_usrp::sptr m_usrp; +        std::shared_ptr<SDRDevice> m_device;  }; - -#endif // HAVE_OUTPUT_UHD +} // namespace Output diff --git a/src/output/SDR.cpp b/src/output/SDR.cpp new file mode 100644 index 0000000..ba296de --- /dev/null +++ b/src/output/SDR.cpp @@ -0,0 +1,433 @@ +/* +   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 <http://www.gnu.org/licenses/>. + */ + +#include "output/SDR.h" + +#include "PcDebug.h" +#include "Log.h" +#include "RemoteControl.h" +#include "Utils.h" + +#include <cmath> +#include <iostream> +#include <assert.h> +#include <stdexcept> +#include <stdio.h> +#include <time.h> +#include <errno.h> +#include <unistd.h> +#include <pthread.h> + +using namespace std; + +namespace Output { + +// Maximum number of frames that can wait in frames +static constexpr size_t FRAMES_MAX_SIZE = 8; + +// If the timestamp is further in the future than +// 100 seconds, abort +static constexpr double TIMESTAMP_ABORT_FUTURE = 100; + +// Add a delay to increase buffers when +// frames are too far in the future +static constexpr double TIMESTAMP_MARGIN_FUTURE = 0.5; + +SDR::SDR(SDRDeviceConfig& config, std::shared_ptr<SDRDevice> device) : +    ModOutput(), +    RemoteControllable("sdr"), +    m_config(config), +    m_running(false), +    m_device(device) +{ +    // muting is remote-controllable +    m_config.muting = false; + +    time_last_frame.tv_sec = 0; +    time_last_frame.tv_nsec = 0; + +    m_device_thread = std::thread(&SDR::process_thread_entry, this); + +    m_dpd_feedback_server = make_shared<DPDFeedbackServer>( +            m_device, +            m_config.dpdFeedbackServerPort, +            m_config.sampleRate); +} + +SDR::~SDR() +{ +    stop(); +} + +void SDR::stop() +{ +    m_running.store(false); + +    FrameData end_marker; +    end_marker.buf.resize(0); +    m_queue.push(end_marker); + +    if (m_device_thread.joinable()) { +        m_device_thread.join(); +    } +} + +int SDR::process(Buffer *dataIn) +{ +    if (m_device) { +        FrameData frame; +        frame.buf.resize(dataIn->getLength()); + +        const uint8_t* pDataIn = (uint8_t*)dataIn->getData(); +        std::copy(pDataIn, pDataIn + dataIn->getLength(), +                frame.buf.begin()); + +        m_eti_source->calculateTimestamp(frame.ts); + +        // TODO check device running + +        if (frame.ts.fct == -1) { +            etiLog.level(info) << +                "SDR output: dropping one frame with invalid FCT"; +        } +        else { +            try { +                if (m_dpd_feedback_server) { +                    m_dpd_feedback_server->set_tx_frame(frame.buf, frame.ts); +                } +            } +            catch (const runtime_error& e) { +                etiLog.level(warn) << +                    "SDR output: Feedback server failed, restarting..."; + +                m_dpd_feedback_server = std::make_shared<DPDFeedbackServer>( +                        m_device, +                        m_config.dpdFeedbackServerPort, +                        m_config.sampleRate); +            } + +            size_t num_frames = m_queue.push_wait_if_full(frame, +                    FRAMES_MAX_SIZE); +            etiLog.log(trace, "SDR,push %zu", num_frames); +        } +    } +    else { +        // Ignore frame +    } + +    return dataIn->getLength(); +} + +void SDR::process_thread_entry() +{ +    // Set thread priority to realtime +    if (int ret = set_realtime_prio(1)) { +        etiLog.level(error) << "Could not set priority for SDR device thread:" << ret; +    } + +    set_thread_name("sdrdevice"); + +    last_tx_time_initialised = false; + +    size_t last_num_underflows = 0; +    size_t pop_prebuffering = FRAMES_MAX_SIZE; + +    m_running.store(true); + +    while (m_running.load()) { +        struct FrameData frame; +        etiLog.log(trace, "SDR,wait"); +        m_queue.wait_and_pop(frame, pop_prebuffering); +        etiLog.log(trace, "SDR,pop"); + +        if (m_running.load() == false or frame.buf.empty()) { +            break; +        } + +        if (m_device) { +            handle_frame(frame); + +            const auto rs = m_device->get_run_statistics(); + +            /* Ensure we fill frames after every underrun and +             * at startup to reduce underrun likelihood. */ +            if (last_num_underflows < rs.num_underruns) { +                pop_prebuffering = FRAMES_MAX_SIZE; +            } +            else { +                pop_prebuffering = 1; +            } + +            last_num_underflows = rs.num_underruns; +        } + +    } + +    m_running.store(false); +    etiLog.level(warn) << "SDR Device thread terminated"; +} + +const char* SDR::name() +{ +    if (m_device) { +        m_name = "OutputSDR("; +        m_name += m_device->device_name(); +        m_name += ")"; +    } +    else { +        m_name = "OutputSDR(<no device>)"; +    } +    return m_name.c_str(); +} + +void SDR::setETISource(EtiSource *etiSource) +{ +    m_eti_source = etiSource; +} + +void SDR::sleep_through_frame() +{ +    struct timespec now; +    if (clock_gettime(CLOCK_MONOTONIC, &now) != 0) { +        stringstream ss; +        ss << "clock_gettime failure: " << strerror(errno); +        throw 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 runtime_error(ss.str()); +        } +    } + +    long delta_us = timespecdiff_us(time_last_frame, now); +    long wait_time_us = transmission_frame_duration_ms(m_config.dabMode); + +    if (wait_time_us - delta_us > 0) { +        usleep(wait_time_us - delta_us); +    } + +    time_last_frame.tv_nsec += wait_time_us * 1000; +    while (time_last_frame.tv_nsec >= 1000000000L) { +        time_last_frame.tv_nsec -= 1000000000L; +        time_last_frame.tv_sec++; +    } +} + +void SDR::handle_frame(struct FrameData& frame) +{ +    // Assumes m_device is valid + +    constexpr double tx_timeout = 20.0; + +    if (not m_device->is_clk_source_ok()) { +        sleep_through_frame(); +        return; +    } + +    double device_time = m_device->get_real_secs(); +    bool timestamp_discontinuity = false; +    const auto& time_spec = frame.ts; + +    if (sourceContainsTimestamp) { +        // Tx time from MNSC and TIST +        const uint32_t tx_second = frame.ts.timestamp_sec; +        const uint32_t tx_pps    = frame.ts.timestamp_pps; + +        if (not frame.ts.timestamp_valid) { +            /* We have not received a full timestamp through +             * MNSC. We sleep through the frame. +             */ +            etiLog.level(info) << +                "OutputSDR: Throwing sample " << frame.ts.fct << +                " away: incomplete timestamp " << tx_second << +                " / " << tx_pps; +            return; +        } + +        if (last_tx_time_initialised) { +            const size_t sizeIn = frame.buf.size() / sizeof(complexf); +            uint64_t increment = (uint64_t)sizeIn * 16384000ul / +                                 (uint64_t)m_config.sampleRate; +                                  // samps  * ticks/s  / (samps/s) +                                  // (samps * ticks * s) / (s * samps) +                                  // ticks + +            uint32_t expected_sec = last_tx_second + increment / 16384000ul; +            uint32_t expected_pps = last_tx_pps + increment % 16384000ul; + +            while (expected_pps >= 16384000) { +                expected_sec++; +                expected_pps -= 16384000; +            } + +            if (expected_sec != tx_second or expected_pps != tx_pps) { +                etiLog.level(warn) << "OutputSDR: timestamp irregularity!" << +                    std::fixed << +                    " Expected " << +                    expected_sec << "+" << (double)expected_pps/16384000.0 << +                    "(" << expected_pps << ")" << +                    " Got " << +                    tx_second << "+" << (double)tx_pps/16384000.0 << +                    "(" << tx_pps << ")"; + +                timestamp_discontinuity = true; +            } +        } + +        last_tx_second = tx_second; +        last_tx_pps    = tx_pps; +        last_tx_time_initialised = true; + +        const double pps_offset = tx_pps / 16384000.0; + +        etiLog.log(trace, "SDR,tist %f", time_spec.get_real_secs()); + +        if (time_spec.get_real_secs() + tx_timeout < device_time) { +            etiLog.level(warn) << +                "OutputSDR: Timestamp in the past! offset: " << +                std::fixed << +                time_spec.get_real_secs() - device_time << +                "  (" << device_time << ")" +                " frame " << frame.ts.fct << +                ", tx_second " << tx_second << +                ", pps " << pps_offset; +            return; +        } + +        if (time_spec.get_real_secs() > device_time + TIMESTAMP_ABORT_FUTURE) { +            etiLog.level(error) << +                "OutputSDR: Timestamp way too far in the future! offset: " << +                std::fixed << +                time_spec.get_real_secs() - device_time; +            throw std::runtime_error("Timestamp error. Aborted."); +        } +    } +    else { // !sourceContainsTimestamp +        if (m_config.muting or m_config.muteNoTimestamps) { +            /* There was some error decoding the timestamp */ +            if (m_config.muting) { +                etiLog.log(info, +                        "OutputSDR: Muting sample %d requested\n", +                        frame.ts.fct); +            } +            else { +                etiLog.log(info, +                        "OutputSDR: Muting sample %d : no timestamp\n", +                        frame.ts.fct); +            } +            return; +        } +    } + +    if (timestamp_discontinuity) { +        frame.ts.timestamp_refresh = true; +    } +    m_device->transmit_frame(frame); +} + +// ======================================= +// Remote Control +// ======================================= +void SDR::set_parameter(const string& parameter, const string& value) +{ +    stringstream ss(value); +    ss.exceptions ( stringstream::failbit | stringstream::badbit ); + +    if (parameter == "txgain") { +        ss >> m_config.txgain; +        m_device->set_txgain(m_config.txgain); +    } +    else if (parameter == "rxgain") { +        ss >> m_config.rxgain; +        // TODO myUsrp->set_rx_gain(m_config.rxgain); +        throw ParameterError("Parameter " + parameter + " is TODO."); +    } +    else if (parameter == "freq") { +        ss >> m_config.frequency; +        m_device->tune(m_config.lo_offset, m_config.frequency); +        m_config.frequency = m_device->get_tx_freq(); +    } +    else if (parameter == "muting") { +        ss >> m_config.muting; +    } +    else if (parameter == "underruns" or +             parameter == "latepackets" or +             parameter == "frames") { +        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 SDR::get_parameter(const string& parameter) const +{ +    stringstream ss; +    if (parameter == "txgain") { +        ss << m_config.txgain; +    } +    else if (parameter == "rxgain") { +        ss << m_config.rxgain; +    } +    else if (parameter == "freq") { +        ss << m_config.frequency; +    } +    else if (parameter == "muting") { +        ss << m_config.muting; +    } +    else if (parameter == "underruns" or +            parameter == "latepackets" or +            parameter == "frames" ) { +        if (not m_device) { +            throw ParameterError("OutputSDR has no device"); +        } +        const auto stat = m_device->get_run_statistics(); + +        if (parameter == "underruns") { +            ss << stat.num_underruns; +        } +        else if (parameter == "latepackets") { +            ss << stat.num_late_packets; +        } +        else if (parameter == "frames") { +            ss << stat.num_frames_modulated; +        } +    } +    else { +        ss << "Parameter '" << parameter << +            "' is not exported by controllable " << get_rc_name(); +        throw ParameterError(ss.str()); +    } +    return ss.str(); +} + +} // namespace Output diff --git a/src/output/SDR.h b/src/output/SDR.h new file mode 100644 index 0000000..d3693da --- /dev/null +++ b/src/output/SDR.h @@ -0,0 +1,97 @@ +/* +   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 + +DESCRIPTION: +   Common interface for all SDR outputs +*/ + +/* +   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 <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#ifdef HAVE_CONFIG_H +#   include <config.h> +#endif + +#include "ModPlugin.h" +#include "EtiReader.h" +#include "output/SDRDevice.h" +#include "output/Feedback.h" + +namespace Output { + +using complexf = std::complex<float>; + +class SDR : public ModOutput, public RemoteControllable { +    public: +        SDR(SDRDeviceConfig& config, std::shared_ptr<SDRDevice> device); +        SDR(const SDR& other) = delete; +        SDR operator=(const SDR& other) = delete; +        ~SDR(); + +        virtual int process(Buffer *dataIn) override; + +        virtual const char* name() override; + +        void setETISource(EtiSource *etiSource); + +        /*********** REMOTE CONTROL ***************/ + +        /* 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: +        void stop(void); +        void process_thread_entry(void); +        void handle_frame(struct FrameData &frame); +        void sleep_through_frame(void); + +        SDRDeviceConfig& m_config; + +        std::atomic<bool> m_running; +        std::thread m_device_thread; +        ThreadsafeQueue<FrameData> m_queue; + +        std::shared_ptr<SDRDevice> m_device; +        std::string m_name; + +        std::shared_ptr<DPDFeedbackServer> m_dpd_feedback_server; + +        EtiSource *m_eti_source = nullptr; +        bool     sourceContainsTimestamp = false; +        bool     last_tx_time_initialised = false; +        uint32_t last_tx_second = 0; +        uint32_t last_tx_pps = 0; + +        struct timespec time_last_frame; + +}; + +} + diff --git a/src/output/SDRDevice.h b/src/output/SDRDevice.h new file mode 100644 index 0000000..856233b --- /dev/null +++ b/src/output/SDRDevice.h @@ -0,0 +1,136 @@ +/* +   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 + +DESCRIPTION: +   Common interface for all SDR outputs +*/ + +/* +   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 <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#ifdef HAVE_CONFIG_H +#   include <config.h> +#endif + +#include <cstdint> +#include <string> +#include <vector> +#include <complex> + +#include "TimestampDecoder.h" + +namespace Output { + +enum refclk_lock_loss_behaviour_t { CRASH, IGNORE }; + +using complexf = std::complex<float>; + +/* This structure is used as initial configuration for all SDR devices. + * It must also contain all remote-controllable settings, otherwise + * they will get lost on a modulator restart. */ +struct SDRDeviceConfig { +    std::string device; +    std::string subDevice; // For UHD + +    long masterClockRate = 32768000; +    unsigned sampleRate = 2048000; +    double frequency = 0.0; +    double lo_offset = 0.0; +    double txgain = 0.0; +    double rxgain = 0.0; +    bool enableSync = false; + +    // When working with timestamps, mute the frames that +    // do not have a timestamp +    bool muteNoTimestamps = false; +    unsigned dabMode = 0; +    unsigned maxGPSHoldoverTime = 0; + +    /* allowed values for UHD : auto, int, sma, mimo */ +    std::string refclk_src; + +    /* allowed values for UHD : int, sma, mimo */ +    std::string pps_src; + +    /* allowed values for UHD : pos, neg */ +    std::string pps_polarity; + +    /* What to do when the reference clock PLL loses lock */ +    refclk_lock_loss_behaviour_t refclk_lock_loss_behaviour; + +    // muting can only be changed using the remote control +    bool muting = false; + +    // TCP port on which to serve TX and RX samples for the +    // digital pre distortion learning tool +    uint16_t dpdFeedbackServerPort = 0; +}; + +// Each frame contains one OFDM frame, and its +// associated timestamp +struct FrameData { +    // Buffer holding frame data +    std::vector<uint8_t> buf; + +    // A full timestamp contains a TIST according to standard +    // and time information within MNSC with tx_second. +    struct frame_timestamp ts; +}; + + +// All SDR Devices must implement the SDRDevice interface +class SDRDevice { +    public: +        struct RunStatistics { +            size_t num_underruns; +            size_t num_late_packets; +            size_t num_overruns; +            size_t num_frames_modulated; //TODO increment +        }; + +        // TODO make some functions const +        virtual void tune(double lo_offset, double frequency) = 0; +        virtual double get_tx_freq(void) = 0; +        virtual void set_txgain(double txgain) = 0; +        virtual double get_txgain(void) = 0; +        virtual void transmit_frame(const struct FrameData& frame) = 0; +        virtual RunStatistics get_run_statistics(void) = 0; +        virtual double get_real_secs(void) = 0; +        virtual void set_rxgain(double rxgain) = 0; +        virtual double get_rxgain(void) = 0; +        virtual size_t receive_frame( +                complexf *buf, +                size_t num_samples, +                struct frame_timestamp& ts, +                double timeout_secs) = 0; + + +        // Return true if GPS and reference clock inputs are ok +        virtual bool is_clk_source_ok(void) = 0; + +        virtual const char* device_name(void) = 0; +}; + +} // namespace Output diff --git a/src/output/Soapy.cpp b/src/output/Soapy.cpp new file mode 100644 index 0000000..e0a6f10 --- /dev/null +++ b/src/output/Soapy.cpp @@ -0,0 +1,264 @@ +/* +   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 + +DESCRIPTION: +   It is an output driver using the SoapySDR library that can output to +   many devices. +*/ + +/* +   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 <http://www.gnu.org/licenses/>. + */ + +#include "output/Soapy.h" + +#ifdef HAVE_SOAPYSDR + +#include <SoapySDR/Errors.hpp> +#include <chrono> +#include <cstdio> + +#include "Log.h" +#include "Utils.h" + +using namespace std; + +namespace Output { + +static constexpr size_t FRAMES_MAX_SIZE = 2; + +Soapy::Soapy(SDRDeviceConfig& config) : +    SDRDevice(), +    m_conf(config) +{ +    etiLog.level(info) << +        "Soapy:Creating the device with: " << +        m_conf.device; + +    try { +        m_device = SoapySDR::Device::make(m_conf.device); +        stringstream ss; +        ss << "SoapySDR driver=" << m_device->getDriverKey(); +        ss << " hardware=" << m_device->getHardwareKey(); +        for (const auto &it : m_device->getHardwareInfo()) { +            ss << "  " << it.first << "=" << it.second; +        } +    } +    catch (const std::exception &ex) { +        etiLog.level(error) << "Error making SoapySDR device: " << +            ex.what(); +        throw std::runtime_error("Cannot create SoapySDR output"); +    } + +    m_device->setMasterClockRate(m_conf.masterClockRate); +    etiLog.level(info) << "SoapySDR master clock rate set to " << +        m_device->getMasterClockRate()/1000.0 << " kHz"; + +    m_device->setSampleRate(SOAPY_SDR_TX, 0, m_conf.sampleRate); +    etiLog.level(info) << "SoapySDR:Actual TX rate: " << +        m_device->getSampleRate(SOAPY_SDR_TX, 0) / 1000.0 << +        " ksps."; + +    tune(m_conf.lo_offset, m_conf.frequency); +    m_conf.frequency = m_device->getFrequency(SOAPY_SDR_TX, 0); +    etiLog.level(info) << "SoapySDR:Actual frequency: " << +        m_conf.frequency / 1000.0 << +        " kHz."; + +    m_device->setGain(SOAPY_SDR_TX, 0, m_conf.txgain); +    etiLog.level(info) << "SoapySDR:Actual tx gain: " << +        m_device->getGain(SOAPY_SDR_TX, 0); + +    const std::vector<size_t> channels({0}); +    m_tx_stream = m_device->setupStream(SOAPY_SDR_TX, "CF32", channels); +    m_device->activateStream(m_tx_stream); + +    m_rx_stream = m_device->setupStream(SOAPY_SDR_RX, "CF32", channels); +} + +Soapy::~Soapy() +{ +    if (m_device != nullptr) { +        if (m_tx_stream != nullptr) { +            m_device->closeStream(m_tx_stream); +        } +        SoapySDR::Device::unmake(m_device); +    } +} + +void Soapy::tune(double lo_offset, double frequency) +{ +    if (not m_device) throw runtime_error("Soapy device not set up"); + +    SoapySDR::Kwargs offset_arg; +    offset_arg["OFFSET"] = to_string(lo_offset); +    m_device->setFrequency(SOAPY_SDR_TX, 0, m_conf.frequency, offset_arg); +} + +double Soapy::get_tx_freq(void) +{ +    if (not m_device) throw runtime_error("Soapy device not set up"); + +    // TODO lo offset +    return m_device->getFrequency(SOAPY_SDR_TX, 0); +} + +void Soapy::set_txgain(double txgain) +{ +    m_conf.txgain = txgain; +    if (not m_device) throw runtime_error("Soapy device not set up"); +    m_device->setGain(SOAPY_SDR_TX, 0, m_conf.txgain); +} + +double Soapy::get_txgain(void) +{ +    if (not m_device) throw runtime_error("Soapy device not set up"); +    return m_device->getGain(SOAPY_SDR_TX, 0); +} + +SDRDevice::RunStatistics Soapy::get_run_statistics(void) +{ +    RunStatistics rs; +    rs.num_underruns = underflows; +    rs.num_overruns = overflows; +    rs.num_late_packets = late_packets; +    rs.num_frames_modulated = num_frames_modulated; +    return rs; +} + + +double Soapy::get_real_secs(void) +{ +    if (m_device) { +        long long time_ns = m_device->getHardwareTime(); +        return time_ns / 1e9; +    } +    else { +        return 0.0; +    } +} + +void Soapy::set_rxgain(double rxgain) +{ +    m_device->setGain(SOAPY_SDR_RX, 0, m_conf.rxgain); +    m_conf.rxgain = m_device->getGain(SOAPY_SDR_RX, 0); +} + +double Soapy::get_rxgain(void) +{ +    return m_device->getGain(SOAPY_SDR_RX, 0); +} + +size_t Soapy::receive_frame( +        complexf *buf, +        size_t num_samples, +        struct frame_timestamp& ts, +        double timeout_secs) +{ +    int flags = 0; +    long long timeNs = ts.get_ns(); +    const size_t numElems = num_samples; + +    void *buffs[1]; +    buffs[0] = buf; + +    m_device->activateStream(m_rx_stream, flags, timeNs, numElems); + +    auto ret = m_device->readStream(m_tx_stream, buffs, num_samples, flags, timeNs); + +    m_device->deactivateStream(m_rx_stream); + +    // TODO update effective receive ts + +    if (ret < 0) { +        throw runtime_error("Soapy readStream error: " + to_string(ret)); +    } + +    return ret; +} + + +bool Soapy::is_clk_source_ok() +{ +    // TODO +    return true; +} + +const char* Soapy::device_name(void) +{ +    return "Soapy"; +} + +void Soapy::transmit_frame(const struct FrameData& frame) +{ +    if (not m_device) throw runtime_error("Soapy device not set up"); + +    // TODO timestamps + +    // The frame buffer contains bytes representing FC32 samples +    const complexf *buf = reinterpret_cast<const complexf*>(frame.buf.data()); +    const size_t numSamples = frame.buf.size() / sizeof(complexf); +    if ((frame.buf.size() % sizeof(complexf)) != 0) { +        throw std::runtime_error("Soapy: invalid buffer size"); +    } + +    // Stream MTU is in samples, not bytes. +    const size_t mtu = m_device->getStreamMTU(m_tx_stream); + +    size_t num_acc_samps = 0; +    while (num_acc_samps < numSamples) { +        const void *buffs[1]; +        buffs[0] = buf + num_acc_samps; + +        const size_t samps_to_send = std::min(numSamples - num_acc_samps, mtu); + +        int flags = 0; + +        auto ret = m_device->writeStream(m_tx_stream, buffs, samps_to_send, flags); + +        if (ret == SOAPY_SDR_TIMEOUT) { +            continue; +        } +        else if (ret == SOAPY_SDR_OVERFLOW) { +            overflows++; +            continue; +        } +        else if (ret == SOAPY_SDR_UNDERFLOW) { +            underflows++; +            continue; +        } + +        if (ret < 0) { +            etiLog.level(error) << "Unexpected stream error " << +                SoapySDR::errToStr(ret); +            throw std::runtime_error("Fault in Soapy"); +        } + +        num_acc_samps += ret; +    } +} + +} // namespace Output + +#endif // HAVE_SOAPYSDR + + diff --git a/src/output/Soapy.h b/src/output/Soapy.h new file mode 100644 index 0000000..c603193 --- /dev/null +++ b/src/output/Soapy.h @@ -0,0 +1,98 @@ +/* +   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 + +DESCRIPTION: +   It is an output driver using the SoapySDR library that can output to +   many devices. +*/ + +/* +   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 <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#ifdef HAVE_CONFIG_H +#   include <config.h> +#endif + +#ifdef HAVE_SOAPYSDR +#include <SoapySDR/Version.hpp> +#include <SoapySDR/Modules.hpp> +#include <SoapySDR/Registry.hpp> +#include <SoapySDR/Device.hpp> + +#include <string> +#include <memory> + +#include "output/SDR.h" +#include "ModPlugin.h" +#include "EtiReader.h" +#include "RemoteControl.h" +#include "ThreadsafeQueue.h" + +namespace Output { + +class Soapy : public Output::SDRDevice +{ +    public: +        Soapy(SDRDeviceConfig& config); +        Soapy(const Soapy& other) = delete; +        Soapy& operator=(const Soapy& other) = delete; +        ~Soapy(); + +        virtual void tune(double lo_offset, double frequency) override; +        virtual double get_tx_freq(void) override; +        virtual void set_txgain(double txgain) override; +        virtual double get_txgain(void) override; +        virtual void transmit_frame(const struct FrameData& frame) override; +        virtual RunStatistics get_run_statistics(void) override; +        virtual double get_real_secs(void) override; + +        virtual void set_rxgain(double rxgain) override; +        virtual double get_rxgain(void) override; +        virtual size_t receive_frame( +                complexf *buf, +                size_t num_samples, +                struct frame_timestamp& ts, +                double timeout_secs) override; + +        // Return true if GPS and reference clock inputs are ok +        virtual bool is_clk_source_ok(void) override; +        virtual const char* device_name(void) override; + +    private: +        SDRDeviceConfig& m_conf; +        SoapySDR::Device *m_device = nullptr; +        SoapySDR::Stream *m_tx_stream = nullptr; +        SoapySDR::Stream *m_rx_stream = nullptr; + +        size_t underflows = 0; +        size_t overflows = 0; +        size_t late_packets = 0; +        size_t num_frames_modulated = 0; //TODO increment +}; + +} // namespace Output + +#endif //HAVE_SOAPYSDR + diff --git a/src/output/UHD.cpp b/src/output/UHD.cpp new file mode 100644 index 0000000..cee35c7 --- /dev/null +++ b/src/output/UHD.cpp @@ -0,0 +1,485 @@ +/* +   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 <http://www.gnu.org/licenses/>. + */ + +#include "output/UHD.h" + +#ifdef HAVE_OUTPUT_UHD + +//#define MDEBUG(fmt, args...) fprintf(LOG, fmt , ## args) +#define MDEBUG(fmt, args...) + +#include "PcDebug.h" +#include "Log.h" +#include "RemoteControl.h" +#include "Utils.h" + +#include <boost/thread/future.hpp> + +#include <uhd/utils/msg.hpp> + +#include <cmath> +#include <iostream> +#include <assert.h> +#include <stdexcept> +#include <stdio.h> +#include <time.h> +#include <errno.h> +#include <unistd.h> +#include <pthread.h> + +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; +        } +    } +} + + + +UHD::UHD( +        SDRDeviceConfig& config) : +    SDRDevice(), +    m_conf(config), +    m_running(false) +{ +    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); + +    m_device_time = std::make_shared<USRPTime>(m_usrp, m_conf); + +    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<OutputUHDFeedback>( +            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<const complexf*>(&frame.buf[0]); + +    uhd::tx_metadata_t md_tx; + +    // TODO check for enableSync? +    if (frame.ts.timestamp_valid) { +        uhd::time_spec_t timespec(frame.ts.timestamp_sec, frame.ts.pps_offset()); +        md_tx.time_spec = timespec; +        md_tx.has_time_spec = true; +    } +    else { +        md_tx.has_time_spec = false; +    } + + +    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); + +        // 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_tx.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_rx; + +    constexpr double timeout = 60; +    size_t samples_read = m_rx_stream->recv(buf, num_samples, md_rx, timeout); + +    // Update the ts with the effective receive TS +    ts.timestamp_sec = md_rx.time_spec.get_full_secs(); +    ts.timestamp_pps = md_rx.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) +{ +    bool ok = true; + +    if (refclk_loss_needs_check()) { +        try { +            if (not m_usrp->get_mboard_sensor("ref_locked", 0).to_bool()) { +                ok = false; + +                etiLog.level(alert) << +                    "OutputUHD: External reference clock lock lost !"; + +                if (m_conf.refclk_lock_loss_behaviour == CRASH) { +                    throw std::runtime_error( +                            "OutputUHD: External reference clock lock lost."); +                } +            } +        } +        catch (uhd::lookup_error &e) { +            suppress_refclk_loss_check = true; +            etiLog.log(warn, "OutputUHD: This USRP does not have mboard " +                    "sensor for ext clock loss. Check disabled."); +        } +    } + +    if (m_device_time) { +        ok |= m_device_time->verify_time(); +    } + +    return ok; +} + +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"; +} + +void UHD::stop_threads() +{ +    m_running.store(false); +    if (m_async_rx_thread.joinable()) { +        m_async_rx_thread.join(); +    } +} + + + +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 " << +                    async_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 + diff --git a/src/output/UHD.h b/src/output/UHD.h new file mode 100644 index 0000000..448fb3f --- /dev/null +++ b/src/output/UHD.h @@ -0,0 +1,127 @@ +/* +   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 + +DESCRIPTION: +   It is an output driver for the USRP family of devices, and uses the UHD +   library. +*/ + +/* +   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 <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#ifdef HAVE_CONFIG_H +#   include <config.h> +#endif + +#ifdef HAVE_OUTPUT_UHD + +#include <uhd/utils/thread_priority.hpp> +#include <uhd/utils/safe_main.hpp> +#include <uhd/usrp/multi_usrp.hpp> +#include <chrono> +#include <memory> +#include <string> +#include <atomic> + +#include "Log.h" +#include "output/SDR.h" +#include "output/USRPTime.h" +#include "TimestampDecoder.h" +#include "RemoteControl.h" +#include "ThreadsafeQueue.h" + +#include <stdio.h> +#include <sys/types.h> + +// If the timestamp is further in the future than +// 100 seconds, abort +#define TIMESTAMP_ABORT_FUTURE 100 + +// Add a delay to increase buffers when +// frames are too far in the future +#define TIMESTAMP_MARGIN_FUTURE 0.5 + +namespace Output { + +class UHD : public Output::SDRDevice +{ +    public: +        UHD(SDRDeviceConfig& config); +        UHD(const UHD& other) = delete; +        UHD& operator=(const UHD& other) = delete; +        ~UHD(); + +        virtual void tune(double lo_offset, double frequency) override; +        virtual double get_tx_freq(void) override; +        virtual void set_txgain(double txgain) override; +        virtual double get_txgain(void) override; +        virtual void transmit_frame(const struct FrameData& frame) override; +        virtual RunStatistics get_run_statistics(void) override; +        virtual double get_real_secs(void) override; + +        virtual void set_rxgain(double rxgain) override; +        virtual double get_rxgain(void) override; +        virtual size_t receive_frame( +                complexf *buf, +                size_t num_samples, +                struct frame_timestamp& ts, +                double timeout_secs) override; + +        // Return true if GPS and reference clock inputs are ok +        virtual bool is_clk_source_ok(void) override; +        virtual const char* device_name(void) override; + +    private: +        SDRDeviceConfig& m_conf; +        uhd::usrp::multi_usrp::sptr m_usrp; +        uhd::tx_streamer::sptr m_tx_stream; +        uhd::rx_streamer::sptr m_rx_stream; +        std::shared_ptr<USRPTime> m_device_time; + +        size_t num_underflows = 0; +        size_t num_overflows = 0; +        size_t num_late_packets = 0; +        size_t num_frames_modulated = 0; //TODO increment +        size_t num_underflows_previous = 0; +        size_t num_late_packets_previous = 0; + +        // Used to print statistics once a second +        std::chrono::steady_clock::time_point last_print_time; + +        // Returns true if we want to verify loss of refclk +        bool refclk_loss_needs_check(void) const; +        bool suppress_refclk_loss_check = false; + +        // Poll asynchronous metadata from UHD +        std::atomic<bool> m_running; +        boost::thread m_async_rx_thread; +        void stop_threads(void); +        void print_async_thread(void); +}; + +} // namespace Output + +#endif // HAVE_OUTPUT_UHD + diff --git a/src/output/USRPTime.cpp b/src/output/USRPTime.cpp new file mode 100644 index 0000000..dcedeab --- /dev/null +++ b/src/output/USRPTime.cpp @@ -0,0 +1,280 @@ +/* +   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 + +DESCRIPTION: +   The part of the UHD output that takes care of the GPSDO. +*/ + +/* +   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 <http://www.gnu.org/licenses/>. + */ + +#include "output/USRPTime.h" + +#ifdef HAVE_OUTPUT_UHD + +//#define MDEBUG(fmt, args...) fprintf(LOG, fmt , ## args) +#define MDEBUG(fmt, args...) + +namespace Output { + +using namespace std; + + +// 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 { +        const string sensor_value = +            usrp->get_mboard_sensor("gps_timelock", 0).to_pp_string(); + +        if (sensor_value.find("TIME LOCKED") == string::npos) { +            etiLog.level(warn) << "OutputUHD: gps_timelock " << sensor_value; +            return false; +        } + +        return true; +    } +    catch (const 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 { +        const 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 (const uhd::lookup_error &e) { +        etiLog.level(warn) << "OutputUHD: no gps_locked sensor"; +        return false; +    } +} + + +USRPTime::USRPTime( +        uhd::usrp::multi_usrp::sptr usrp, +        SDRDeviceConfig& conf) : +    m_usrp(usrp), +    m_conf(conf), +    time_last_check(timepoint_t::clock::now()) +{ +    if (m_conf.enableSync and (m_conf.pps_src == "none")) { +        set_usrp_time_from_localtime(); +    } +} + +bool USRPTime::verify_time() +{ +    if (not gpsfix_needs_check()) { +        return true; +    } + +    /* During bootup, we say the gpsdo is not ok, and we poll the GPSDO until +     * we reach lock. Then we sync time. If we do not reach lock in time, we +     * crash. +     * +     * Once we are synced and we have lock, everything ok. If we lose lock for +     * a number of seconds, we switch to the lost_fix state. +     * +     * In the lost fix state, we return false to get the TX muted, and we monitor. +     * If the fix comes back, we unmute. If we reach the timeout, we crash. +     */ + +    check_gps(); + +    const auto duration_without_fix = +        gps_fix_check_interval * num_checks_without_gps_fix; + +    switch (gps_state) { +        case gps_state_e::bootup: +            if (duration_without_fix > initial_gps_fix_wait) { +                throw runtime_error("GPS did not fix in " + +                        to_string(initial_gps_fix_wait) + " seconds"); +            } + +            if (num_checks_without_gps_fix == 0) { +                if (m_conf.pps_src != "none") { +                    set_usrp_time_from_pps(); +                } +                gps_state = gps_state_e::monitor_fix; +                return true; +            } + +            return false; + +        case gps_state_e::monitor_fix: +            if (duration_without_fix > m_conf.maxGPSHoldoverTime) { +                throw runtime_error("Lost GPS Fix for " + +                        to_string(duration_without_fix) + " seconds"); +            } + +            return true; +    } + +    throw logic_error("End of USRPTime::verify_time() reached"); +} + +void USRPTime::check_gps() +{ +    timepoint_t time_now = timepoint_t::clock::now(); + +    // Divide interval by two because we alternate between +    // launch and check +    const auto checkinterval = chrono::seconds(lrint(gps_fix_check_interval/2.0)); + +    if (gpsfix_needs_check() and time_last_check + checkinterval < time_now) { +        time_last_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; +                } +            } +        } +        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<bool>( +                        boost::bind(check_gps_locked, m_usrp) ); +            } +            else { +                gps_fix_pt = boost::packaged_task<bool>( +                        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)); +        } +    } +} + +bool USRPTime::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 USRPTime::gpsdo_is_ettus() const +{ +    return (m_conf.refclk_src == "gpsdo-ettus"); +} + +void USRPTime::set_usrp_time_from_localtime() +{ +    etiLog.level(warn) << +        "OutputUHD: WARNING:" +        " you are using synchronous transmission without PPS input!"; + +    struct timespec now; +    if (clock_gettime(CLOCK_REALTIME, &now)) { +        etiLog.level(error) << "OutputUHD: could not get time :" << +            strerror(errno); +    } +    else { +        const uhd::time_spec_t t(now.tv_sec, (double)now.tv_nsec / 1e9); +        m_usrp->set_time_now(t); +        etiLog.level(info) << "OutputUHD: Setting USRP time to " << +            std::fixed << t.get_real_secs(); +    } +} + +void USRPTime::set_usrp_time_from_pps() +{ +    /* 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()); +} + + +} // namespace Output + +#endif // HAVE_OUTPUT_UHD diff --git a/src/output/USRPTime.h b/src/output/USRPTime.h new file mode 100644 index 0000000..7527f21 --- /dev/null +++ b/src/output/USRPTime.h @@ -0,0 +1,116 @@ +/* +   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 + +DESCRIPTION: +   The part of the UHD output that takes care of the GPSDO and setting device +   time. +*/ + +/* +   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 <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#ifdef HAVE_CONFIG_H +#   include <config.h> +#endif + +#ifdef HAVE_OUTPUT_UHD + +#include <uhd/usrp/multi_usrp.hpp> +#include <chrono> +#include <memory> +#include <string> +#include <atomic> + +#include "Log.h" +#include "output/SDR.h" +#include "TimestampDecoder.h" +#include "RemoteControl.h" +#include "ThreadsafeQueue.h" + +#include <stdio.h> +#include <sys/types.h> + +namespace Output { + +class USRPTime { +    public: +        USRPTime( uhd::usrp::multi_usrp::sptr usrp, +                SDRDeviceConfig& conf); + +        // Verifies the GPSDO state, that the device time is ok. +        // Returns true if all ok. +        // Should be called more often than the gps_fix_check_interval +        bool verify_time(void); + +        // Wait time in seconds to get fix +        static const int initial_gps_fix_wait = 180; + +        // Interval for checking the GPS at runtime +        static constexpr double gps_fix_check_interval = 10.0; // seconds + +    private: +        enum class gps_state_e { +            /* At startup, the LEA-M8F GPSDO gets issued a hotstart request to +             * make sure we will not sync time on a PPS edge that is generated +             * while the GPSDO is in holdover. In the bootup state, we wait for +             * the first PPS after hotstart, and then sync time. +             */ +            bootup, + +            /* Once the system is up, we check lock every now and then. If the +             * fix is lost for too long, we crash. +             */ +            monitor_fix, +        }; + +        void check_gps(); + +        uhd::usrp::multi_usrp::sptr m_usrp; +        SDRDeviceConfig& m_conf; + +        gps_state_e gps_state = gps_state_e::bootup; +        int num_checks_without_gps_fix = 1; + +        using timepoint_t = std::chrono::time_point<std::chrono::steady_clock>; +        timepoint_t time_last_check; + +        boost::packaged_task<bool> gps_fix_pt; +        boost::unique_future<bool> gps_fix_future; +        boost::thread gps_fix_task; + +        // Returns true if we want to check for the gps_timelock sensor +        bool gpsfix_needs_check(void) const; + +        // Return true if the gpsdo is from ettus, false if it is the ODR +        // LEA-M8F board is used +        bool gpsdo_is_ettus(void) const; + +        void set_usrp_time_from_localtime(void); +        void set_usrp_time_from_pps(void); +}; + +} // namespace Output + +#endif // HAVE_OUTPUT_UHD  | 
