From 70c5ba0867945bb097f466262ce03470b690518f Mon Sep 17 00:00:00 2001 From: "Matthias P. Braendli" Date: Fri, 8 Jan 2016 11:49:26 +0100 Subject: Add TAI handling for EDI with TIST --- configure.ac | 6 ++ src/ClockTAI.cpp | 220 +++++++++++++++++++++++++++++++++++++++++++ src/ClockTAI.h | 85 +++++++++++++++++ src/DabMultiplexer.cpp | 36 +++++++ src/DabMultiplexer.h | 3 + src/Makefile.am | 42 +++++---- src/dabOutput/edi/TagItems.h | 35 +++++++ 7 files changed, 410 insertions(+), 17 deletions(-) create mode 100644 src/ClockTAI.cpp create mode 100644 src/ClockTAI.h diff --git a/configure.ac b/configure.ac index f23df79..e7e169c 100644 --- a/configure.ac +++ b/configure.ac @@ -200,6 +200,8 @@ AS_IF([test "x$enable_output_zeromq" = "xyes"], AC_ARG_ENABLE([output_edi], [AS_HELP_STRING([--disable-output-edi], [Disable EDI output])], [], [enable_output_edi=yes]) +AS_IF([test "x$enable_output_edi" = "xyes"], + AC_CHECK_LIB(curl, curl_easy_init, [true], [AC_MSG_ERROR([cURL is required for EDI output])])) AS_IF([test "x$enable_output_edi" = "xyes"], [AC_DEFINE(HAVE_OUTPUT_EDI, [1], [Define if EDI output is enabled])]) @@ -207,6 +209,10 @@ AS_IF([test "x$enable_output_edi" = "xyes"], AM_CONDITIONAL([HAVE_ZEROMQ_TEST], [test "x$enable_output_zeromq" = "xyes" -o "x$enable_input_zeromq" = "xyes"]) +# Link against cURL +AM_CONDITIONAL([HAVE_CURL_TEST], + [test "x$enable_output_edi" = "xyes"]) + # Formats # RAW diff --git a/src/ClockTAI.cpp b/src/ClockTAI.cpp new file mode 100644 index 0000000..1b2d4ef --- /dev/null +++ b/src/ClockTAI.cpp @@ -0,0 +1,220 @@ +/* + Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, + 2011, 2012 Her Majesty the Queen in Right of Canada (Communications + Research Center Canada) + + Copyright (C) 2016 + Matthias P. Braendli, matthias.braendli@mpb.li + + http://www.opendigitalradio.org + */ +/* + This file is part of ODR-DabMux. + + ODR-DabMux 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-DabMux 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-DabMux. If not, see . +*/ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "ClockTAI.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +/* Last line from bulletin, example: + 2015 JUL 1 =JD 2457204.5 TAI-UTC= 36.0 S + (MJD - 41317.) X 0.0 S + */ + +ClockTAI::ClockTAI() +{ + m_offset = 0; + m_bulletin_download_time.tv_nsec = 0; + m_bulletin_download_time.tv_sec = 0; +} + +int ClockTAI::get_offset() +{ + struct timespec time_now; + + int err = clock_gettime(CLOCK_REALTIME, &time_now); + if (err) { + throw std::runtime_error("ClockTAI::get_offset() clock_gettime failed"); + } + + if (time_now.tv_sec - m_bulletin_download_time.tv_sec > + 3600 * 24 * 31) { + // Refresh if it's older than one month. Leap seconds are + // announced several months in advance + + if (download_tai_utc_bulletin(tai_data_url1) == 0) { + m_offset = parse_tai_offset(); + } + else if (download_tai_utc_bulletin(tai_data_url2) == 0) { + m_offset = parse_tai_offset(); + } + } + + return m_offset; + + throw std::runtime_error("Could not fetch TAI-UTC offset"); +} + +#if SUPPORT_SETTING_CLOCK_TAI +int ClockTAI::update_local_tai_clock(int offset) +{ + struct timex timex_request; + timex_request.modes = ADJ_TAI; + timex_request.constant = offset; + + int err = adjtimex(&timex_request); + if (err == -1) { + perror("adjtimex"); + } + + printf("adjtimex: %d, tai %d\n", err, timex_request.tai); + + return err; +} +#endif + +int ClockTAI::parse_tai_offset() +{ + std::regex regex_offset("TAI-UTC= *([0-9.]+)"); + + int tai_utc_offset = 0; + + int linecount = 0; + + for (std::string line; std::getline(m_bulletin, line); ) { + linecount++; + auto words_begin = + std::sregex_token_iterator( + line.begin(), line.end(), regex_offset, 1); + auto words_end = std::sregex_token_iterator(); + + for (auto w = words_begin; w != words_end; ++w) { + std::string s(*w); + tai_utc_offset = std::atoi(s.c_str()); + } + + } + + if (linecount == 0) { + throw std::runtime_error("No data in TAI bulletin"); + } + + // With the current evolution of the offset, we're probably going + // to reach 500 long after DAB gets replaced by another standard. + if (tai_utc_offset < 0 or tai_utc_offset > 500) { + std::stringstream ss; + ss << "TAI offset " << tai_utc_offset << " out of range"; + throw std::range_error(ss.str()); + } + + return tai_utc_offset; +} + +size_t ClockTAI::fill_bulletin(char *ptr, size_t size, size_t nmemb) +{ + size_t len = size * nmemb; + for (size_t i = 0; i < len; i++) { + m_bulletin << ptr[i]; + } + return len; +} + +size_t ClockTAI::fill_bulletin_cb( + char *ptr, size_t size, size_t nmemb, void *ctx) +{ + return ((ClockTAI*)ctx)->fill_bulletin(ptr, size, nmemb); +} + +int ClockTAI::download_tai_utc_bulletin(const char* url) +{ + int r = 0; + + CURL *curl; + CURLcode res; + + curl = curl_easy_init(); + if (curl) { + curl_easy_setopt(curl, CURLOPT_URL, url); + /* example.com is redirected, so we tell libcurl to follow redirection */ + curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, ClockTAI::fill_bulletin_cb); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, this); + + res = curl_easy_perform(curl); + if (res != CURLE_OK) { + fprintf(stderr, "curl_easy_perform() failed: %s\n", + curl_easy_strerror(res)); + + r = 1; + } + else { + // Save the download time of the bulletin + int err = clock_gettime(CLOCK_REALTIME, &m_bulletin_download_time); + if (err) { + perror("REALTIME clock_gettime failed"); + r = 1; + } + } + + /* always cleanup */ + curl_easy_cleanup(curl); + } + return r; +} + +#if 0 +// Example testing code +void debug_tai_clk() +{ + struct timespec rt_clk; + + int err = clock_gettime(CLOCK_REALTIME, &rt_clk); + if (err) { + perror("REALTIME clock_gettime failed"); + } + + struct timespec tai_clk; + + err = clock_gettime(CLOCK_TAI, &tai_clk); + if (err) { + perror("TAI clock_gettime failed"); + } + + printf("RT - TAI = %ld\n", rt_clk.tv_sec - tai_clk.tv_sec); + + + struct timex timex_request; + timex_request.modes = 0; // Do not set anything + + err = adjtimex(&timex_request); + if (err == -1) { + perror("adjtimex"); + } + + printf("adjtimex: %d, tai %d\n", err, timex_request.tai); +} +#endif + diff --git a/src/ClockTAI.h b/src/ClockTAI.h new file mode 100644 index 0000000..027e6db --- /dev/null +++ b/src/ClockTAI.h @@ -0,0 +1,85 @@ +/* + Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, + 2011, 2012 Her Majesty the Queen in Right of Canada (Communications + Research Center Canada) + + Copyright (C) 2016 + Matthias P. Braendli, matthias.braendli@mpb.li + + http://www.opendigitalradio.org + */ +/* + This file is part of ODR-DabMux. + + ODR-DabMux 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-DabMux 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-DabMux. If not, see . +*/ + +/* The EDI output needs TAI clock, according to ETSI TS 102 693 Annex F + * "EDI Timestamps". This module sets the local CLOCK_TAI clock by + * setting the TAI-UTC offset using adjtimex. + * + * This functionality requires Linux 3.10 (30 Jun 2013) or newer */ + +#ifndef __CLOCK_TAI_H_ +#define __CLOCK_TAI_H_ + +#include +#include +#include +#include + +// EDI needs to know UTC-TAI, but doesn't need the CLOCK_TAI to be set +#define SUPPORT_SETTING_CLOCK_TAI 0 + +/* Loads, parses and represents TAI-UTC offset information from the USNO bulletin */ +class ClockTAI { + public: + ClockTAI(); + + // Fetch the bulletin from the USNO website and return the current + // TAI-UTC offset. + // Throws runtime_error on failure. + int get_offset(void); + +#if SUPPORT_SETTING_CLOCK_TAI + // Update the local TAI clock according to the TAI-UTC offset + // return 0 on success + int update_local_tai_clock(int offset); +#endif + + private: + // leap seconds insertion bulletin is available at + const char* tai_data_url1 = "http://maia.usno.navy.mil/ser7/tai-utc.dat"; + const char* tai_data_url2 = "http://toshi.nofs.navy.mil/ser7/tai-utc.dat"; + + int m_offset; + std::stringstream m_bulletin; + struct timespec m_bulletin_download_time; + + // Load bulletin into m_bulletin, return 0 on success + int download_tai_utc_bulletin(const char* url); + + // read TAI offset from m_bulletin + int parse_tai_offset(void); + + // callback that receives data from cURL + size_t fill_bulletin(char *ptr, size_t size, size_t nmemb); + + // static callback wrapper for cURL + static size_t fill_bulletin_cb( + char *ptr, size_t size, size_t nmemb, void *ctx); +}; + +#endif // __CLOCK_TAI_H_ + diff --git a/src/DabMultiplexer.cpp b/src/DabMultiplexer.cpp index fd59b8f..d9e1a10 100644 --- a/src/DabMultiplexer.cpp +++ b/src/DabMultiplexer.cpp @@ -239,6 +239,30 @@ void DabMultiplexer::prepare() */ gettimeofday(&mnsc_time, NULL); +#if HAVE_OUTPUT_EDI + // Try to load offset once + + bool tist_enabled = m_pt.get("general.tist", false); + + try { + m_clock_tai.get_offset(); + } + catch (std::runtime_error& e) { + const char* err_msg = + "Could not initialise TAI clock properly required by " + "EDI with timestamp. Do you have a working internet " + "connection?"; + + if (tist_enabled and edi_conf.enabled) { + etiLog.level(error) << err_msg; + throw e; + } + else { + etiLog.level(warn) << err_msg; + } + } +#endif + // Shift ms by 13 to Timestamp level 2, see below in Section TIST timestamp = (mnsc_time.tv_usec / 1000) << 13; } @@ -450,6 +474,18 @@ void DabMultiplexer::mux_frame(std::vector >& outputs edi_tagDETI.atstf = 1; edi_tagDETI.utco = 0; edi_tagDETI.seconds = 0; + try { + bool tist_enabled = m_pt.get("general.tist", false); + + if (tist_enabled and edi_conf.enabled) { + edi_tagDETI.set_utco(m_clock_tai.get_offset()); + } + + edi_tagDETI.set_seconds(mnsc_time); + } + catch (std::runtime_error& e) { + etiLog.level(error) << "Could not get UTC-TAI offset for EDI timestamp"; + } date = getDabTime(); diff --git a/src/DabMultiplexer.h b/src/DabMultiplexer.h index aa468ea..7b3834e 100644 --- a/src/DabMultiplexer.h +++ b/src/DabMultiplexer.h @@ -45,6 +45,7 @@ #include "MuxElements.h" #include "RemoteControl.h" #include "Eti.h" +#include "ClockTAI.h" #include #include #include @@ -136,6 +137,8 @@ class DabMultiplexer : public RemoteControllable { std::shared_ptr ensemble_next; #if HAVE_OUTPUT_EDI + ClockTAI m_clock_tai; + std::ofstream edi_debug_file; UdpSocket edi_output; diff --git a/src/Makefile.am b/src/Makefile.am index 1cf8dbb..4c5cd60 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -39,9 +39,16 @@ bin_PROGRAMS=odr-dabmux odr-bridgetest ZMQ_LIBS = endif +if HAVE_CURL_TEST +CURL_LIBS =-lcurl +else +CURL_LIBS = +endif + odr_dabmux_CFLAGS =-Wall -I$(FARSYNC_DIR) $(GITVERSION_FLAGS) odr_dabmux_CXXFLAGS =-Wall -I$(FARSYNC_DIR) $(GITVERSION_FLAGS) -odr_dabmux_LDADD =$(FEC_LIBS) $(ZMQ_LIBS) -lpthread -lboost_thread -lboost_system +odr_dabmux_LDADD =$(FEC_LIBS) $(ZMQ_LIBS) $(CURL_LIBS) \ + -lpthread -lboost_thread -lboost_system odr_dabmux_SOURCES =DabMux.cpp DabMux.h \ DabMultiplexer.cpp DabMultiplexer.h \ dabInput.h dabInput.cpp \ @@ -76,32 +83,33 @@ odr_dabmux_SOURCES =DabMux.cpp DabMux.h \ dabOutput/edi/TagItems.cpp dabOutput/edi/TagItems.h \ dabOutput/edi/TagPacket.cpp dabOutput/edi/TagPacket.h \ dabOutput/edi/PFT.cpp dabOutput/edi/PFT.h \ - bridge.h bridge.c \ - utils.cpp utils.h \ - MuxElements.cpp MuxElements.h \ - RemoteControl.cpp RemoteControl.h \ - ParserCmdline.cpp ParserCmdline.h \ + ClockTAI.h ClockTAI.cpp \ ConfigParser.cpp ConfigParser.h \ + Dmb.h Dmb.cpp \ Eti.h Eti.cpp \ - Log.h Log.cpp \ - UdpSocket.h UdpSocket.cpp \ InetAddress.h InetAddress.cpp \ - prbs.h prbs.c \ - crc.h crc.c \ - dabUtils.h dabUtils.cpp \ - PcDebug.h \ - Dmb.h Dmb.cpp \ Interleaver.h Interleaver.cpp \ - ReedSolomon.h ReedSolomon.cpp \ - mpeg.h mpeg.c \ + Log.h Log.cpp \ ManagementServer.h ManagementServer.cpp \ + MuxElements.cpp MuxElements.h \ + ParserCmdline.cpp ParserCmdline.h \ + PcDebug.h \ + ReedSolomon.h ReedSolomon.cpp \ + RemoteControl.cpp RemoteControl.h \ TcpServer.h TcpServer.cpp \ TcpSocket.h TcpSocket.cpp \ - zmq.hpp \ + UdpSocket.h UdpSocket.cpp \ + bridge.h bridge.c \ + crc.h crc.c \ + dabUtils.h dabUtils.cpp \ + fig/FIG.h \ fig/FIG0.cpp fig/FIG0.h \ fig/FIG1.cpp fig/FIG1.h \ fig/FIGCarousel.cpp fig/FIGCarousel.h \ - fig/FIG.h + mpeg.h mpeg.c \ + prbs.h prbs.c \ + utils.cpp utils.h \ + zmq.hpp odr_bridgetest_CFLAGS =-DBRIDGE_TEST odr_bridgetest_SOURCES =bridge.c \ diff --git a/src/dabOutput/edi/TagItems.h b/src/dabOutput/edi/TagItems.h index 355b6e6..dee857a 100644 --- a/src/dabOutput/edi/TagItems.h +++ b/src/dabOutput/edi/TagItems.h @@ -74,8 +74,43 @@ class TagDETI : public TagItem // ATST (optional) bool atstf; // presence of atst data + + /* UTCO: Offset (in seconds) between UTC and the Seconds value. The + * value is expressed as an unsigned 8-bit quantity. As of February + * 2009, the value shall be 2 and shall change as a result of each + * modification of the number of leap seconds, as proscribed by + * International Earth Rotation and Reference Systems Service (IERS). + * + * According to Annex F + * EDI = TAI - 32s (constant) + * EDI = UTC + UTCO + * we derive + * UTCO = TAI-UTC - 32 + * where the TAI-UTC offset is given by the USNO bulletin using + * the ClockTAI module. + */ uint8_t utco; + + void set_utco(int tai_utc_offset) { utco = tai_utc_offset - 32; } + + /* The number of SI seconds since 2000-01-01 T 00:00:00 UTC as an + * unsigned 32-bit quantity + */ uint32_t seconds; + + void set_seconds(struct timeval tv) + { + time_t posix_timestamp_1_jan_2000 = 946684800; + seconds = tv.tv_sec - posix_timestamp_1_jan_2000; + } + + + /* TSTA: Shall be the 24 least significant bits of the Time Stamp + * (TIST) field from the STI-D(LI) Frame. The full definition for the + * STI TIST can be found in annex B of EN 300 797 [4]. The most + * significant 8 bits of the TIST field of the incoming STI-D(LI) + * frame, if required, may be carried in the RFAD field. + */ uint32_t tsta; // the FIC (optional) -- cgit v1.2.3