OBECA Receive Process
Classes | Functions | Variables
main.cpp File Reference

Contains the program entry point, command line parameter handling, and the main runloop for data processing. More...

#include <argp.h>
#include <cstdlib>
#include <libconfig.h++>
#include "CasFrameProcessor.h"
#include "Gw.h"
#include "LimeSdrReader.h"
#include "MbsfnFrameProcessor.h"
#include "MeasurementFileWriter.h"
#include "Phy.h"
#include "RestHandler.h"
#include "Rrc.h"
#include "Version.h"
#include "spdlog/async.h"
#include "spdlog/spdlog.h"
#include "spdlog/sinks/syslog_sink.h"
#include "srslte/srslte.h"
#include "srslte/upper/pdcp.h"
#include "srslte/upper/rlc.h"
#include "thread_pool.hpp"
Include dependency graph for main.cpp:

Go to the source code of this file.

Classes

struct  arguments
 Holds all options passed on the command line. More...
 

Functions

void set_params (const std::string &ant, unsigned fc, double g, unsigned sr, unsigned bw)
 Set new SDR parameters and initialize resynchronisation. More...
 
auto main (int argc, char **argv) -> int
 Main entry point for the program. More...
 

Variables

void(* argp_program_version_hook )(FILE *, struct argp_state *) = print_version
 
const char * argp_program_bug_address = "Austrian Broadcasting Services <obeca@ors.at>"
 

Detailed Description

Contains the program entry point, command line parameter handling, and the main runloop for data processing.

Definition in file main.cpp.

Function Documentation

◆ main()

auto main ( int  argc,
char **  argv 
) -> int

Main entry point for the program.

Parameters
argcCommand line agument count
argvCommand line arguments
Returns
0 on clean exit, -1 on failure

Definition at line 212 of file main.cpp.

