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-next.tar.gz dabmux-next.tar.bz2 dabmux-next.zip |
Add runtime linkage-set reloadnext
-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; |