diff options
| author | Matthias P. Braendli <matthias.braendli@mpb.li> | 2025-09-11 15:37:32 +0200 | 
|---|---|---|
| committer | Matthias P. Braendli <matthias.braendli@mpb.li> | 2025-09-11 15:37:32 +0200 | 
| commit | c84727c8ec0f99d66d1ad7d4716de79b6235d4d1 (patch) | |
| tree | f0ba66da2af142a7eb2413263b54a72e2184d2c1 | |
| parent | 90e9f058450cfb8bc2f06b02c60ba8cb533c2738 (diff) | |
| download | dabmux-c84727c8ec0f99d66d1ad7d4716de79b6235d4d1.tar.gz dabmux-c84727c8ec0f99d66d1ad7d4716de79b6235d4d1.tar.bz2 dabmux-c84727c8ec0f99d66d1ad7d4716de79b6235d4d1.zip  | |
Add runtime linkage-set reload
| -rw-r--r-- | src/ConfigParser.cpp | 15 | ||||
| -rw-r--r-- | src/ConfigParser.h | 8 | ||||
| -rw-r--r-- | src/DabMultiplexer.cpp | 75 | ||||
| -rw-r--r-- | src/DabMultiplexer.h | 78 | ||||
| -rw-r--r-- | src/DabMux.cpp | 44 | ||||
| -rw-r--r-- | src/ManagementServer.cpp | 4 | ||||
| -rw-r--r-- | src/ManagementServer.h | 16 | ||||
| -rw-r--r-- | src/MuxElements.h | 18 | 
8 files changed, 161 insertions, 97 deletions
diff --git a/src/ConfigParser.cpp b/src/ConfigParser.cpp index 7d166b6..2d500b3 100644 --- a/src/ConfigParser.cpp +++ b/src/ConfigParser.cpp @@ -3,7 +3,7 @@     2011, 2012 Her Majesty the Queen in Right of Canada (Communications     Research Center Canada) -   Copyright (C) 2022 +   Copyright (C) 2025     Matthias P. Braendli, matthias.braendli@mpb.li      http://www.opendigitalradio.org @@ -110,10 +110,10 @@ static void parse_fig2_label(ptree& pt, DabLabel& label) {  }  // Parse the linkage section -static void parse_linkage(ptree& pt, -        std::shared_ptr<dabEnsemble> ensemble) +void parse_linkage( +        const boost::optional<boost::property_tree::ptree&> pt_linking, +        std::vector<std::shared_ptr<LinkageSet> >& linkageSets)  { -    auto pt_linking = pt.get_child_optional("linking");      if (pt_linking) {          for (const auto& it : *pt_linking) {              const string setuid = it.first; @@ -132,7 +132,7 @@ static void parse_linkage(ptree& pt,              string service_uid = pt_set.get("keyservice", "");              auto linkageset = make_shared<LinkageSet>(setuid, lsn, active, hard, international); -            linkageset->keyservice = service_uid; // TODO check if it exists +            linkageset->keyservice = service_uid; // existence check is done in validate_linkage_sets()              auto pt_list = pt_set.get_child_optional("list"); @@ -189,7 +189,7 @@ static void parse_linkage(ptree& pt,                      linkageset->id_list.push_back(link);                  }              } -            ensemble->linkagesets.push_back(linkageset); +            linkageSets.push_back(linkageset);          }      }  } @@ -910,7 +910,8 @@ void parse_ptree(      } -    parse_linkage(pt, ensemble); +    const auto pt_linking = pt.get_child_optional("linking"); +    parse_linkage(pt_linking, ensemble->linkagesets);      parse_freq_info(pt, ensemble);      parse_other_service_linking(pt, ensemble);  } diff --git a/src/ConfigParser.h b/src/ConfigParser.h index 9ca6c81..038247b 100644 --- a/src/ConfigParser.h +++ b/src/ConfigParser.h @@ -3,7 +3,7 @@     2011, 2012 Her Majesty the Queen in Right of Canada (Communications     Research Center Canada) -   Copyright (C) 2016 +   Copyright (C) 2025     Matthias P. Braendli, matthias.braendli@mpb.li      The Configuration parser sets up the ensemble according @@ -34,6 +34,10 @@  #include <boost/property_tree/ptree.hpp>  #include <memory> -void parse_ptree(boost::property_tree::ptree& pt, +void parse_ptree( +        boost::property_tree::ptree& pt,          std::shared_ptr<dabEnsemble> ensemble); +void parse_linkage( +        const boost::optional<boost::property_tree::ptree&> pt_linking, +        std::vector<std::shared_ptr<LinkageSet> >& linkageSets); diff --git a/src/DabMultiplexer.cpp b/src/DabMultiplexer.cpp index c665f2c..7a8ac97 100644 --- a/src/DabMultiplexer.cpp +++ b/src/DabMultiplexer.cpp @@ -26,6 +26,9 @@  #include <cmath>  #include <set>  #include <memory> +#include <boost/property_tree/info_parser.hpp> +#include <boost/property_tree/json_parser.hpp> +  #include "DabMultiplexer.h"  #include "ConfigParser.h"  #include "ManagementServer.h" @@ -132,15 +135,35 @@ std::pair<uint32_t, std::time_t> MuxTime::get_milliseconds_seconds()  } -DabMultiplexer::DabMultiplexer(boost::property_tree::ptree pt) : +void DabMultiplexerConfig::read(const std::string& filename) +{ +    m_config_file = ""; +    try { +        if (stringEndsWith(filename, ".json")) { +            read_json(filename, pt); +        } +        else { +            read_info(filename, pt); +        } +        m_config_file = filename; +    } +    catch (const boost::property_tree::file_parser_error& e) +    { +        etiLog.level(warn) << "Failed to read " << filename; +    } +} + +DabMultiplexer::DabMultiplexer(DabMultiplexerConfig& config) :      RemoteControllable("mux"), -    m_pt(pt), +    m_config(config),      m_time(),      ensemble(std::make_shared<dabEnsemble>()), -    m_clock_tai(split_pipe_separated_string(pt.get("general.tai_clock_bulletins", ""))), +    m_clock_tai(split_pipe_separated_string(m_config.pt.get("general.tai_clock_bulletins", ""))),      fig_carousel(ensemble, [&]() { return m_time.get_milliseconds_seconds(); })  {      RC_ADD_PARAMETER(frames, "Show number of frames generated [read-only]"); +    RC_ADD_PARAMETER(tist_offset, "Configured tist-offset"); +    RC_ADD_PARAMETER(reload_linkagesets, "Write 1 to this parameter to trigger a reload of the linkage sets from the config [write-only]");      rcs.enrol(&m_clock_tai);  } @@ -160,7 +183,7 @@ void DabMultiplexer::set_edi_config(const edi::configuration_t& new_edi_conf)  // Run a set of checks on the configuration  void DabMultiplexer::prepare(bool require_tai_clock)  { -    parse_ptree(m_pt, ensemble); +    parse_ptree(m_config.pt, ensemble);      rcs.enrol(this);      rcs.enrol(ensemble.get()); @@ -186,11 +209,11 @@ void DabMultiplexer::prepare(bool require_tai_clock)          throw MuxInitException();      } -    const uint32_t tist_at_fct0_ms = m_pt.get<double>("general.tist_at_fct0", 0); -    currentFrame = m_time.init(tist_at_fct0_ms, m_pt.get<double>("general.tist_offset", 0.0)); +    const uint32_t tist_at_fct0_ms = m_config.pt.get<double>("general.tist_at_fct0", 0); +    currentFrame = m_time.init(tist_at_fct0_ms, m_config.pt.get<double>("general.tist_offset", 0.0));      m_time.mnsc_increment_time = false; -    bool tist_enabled = m_pt.get("general.tist", false); +    bool tist_enabled = m_config.pt.get("general.tist", false);      auto tist_edi_time = m_time.get_tist_seconds();      const auto timestamp = tist_edi_time.first; @@ -439,6 +462,32 @@ void DabMultiplexer::prepare_data_inputs()      }  } +void DabMultiplexer::reload_linkagesets() +{ +    try { +        DabMultiplexerConfig new_conf; +        new_conf.read(m_config.config_file()); +        if (new_conf.valid()) { +            const auto pt_linking = new_conf.pt.get_child_optional("linking"); +            std::vector<std::shared_ptr<LinkageSet> > linkagesets; +            parse_linkage(pt_linking, linkagesets); + +            etiLog.level(info) << "Validating " << linkagesets.size() << " new linkage sets."; + +            if (ensemble->validate_linkage_sets(ensemble->services, linkagesets)) { +                ensemble->linkagesets = linkagesets; +                etiLog.level(info) << "Loaded new linkage sets."; +            } +            else { +                etiLog.level(warn) << "New linkage set validation failed"; +            } +        } +    } +    catch (const std::exception& e) +    { +        etiLog.level(warn) << "Failed to update linkage sets: " << e.what(); +    } +}  /*  Each call creates one ETI frame */  void DabMultiplexer::mux_frame(std::vector<std::shared_ptr<DabOutput> >& outputs) @@ -458,7 +507,7 @@ void DabMultiplexer::mux_frame(std::vector<std::shared_ptr<DabOutput> >& outputs      // The above Tag Items will be assembled into a TAG Packet      edi::TagPacket edi_tagpacket(edi_conf.tagpacket_alignment); -    const bool tist_enabled = m_pt.get("general.tist", false); +    const bool tist_enabled = m_config.pt.get("general.tist", false);      int tai_utc_offset = 0;      if (tist_enabled and m_tai_clock_required) { @@ -718,7 +767,7 @@ void DabMultiplexer::mux_frame(std::vector<std::shared_ptr<DabOutput> >& outputs      index = (FLtmp + 2 + 1) * 4;      eti_TIST *tist = (eti_TIST *) & etiFrame[index]; -    bool enableTist = m_pt.get("general.tist", false); +    bool enableTist = m_config.pt.get("general.tist", false);      if (enableTist) {          tist->TIST = htonl(timestamp) | 0xff;          edi_tagDETI.tsta = timestamp & 0xffffff; @@ -848,6 +897,9 @@ void DabMultiplexer::set_parameter(const std::string& parameter,      else if (parameter == "tist_offset") {          m_time.set_tist_offset(std::stod(value));      } +    else if (parameter == "reload_linkagesets") { +        reload_linkagesets(); +    }      else {          stringstream ss;          ss << "Parameter '" << parameter << @@ -866,6 +918,11 @@ const std::string DabMultiplexer::get_parameter(const std::string& parameter) co      else if (parameter == "tist_offset") {          ss << m_time.tist_offset();      } +    else if (parameter == "reload_linkagesets") { +        ss << "Parameter '" << parameter << +            "' is not write-only in controllable " << get_rc_name(); +        throw ParameterError(ss.str()); +    }      else {          ss << "Parameter '" << parameter <<              "' is not exported by controllable " << get_rc_name(); diff --git a/src/DabMultiplexer.h b/src/DabMultiplexer.h index 9306eed..620e65d 100644 --- a/src/DabMultiplexer.h +++ b/src/DabMultiplexer.h @@ -45,47 +45,59 @@ constexpr uint32_t ETI_FSYNC1 = 0x49C5F8;  class MuxTime {      private: -    std::time_t m_edi_time = 0; -    uint32_t m_pps_offset_ms = 0; -    int64_t m_tist_offset_ms = 0; +        std::time_t m_edi_time = 0; +        uint32_t m_pps_offset_ms = 0; +        int64_t m_tist_offset_ms = 0;      public: -    std::pair<uint32_t, std::time_t> get_tist_seconds(); -    std::pair<uint32_t, std::time_t> get_milliseconds_seconds(); - - -    /* Pre v3 odr-dabmux did the MNSC calculation differently, -     * which works with the easydabv2. The rework in odr-dabmux, -     * deriving MNSC time from EDI time broke this. -     * -     * That's why we're now tracking MNSC time in separate variables, -     * to get the same behaviour back. -     * -     * I'm not aware of any devices using MNSC time besides the -     * easydab. ODR-DabMod now considers EDI seconds or ZMQ metadata. -     */ -    bool mnsc_increment_time = false; -    std::time_t mnsc_time = 0; - -    /* Setup the time and return the initial currentFrame counter value */ -    uint64_t init(uint32_t tist_at_fct0_ms, double tist_offset); -    void increment_timestamp(); -    double tist_offset() const { return m_tist_offset_ms / 1000.0; } -    void set_tist_offset(double new_tist_offset); +        std::pair<uint32_t, std::time_t> get_tist_seconds(); +        std::pair<uint32_t, std::time_t> get_milliseconds_seconds(); + + +        /* Pre v3 odr-dabmux did the MNSC calculation differently, +         * which works with the easydabv2. The rework in odr-dabmux, +         * deriving MNSC time from EDI time broke this. +         * +         * That's why we're now tracking MNSC time in separate variables, +         * to get the same behaviour back. +         * +         * I'm not aware of any devices using MNSC time besides the +         * easydab. ODR-DabMod now considers EDI seconds or ZMQ metadata. +         */ +        bool mnsc_increment_time = false; +        std::time_t mnsc_time = 0; + +        /* Setup the time and return the initial currentFrame counter value */ +        uint64_t init(uint32_t tist_at_fct0_ms, double tist_offset); +        void increment_timestamp(); +        double tist_offset() const { return m_tist_offset_ms / 1000.0; } +        void set_tist_offset(double new_tist_offset); +}; + +class DabMultiplexerConfig { +    public: +        boost::property_tree::ptree pt; + +        void read(const std::string& filename); +        bool valid() const { return m_config_file != ""; } +        std::string config_file() const { return m_config_file; } + +    private: +        std::string m_config_file;  };  class DabMultiplexer : public RemoteControllable {      public: -        DabMultiplexer(boost::property_tree::ptree pt); +        DabMultiplexer(DabMultiplexerConfig& config);          DabMultiplexer(const DabMultiplexer& other) = delete;          DabMultiplexer& operator=(const DabMultiplexer& other) = delete; -        ~DabMultiplexer(); +        virtual ~DabMultiplexer();          void prepare(bool require_tai_clock);          void mux_frame(std::vector<std::shared_ptr<DabOutput> >& outputs); -        void print_info(void); +        void print_info();          void set_edi_config(const edi::configuration_t& new_edi_conf); @@ -99,11 +111,13 @@ class DabMultiplexer : public RemoteControllable {          virtual const json::map_t get_all_values() const;      private: -        void prepare_subchannels(void); -        void prepare_services_components(void); -        void prepare_data_inputs(void); +        void prepare_subchannels(); +        void prepare_services_components(); +        void prepare_data_inputs(); + +        void reload_linkagesets(); -        boost::property_tree::ptree m_pt; +        DabMultiplexerConfig& m_config;          MuxTime m_time;          uint64_t currentFrame = 0; diff --git a/src/DabMux.cpp b/src/DabMux.cpp index 0066629..7b5f5d6 100644 --- a/src/DabMux.cpp +++ b/src/DabMux.cpp @@ -31,8 +31,6 @@  #include <memory>  #include <boost/property_tree/ptree.hpp> -#include <boost/property_tree/info_parser.hpp> -#include <boost/property_tree/json_parser.hpp>  #include <ctime>  #include <cstdlib>  #include <cstdio> @@ -132,12 +130,13 @@ int main(int argc, char *argv[])      }      int returnCode = 0; -    ptree pt;      std::vector<std::shared_ptr<DabOutput> > outputs;      try {          string conf_file = ""; +        DabMultiplexerConfig mux_conf; +          if (argc == 2) { // Assume the only argument is a config file              conf_file = argv[1]; @@ -154,8 +153,7 @@ int main(int argc, char *argv[])                  }                  conf_file = argv[2]; - -                read_info(conf_file, pt); +                mux_conf.read(conf_file);              }              catch (runtime_error &e) {                  throw MuxInitException(e.what()); @@ -168,23 +166,18 @@ int main(int argc, char *argv[])          }          try { -            if (stringEndsWith(conf_file, ".json")) { -                read_json(conf_file, pt); -            } -            else { -                read_info(conf_file, pt); -            } +            mux_conf.read(conf_file);          }          catch (runtime_error &e) {              throw MuxInitException(e.what());          }          /* Enable Logging to syslog conditionally */ -        if (pt.get<bool>("general.syslog", false)) { +        if (mux_conf.pt.get<bool>("general.syslog", false)) {              etiLog.register_backend(std::make_shared<LogToSyslog>());          } -        const auto startupcheck = pt.get<string>("general.startupcheck", ""); +        const auto startupcheck = mux_conf.pt.get<string>("general.startupcheck", "");          if (not startupcheck.empty()) {              etiLog.level(info) << "Running startup check '" << startupcheck << "'";              int wstatus = system(startupcheck.c_str()); @@ -204,26 +197,26 @@ int main(int argc, char *argv[])              }          } -        int mgmtserverport = pt.get<int>("general.managementport", -                             pt.get<int>("general.statsserverport", 0) ); +        int mgmtserverport = mux_conf.pt.get<int>("general.managementport", +                             mux_conf.pt.get<int>("general.statsserverport", 0) );          /* Management: stats and config server */          get_mgmt_server().open(mgmtserverport);          /************** READ REMOTE CONTROL PARAMETERS *************/ -        int telnetport = pt.get<int>("remotecontrol.telnetport", 0); +        int telnetport = mux_conf.pt.get<int>("remotecontrol.telnetport", 0);          if (telnetport != 0) {              auto rc = std::make_shared<RemoteControllerTelnet>(telnetport);              rcs.add_controller(rc);          } -        auto zmqendpoint = pt.get<string>("remotecontrol.zmqendpoint", ""); +        auto zmqendpoint = mux_conf.pt.get<string>("remotecontrol.zmqendpoint", "");          if (not zmqendpoint.empty()) {              auto rc = std::make_shared<RemoteControllerZmq>(zmqendpoint);              rcs.add_controller(rc);          } -        DabMultiplexer mux(pt); +        DabMultiplexer mux(mux_conf);          etiLog.level(info) <<                  PACKAGE_NAME << " " << @@ -240,7 +233,7 @@ int main(int argc, char *argv[])          /******************** READ OUTPUT PARAMETERS ***************/          set<string> all_output_names;          bool output_require_tai_clock = false; -        ptree pt_outputs = pt.get_child("outputs"); +        ptree pt_outputs = mux_conf.pt.get_child("outputs");          for (auto ptree_pair : pt_outputs) {              string outputuid = ptree_pair.first; @@ -444,7 +437,6 @@ int main(int argc, char *argv[])                  }                  outputs.push_back(output); -              }          } @@ -464,7 +456,7 @@ int main(int argc, char *argv[])              edi_conf.print();          } -        size_t limit = pt.get("general.nbframes", 0); +        const size_t limit = mux_conf.pt.get("general.nbframes", 0);          etiLog.level(info) << "Start loop";          /*   Each iteration of the main loop creates one ETI frame */ @@ -473,6 +465,7 @@ int main(int argc, char *argv[])              mux.mux_frame(outputs);              if (limit && currentFrame >= limit) { +                etiLog.level(info) << "Max number of ETI frames reached: " << currentFrame;                  break;              } @@ -491,17 +484,12 @@ int main(int argc, char *argv[])                      mgmt_server.restart();                  } -                mgmt_server.update_ptree(pt); +                mgmt_server.update_ptree(mux_conf.pt);              }          } - -        if (limit) { -            etiLog.level(info) << "Max number of ETI frames reached: " << currentFrame; -        }      }      catch (const MuxInitException& except) { -        etiLog.level(error) << "Multiplex initialisation aborted: " << -            except.what(); +        etiLog.level(error) << "Multiplex initialisation aborted: " << except.what();          returnCode = 1;      }      catch (const std::invalid_argument& except) { diff --git a/src/ManagementServer.cpp b/src/ManagementServer.cpp index 2c25a7a..7344b8b 100644 --- a/src/ManagementServer.cpp +++ b/src/ManagementServer.cpp @@ -473,7 +473,7 @@ void InputStat::notifyPeakLevels(int peak_left, int peak_right)      }  } -void InputStat::notifyUnderrun(void) +void InputStat::notifyUnderrun()  {      unique_lock<mutex> lock(m_mutex); @@ -492,7 +492,7 @@ void InputStat::notifyUnderrun(void)      }  } -void InputStat::notifyOverrun(void) +void InputStat::notifyOverrun()  {      unique_lock<mutex> lock(m_mutex); diff --git a/src/ManagementServer.h b/src/ManagementServer.h index d328f88..93ad28c 100644 --- a/src/ManagementServer.h +++ b/src/ManagementServer.h @@ -93,20 +93,20 @@ class InputStat          InputStat(const InputStat& other) = delete;          InputStat& operator=(const InputStat& other) = delete;          ~InputStat(); -        void registerAtServer(void); +        void registerAtServer(); -        std::string get_name(void) const { return m_name; } +        std::string get_name() const { return m_name; }          /* This function is called for every frame read by           * the multiplexer */          void notifyBuffer(long bufsize);          void notifyTimestampOffset(double offset);          void notifyPeakLevels(int peak_left, int peak_right); -        void notifyUnderrun(void); -        void notifyOverrun(void); +        void notifyUnderrun(); +        void notifyOverrun();          void notifyVersion(const std::string& version, uint32_t uptime_s); -        std::string encodeValuesJSON(void); -        input_state_t determineState(void); +        std::string encodeValuesJSON(); +        input_state_t determineState();      private:          std::string m_name; @@ -183,7 +183,7 @@ class ManagementServer          void update_ptree(const boost::property_tree::ptree& pt);          bool fault_detected() const { return m_fault; } -        void restart(void); +        void restart();      private:          void restart_thread(long); @@ -192,7 +192,7 @@ class ManagementServer          zmq::context_t m_zmq_context;          zmq::socket_t  m_zmq_sock; -        void serverThread(void); +        void serverThread();          void handle_message(zmq::message_t& zmq_message);          bool isInputRegistered(std::string& id); diff --git a/src/MuxElements.h b/src/MuxElements.h index 0266671..dfc4380 100644 --- a/src/MuxElements.h +++ b/src/MuxElements.h @@ -84,7 +84,7 @@ class MuxInitException : public std::exception          MuxInitException(const std::string m = "ODR-DabMux initialisation error")              throw()              : msg(m) {} -        ~MuxInitException(void) throw() {} +        ~MuxInitException() throw() {}          const char* what() const throw() { return msg.c_str(); }      private:          std::string msg; @@ -137,12 +137,12 @@ class AnnouncementCluster : public RemoteControllable {          uint16_t flags = 0;          std::string subchanneluid; -        std::string tostring(void) const; +        std::string tostring() const;          /* Check if the activation/deactivation timeout occurred,           * and return of if the Announcement is active           */ -        bool is_active(void); +        bool is_active();      private:          mutable std::mutex m_active_mutex; @@ -372,7 +372,7 @@ struct dabProtectionEEP {      // select EEP profile A and B.      // Other values are for future use, see      // EN 300 401 Clause 6.2.1 "Basic sub-channel organisation" -    uint8_t GetOption(void) const { +    uint8_t GetOption() const {          return (this->profile == EEP_A) ? 0 : 1;      }  }; @@ -402,16 +402,16 @@ public:              protection() { }      // Calculate subchannel size in number of CU -    unsigned short getSizeCu(void) const; +    unsigned short getSizeCu() const;      // Calculate subchannel size in number of bytes -    unsigned short getSizeByte(void) const; +    unsigned short getSizeByte() const;      // Calculate subchannel size in number of uint32_t -    unsigned short getSizeWord(void) const; +    unsigned short getSizeWord() const;      // Calculate subchannel size in number of uint64_t -    unsigned short getSizeDWord(void) const; +    unsigned short getSizeDWord() const;      // Read from the input, using the correct buffer management      size_t readFrame(uint8_t *buffer, size_t size, std::time_t seconds, int utco, uint32_t tsta); @@ -574,7 +574,7 @@ class LinkageSet {                  bool hard,                  bool international); -        std::string get_name(void) const { return m_name; } +        std::string get_name() const { return m_name; }          std::list<ServiceLink> id_list;  | 
