/* Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012 Her Majesty the Queen in Right of Canada (Communications Research Center Canada) Copyright (C) 2023 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 . */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include "INIReader.h" #include "ConfigParser.h" #include "Utils.h" #include "Log.h" #include "Events.h" using namespace std; static GainMode parse_gainmode(const std::string &gainMode_setting) { string gainMode_minuscule(gainMode_setting); std::transform(gainMode_minuscule.begin(), gainMode_minuscule.end(), gainMode_minuscule.begin(), ::tolower); if (gainMode_minuscule == "0" or gainMode_minuscule == "fix") { return GainMode::GAIN_FIX; } else if (gainMode_minuscule == "1" or gainMode_minuscule == "max") { return GainMode::GAIN_MAX; } else if (gainMode_minuscule == "2" or gainMode_minuscule == "var") { return GainMode::GAIN_VAR; } cerr << "Modulator gainmode setting '" << gainMode_setting << "' not recognised." << endl; throw std::runtime_error("Configuration error"); } static FFTEngine parse_fft_engine(const std::string &fft_engine_setting) { string fft_engine_minuscule(fft_engine_setting); std::transform(fft_engine_minuscule.begin(), fft_engine_minuscule.end(), fft_engine_minuscule.begin(), ::tolower); if (fft_engine_minuscule == "fftw") { return FFTEngine::FFTW; } else if (fft_engine_minuscule == "kiss") { return FFTEngine::KISS; } else if (fft_engine_minuscule == "dexter") { return FFTEngine::DEXTER; } cerr << "Modulator fft_engine setting '" << fft_engine_setting << "' not recognised." << endl; throw std::runtime_error("Configuration error"); } static void parse_configfile( const std::string& configuration_file, mod_settings_t& mod_settings) { // First read parameters from the file INIReader pt(configuration_file); int line_err = pt.ParseError(); if (line_err) { std::cerr << "Error, cannot read configuration file '" << configuration_file.c_str() << "'" << std::endl; std::cerr << "At line: " << line_err << std::endl; throw std::runtime_error("Cannot read configuration file"); } mod_settings.startupCheck = pt.Get("general.startupcheck", ""); // remote controller interfaces: if (pt.GetInteger("remotecontrol.telnet", 0) == 1) { try { int telnetport = pt.GetInteger("remotecontrol.telnetport", 0); auto telnetrc = make_shared(telnetport); rcs.add_controller(telnetrc); } catch (const std::exception &e) { std::cerr << "Error: " << e.what() << "\n"; std::cerr << " telnet remote control enabled, but no telnetport defined.\n"; throw std::runtime_error("Configuration error"); } } #if defined(HAVE_ZEROMQ) if (pt.GetInteger("remotecontrol.zmqctrl", 0) == 1) { try { std::string zmqCtrlEndpoint = pt.Get("remotecontrol.zmqctrlendpoint", ""); auto zmqrc = make_shared(zmqCtrlEndpoint); rcs.add_controller(zmqrc); } catch (const std::exception &e) { std::cerr << "Error: " << e.what() << "\n"; std::cerr << " zmq remote control enabled, but no endpoint defined.\n"; throw std::runtime_error("Configuration error"); } } #endif // input params: if (pt.GetInteger("input.loop", 0) == 1) { mod_settings.loop = true; } mod_settings.inputTransport = pt.Get("input.transport", "file"); mod_settings.edi_max_delay_ms = pt.GetReal("input.edi_max_delay", 0.0); mod_settings.inputName = pt.Get("input.source", "/dev/stdin"); // log parameters: const string events_endpoint = pt.Get("log.events_endpoint", ""); if (not events_endpoint.empty()) { #if defined(HAVE_ZEROMQ) events.bind(events_endpoint); #else throw std::runtime_error("Cannot configure events sender when compiled without zeromq"); #endif } if (pt.GetInteger("log.syslog", 0) == 1) { etiLog.register_backend(make_shared()); } if (pt.GetInteger("log.filelog", 0) == 1) { std::string logfilename; try { logfilename = pt.Get("log.filename", ""); } catch (const std::exception &e) { std::cerr << "Error: " << e.what() << "\n"; std::cerr << " Configuration enables file log, but does not specify log filename\n"; throw std::runtime_error("Configuration error"); } etiLog.register_backend(make_shared(logfilename)); } std::string trace_filename = pt.Get("log.trace", ""); if (not trace_filename.empty()) { etiLog.register_backend(make_shared(trace_filename)); } mod_settings.showProcessTime = pt.GetInteger("log.show_process_time", mod_settings.showProcessTime); // modulator parameters: const string fft_engine_setting = pt.Get("modulator.fft_engine", "fftw"); mod_settings.fftEngine = parse_fft_engine(fft_engine_setting); const string gainMode_setting = pt.Get("modulator.gainmode", "var"); mod_settings.gainMode = parse_gainmode(gainMode_setting); mod_settings.gainmodeVariance = pt.GetReal("modulator.normalise_variance", mod_settings.gainmodeVariance); mod_settings.dabMode = pt.GetInteger("modulator.mode", mod_settings.dabMode); mod_settings.clockRate = pt.GetInteger("modulator.dac_clk_rate", (size_t)0); mod_settings.digitalgain = pt.GetReal("modulator.digital_gain", mod_settings.digitalgain); mod_settings.outputRate = pt.GetInteger("modulator.rate", mod_settings.outputRate); mod_settings.ofdmWindowOverlap = pt.GetInteger("modulator.ofdmwindowing", mod_settings.ofdmWindowOverlap); // FIR Filter parameters: if (pt.GetInteger("firfilter.enabled", 0) == 1) { mod_settings.filterTapsFilename = pt.Get("firfilter.filtertapsfile", "default"); } // Poly coefficients: if (pt.GetInteger("poly.enabled", 0) == 1) { mod_settings.polyCoefFilename = pt.Get("poly.polycoeffile", "dpd/poly.coef"); mod_settings.polyNumThreads = pt.GetInteger("poly.num_threads", 0); } // Crest factor reduction if (pt.GetInteger("cfr.enabled", 0) == 1) { mod_settings.enableCfr = true; mod_settings.cfrClip = pt.GetReal("cfr.clip", 0.0); mod_settings.cfrErrorClip = pt.GetReal("cfr.error_clip", 0.0); } // Output options std::string output_selected = pt.Get("output.output", ""); if(output_selected == "") { std::cerr << "Error:\n"; std::cerr << " Configuration does not specify output\n"; throw std::runtime_error("Configuration error"); } if (output_selected == "file") { mod_settings.outputName = pt.Get("fileoutput.filename", ""); if(mod_settings.outputName == "") { std::cerr << "Error:\n"; std::cerr << " Configuration does not specify file name for file output\n"; throw std::runtime_error("Configuration error"); } mod_settings.fileOutputShowMetadata = (pt.GetInteger("fileoutput.show_metadata", 0) > 0); mod_settings.useFileOutput = true; mod_settings.fileOutputFormat = pt.Get("fileoutput.format", mod_settings.fileOutputFormat); } #if defined(HAVE_OUTPUT_UHD) else if (output_selected == "uhd") { 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; sdr_device_config.subDevice = pt.Get("uhdoutput.subdevice", ""); sdr_device_config.masterClockRate = pt.GetInteger("uhdoutput.master_clock_rate", 0); 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 (sdr_device_config.device.find("type=") != std::string::npos) { std::cerr << "Warning:" "setting type in [uhd] device is deprecated !\n"; } sdr_device_config.txgain = pt.GetReal("uhdoutput.txgain", 0.0); sdr_device_config.tx_antenna = pt.Get("uhdoutput.tx_antenna", ""); sdr_device_config.rx_antenna = pt.Get("uhdoutput.rx_antenna", "RX2"); sdr_device_config.rxgain = pt.GetReal("uhdoutput.rxgain", 0.0); sdr_device_config.frequency = pt.GetReal("uhdoutput.frequency", 0); sdr_device_config.bandwidth = pt.GetReal("uhdoutput.bandwidth", 0); std::string chan = pt.Get("uhdoutput.channel", ""); sdr_device_config.dabMode = mod_settings.dabMode; 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 (sdr_device_config.frequency == 0) { sdr_device_config.frequency = parse_channel(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"); } sdr_device_config.lo_offset = pt.GetReal("uhdoutput.lo_offset", 0); 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") { sdr_device_config.refclk_lock_loss_behaviour = Output::CRASH; } else if (behave == "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"); } sdr_device_config.maxGPSHoldoverTime = pt.GetInteger("uhdoutput.max_gps_holdover_time", 0); sdr_device_config.dpdFeedbackServerPort = pt.GetInteger("uhdoutput.dpd_port", 0); mod_settings.sdr_device_config = sdr_device_config; mod_settings.useUHDOutput = true; } #endif // defined(HAVE_OUTPUT_UHD) #if defined(HAVE_SOAPYSDR) else if (output_selected == "soapysdr") { auto& outputsoapy_conf = mod_settings.sdr_device_config; outputsoapy_conf.device = pt.Get("soapyoutput.device", ""); outputsoapy_conf.masterClockRate = pt.GetInteger("soapyoutput.master_clock_rate", 0); outputsoapy_conf.txgain = pt.GetReal("soapyoutput.txgain", 0.0); outputsoapy_conf.tx_antenna = pt.Get("soapyoutput.tx_antenna", ""); outputsoapy_conf.lo_offset = pt.GetReal("soapyoutput.lo_offset", 0.0); outputsoapy_conf.frequency = pt.GetReal("soapyoutput.frequency", 0); outputsoapy_conf.bandwidth = pt.GetReal("soapyoutput.bandwidth", 0); std::string chan = pt.Get("soapyoutput.channel", ""); outputsoapy_conf.dabMode = mod_settings.dabMode; if (outputsoapy_conf.frequency == 0 && chan == "") { std::cerr << " soapy output enabled, but neither frequency nor channel defined.\n"; throw std::runtime_error("Configuration error"); } else if (outputsoapy_conf.frequency == 0) { outputsoapy_conf.frequency = parse_channel(chan); } else if (outputsoapy_conf.frequency != 0 && chan != "") { std::cerr << " soapy output: cannot define both frequency and channel.\n"; throw std::runtime_error("Configuration error"); } outputsoapy_conf.dpdFeedbackServerPort = pt.GetInteger("soapyoutput.dpd_port", 0); mod_settings.useSoapyOutput = true; } #endif // defined(HAVE_SOAPYSDR) #if defined(HAVE_DEXTER) else if (output_selected == "dexter") { auto& outputdexter_conf = mod_settings.sdr_device_config; outputdexter_conf.txgain = pt.GetReal("dexteroutput.txgain", 0.0); outputdexter_conf.lo_offset = pt.GetReal("dexteroutput.lo_offset", 0.0); outputdexter_conf.frequency = pt.GetReal("dexteroutput.frequency", 0); std::string chan = pt.Get("dexteroutput.channel", ""); outputdexter_conf.dabMode = mod_settings.dabMode; outputdexter_conf.maxGPSHoldoverTime = pt.GetInteger("dexteroutput.max_gps_holdover_time", 0); if (outputdexter_conf.frequency == 0 && chan == "") { std::cerr << " dexter output enabled, but neither frequency nor channel defined.\n"; throw std::runtime_error("Configuration error"); } else if (outputdexter_conf.frequency == 0) { outputdexter_conf.frequency = parse_channel(chan); } else if (outputdexter_conf.frequency != 0 && chan != "") { std::cerr << " dexter output: cannot define both frequency and channel.\n"; throw std::runtime_error("Configuration error"); } mod_settings.useDexterOutput = true; } #endif // defined(HAVE_DEXTER) #if defined(HAVE_LIMESDR) else if (output_selected == "limesdr") { auto& outputlime_conf = mod_settings.sdr_device_config; outputlime_conf.device = pt.Get("limeoutput.device", ""); outputlime_conf.masterClockRate = pt.GetInteger("limeoutput.master_clock_rate", 0); outputlime_conf.txgain = pt.GetReal("limeoutput.txgain", 0.0); outputlime_conf.tx_antenna = pt.Get("limeoutput.tx_antenna", ""); outputlime_conf.lo_offset = pt.GetReal("limeoutput.lo_offset", 0.0); outputlime_conf.frequency = pt.GetReal("limeoutput.frequency", 0); std::string chan = pt.Get("limeoutput.channel", ""); outputlime_conf.dabMode = mod_settings.dabMode; outputlime_conf.upsample = pt.GetInteger("limeoutput.upsample", 1); if (outputlime_conf.frequency == 0 && chan == "") { std::cerr << " Lime output enabled, but neither frequency nor channel defined.\n"; throw std::runtime_error("Configuration error"); } else if (outputlime_conf.frequency == 0) { outputlime_conf.frequency = parse_channel(chan); } else if (outputlime_conf.frequency != 0 && chan != "") { std::cerr << " Lime output: cannot define both frequency and channel.\n"; throw std::runtime_error("Configuration error"); } outputlime_conf.dpdFeedbackServerPort = pt.GetInteger("limeoutput.dpd_port", 0); mod_settings.useLimeOutput = true; } #endif // defined(HAVE_LIMESDR) #if defined(HAVE_BLADERF) else if (output_selected == "bladerf") { auto& outputbladerf_conf = mod_settings.sdr_device_config; outputbladerf_conf.device = pt.Get("bladerfoutput.device", ""); outputbladerf_conf.refclk_src = pt.Get("bladerfoutput.refclk_source", ""); outputbladerf_conf.txgain = pt.GetReal("bladerfoutput.txgain", 0.0); outputbladerf_conf.frequency = pt.GetReal("bladerfoutput.frequency", 0); outputbladerf_conf.bandwidth = pt.GetReal("bladerfoutput.bandwidth", 0); std::string chan = pt.Get("bladerfoutput.channel", ""); outputbladerf_conf.dabMode = mod_settings.dabMode; if (outputbladerf_conf.frequency == 0 && chan == "") { std::cerr << " BladeRF output enabled, but neither frequency nor channel defined.\n"; throw std::runtime_error("Configuration error"); } else if (outputbladerf_conf.frequency == 0) { outputbladerf_conf.frequency = parse_channel(chan); } else if (outputbladerf_conf.frequency != 0 && chan != "") { std::cerr << " BladeRF output: cannot define both frequency and channel.\n"; throw std::runtime_error("Configuration error"); } outputbladerf_conf.dpdFeedbackServerPort = pt.GetInteger("bladerfoutput.dpd_port", 0); mod_settings.useBladeRFOutput = true; } #endif // defined(HAVE_BLADERF) #if defined(HAVE_ZEROMQ) else if (output_selected == "zmq") { mod_settings.outputName = pt.Get("zmqoutput.listen", ""); mod_settings.zmqOutputSocketType = pt.Get("zmqoutput.socket_type", ""); mod_settings.useZeroMQOutput = true; } #endif else { std::cerr << "Error: Invalid output defined.\n"; throw std::runtime_error("Configuration error"); } #if defined(HAVE_OUTPUT_UHD) || defined(HAVE_DEXTER) mod_settings.sdr_device_config.enableSync = (pt.GetInteger("delaymanagement.synchronous", 0) == 1); mod_settings.sdr_device_config.muteNoTimestamps = (pt.GetInteger("delaymanagement.mutenotimestamps", 0) == 1); if (mod_settings.sdr_device_config.enableSync) { std::string delay_mgmt = pt.Get("delaymanagement.management", ""); std::string fixedoffset = pt.Get("delaymanagement.fixedoffset", ""); std::string offset_filename = pt.Get("delaymanagement.dynamicoffsetfile", ""); if (not(delay_mgmt.empty() and fixedoffset.empty() and offset_filename.empty())) { std::cerr << "Warning: you are using the old config syntax for the offset management.\n"; std::cerr << " Please see the example.ini configuration for the new settings.\n"; } try { mod_settings.tist_offset_s = pt.GetReal("delaymanagement.offset", 0.0); } catch (const std::exception &e) { std::cerr << "Error: delaymanagement: synchronous is enabled, but no offset defined!\n"; throw std::runtime_error("Configuration error"); } } #endif /* Read TII parameters from config file */ mod_settings.tiiConfig.enable = pt.GetInteger("tii.enable", 0); mod_settings.tiiConfig.comb = pt.GetInteger("tii.comb", 0); mod_settings.tiiConfig.pattern = pt.GetInteger("tii.pattern", 0); mod_settings.tiiConfig.old_variant = pt.GetInteger("tii.old_variant", 0); } void parse_args(int argc, char **argv, mod_settings_t& mod_settings) { bool use_configuration_cmdline = false; bool use_configuration_file = false; std::string configuration_file; // No argument given ? You can't be serious ! Show usage. if (argc == 1) { printUsage(argv[0]); throw std::invalid_argument("Invalid command line options"); } while (true) { int c = getopt(argc, argv, "a:C:c:f:F:g:G:hlm:o:O:r:T:u:V"); if (c == -1) { break; } if (c != 'C') { use_configuration_cmdline = true; } switch (c) { case 'C': use_configuration_file = true; configuration_file = optarg; break; case 'a': mod_settings.digitalgain = strtof(optarg, NULL); break; case 'c': mod_settings.clockRate = strtol(optarg, NULL, 0); break; case 'f': #if defined(HAVE_OUTPUT_UHD) if (mod_settings.useUHDOutput) { throw std::invalid_argument("Options -u and -f are mutually exclusive"); } #endif mod_settings.outputName = optarg; mod_settings.useFileOutput = true; break; case 'F': if (mod_settings.useFileOutput) { mod_settings.fileOutputFormat = optarg; } #if defined(HAVE_OUTPUT_UHD) else if (mod_settings.useUHDOutput) { mod_settings.sdr_device_config.frequency = strtof(optarg, NULL); } #endif else { throw std::invalid_argument("Cannot use -F before setting output!"); } break; case 'g': mod_settings.gainMode = parse_gainmode(optarg); break; case 'G': #if defined(HAVE_OUTPUT_UHD) mod_settings.sdr_device_config.txgain = strtod(optarg, NULL); #endif break; case 'l': mod_settings.loop = true; break; case 'o': mod_settings.tist_offset_s = strtod(optarg, NULL); #if defined(HAVE_OUTPUT_UHD) mod_settings.sdr_device_config.enableSync = true; #endif break; case 'm': mod_settings.dabMode = strtol(optarg, NULL, 0); break; case 'r': mod_settings.outputRate = strtol(optarg, NULL, 0); break; case 'T': mod_settings.filterTapsFilename = optarg; break; case 'u': #if defined(HAVE_OUTPUT_UHD) if (mod_settings.useFileOutput) { throw std::invalid_argument("Options -u and -f are mutually exclusive"); } 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 = true; #else throw std::invalid_argument("Cannot select UHD output, not compiled in!"); #endif break; case 'V': printVersion(); throw std::invalid_argument(""); break; case '?': case 'h': printUsage(argv[0]); throw std::invalid_argument(""); break; default: { string optstr(1, c); throw std::invalid_argument("Invalid command line option: -" + optstr); } } } // If only one argument is given, interpret as configuration file name if (argc == 2) { use_configuration_file = true; configuration_file = argv[1]; } if (use_configuration_file && use_configuration_cmdline) { fprintf(stderr, "Warning: configuration file and command " "line parameters are defined:\n\t" "Command line parameters override settings " "in the configuration file !\n"); } // Setting ETI input filename if (use_configuration_cmdline && mod_settings.inputName == "") { if (optind < argc) { mod_settings.inputName = argv[optind++]; if (mod_settings.inputName.substr(0, 4) == "zmq+" && mod_settings.inputName.find("://") != std::string::npos) { throw std::runtime_error("Support for ZeroMQ input transport has been removed."); } else if (mod_settings.inputName.substr(0, 6) == "tcp://") { mod_settings.inputTransport = "tcp"; } else if (mod_settings.inputName.substr(0, 6) == "udp://") { mod_settings.inputTransport = "edi"; } } else { mod_settings.inputName = "/dev/stdin"; } } // Checking unused arguments if (use_configuration_cmdline && optind != argc) { string invalid = "Invalid arguments:"; while (optind != argc) { invalid += argv[optind++]; } printUsage(argv[0]); etiLog.level(error) << "Received invalid command line arguments: " + invalid; throw std::invalid_argument("Invalid command line options"); } if (use_configuration_file) { parse_configfile(configuration_file, mod_settings); } }