34 #include <libconfig.h++>
36 #include "CasFrameProcessor.h"
38 #include "LimeSdrReader.h"
39 #include "MbsfnFrameProcessor.h"
40 #include "MeasurementFileWriter.h"
42 #include "RestHandler.h"
45 #include "spdlog/async.h"
46 #include "spdlog/spdlog.h"
47 #include "spdlog/sinks/syslog_sink.h"
48 #include "srslte/srslte.h"
49 #include "srslte/upper/pdcp.h"
50 #include "srslte/upper/rlc.h"
51 #include "thread_pool.hpp"
53 using libconfig::Config;
54 using libconfig::FileIOException;
55 using libconfig::ParseException;
57 using std::placeholders::_1;
58 using std::placeholders::_2;
59 using std::placeholders::_3;
61 static void print_version(FILE *stream,
struct argp_state *state);
62 void (*argp_program_version_hook)(FILE *,
struct argp_state *) = print_version;
63 const char *argp_program_bug_address =
"Austrian Broadcasting Services <obeca@ors.at>";
64 static char doc[] =
"OBECA Receive Process";
66 static struct argp_option options[] = {
67 {
"config",
'c',
"FILE", 0,
"Configuration file (default: /etc/rp.conf)", 0},
68 {
"log-level",
'l',
"LEVEL", 0,
69 "Log verbosity: 0 = trace, 1 = debug, 2 = info, 3 = warn, 4 = error, 5 = "
70 "critical, 6 = none. Default: 2.",
72 {
"srslte-log-level",
's',
"LEVEL", 0,
73 "Log verbosity for srslte: 0 = debug, 1 = info, 2 = warn, 3 = error, 4 = "
76 {
"sample-file",
'f',
"FILE", 0,
77 "Sample file in 4 byte float interleaved format to read I/Q data from. If "
78 "present, the data from this file will be decoded instead of live SDR "
79 "data. The channel bandwith must be specified with the --file-bandwidth "
80 "flag, and the sample rate of the file must be suitable for this "
83 {
"write-sample-file",
'w',
"FILE", 0,
84 "Create a sample file in 4 byte float interleaved format containing the "
85 "raw received I/Q data.",
87 {
"file-bandwidth",
'b',
"BANDWIDTH (MHz)", 0,
88 "If decoding data from a file, specify the channel bandwidth of the "
89 "recorded data in MHz here (e.g. 5)",
91 {
"override_nof_prb",
'p',
"# PRB", 0,
92 "Override the number of PRB received in the MIB", 0},
93 {
nullptr, 0,
nullptr, 0,
nullptr, 0}};
112 static auto parse_opt(
int key,
char *arg,
struct argp_state *state) -> error_t {
123 static_cast<unsigned>(strtoul(arg,
nullptr, 10));
136 static_cast<int8_t
>(strtol(arg,
nullptr, 10));
142 return ARGP_ERR_UNKNOWN;
147 static struct argp argp = {options, parse_opt,
nullptr, doc,
148 nullptr,
nullptr,
nullptr};
153 void print_version(FILE *stream,
struct argp_state * ) {
154 fprintf(stream,
"%s.%s.%s\n", std::to_string(VERSION_MAJOR).c_str(),
155 std::to_string(VERSION_MINOR).c_str(),
156 std::to_string(VERSION_PATCH).c_str());
161 static unsigned sample_rate = 7680000;
162 static unsigned search_sample_rate = 7680000;
163 static unsigned frequency = 667000000;
164 static uint32_t bandwidth = 10000000;
165 static double gain = 0.9;
166 static std::string antenna =
"LNAW";
168 static unsigned mbsfn_nof_prb = 0;
169 static unsigned cas_nof_prb = 0;
179 static bool restart =
false;
191 void set_params(
const std::string& ant,
unsigned fc,
double g,
unsigned sr,
unsigned bw) {
196 if (g > 1) { g = 1; }
197 if (g < 0) { g = 0; }
199 spdlog::info(
"RESTful API requesting new parameters: fc {}, bw {}, rate {}, gain {}, antenna {}",
200 frequency, bandwidth, sample_rate, gain, antenna);
212 auto main(
int argc,
char **argv) ->
int {
218 argp_parse(&argp, argc, argv, 0,
nullptr, &
arguments);
223 }
catch(
const FileIOException &fioex) {
226 }
catch(
const ParseException &pex) {
227 spdlog::error(
"Config parse error at {}:{} - {}. Exiting.",
228 pex.getFile(), pex.getLine(), pex.getError());
233 std::string ident =
"rp";
234 auto syslog_logger = spdlog::syslog_logger_mt(
"syslog", ident, LOG_PID | LOG_PERROR);
238 spdlog::set_pattern(
"[%H:%M:%S.%f %z] [%^%l%$] [thr %t] %v");
240 spdlog::set_default_logger(syslog_logger);
241 spdlog::info(
"OBECA rp v{}.{}.{} starting up", VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH);
245 spdlog::info(
"Initialising LimeSDR");
249 spdlog::error(
"Failed to initialize I/Q data source.");
253 cfg.lookupValue(
"sdr.search_sample_rate_hz", sample_rate);
254 search_sample_rate = sample_rate;
255 cfg.lookupValue(
"sdr.center_frequency_hz", frequency);
256 cfg.lookupValue(
"sdr.normalized_gain", gain);
257 cfg.lookupValue(
"sdr.antenna", antenna);
259 if (!lime.
tune(frequency, sample_rate, bandwidth, gain, antenna)) {
260 spdlog::error(
"Failed to set initial center frequency. Exiting.");
265 srslte_use_standard_symbol_size(
true);
268 unsigned thread_cnt = 4;
269 cfg.lookupValue(
"phy.threads", thread_cnt);
271 cfg.lookupValue(
"phy.thread_priority_rt", phy_prio);
272 thread_pool pool{ thread_cnt + 1, phy_prio };
275 struct sched_param thread_param = {};
276 thread_param.sched_priority = 20;
277 cfg.lookupValue(
"phy.main_thread_priority_rt", thread_param.sched_priority);
279 spdlog::info(
"Raising main thread to realtime scheduling priority {}", thread_param.sched_priority);
281 int error = pthread_setschedparam(pthread_self(), SCHED_RR, &thread_param);
283 spdlog::error(
"Cannot set main thread priority to realtime: {}. Thread will run at default priority.", strerror(error));
286 bool enable_measurement_file =
false;
287 cfg.lookupValue(
"measurement_file.enabled", enable_measurement_file);
299 srslte::pdcp pdcp(
nullptr,
"PDCP");
300 srslte::rlc rlc(
"RLC");
301 srslte::timer_handler timers;
303 Rrc rrc(cfg, phy, rlc);
307 rlc.init(&pdcp, &rrc, &timers, 0 );
308 pdcp.init(&rlc, &rrc, &gw);
310 auto srs_level = srslte::LOG_LEVEL_WARNING;
312 case 0: srs_level = srslte::LOG_LEVEL_DEBUG;
break;
313 case 1: srs_level = srslte::LOG_LEVEL_INFO;
break;
314 case 2: srs_level = srslte::LOG_LEVEL_WARNING;
break;
315 case 3: srs_level = srslte::LOG_LEVEL_ERROR;
break;
316 case 4: srs_level = srslte::LOG_LEVEL_NONE;
break;
320 srslte::log_ref rlc_log = srslte::logmap::get(
"RLC");
321 rlc_log->set_level(srs_level);
322 rlc_log->set_hex_limit(128);
324 srslte::log_ref mac_log = srslte::logmap::get(
"MAC");
325 mac_log->set_level(srs_level);
326 mac_log->set_hex_limit(128);
328 state_t state = searching;
331 std::string uri =
"http://0.0.0.0:3000/rp-api/";
332 cfg.lookupValue(
"restful_api.uri", uri);
333 spdlog::info(
"Starting RESTful API handler at {}", uri);
338 if (!cas_processor.
init()) {
339 spdlog::error(
"Failed to create CAS processor. Exiting.");
343 std::vector<MbsfnFrameProcessor*> mbsfn_processors;
344 for (
int i = 0; i < thread_cnt; i++) {
347 spdlog::error(
"Failed to create MBSFN processor. Exiting.");
350 mbsfn_processors.push_back(p);
358 uint32_t measurement_interval = 5;
359 cfg.lookupValue(
"measurement_file.interval_secs", measurement_interval);
360 measurement_interval *= 1000;
368 if (state == searching) {
371 sample_rate = search_sample_rate;
372 lime.
tune(frequency, sample_rate, bandwidth, gain, antenna);
382 cas_nof_prb = mbsfn_nof_prb = phy.
nr_prb();
383 unsigned new_srate = srslte_sampling_freq_hz(cas_nof_prb);
384 spdlog::info(
"Setting sample rate {} Mhz for {} PRB / {} Mhz channel width", new_srate/1000000.0, phy.
nr_prb(),
388 bandwidth = (cas_nof_prb * 200000) * 1.2;
389 lime.
tune(frequency, new_srate, bandwidth, gain, antenna);
391 spdlog::debug(
"Synchronizing subframe");
397 }
else if (state == syncing) {
401 unsigned max_frames = 200;
402 bool sfn_sync =
false;
403 while (!sfn_sync && max_frames-- > 0) {
407 if (max_frames == 0 && !sfn_sync) {
409 spdlog::warn(
"Synchronization failed. Going back to search state.");
416 spdlog::info(
"Decoded MIB at target sample rate, TTI is {}. Subframe synchronized.", phy.
tti());
421 for (
int i = 0; i < thread_cnt; i++) {
422 mbsfn_processors[i]->unlock();
435 while (state == processing) {
436 tti = (tti + 1) % 10240;
442 spdlog::debug(
"sending tti {} to regular processor", tti);
443 pool.push([ObjectPtr = &cas_processor, tti] {
444 ObjectPtr->process(tti);
450 rest_handler.add_cinr_value(cas_processor.
cinr_db());
456 unsigned new_srate = srslte_sampling_freq_hz(mbsfn_nof_prb);
457 spdlog::info(
"Setting sample rate {} Mhz for MBSFN with {} PRB / {} Mhz channel width", new_srate/1000000.0, mbsfn_nof_prb,
458 mbsfn_nof_prb * 0.2);
461 bandwidth = (mbsfn_nof_prb * 200000) * 1.2;
462 lime.
tune(frequency, new_srate, bandwidth, gain, antenna);
464 spdlog::debug(
"Synchronizing subframe after PRB extension");
473 sample_rate = search_sample_rate;
474 lime.
tune(frequency, sample_rate, bandwidth, gain, antenna);
484 spdlog::debug(
"sending tti {} to mbsfn proc {}", tti, mb_idx);
488 if (!restart && phy.
get_next_frame(mbsfn_processors[mb_idx]->get_rx_buffer_and_lock(), mbsfn_processors[mb_idx]->rx_buffer_size())) {
491 if (!mbsfn_processors[mb_idx]->mbsfn_configured()) {
493 switch (phy.mbsfn_subcarrier_spacing()) {
494 case Phy::SubcarrierSpacing::df_15kHz: scs = SRSLTE_SCS_15KHZ;
break;
495 case Phy::SubcarrierSpacing::df_7kHz5: scs = SRSLTE_SCS_7KHZ5;
break;
496 case Phy::SubcarrierSpacing::df_1kHz25: scs = SRSLTE_SCS_1KHZ25;
break;
498 auto cell = phy.
cell();
499 cell.nof_prb = cell.mbsfn_prb;
500 mbsfn_processors[mb_idx]->set_cell(cell);
501 mbsfn_processors[mb_idx]->configure_mbsfn(phy.
mbsfn_area_id(), scs);
503 pool.push([ObjectPtr = mbsfn_processors[mb_idx], tti] {
504 ObjectPtr->process(tti);
509 mbsfn_processors[mb_idx]->unlock();
513 spdlog::warn(
"Synchronization lost while processing. Going back to searching state.");
515 sample_rate = search_sample_rate;
516 lime.
tune(frequency, sample_rate, bandwidth, gain, antenna);
524 mb_idx =
static_cast<int>((mb_idx + 1) % thread_cnt);
528 if (tick%measurement_interval == 0) {
531 std::vector<std::string> cols;
533 spdlog::info(
"CINR {:.2f} dB", rest_handler.
cinr_db() );
534 cols.push_back(std::to_string(rest_handler.
cinr_db()));
536 spdlog::info(
"PDSCH: MCS {}, BLER {}, BER {}",
538 ((rest_handler.
_pdsch.errors * 1.0) / (rest_handler.
_pdsch.total * 1.0)),
540 cols.push_back(std::to_string(rest_handler.
_pdsch.mcs));
541 cols.push_back(std::to_string(((rest_handler.
_pdsch.errors * 1.0) / (rest_handler.
_pdsch.total * 1.0))));
542 cols.push_back(std::to_string(rest_handler.
_pdsch.ber));
544 spdlog::info(
"MCCH: MCS {}, BLER {}, BER {}",
545 rest_handler.
_mcch.mcs,
546 ((rest_handler.
_mcch.errors * 1.0) / (rest_handler.
_mcch.total * 1.0)),
547 rest_handler.
_mcch.ber);
549 cols.push_back(std::to_string(rest_handler.
_mcch.mcs));
550 cols.push_back(std::to_string(((rest_handler.
_mcch.errors * 1.0) / (rest_handler.
_mcch.total * 1.0))));
551 cols.push_back(std::to_string(rest_handler.
_mcch.ber));
553 auto mch_info = phy.mch_info();
555 std::for_each(std::begin(mch_info), std::end(mch_info), [&cols, &mch_idx, &rest_handler](
Phy::mch_info_t const& mch) {
556 spdlog::info(
"MCH {}: MCS {}, BLER {}, BER {}",
559 (rest_handler.
_mch[mch_idx].errors * 1.0) / (rest_handler.
_mch[mch_idx].total * 1.0),
560 rest_handler.
_mch[mch_idx].ber);
561 cols.push_back(std::to_string(mch_idx));
562 cols.push_back(std::to_string(mch.mcs));
563 cols.push_back(std::to_string((rest_handler.
_mch[mch_idx].errors * 1.0) / (rest_handler.
_mch[mch_idx].total * 1.0)));
564 cols.push_back(std::to_string(rest_handler.
_mch[mch_idx].ber));
567 std::for_each(std::begin(mch.mtchs), std::end(mch.mtchs), [&mtch_idx](
Phy::mtch_info_t const& mtch) {
568 spdlog::info(
" MTCH {}: LCID {}, TMGI 0x{}, {}",
577 spdlog::info(
"-----");
578 if (enable_measurement_file) {
587 for (
int i = 0; i < thread_cnt; i++) {
588 delete( mbsfn_processors[i] );