aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Makefile.am2
-rw-r--r--TODO8
-rw-r--r--src/Utils.cpp14
-rw-r--r--src/Utils.h4
-rw-r--r--src/output/SDR.cpp42
-rw-r--r--src/output/SDR.h4
-rw-r--r--src/output/UHD.cpp292
-rw-r--r--src/output/UHD.h33
-rw-r--r--src/output/USRPTime.cpp280
-rw-r--r--src/output/USRPTime.h116
10 files changed, 496 insertions, 299 deletions
diff --git a/Makefile.am b/Makefile.am
index 6e5c1a3..2d56842 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -90,6 +90,8 @@ odr_dabmod_SOURCES = src/DabMod.cpp \
src/output/Soapy.h \
src/output/UHD.cpp \
src/output/UHD.h \
+ src/output/USRPTime.cpp \
+ src/output/USRPTime.h \
src/InputFileReader.cpp \
src/InputMemory.cpp \
src/InputMemory.h \
diff --git a/TODO b/TODO
index c175926..cf7f9df 100644
--- a/TODO
+++ b/TODO
@@ -15,12 +15,14 @@ This would enable SFN support with LimeSDR devices.
Move dpd port from uhd section to somewhere else.
-Clean up and separate GPS and refclk checks. Ensure muting is set properly at startup.
+Clean up and separate GPS and refclk checks.
+ * *done* handle UHD GPSDO and time
+ * handle SoapySDR time
+ * Add refclk stuff and timestamps to Soapy.
+ * Ensure muting is set properly at startup.
Add antenna selection to config.
-Add refclk stuff and timestamps to Soapy.
-
*done* Make an abstraction for the DPD feedback server, use it for Soapy and UHD.
Move staticdelay into a new process block
diff --git a/src/Utils.cpp b/src/Utils.cpp
index cd116c7..385253b 100644
--- a/src/Utils.cpp
+++ b/src/Utils.cpp
@@ -210,3 +210,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 8da3a1b..2bc6cb3 100644
--- a/src/Utils.h
+++ b/src/Utils.h
@@ -67,3 +67,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/output/SDR.cpp b/src/output/SDR.cpp
index 356ae4f..ac25061 100644
--- a/src/output/SDR.cpp
+++ b/src/output/SDR.cpp
@@ -63,9 +63,12 @@ SDR::SDR(SDRDeviceConfig& config, std::shared_ptr<SDRDevice> device) :
m_running(false),
m_device(device)
{
- // muting is remote-controllable, and reset by the GPS fix check
+ // 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>(
@@ -95,11 +98,6 @@ void SDR::stop()
int SDR::process(Buffer *dataIn)
{
if (m_device) {
- if (not m_device->is_clk_source_ok()) {
- // Ignore frame
- return dataIn->getLength();
- }
-
FrameData frame;
frame.buf.resize(dataIn->getLength());
@@ -210,6 +208,37 @@ 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
@@ -217,6 +246,7 @@ void SDR::handle_frame(struct FrameData& frame)
constexpr double tx_timeout = 20.0;
if (not m_device->is_clk_source_ok()) {
+ sleep_through_frame();
return;
}
diff --git a/src/output/SDR.h b/src/output/SDR.h
index 5593ce3..d3693da 100644
--- a/src/output/SDR.h
+++ b/src/output/SDR.h
@@ -70,6 +70,7 @@ class SDR : public ModOutput, public RemoteControllable {
void stop(void);
void process_thread_entry(void);
void handle_frame(struct FrameData &frame);
+ void sleep_through_frame(void);
SDRDeviceConfig& m_config;
@@ -87,6 +88,9 @@ class SDR : public ModOutput, public RemoteControllable {
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/UHD.cpp b/src/output/UHD.cpp
index 54ff6c2..55beacc 100644
--- a/src/output/UHD.cpp
+++ b/src/output/UHD.cpp
@@ -28,6 +28,9 @@
#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"
@@ -81,46 +84,6 @@ static void uhd_msg_handler(uhd::msg::type_t type, const std::string &msg)
}
-// Check function for GPS TIMELOCK sensor from the ODR LEA-M8F board GPSDO
-static bool check_gps_timelock(uhd::usrp::multi_usrp::sptr usrp)
-{
- try {
- std::string sensor_value(
- usrp->get_mboard_sensor("gps_timelock", 0).to_pp_string());
-
- if (sensor_value.find("TIME LOCKED") == std::string::npos) {
- etiLog.level(warn) << "OutputUHD: gps_timelock " << sensor_value;
- return false;
- }
-
- return true;
- }
- catch (uhd::lookup_error &e) {
- etiLog.level(warn) << "OutputUHD: no gps_timelock sensor";
- return false;
- }
-}
-
-// Check function for GPS LOCKED sensor from the Ettus GPSDO
-static bool check_gps_locked(uhd::usrp::multi_usrp::sptr usrp)
-{
- try {
- uhd::sensor_value_t sensor_value(
- usrp->get_mboard_sensor("gps_locked", 0));
- if (not sensor_value.to_bool()) {
- etiLog.level(warn) << "OutputUHD: gps_locked " <<
- sensor_value.to_pp_string();
- return false;
- }
-
- return true;
- }
- catch (uhd::lookup_error &e) {
- etiLog.level(warn) << "OutputUHD: no gps_locked sensor";
- return false;
- }
-}
-
UHD::UHD(
SDRDeviceConfig& config) :
@@ -128,11 +91,6 @@ UHD::UHD(
m_conf(config),
m_running(false)
{
- // Variables needed for GPS fix check
- first_gps_fix_check.tv_sec = 0;
- last_gps_fix_check.tv_sec = 0;
- time_last_frame.tv_sec = 0;
-
std::stringstream device;
device << m_conf.device;
@@ -183,6 +141,8 @@ UHD::UHD(
}
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);
@@ -388,8 +348,34 @@ size_t UHD::receive_frame(
// Return true if GPS and reference clock inputs are ok
bool UHD::is_clk_source_ok(void)
{
- //TODO
- return true;
+ 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)
@@ -406,27 +392,6 @@ bool UHD::refclk_loss_needs_check() const
return m_conf.refclk_src != "internal";
}
-bool UHD::gpsfix_needs_check() const
-{
- if (m_conf.refclk_src == "internal") {
- return false;
- }
- else if (m_conf.refclk_src == "gpsdo") {
- return (m_conf.maxGPSHoldoverTime != 0);
- }
- else if (m_conf.refclk_src == "gpsdo-ettus") {
- return (m_conf.maxGPSHoldoverTime != 0);
- }
- else {
- return false;
- }
-}
-
-bool UHD::gpsdo_is_ettus() const
-{
- return (m_conf.refclk_src == "gpsdo-ettus");
-}
-
void UHD::stop_threads()
{
m_running.store(false);
@@ -436,197 +401,6 @@ void UHD::stop_threads()
}
-static int transmission_frame_duration_ms(unsigned int dabMode)
-{
- switch (dabMode) {
- // could happen when called from constructor and we take the mode from ETI
- case 0: return 0;
-
- case 1: return 96;
- case 2: return 24;
- case 3: return 24;
- case 4: return 48;
- default:
- throw std::runtime_error("OutputUHD: invalid DAB mode");
- }
-}
-
-
-void UHD::set_usrp_time()
-{
- if (m_conf.enableSync and (m_conf.pps_src == "none")) {
- etiLog.level(warn) <<
- "OutputUHD: WARNING:"
- " you are using synchronous transmission without PPS input!";
-
- struct timespec now;
- if (clock_gettime(CLOCK_REALTIME, &now)) {
- perror("OutputUHD:Error: could not get time: ");
- etiLog.level(error) << "OutputUHD: could not get time";
- }
- else {
- m_usrp->set_time_now(uhd::time_spec_t(now.tv_sec));
- etiLog.level(info) << "OutputUHD: Setting USRP time to " <<
- std::fixed <<
- uhd::time_spec_t(now.tv_sec).get_real_secs();
- }
- }
-
- if (m_conf.pps_src != "none") {
- /* handling time for synchronisation: wait until the next full
- * second, and set the USRP time at next PPS */
- struct timespec now;
- time_t seconds;
- if (clock_gettime(CLOCK_REALTIME, &now)) {
- etiLog.level(error) << "OutputUHD: could not get time :" <<
- strerror(errno);
- throw std::runtime_error("OutputUHD: could not get time.");
- }
- else {
- seconds = now.tv_sec;
-
- MDEBUG("OutputUHD:sec+1: %ld ; now: %ld ...\n", seconds+1, now.tv_sec);
- while (seconds + 1 > now.tv_sec) {
- usleep(1);
- if (clock_gettime(CLOCK_REALTIME, &now)) {
- etiLog.level(error) << "OutputUHD: could not get time :" <<
- strerror(errno);
- throw std::runtime_error("OutputUHD: could not get time.");
- }
- }
- MDEBUG("OutputUHD:sec+1: %ld ; now: %ld ...\n", seconds+1, now.tv_sec);
- /* We are now shortly after the second change. */
-
- usleep(200000); // 200ms, we want the PPS to be later
- m_usrp->set_time_unknown_pps(uhd::time_spec_t(seconds + 2));
- etiLog.level(info) << "OutputUHD: Setting USRP time next pps to " <<
- std::fixed <<
- uhd::time_spec_t(seconds + 2).get_real_secs();
- }
-
- usleep(1e6);
- etiLog.log(info, "OutputUHD: USRP time %f\n",
- m_usrp->get_time_now().get_real_secs());
- }
-}
-
-void UHD::initial_gps_check()
-{
- if (first_gps_fix_check.tv_sec == 0) {
- etiLog.level(info) << "Waiting for GPS fix";
-
- if (clock_gettime(CLOCK_MONOTONIC, &first_gps_fix_check) != 0) {
- stringstream ss;
- ss << "clock_gettime failure: " << strerror(errno);
- throw std::runtime_error(ss.str());
- }
- }
-
- check_gps();
-
- if (last_gps_fix_check.tv_sec >
- first_gps_fix_check.tv_sec + initial_gps_fix_wait) {
- stringstream ss;
- ss << "GPS did not show time lock in " <<
- initial_gps_fix_wait << " seconds";
- throw std::runtime_error(ss.str());
- }
-
- if (time_last_frame.tv_sec == 0) {
- if (clock_gettime(CLOCK_MONOTONIC, &time_last_frame) != 0) {
- stringstream ss;
- ss << "clock_gettime failure: " << strerror(errno);
- throw std::runtime_error(ss.str());
- }
- }
-
- struct timespec now;
- if (clock_gettime(CLOCK_MONOTONIC, &now) != 0) {
- stringstream ss;
- ss << "clock_gettime failure: " << strerror(errno);
- throw std::runtime_error(ss.str());
- }
-
- long delta_us = timespecdiff_us(time_last_frame, now);
- long wait_time_us = transmission_frame_duration_ms(m_conf.dabMode);
-
- if (wait_time_us - delta_us > 0) {
- usleep(wait_time_us - delta_us);
- }
-
- time_last_frame.tv_nsec += wait_time_us * 1000;
- if (time_last_frame.tv_nsec >= 1000000000L) {
- time_last_frame.tv_nsec -= 1000000000L;
- time_last_frame.tv_sec++;
- }
-}
-
-void UHD::check_gps()
-{
- struct timespec time_now;
- if (clock_gettime(CLOCK_MONOTONIC, &time_now) != 0) {
- stringstream ss;
- ss << "clock_gettime failure: " << strerror(errno);
- throw std::runtime_error(ss.str());
- }
-
- // Divide interval by two because we alternate between
- // launch and check
- if (gpsfix_needs_check() and
- last_gps_fix_check.tv_sec + gps_fix_check_interval/2.0 <
- time_now.tv_sec) {
- last_gps_fix_check = time_now;
-
- // Alternate between launching thread and checking the
- // result.
- if (gps_fix_task.joinable()) {
- if (gps_fix_future.has_value()) {
-
- gps_fix_future.wait();
-
- gps_fix_task.join();
-
- if (not gps_fix_future.get()) {
- if (num_checks_without_gps_fix == 0) {
- etiLog.level(alert) <<
- "OutputUHD: GPS Time Lock lost";
- }
- num_checks_without_gps_fix++;
- }
- else {
- if (num_checks_without_gps_fix) {
- etiLog.level(info) <<
- "OutputUHD: GPS Time Lock recovered";
- }
- num_checks_without_gps_fix = 0;
- }
-
- if (gps_fix_check_interval * num_checks_without_gps_fix >
- m_conf.maxGPSHoldoverTime) {
- std::stringstream ss;
- ss << "Lost GPS Time Lock for " << gps_fix_check_interval *
- num_checks_without_gps_fix << " seconds";
- throw std::runtime_error(ss.str());
- }
- }
- }
- else {
- // Checking the sensor here takes too much
- // time, it has to be done in a separate thread.
- if (gpsdo_is_ettus()) {
- gps_fix_pt = boost::packaged_task<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));
- }
- }
-}
void UHD::print_async_thread()
{
diff --git a/src/output/UHD.h b/src/output/UHD.h
index 5ae477b..4d3eecc 100644
--- a/src/output/UHD.h
+++ b/src/output/UHD.h
@@ -47,6 +47,7 @@ DESCRIPTION:
#include "Log.h"
#include "output/SDR.h"
+#include "output/USRPTime.h"
#include "TimestampDecoder.h"
#include "RemoteControl.h"
#include "ThreadsafeQueue.h"
@@ -54,9 +55,6 @@ DESCRIPTION:
#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
@@ -100,6 +98,7 @@ class UHD : public Output::SDRDevice
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;
@@ -110,21 +109,6 @@ class UHD : public Output::SDRDevice
uhd::tx_metadata_t md;
- // 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
-
// Used to print statistics once a second
std::chrono::steady_clock::time_point last_print_time;
@@ -132,24 +116,11 @@ class UHD : public Output::SDRDevice
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;
-
// 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);
-
- void check_gps();
-
- void set_usrp_time();
-
- void initial_gps_check();
};
} // namespace Output
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