212  {
213  struct arguments arguments;
214  /* Default values */
215  arguments.config_file = "/etc/rp.conf";
216  arguments.sample_file = nullptr;
217  arguments.write_sample_file = nullptr;
218  argp_parse(&argp, argc, argv, 0, nullptr, &arguments);
219 
220  // Read and parse the configuration file
221  try {
222  cfg.readFile(arguments.config_file);
223  } catch(const FileIOException &fioex) {
224  spdlog::error("I/O error while reading config file at {}. Exiting.", arguments.config_file);
225  exit(1);
226  } catch(const ParseException &pex) {
227  spdlog::error("Config parse error at {}:{} - {}. Exiting.",
228  pex.getFile(), pex.getLine(), pex.getError());
229  exit(1);
230  }
231 
232  // Set up logging
233  std::string ident = "rp";
234  auto syslog_logger = spdlog::syslog_logger_mt("syslog", ident, LOG_PID | LOG_PERROR);
235 
236  spdlog::set_level(
237  static_cast<spdlog::level::level_enum>(arguments.log_level));
238  spdlog::set_pattern("[%H:%M:%S.%f %z] [%^%l%$] [thr %t] %v");
239 
240  spdlog::set_default_logger(syslog_logger);
241  spdlog::info("OBECA rp v{}.{}.{} starting up", VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH);
242 
243 
244  // Init and tune the SDR
245  spdlog::info("Initialising LimeSDR");
246  LimeSdrReader lime(cfg);
247 
248  if (!lime.init(0, arguments.sample_file, arguments.write_sample_file)) {
249  spdlog::error("Failed to initialize I/Q data source.");
250  exit(1);
251  }
252 
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);
258 
259  if (!lime.tune(frequency, sample_rate, bandwidth, gain, antenna)) {
260  spdlog::error("Failed to set initial center frequency. Exiting.");
261  exit(1);
262  }
263 
264  srslte_verbose = arguments.log_level == 1 ? 2 : 0;
265  srslte_use_standard_symbol_size(true);
266 
267  // Create a thread pool for the frame processors
268  unsigned thread_cnt = 4;
269  cfg.lookupValue("phy.threads", thread_cnt);
270  int phy_prio = 10;
271  cfg.lookupValue("phy.thread_priority_rt", phy_prio);
272  thread_pool pool{ thread_cnt + 1, phy_prio };
273 
274  // Elevate execution to real time scheduling
275  struct sched_param thread_param = {};
276  thread_param.sched_priority = 20;
277  cfg.lookupValue("phy.main_thread_priority_rt", thread_param.sched_priority);
278 
279  spdlog::info("Raising main thread to realtime scheduling priority {}", thread_param.sched_priority);
280 
281  int error = pthread_setschedparam(pthread_self(), SCHED_RR, &thread_param);
282  if (error != 0) {
283  spdlog::error("Cannot set main thread priority to realtime: {}. Thread will run at default priority.", strerror(error));
284  }
285 
286  bool enable_measurement_file = false;
287  cfg.lookupValue("measurement_file.enabled", enable_measurement_file);
288  MeasurementFileWriter measurement_file(cfg);
289 
290  // Create the layer components: Phy, RLC, RRC and GW
291  Phy phy(
292  cfg,
293  std::bind(&LimeSdrReader::getSamples, &lime, _1, _2, _3), // NOLINT
294  arguments.file_bw * 5,
296 
297  phy.init();
298 
299  srslte::pdcp pdcp(nullptr, "PDCP");
300  srslte::rlc rlc("RLC");
301  srslte::timer_handler timers;
302 
303  Rrc rrc(cfg, phy, rlc);
304  Gw gw(cfg, phy);
305  gw.init();
306 
307  rlc.init(&pdcp, &rrc, &timers, 0 /* RB_ID_SRB0 */);
308  pdcp.init(&rlc, &rrc, &gw);
309 
310  auto srs_level = srslte::LOG_LEVEL_WARNING;
311  switch (arguments.srs_log_level) {
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;
317  }
318 
319  // Configure srsLTE logging
320  srslte::log_ref rlc_log = srslte::logmap::get("RLC");
321  rlc_log->set_level(srs_level);
322  rlc_log->set_hex_limit(128);
323 
324  srslte::log_ref mac_log = srslte::logmap::get("MAC");
325  mac_log->set_level(srs_level);
326  mac_log->set_hex_limit(128);
327 
328  state_t state = searching;
329 
330  // Create the RESTful API handler
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);
334  RestHandler rest_handler(cfg, uri, state, lime, phy, set_params);
335 
336  // Initialize one CAS and thered_cnt MBSFN frame processors
337  CasFrameProcessor cas_processor(cfg, phy, rlc, rest_handler);
338  if (!cas_processor.init()) {
339  spdlog::error("Failed to create CAS processor. Exiting.");
340  exit(1);
341  }
342 
343  std::vector<MbsfnFrameProcessor*> mbsfn_processors;
344  for (int i = 0; i < thread_cnt; i++) {
345  auto p = new MbsfnFrameProcessor(cfg, rlc, phy, mac_log, rest_handler);
346  if (!p->init()) {
347  spdlog::error("Failed to create MBSFN processor. Exiting.");
348  exit(1);
349  }
350  mbsfn_processors.push_back(p);
351  }
352 
353  // Start receiving sample data
354  lime.start();
355 
356  uint32_t tti = 0;
357 
358  uint32_t measurement_interval = 5;
359  cfg.lookupValue("measurement_file.interval_secs", measurement_interval);
360  measurement_interval *= 1000;
361  uint32_t tick = 0;
362 
363  // Initial state: searching a cell
364  state = searching;
365 
366  // Start the main processing loop
367  for (;;) {
368  if (state == searching) {
369  if (restart) {
370  lime.stop();
371  sample_rate = search_sample_rate; // sample rate for searching
372  lime.tune(frequency, sample_rate, bandwidth, gain, antenna);
373  lime.start();
374  }
375  // In searching state, clear the receive buffer and try to find a cell at the configured frequency and synchronize with it
376  restart = false;
377  lime.clear_buffer();
378  bool cell_found = phy.cell_search();
379  if (cell_found) {
380  // A cell has been found. We now know the required number of PRB = bandwidth of the carrier. Set the approproiate
381  // sample rate...
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(),
385  phy.nr_prb() * 0.2);
386  lime.stop();
387 
388  bandwidth = (cas_nof_prb * 200000) * 1.2;
389  lime.tune(frequency, new_srate, bandwidth, gain, antenna);
390  lime.start();
391  spdlog::debug("Synchronizing subframe");
392  // ... and move to syncing state.
393  state = syncing;
394  } else {
395  sleep(1);
396  }
397  } else if (state == syncing) {
398  // In syncing state, we already know the cell we want to camp on, and the SDR is tuned to the required
399  // sample rate for its number of PRB / bandwidth. We now synchronize PSS/SSS and receive the MIB once again
400  // at this sample rate.
401  unsigned max_frames = 200;
402  bool sfn_sync = false;
403  while (!sfn_sync && max_frames-- > 0) {
404  sfn_sync = phy.synchronize_subframe();
405  }
406 
407  if (max_frames == 0 && !sfn_sync) {
408  // Failed. Back to square one: search state.
409  spdlog::warn("Synchronization failed. Going back to search state.");
410  state = searching;
411  sleep(1);
412  }
413 
414  if (sfn_sync) {
415  // We're locked on to the cell, and have succesfully received the MIB at the target sample rate.
416  spdlog::info("Decoded MIB at target sample rate, TTI is {}. Subframe synchronized.", phy.tti());
417 
418  // Set the cell parameters in the CAS and MBSFN processors
419  cas_processor.set_cell(phy.cell());
420 
421  for (int i = 0; i < thread_cnt; i++) {
422  mbsfn_processors[i]->unlock();
423  }
424 
425  // Get the initial TTI / subframe ID (= system frame number * 10 + subframe number)
426  tti = phy.tti();
427  // Reset the RRC
428  rrc.reset();
429 
430  // Ready to receive actual data. Go to processing state.
431  state = processing;
432  }
433  } else { // processing
434  int mb_idx = 0;
435  while (state == processing) {
436  tti = (tti + 1) % 10240; // Clamp the TTI
437  if (tti%40 == 0) {
438  // This is subframe 0 in a radio frame divisible by 4, and hence a CAS frame.
439  // Get the samples from the SDR interface, hand them to a CAS processor, and start it
440  // on a thread from the pool.
441  if (!restart && phy.get_next_frame(cas_processor.rx_buffer(), cas_processor.rx_buffer_size())) {
442  spdlog::debug("sending tti {} to regular processor", tti);
443  pool.push([ObjectPtr = &cas_processor, tti] {
444  ObjectPtr->process(tti);
445  });
446 
447  // Set constellation diagram data and rx params for CAS in the REST API handler
448  rest_handler._ce_values = std::move(cas_processor.ce_values());
449  rest_handler._pdsch.SetData(cas_processor.pdsch_data());
450  rest_handler.add_cinr_value(cas_processor.cinr_db());
451 
452  if (phy.nof_mbsfn_prb() != mbsfn_nof_prb)
453  {
454  mbsfn_nof_prb = phy.nof_mbsfn_prb();
455  // Adjust the sample rate to fit the wider MBSFN bandwidth
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);
459  lime.stop();
460 
461  bandwidth = (mbsfn_nof_prb * 200000) * 1.2;
462  lime.tune(frequency, new_srate, bandwidth, gain, antenna);
463  lime.start();
464  spdlog::debug("Synchronizing subframe after PRB extension");
465  // ... and move to syncing state.
466  phy.set_cell();
467  cas_processor.set_cell(phy.cell());
468  state = syncing;
469  }
470  } else {
471  // Failed to receive data, or sync lost. Go back to searching state.
472  lime.stop();
473  sample_rate = search_sample_rate; // sample rate for searching
474  lime.tune(frequency, sample_rate, bandwidth, gain, antenna);
475  lime.start();
476  rrc.reset();
477  phy.reset();
478 
479  sleep(1);
480  state = searching;
481  }
482  } else {
483  // All other frames in FeMBMS dedicated mode are MBSFN frames.
484  spdlog::debug("sending tti {} to mbsfn proc {}", tti, mb_idx);
485 
486  // Get the samples from the SDR interface, hand them to an MNSFN processor, and start it
487  // on a thread from the pool. Getting the buffer pointer from the pool also locks this processor.
488  if (!restart && phy.get_next_frame(mbsfn_processors[mb_idx]->get_rx_buffer_and_lock(), mbsfn_processors[mb_idx]->rx_buffer_size())) {
489  if (phy.mcch_configured()) {
490  // If data frm SIB1/SIB13 has been received in CAS, configure the processors accordingly
491  if (!mbsfn_processors[mb_idx]->mbsfn_configured()) {
492  srslte_scs_t scs;
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;
497  }
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);
502  }
503  pool.push([ObjectPtr = mbsfn_processors[mb_idx], tti] {
504  ObjectPtr->process(tti);
505  });
506  } else {
507  // Nothing to do yet, we lack the data from SIB1/SIB13
508  // Discard the samples and unlock the processor.
509  mbsfn_processors[mb_idx]->unlock();
510  }
511  } else {
512  // Failed to receive data, or sync lost. Go back to searching state.
513  spdlog::warn("Synchronization lost while processing. Going back to searching state.");
514  lime.stop();
515  sample_rate = search_sample_rate; // sample rate for searching
516  lime.tune(frequency, sample_rate, bandwidth, gain, antenna);
517  lime.start();
518 
519  state = searching;
520  sleep(1);
521  rrc.reset();
522  phy.reset();
523  }
524  mb_idx = static_cast<int>((mb_idx + 1) % thread_cnt);
525  }
526 
527  tick++;
528  if (tick%measurement_interval == 0) {
529  // It's time to output rx info to the measurement file and to syslog.
530  // Collect the relevant info and write it out.
531  std::vector<std::string> cols;
532 
533  spdlog::info("CINR {:.2f} dB", rest_handler.cinr_db() );
534  cols.push_back(std::to_string(rest_handler.cinr_db()));
535 
536  spdlog::info("PDSCH: MCS {}, BLER {}, BER {}",
537  rest_handler._pdsch.mcs,
538  ((rest_handler._pdsch.errors * 1.0) / (rest_handler._pdsch.total * 1.0)),
539  rest_handler._pdsch.ber);
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));
543 
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);
548 
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));
552 
553  auto mch_info = phy.mch_info();
554  int mch_idx = 0;
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 {}",
557  mch_idx,
558  mch.mcs,
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));
565 
566  int mtch_idx = 0;
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{}, {}",
569  mtch_idx,
570  mtch.lcid,
571  mtch.tmgi,
572  mtch.dest);
573  mtch_idx++;
574  });
575  mch_idx++;
576  });
577  spdlog::info("-----");
578  if (enable_measurement_file) {
579  measurement_file.WriteLogValues(cols);
580  }
581  }
582  }
583  }
584  }
585 
586  // Main loop ended by signal. Free the MBSFN processors, and bail.
587  for (int i = 0; i < thread_cnt; i++) {
588  delete( mbsfn_processors[i] );
589  }
590 exit:
591  return 0;
592 }

