5G-MAG Reference Tools - MBMS Modem
main.cpp
Go to the documentation of this file.
1 // 5G-MAG Reference Tools
2 // MBMS Modem Process
3 //
4 // Copyright (C) 2021 Klaus Kühnhammer (Österreichische Rundfunksender GmbH & Co KG)
5 //
6 // This program is free software: you can redistribute it and/or modify
7 // it under the terms of the GNU Affero General Public License as published by
8 // the Free Software Foundation, either version 3 of the License, or
9 // (at your option) any later version.
10 //
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU Affero General Public License for more details.
15 //
16 // You should have received a copy of the GNU Affero General Public License
17 // along with this program. If not, see <http://www.gnu.org/licenses/>.
18 //
19 
31 #include <argp.h>
32 
33 #include <cstdlib>
34 #include <libconfig.h++>
35 
36 #include "CasFrameProcessor.h"
37 #include "Gw.h"
38 #include "SdrReader.h"
39 #include "MbsfnFrameProcessor.h"
40 #include "MeasurementFileWriter.h"
41 #include "Phy.h"
42 #include "RestHandler.h"
43 #include "Rrc.h"
44 #include "Version.h"
45 #include "spdlog/async.h"
46 #include "spdlog/spdlog.h"
47 #include "spdlog/sinks/syslog_sink.h"
48 #include "srsran/srsran.h"
49 #include "srsran/upper/pdcp.h"
50 #include "srsran/rlc/rlc.h"
51 #include "thread_pool.hpp"
52 
53 using libconfig::Config;
54 using libconfig::FileIOException;
55 using libconfig::ParseException;
56 
57 using std::placeholders::_1;
58 using std::placeholders::_2;
59 using std::placeholders::_3;
60 
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 = "5G-MAG Reference Tools <reference-tools@5g-mag.com>";
64 static char doc[] = "5G-MAG-RT MBMS Modem Process"; // NOLINT
65 
66 static struct argp_option options[] = { // NOLINT
67  {"config", 'c', "FILE", 0, "Configuration file (default: /etc/5gmag-rt.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.",
71  0},
72  {"srsran-log-level", 's', "LEVEL", 0,
73  "Log verbosity for srsran: 0 = debug, 1 = info, 2 = warn, 3 = error, 4 = "
74  "none, Default: 4.",
75  0},
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 "
81  "bandwidth.",
82  0},
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.",
86  0},
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)",
90  0},
91  {"override_nof_prb", 'p', "# PRB", 0,
92  "Override the number of PRB received in the MIB", 0},
93  {"sdr_devices", 'd', nullptr, 0,
94  "Prints a list of all available SDR devices", 0},
95  {"repeat", 'r', nullptr, 0,
96  "Replay the sample file endlessly (default: false)", 0},
97 
98  {nullptr, 0, nullptr, 0, nullptr, 0}};
99 
103 struct arguments {
104  const char *config_file = {};
105  unsigned log_level = 2;
106  unsigned srs_log_level = 4;
107  int8_t override_nof_prb = -1;
108  const char *sample_file = {};
109  uint8_t file_bw = 0;
110  const char
112  bool list_sdr_devices = false;
113  bool repeat_sample_file = false;
114 };
115 
119 static auto parse_opt(int key, char *arg, struct argp_state *state) -> error_t {
120  auto arguments = static_cast<struct arguments *>(state->input);
121  switch (key) {
122  case 'c':
123  arguments->config_file = arg;
124  break;
125  case 'l':
126  arguments->log_level = static_cast<unsigned>(strtoul(arg, nullptr, 10));
127  break;
128  case 's':
130  static_cast<unsigned>(strtoul(arg, nullptr, 10));
131  break;
132  case 'f':
133  arguments->sample_file = arg;
134  break;
135  case 'w':
137  break;
138  case 'b':
139  arguments->file_bw = static_cast<uint8_t>(strtoul(arg, nullptr, 10));
140  break;
141  case 'p':
143  static_cast<int8_t>(strtol(arg, nullptr, 10));
144  break;
145  case 'd':
146  arguments->list_sdr_devices = true;
147  break;
148  case 'r':
150  break;
151  case ARGP_KEY_ARG:
152  argp_usage(state);
153  break;
154  default:
155  return ARGP_ERR_UNKNOWN;
156  }
157  return 0;
158 }
159 
160 static struct argp argp = {options, parse_opt, nullptr, doc,
161  nullptr, nullptr, nullptr};
162 
166 void print_version(FILE *stream, struct argp_state * /*state*/) {
167  fprintf(stream, "%s.%s.%s\n", std::to_string(VERSION_MAJOR).c_str(),
168  std::to_string(VERSION_MINOR).c_str(),
169  std::to_string(VERSION_PATCH).c_str());
170 }
171 
172 static Config cfg;
174 static unsigned sample_rate = 7680000;
175 static unsigned search_sample_rate = 7680000;
176 static unsigned frequency = 667000000;
177 static uint32_t bandwidth = 10000000;
178 static double gain = 0.9;
179 static std::string antenna = "LNAW";
180 static bool use_agc = false;
181 
182 static unsigned mbsfn_nof_prb = 0;
183 static unsigned cas_nof_prb = 0;
184 
193 static bool restart = false;
194 
205 void set_params(const std::string& ant, unsigned fc, double g, unsigned sr, unsigned bw) {
206  sample_rate = sr;
207  frequency = fc;
208  bandwidth = bw;
209  antenna = ant;
210  gain = g;
211  spdlog::info("RESTful API requesting new parameters: fc {}, bw {}, rate {}, gain {}, antenna {}",
213 
214  restart = true;
215 }
216 
224 auto main(int argc, char **argv) -> int {
225  struct arguments arguments;
226  /* Default values */
227  arguments.config_file = "/etc/5gmag-rt.conf";
228  arguments.sample_file = nullptr;
229  arguments.write_sample_file = nullptr;
230  argp_parse(&argp, argc, argv, 0, nullptr, &arguments);
231 
232  // Read and parse the configuration file
233  try {
234  cfg.readFile(arguments.config_file);
235  } catch(const FileIOException &fioex) {
236  spdlog::error("I/O error while reading config file at {}. Exiting.", arguments.config_file);
237  exit(1);
238  } catch(const ParseException &pex) {
239  spdlog::error("Config parse error at {}:{} - {}. Exiting.",
240  pex.getFile(), pex.getLine(), pex.getError());
241  exit(1);
242  }
243 
244  // Set up logging
245  std::string ident = "modem";
246  auto syslog_logger = spdlog::syslog_logger_mt("syslog", ident, LOG_PID | LOG_PERROR | LOG_CONS );
247 
248  spdlog::set_level(
249  static_cast<spdlog::level::level_enum>(arguments.log_level));
250  spdlog::set_pattern("[%H:%M:%S.%f %z] [%^%l%$] [thr %t] %v");
251 
252  spdlog::set_default_logger(syslog_logger);
253  spdlog::info("5g-mag-rt modem v{}.{}.{} starting up", VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH);
254 
255  // Init and tune the SDR
256  auto rx_channels = 1;
257  cfg.lookupValue("modem.sdr.rx_channels", rx_channels);
258  spdlog::info("Initialising SDR with {} RX channel(s)", rx_channels);
259  SdrReader sdr(cfg, rx_channels);
261  sdr.enumerateDevices();
262  exit(0);
263  }
264 
265  std::string sdr_dev = "driver=lime";
266  cfg.lookupValue("modem.sdr.device_args", sdr_dev);
268  spdlog::error("Failed to initialize I/Q data source.");
269  exit(1);
270  }
271 
272  cfg.lookupValue("modem.sdr.search_sample_rate_hz", sample_rate);
274 
275  unsigned long long center_frequency = frequency;
276  if (!cfg.lookupValue("modem.sdr.center_frequency_hz", center_frequency)) {
277  spdlog::error("Unable to parse center_frequency_hz - values must have a ‘L’ character appended");
278  exit(1);
279  }
280  // We needed unsigned long long for correct parsing,
281  // but unsigned is required
282  if (center_frequency <= UINT_MAX) {
283  frequency = static_cast<unsigned>(center_frequency);
284  } else {
285  spdlog::error("Configured center_frequency_hz is {}, maximal value supported is {}.",
286  center_frequency, UINT_MAX);
287  exit(1);
288  }
289 
290 
291  cfg.lookupValue("modem.sdr.normalized_gain", gain);
292  cfg.lookupValue("modem.sdr.antenna", antenna);
293  cfg.lookupValue("modem.sdr.use_agc", use_agc);
294 
296  spdlog::error("Failed to set initial center frequency. Exiting.");
297  exit(1);
298  }
299 
300  set_srsran_verbose_level(arguments.log_level <= 1 ? SRSRAN_VERBOSE_DEBUG : SRSRAN_VERBOSE_NONE);
301  srsran_use_standard_symbol_size(true);
302 
303  // Create a thread pool for the frame processors
304  unsigned thread_cnt = 4;
305  cfg.lookupValue("modem.phy.threads", thread_cnt);
306  int phy_prio = 10;
307  cfg.lookupValue("modem.phy.thread_priority_rt", phy_prio);
308  thread_pool pool{ thread_cnt + 1, phy_prio };
309 
310  // Elevate execution to real time scheduling
311  struct sched_param thread_param = {};
312  thread_param.sched_priority = 20;
313  cfg.lookupValue("modem.phy.main_thread_priority_rt", thread_param.sched_priority);
314 
315  spdlog::info("Raising main thread to realtime scheduling priority {}", thread_param.sched_priority);
316 
317  int error = pthread_setschedparam(pthread_self(), SCHED_RR, &thread_param);
318  if (error != 0) {
319  spdlog::error("Cannot set main thread priority to realtime: {}. Thread will run at default priority.", strerror(error));
320  }
321 
322  bool enable_measurement_file = false;
323  cfg.lookupValue("modem.measurement_file.enabled", enable_measurement_file);
324  MeasurementFileWriter measurement_file(cfg);
325 
326  // Create the layer components: Phy, RLC, RRC and GW
327  Phy phy(
328  cfg,
329  std::bind(&SdrReader::get_samples, &sdr, _1, _2, _3), // NOLINT
330  arguments.file_bw ? arguments.file_bw * 5 : 25,
332  rx_channels);
333 
334  phy.init();
335 
336  srsran::pdcp pdcp(nullptr, "PDCP");
337  srsran::rlc rlc("RLC");
338  srsran::timer_handler timers;
339 
340  Rrc rrc(cfg, phy, rlc);
341  Gw gw(cfg, phy);
342  gw.init();
343 
344  rlc.init(&pdcp, &rrc, &timers, 0 /* RB_ID_SRB0 */);
345  pdcp.init(&rlc, &rrc, &gw);
346 
347  auto srs_level = srslog::basic_levels::none;
348  switch (arguments.srs_log_level) {
349  case 0: srs_level = srslog::basic_levels::debug; break;
350  case 1: srs_level = srslog::basic_levels::info; break;
351  case 2: srs_level = srslog::basic_levels::warning; break;
352  case 3: srs_level = srslog::basic_levels::error; break;
353  case 4: srs_level = srslog::basic_levels::none; break;
354  }
355 
356  // Configure srsLTE logging
357  auto& mac_log = srslog::fetch_basic_logger("MAC", false);
358  mac_log.set_level(srs_level);
359  auto& phy_log = srslog::fetch_basic_logger("PHY", false);
360  phy_log.set_level(srs_level);
361  auto& rlc_log = srslog::fetch_basic_logger("RLC", false);
362  rlc_log.set_level(srs_level);
363  auto& asn1_log = srslog::fetch_basic_logger("ASN1", false);
364  asn1_log.set_level(srs_level);
365 
366 
367  state_t state = searching;
368 
369  // Create the RESTful API handler
370  std::string uri = "http://0.0.0.0:3010/modem-api/";
371  cfg.lookupValue("modem.restful_api.uri", uri);
372  spdlog::info("Starting RESTful API handler at {}", uri);
373  RestHandler rest_handler(cfg, uri, state, sdr, phy, set_params);
374 
375  // Initialize one CAS and thered_cnt MBSFN frame processors
376  CasFrameProcessor cas_processor(cfg, phy, rlc, rest_handler, rx_channels);
377  if (!cas_processor.init()) {
378  spdlog::error("Failed to create CAS processor. Exiting.");
379  exit(1);
380  }
381 
382  // We need the cas processor to be accesible within the rest_handler object to gather all the values display in the rt-wui
383  rest_handler.set_cas_processor(&cas_processor);
384 
385  std::vector<MbsfnFrameProcessor*> mbsfn_processors;
386  for (int i = 0; i < thread_cnt; i++) {
387  auto p = new MbsfnFrameProcessor(cfg, rlc, phy, mac_log, rest_handler, rx_channels);
388  if (!p->init()) {
389  spdlog::error("Failed to create MBSFN processor. Exiting.");
390  exit(1);
391  }
392  mbsfn_processors.push_back(p);
393  }
394 
395  rest_handler.start(); // Start the listener, we need to do it after storing the cas into the rest_handler, otherwise we will get segfault.
396  // Start receiving sample data
397  sdr.start();
398 
399  // Variables to store the measure BLER of PDSCH and MCH/MCCH.
400  uint32_t mch_bler_global = 0;
401  uint32_t mcch_bler_global = 0;
402  uint32_t pdsch_bler_global = 0;
403  uint32_t mch_total_global = 0;
404  uint32_t mcch_total_global = 0;
405  uint32_t pdsch_total_global = 0;
406 
407  // Variables to store the times the receiver losses the signal and the lost subframes between resynchronizations.
408  uint32_t sync_losses = 0;
409  uint32_t lost_subframes = 0;
410  uint32_t measurements = 0.0f;
411 
412  uint32_t tti = 0;
413 
414  float measurement_interval_f = 5;
415  cfg.lookupValue("modem.measurement_file.interval_secs", measurement_interval_f);
416  uint32_t measurement_interval = measurement_interval_f * 1000;
417  uint32_t tick = 0;
418 
419  // Initial state: searching a cell
420  state = searching;
421 
422  uint8_t mb_idx = 0;
423  // Start the main processing loop
424  for (;;) { // Only one main loop, any time therè's a change of state we force next iteration with continue. This way there's no need of nested loops within the cases.
425  switch (state) {
426  case processing: { // processing
427  tti = (tti + 1) % 10240; // Clamp the TTI
428  if (phy.is_cas_subframe(tti)) {
429  // Get the samples from the SDR interface, hand them to a CAS processor, and start it
430  // on a thread from the pool.
431  if (!restart && phy.get_next_frame(cas_processor.get_rx_buffer_and_lock(), cas_processor.rx_buffer_size())) {
432  spdlog::debug("sending tti {} to regular processor", tti);
433  pool.push([ObjectPtr = &cas_processor, tti, &rest_handler] {
434  if (ObjectPtr->process(tti)) {
435  // Set constellation diagram data and rx params for CAS in the REST API handler
436  rest_handler.add_cinr_value(ObjectPtr->cinr_db());
437  }
438  });
439 
440 
441  if (phy.nof_mbsfn_prb() != mbsfn_nof_prb)
442  {
443  // Handle the non-LTE bandwidths (6, 7 and 8 MHz). In these cases, CAS stays at the original bandwidth, but the MBSFN
444  // portion of the frames can be wider. We need to...
445 
447 
448  // ...adjust the SDR's sample rate to fit the wider MBSFN bandwidth...
449  unsigned new_srate = srsran_sampling_freq_hz(mbsfn_nof_prb);
450  spdlog::info("Setting sample rate {} Mhz for MBSFN with {} PRB / {} Mhz channel width", new_srate/1000000.0, mbsfn_nof_prb,
451  mbsfn_nof_prb * 0.2);
452  sdr.stop();
453 
454  bandwidth = (mbsfn_nof_prb * 200000) * 1.2;
455  sdr.tune(frequency, new_srate, bandwidth, gain, antenna, use_agc);
456 
457  // ... configure the PHY and CAS processor to decode a narrow CAS and wider MBSFN, and move back to syncing state
458  // after reconfiguring and restarting the SDR.
459  phy.set_cell();
460  cas_processor.set_cell(phy.cell());
461 
462  sdr.start();
463  spdlog::info("Synchronizing subframe after PRB extension");
464  state = syncing;
465  }
466  } else {
467  // Failed to receive data, or sync lost. Go back to searching state.
468  spdlog::warn("Synchronization lost while processing. Going back to searching state.");
469  sync_losses++;
470  state = syncing;
471  }
472  } else {
473  // All other frames in FeMBMS dedicated mode are MBSFN frames.
474  spdlog::debug("sending tti {} to mbsfn proc {}", tti, mb_idx);
475 
476  // Get the samples from the SDR interface, hand them to an MNSFN processor, and start it
477  // on a thread from the pool. Getting the buffer pointer from the pool also locks this processor.
478  if (!restart && phy.get_next_frame(mbsfn_processors[mb_idx]->get_rx_buffer_and_lock(), mbsfn_processors[mb_idx]->rx_buffer_size())) {
479  if (phy.mcch_configured() && phy.is_mbsfn_subframe(tti)) {
480  // If data frm SIB1/SIB13 has been received in CAS, configure the processors accordingly
481  if (!mbsfn_processors[mb_idx]->mbsfn_configured()) {
482  srsran_scs_t scs = SRSRAN_SCS_15KHZ;
483  switch (phy.mbsfn_subcarrier_spacing()) {
484  case Phy::SubcarrierSpacing::df_15kHz: scs = SRSRAN_SCS_15KHZ; break;
485  case Phy::SubcarrierSpacing::df_7kHz5: scs = SRSRAN_SCS_7KHZ5; break;
486  case Phy::SubcarrierSpacing::df_1kHz25: scs = SRSRAN_SCS_1KHZ25; break;
487  }
488  auto cell = phy.cell();
489  cell.nof_prb = cell.mbsfn_prb;
490  mbsfn_processors[mb_idx]->set_cell(cell);
491  mbsfn_processors[mb_idx]->configure_mbsfn(phy.mbsfn_area_id(), scs);
492  }
493  pool.push([ObjectPtr = mbsfn_processors[mb_idx], tti] {
494  ObjectPtr->process(tti);
495  });
496  } else {
497  // Nothing to do yet, we lack the data from SIB1/SIB13
498  // Discard the samples and unlock the processor.
499  mbsfn_processors[mb_idx]->unlock();
500  }
501  } else {
502  // Failed to receive data, or sync lost. Go back to searching state.
503  spdlog::warn("Synchronization lost while processing. Going back to searching state.");
504  sync_losses++;
505  state = syncing;
506  }
507  mb_idx = static_cast<int>((mb_idx + 1) % thread_cnt);
508  }
509  }
510  break;
511 
512  case searching: {
513  if (restart) { // Triggered from the rt-wui
514  sdr.stop();
515  sample_rate = search_sample_rate; // sample rate for searching
517  sdr.start();
518  }
519 
520  // We're at the search sample rate, and there's no point in creating a sample file. rtop the sample writer, if enabled.
521  //sdr.disableSampleFileWriting();
522 
523  // In searching state, clear the receive buffer and try to find a cell at the configured frequency and synchronize with it
524  restart = false;
525  // sdr.clear_buffer();
526  bool cell_found = phy.cell_search();
527  if (cell_found) {
528  // A cell has been found. We now know the required number of PRB = bandwidth of the carrier. Set the approproiate
529  // sample rate...
530  cas_nof_prb = mbsfn_nof_prb = phy.nr_prb();
531 
533  // Samples files are recorded at a fixed sample rate that can be determined from the bandwidth command line argument.
534  // If we're decoding from file, do not readjust the rate to match the CAS PRBs, but stay at this rate and instead configure the
535  // PHY to decode a narrow CAS from a wider channel.
538  phy.set_cell();
539  } else {
540  // When decoding from the air, configure the SDR accordingly
541  unsigned new_srate = srsran_sampling_freq_hz(cas_nof_prb);
542  spdlog::info("Setting sample rate {} Mhz for {} PRB / {} Mhz channel width", new_srate/1000000.0, phy.nr_prb(),
543  phy.nr_prb() * 0.2);
544  sdr.stop();
545  sdr.clear_buffer();
546  bandwidth = (cas_nof_prb * 200000) * 1.2;
547  sdr.tune(frequency, new_srate, bandwidth, gain, antenna, use_agc);
548  //sleep(1);
549 
550  sdr.start();
551  }
552  spdlog::debug("Synchronizing subframe");
553  // ... and move to syncing state.
554  state = syncing;
555  } else {
556  //sleep(1);
557  }
558  }
559  break;
560  case syncing: {
561  // In syncing state, we already know the cell we want to camp on, and the SDR is tuned to the required
562  // sample rate for its number of PRB / bandwidth. We now synchronize PSS/SSS and receive the MIB once again
563  // at this sample rate.
564  if (restart) { // The only way of going to search is restarting
565  state = searching; // Jump to searching where the restart is actually handled.
566  continue; // We need to skip the next step and go to the next iteration.
567  }
568 
569  bool sfn_sync = false;
570  sfn_sync = phy.synchronize_subframe();
571 
572  if (sfn_sync) {
573  // We're locked on to the cell, and have succesfully received the MIB at the target sample rate.
574  lost_subframes += (((phy.tti() < tti) * 10240 + phy.tti())-tti) * cas_processor.is_started() ; // cas_processor has started when it has a valid cell set.
575  spdlog::info("Decoded MIB at target sample rate, TTI is {}. Subframe synchronized, sync lost in TTI {}, {} subframes lost, {} total subframe lost, sync losses {}.", phy.tti(), tti, (((phy.tti() < tti) * 10240 + phy.tti())-tti) * cas_processor.is_started(), lost_subframes, sync_losses);
576 
577  // Set the cell parameters in the CAS processor, and set started to true
578  cas_processor.set_cell(phy.cell());
579 
580  for (int i = 0; i < thread_cnt; i++) {
581  mbsfn_processors[i]->unlock();
582  }
583  cas_processor.unlock(); // We need to unlock because we felt here from processing after calling get_rx_buffer_and_lock()
584 
585  // Get the initial TTI / subframe ID (= system frame number * 10 + subframe number)
586  tti = phy.tti();
587  // Reset the RRC
588  rrc.reset();
589 
590  // Ready to receive actual data. Go to processing state.
591  state = processing;
592 
593  // If sample file creation is enabled, start writing out samples now that we're at the target sample rate
595  }
596  }
597  break;
598  default:
599  //we are not supposed to be here.
600  break;
601  }
602 
603  tick++;
604  if (tick%measurement_interval == 0) {
605  // It's time to output rx info to the measurement file and to syslog.
606  // Collect the relevant info and write it out.
607  std::vector<std::string> cols;
608  cols.reserve(11);
609 
610  if (state == processing) {
611  spdlog::info("CINR {:.2f} dB", rest_handler.cinr_db() );
612  cols.push_back(std::to_string((float)rest_handler.cinr_db()));
613 
614  // Wait to finish and lock, we don't want to update total and errors independently. Yes, it's a blocking solution, but, what other way is possible?
615  cas_processor.lock();
616 
617  spdlog::info("PDSCH: MCS {}, BLER {}",
618  rest_handler._pdsch.mcs,
619  ((rest_handler._pdsch.errors > 0 && rest_handler._pdsch.total > 0) ? (rest_handler._pdsch.errors * 1.0) / (rest_handler._pdsch.total * 1.0) : 0));
620 
621  cols.push_back(std::to_string(rest_handler._pdsch.mcs));
622  cols.push_back(std::to_string(((rest_handler._pdsch.errors * 1.0) / (rest_handler._pdsch.total * 1.0))));
623 
624  pdsch_bler_global += rest_handler._pdsch.errors;
625  pdsch_total_global += rest_handler._pdsch.total;
626  rest_handler._pdsch.errors = 0;
627  rest_handler._pdsch.total = 0;
628  // We are done with the CAS
629  cas_processor.unlock();
630 
631  // Wait for all the mbsfn processors to finish, to avoid having an update on total or errors while accesing to the values that leads to having a wrong BLER.
632  for (int i = 0; i < thread_cnt; i++) {
633  mbsfn_processors[i]->lock();
634  }
635  spdlog::info("MCCH: MCS {}, BLER {}",
636  rest_handler._mcch.mcs,
637  ((rest_handler._mcch.errors > 0 && rest_handler._mcch.total > 0) ? (rest_handler._mcch.errors * 1.0) / (rest_handler._mcch.total * 1.0) : 0));
638 
639  cols.push_back(std::to_string(rest_handler._mcch.mcs));
640  cols.push_back(std::to_string(((rest_handler._mcch.errors * 1.0) / (rest_handler._mcch.total * 1.0))));
641 
642  cols.push_back(std::to_string(tti)); // Current TTI
643  cols.emplace_back(std::string("")); // Blank to maintain the column, only used when desync.
644  cols.push_back(std::to_string(lost_subframes)); // Total amount of lost subframes
645 
646  auto mch_info = phy.mch_info();
647  int mch_idx = 0;
648  std::for_each(std::begin(mch_info), std::end(mch_info), [&cols, &mch_idx, &rest_handler, &mch_bler_global, &mch_total_global](Phy::mch_info_t const& mch) {
649 
650  spdlog::info("MCH {}: MCS {}, BLER {}",
651  mch_idx,
652  mch.mcs,
653  ((rest_handler._mch[mch_idx].errors > 0 && rest_handler._mch[mch_idx].total > 0) ? (rest_handler._mch[mch_idx].errors * 1.0) / (rest_handler._mch[mch_idx].total * 1.0) : 0));
654 
655  cols.push_back(std::to_string(mch_idx));
656  cols.push_back(std::to_string(mch.mcs));
657  cols.push_back(std::to_string((rest_handler._mch[mch_idx].errors * 1.0) / (rest_handler._mch[mch_idx].total * 1.0)));
658 
659  int mtch_idx = 0;
660  std::for_each(std::begin(mch.mtchs), std::end(mch.mtchs), [&mtch_idx, &mch_bler_global, &mch_total_global](Phy::mtch_info_t const& mtch) {
661  spdlog::info(" MTCH {}: LCID {}, TMGI 0x{}, {}",
662  mtch_idx,
663  mtch.lcid,
664  mtch.tmgi,
665  mtch.dest);
666  mtch_idx++;
667  });
668 
669  mch_bler_global += rest_handler._mch[mch_idx].errors;
670  mch_total_global += rest_handler._mch[mch_idx].total;
671  rest_handler._mch[mch_idx].errors = 0;
672  rest_handler._mch[mch_idx].total = 0;
673  mch_idx++;
674  });
675 
676  mcch_bler_global += rest_handler._mcch.errors;
677  mcch_total_global += rest_handler._mcch.total;
678  rest_handler._mcch.errors = 0;
679  rest_handler._mcch.total = 0;
680  // We can unlock cas and mbsfn processor at this point, every variable has been saved in the rest_handler.
681  for (int i = 0; i < thread_cnt; i++) {
682  mbsfn_processors[i]->unlock();
683  }
684  } else if (state == syncing) { // In syncing and searching states we place in every row and column NaN, this way is easier to process after, since every time measured theres always a row in the csv.
685  cols.emplace_back(std::string("NOT SYNC - SYNCING..."));
686  cols.emplace_back(std::string("nan"));
687  cols.emplace_back(std::string("nan"));
688  cols.emplace_back(std::string("nan"));
689  cols.emplace_back(std::string("nan"));
690  cols.emplace_back(std::string("nan"));
691  cols.push_back(std::to_string(sync_losses));
692  cols.push_back(std::to_string(lost_subframes));
693  cols.emplace_back(std::string("nan"));
694  cols.emplace_back(std::string("nan"));
695  cols.emplace_back(std::string("nan"));
696 
697  } else {
698  cols.emplace_back(std::string("SEARCHING FOR A CELL..."));
699  cols.emplace_back(std::string("nan"));
700  cols.emplace_back(std::string("nan"));
701  cols.emplace_back(std::string("nan"));
702  cols.emplace_back(std::string("nan"));
703  cols.emplace_back(std::string("nan"));
704  cols.emplace_back(std::string(""));
705  cols.push_back(std::to_string(lost_subframes));
706  cols.emplace_back(std::string("nan"));
707  cols.emplace_back(std::string("nan"));
708  cols.emplace_back(std::string("nan"));
709  }
710 
711  if (enable_measurement_file) {
712  measurement_file.WriteLogValues(cols);
713  }
714  measurements++;
715 
716  spdlog::info("------ Global statistics ------ \n\t\tMCH BLER {}, \n\t\tMCH TOTAL ERRORS {}, \n\t\tMCCH BLER: {}, \n\t\tPDSCH BLER: {}, \n\t\tSYNC LOSSES: {}, \n\t\tSF PROCESSED: {}",
717  ((mch_bler_global > 0 && mch_total_global > 0) ? (mch_bler_global * 1.0) / (mch_total_global * 1.0) : 0),
718  mch_bler_global,
719  ((mcch_bler_global > 0 && mcch_total_global > 0) ? (mcch_bler_global * 1.0) / (mcch_total_global * 1.0) : 0),
720  ((pdsch_bler_global > 0 && pdsch_total_global > 0) ? (pdsch_bler_global * 1.0) / (pdsch_total_global * 1.0) : 0),
721  sync_losses,
722  tick);
723 
724  spdlog::info("Total subframe lost {}, sync losses {}.", lost_subframes, sync_losses);
725 
726  }
727  }
728 
729  // Main loop ended by signal. Free the MBSFN processors, and bail.
730  for (int i = 0; i < thread_cnt; i++) {
731  delete( mbsfn_processors[i] );
732  }
733 exit:
734  return 0;
735 }
state_t
Definition: RestHandler.h:39
@ syncing
Definition: RestHandler.h:39
@ searching
Definition: RestHandler.h:39
@ processing
Definition: RestHandler.h:39
Frame processor for CAS subframes.
cf_t ** get_rx_buffer_and_lock()
Get a handle of the signal buffer to store samples for processing in.
bool is_started()
Returns if the CasFrameProcessor is started or not.
bool init()
Initialize signal- and softbuffers, init all underlying components.
void set_cell(srsran_cell_t cell)
Set the parameters for the cell (Nof PRB, etc).
uint32_t rx_buffer_size()
Size of the signal buffer.
void unlock()
Unlock the processor.
Network gateway component.
Definition: Gw.h:36
void init()
Creates the TUN interface according to params from Cfg.
Definition: Gw.cpp:99
Frame processor for MBSFN subframes.
Writes measurement data / current reception parameters to a file.
void WriteLogValues(const std::vector< std::string > &values)
Write a line containing the passed values.
The PHY component.
Definition: Phy.h:42
bool mcch_configured()
Return true if MCCH has been configured.
Definition: Phy.h:133
SubcarrierSpacing mbsfn_subcarrier_spacing()
Definition: Phy.h:263
unsigned nr_prb()
Get the current number of PRB.
Definition: Phy.h:98
uint8_t mbsfn_area_id()
Returns the current MBSFN area ID.
Definition: Phy.h:138
const std::vector< mch_info_t > & mch_info()
Definition: Phy.h:253
bool get_next_frame(cf_t **buffer, uint32_t size)
Get the sample data for the next subframe.
Definition: Phy.cpp:225
srsran_cell_t cell()
Get the current cell (with params adjusted for MBSFN)
Definition: Phy.h:91
bool synchronize_subframe()
Synchronizes PSS/SSS and tries to deocode the MIB.
Definition: Phy.cpp:64
uint8_t nof_mbsfn_prb()
Get number of PRB in MBSFN/PMCH.
Definition: Phy.h:153
void set_cell()
Definition: Phy.cpp:188
bool is_cas_subframe(unsigned tti)
Definition: Phy.cpp:308
uint32_t tti()
Get the current subframe TTI.
Definition: Phy.h:103
bool init()
Initialize the underlying components.
Definition: Phy.cpp:197
bool cell_search()
Search for a cell.
Definition: Phy.cpp:96
void set_nof_mbsfn_prb(uint8_t prb)
Override number of PRB in MBSFN/PMCH.
Definition: Phy.h:158
bool is_mbsfn_subframe(unsigned tti)
Definition: Phy.cpp:319
The RESTful API handler.
Definition: RestHandler.h:47
void set_cas_processor(CasFrameProcessor *cas_processor)
Save the pointer to the CasFrameProcessor.
Definition: RestHandler.h:156
float cinr_db()
Current instantaneous CINR value.
Definition: RestHandler.h:144
void start()
Start function for the listener.
Definition: RestHandler.h:73
ChannelInfo _mcch
RX info for MCCH.
Definition: RestHandler.h:134
std::map< uint32_t, ChannelInfo > _mch
RX info for MCHs.
Definition: RestHandler.h:139
ChannelInfo _pdsch
RX info for PDSCH.
Definition: RestHandler.h:129
Simple RRC component between PHY and RLC.
Definition: Rrc.h:34
void reset()
Definition: Rrc.h:57
Interface to the SDR stick.
Definition: SdrReader.h:37
void stop()
Stop reading samples from the SDR.
Definition: SdrReader.cpp:255
void start()
Start reading samples from the SDR.
Definition: SdrReader.cpp:216
bool init(const std::string &device_args, const char *sample_file, const char *write_sample_file, bool repeat_sample_file)
Initializes the SDR interface and creates a ring buffer according to the params from Cfg.
Definition: SdrReader.cpp:66
void enumerateDevices()
Prints a list of all available SDR devices.
Definition: SdrReader.cpp:49
int get_samples(cf_t *data[SRSRAN_MAX_CHANNELS], uint32_t nsamples, srsran_timestamp_t *rx_time)
Store nsamples count samples into the buffer at data.
Definition: SdrReader.cpp:345
void clear_buffer()
Clear all samples from the rx buffers.
Definition: SdrReader.cpp:109
bool tune(uint32_t frequency, uint32_t sample_rate, uint32_t bandwidth, double gain, const std::string &antenna, bool use_agc)
Tune the SDR to the desired frequency, and set gain, filter and antenna parameters.
Definition: SdrReader.cpp:169
void enableSampleFileWriting()
If sample file creation is enabled, writing samples starts after this call.
Definition: SdrReader.h:134
static Config cfg
Global configuration object.
Definition: main.cpp:172
static std::string antenna
Antenna input to be used.
Definition: main.cpp:179
static unsigned mbsfn_nof_prb
Definition: main.cpp:182
static bool use_agc
Definition: main.cpp:180
static unsigned sample_rate
Sample rate of the SDR.
Definition: main.cpp:174
static void print_version(FILE *stream, struct argp_state *state)
Print the program version in MAJOR.MINOR.PATCH format.
Definition: main.cpp:166
auto main(int argc, char **argv) -> int
Main entry point for the program.
Definition: main.cpp:224
static bool restart
Restart flag.
Definition: main.cpp:193
static unsigned cas_nof_prb
Definition: main.cpp:183
static uint32_t bandwidth
Low pass filter bandwidth for the SDR.
Definition: main.cpp:177
static unsigned search_sample_rate
Sample rate of the SDR.
Definition: main.cpp:175
const char * argp_program_bug_address
Definition: main.cpp:63
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:205
static unsigned frequency
Center freqeuncy the SDR is tuned to.
Definition: main.cpp:176
static struct argp argp
Definition: main.cpp:160
static struct argp_option options[]
Definition: main.cpp:66
static double gain
Overall system gain for the SDR.
Definition: main.cpp:178
void(* argp_program_version_hook)(FILE *, struct argp_state *)
Definition: main.cpp:62
static auto parse_opt(int key, char *arg, struct argp_state *state) -> error_t
Parses the command line options into the arguments struct.
Definition: main.cpp:119
static char doc[]
Definition: main.cpp:64
std::vector< mtch_info_t > mtchs
Definition: Phy.h:250
Holds all options passed on the command line.
Definition: main.cpp:103
uint8_t file_bw
bandwidth of the sample file
Definition: main.cpp:109
const char * config_file
file path of the config file.
Definition: main.cpp:104
const char * write_sample_file
file path of the created sample file.
Definition: main.cpp:111
bool repeat_sample_file
Definition: main.cpp:113
unsigned srs_log_level
srsLTE log level
Definition: main.cpp:106
int8_t override_nof_prb
ovride PRB number
Definition: main.cpp:107
unsigned log_level
log level
Definition: main.cpp:105
bool list_sdr_devices
Definition: main.cpp:112
const char * sample_file
file path of the sample file.
Definition: main.cpp:108