◆ set_params()

void set_params ( const std::string &  ant,
unsigned  fc,
double  g,
unsigned  sr,
unsigned  bw 
)

Set new SDR parameters and initialize resynchronisation.

This function is used by the RESTful API handler to modify the SDR params.

Parameters
antName of the antenna input (For LimeSDR Mini: LNAW, LNAL)
fcCenter frequency to tune to (in Hz)
gainTotal system gain to set [0..1]
srSample rate (in Hz)
bwLow pass filter bandwidth (in Hz)

Definition at line 191 of file main.cpp.

191  {
192  sample_rate = sr;
193  frequency = fc;
194  bandwidth = bw;
195  antenna = ant;
196  if (g > 1) { g = 1; }
197  if (g < 0) { g = 0; }
198  gain = g;
199  spdlog::info("RESTful API requesting new parameters: fc {}, bw {}, rate {}, gain {}, antenna {}",
200  frequency, bandwidth, sample_rate, gain, antenna);
201 
202  restart = true;
203 }
Rrc
Simple RRC component between PHY and RLC.
Definition: Rrc.h:34
arguments::srs_log_level
unsigned srs_log_level
srsLTE log level
Definition: main.cpp:101
MeasurementFileWriter
Writes measurement data / current reception parameters to a file.
Definition: MeasurementFileWriter.h:35
arguments::log_level
unsigned log_level
log level
Definition: main.cpp:100
LimeSdrReader::getSamples
int getSamples(cf_t *data, uint32_t nsamples, srslte_timestamp_t *rx_time)
Store nsamples count samples into the buffer at data.
Definition: LimeSdrReader.cpp:306
Gw
Network gateway component.
Definition: Gw.h:34
arguments
Holds all options passed on the command line.
Definition: main.cpp:98
CasFrameProcessor
Frame processor for CAS subframes.
Definition: CasFrameProcessor.h:38
Phy
The PHY component.
Definition: Phy.h:43
Phy::mtch_info_t
Definition: Phy.h:158
MbsfnFrameProcessor
Frame processor for MBSFN subframes.
Definition: MbsfnFrameProcessor.h:39
arguments::sample_file
const char * sample_file
file path of the sample file.
Definition: main.cpp:103
set_params
void set_params(const std::string &ant, unsigned fc, double g, unsigned sr, unsigned bw)
Set new SDR parameters and initialize resynchronisation.
Definition: main.cpp:191
arguments::override_nof_prb
int8_t override_nof_prb
ovride PRB number
Definition: main.cpp:102
arguments::write_sample_file
const char * write_sample_file
file path of the created sample file.
Definition: main.cpp:106
RestHandler
The RESTful API handler.
Definition: RestHandler.h:47
arguments::config_file
const char * config_file
file path of the config file.
Definition: main.cpp:99
Phy::mch_info_t
Definition: Phy.h:163
LimeSdrReader
Interface to LimeSDR via LimeSuite.
Definition: LimeSdrReader.h:36
arguments::file_bw
uint8_t file_bw
bandwidth of the sample file
Definition: main.cpp:104