From eb8a188abbfdbc354d7605543d487cd09f697c91 Mon Sep 17 00:00:00 2001 From: Bartosz Podrygajlo Date: Thu, 7 May 2026 12:25:25 +0200 Subject: [PATCH] docs: add a step-by-step guide for OAI ZMQ radio Added a tutorial describing different ways to use OAI ZMQ radio: - Connect OAI gNB and OAI NR UE - Connect OCUDU split8 gNB and OAI NR UE - Connect OAI gNB and OAI NR UE and inject noise in Downlink using GNU radio companion sample app Signed-off-by: Bartosz Podrygajlo --- doc/README.md | 1 + doc/noise_injector.grc | 520 +++++++++++++++++++++++++++++++++++++++++ doc/zmq_tutorial.md | 177 ++++++++++++++ 3 files changed, 698 insertions(+) create mode 100644 doc/noise_injector.grc create mode 100644 doc/zmq_tutorial.md diff --git a/doc/README.md b/doc/README.md index 02be593a7e..0893bc42a5 100644 --- a/doc/README.md +++ b/doc/README.md @@ -58,6 +58,7 @@ There is some general information in the [OpenAirInterface Gitlab Wiki](https:// - [How to setup gNB frequency](./gNB_frequency_setup.md) - [How to use the RT data recording app](./data_recording.md) - [How to use packages](./packages.md) +- [How to use OAI ZMQ radio](./zmq_tutorial.md) Legacy unmaintained files: - [`L2NFAPI_NOS1.md`](./L2NFAPI_NOS1.md), [`L2NFAPI_S1.md`](./L2NFAPI_S1.md): diff --git a/doc/noise_injector.grc b/doc/noise_injector.grc new file mode 100644 index 0000000000..a3a696a69f --- /dev/null +++ b/doc/noise_injector.grc @@ -0,0 +1,520 @@ +# SPDX-License-Identifier: LicenseRef-CSSL-1.0 +options: + parameters: + author: '' + catch_exceptions: 'True' + category: '[GRC Hier Blocks]' + cmake_opt: '' + comment: '' + copyright: '' + description: '' + gen_cmake: 'On' + gen_linking: dynamic + generate_options: qt_gui + hier_block_src_path: '.:' + id: untitled + max_nouts: '0' + output_language: python + placement: (0,0) + qt_qss_theme: '' + realtime_scheduling: '' + run: 'True' + run_command: '{python} -u {filename}' + run_options: prompt + sizing_mode: fixed + thread_safe_setters: '' + title: Simple noise injector + window_size: (1000,1000) + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [56, 72.0] + rotation: 0 + state: enabled + +blocks: +- name: noise_amp + id: variable_qtgui_range + parameters: + comment: '' + gui_hint: '' + label: '' + min_len: '200' + orient: QtCore.Qt.Horizontal + rangeType: float + start: '0' + step: '0.001' + stop: '1' + value: '0' + widget: counter_slider + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [376, 56.0] + rotation: 0 + state: enabled +- name: samp_rate + id: variable + parameters: + comment: '' + value: '32000000' + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [240, 72.0] + rotation: 0 + state: enabled +- name: analog_fastnoise_source_x_0 + id: analog_fastnoise_source_x + parameters: + affinity: '' + alias: '' + amp: noise_amp + comment: '' + maxoutbuf: '0' + minoutbuf: '0' + noise_type: analog.GR_GAUSSIAN + samples: '8192' + seed: '0' + type: complex + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [304, 224.0] + rotation: 0 + state: enabled +- name: blocks_add_xx_0 + id: blocks_add_xx + parameters: + affinity: '' + alias: '' + comment: '' + maxoutbuf: '0' + minoutbuf: '0' + num_inputs: '2' + type: complex + vlen: '1' + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [624, 296.0] + rotation: 0 + state: enabled +- name: blocks_throttle2_0_0 + id: blocks_throttle2 + parameters: + affinity: '' + alias: '' + comment: '' + ignoretag: 'True' + limit: auto + maximum: '0.1' + maxoutbuf: '0' + minoutbuf: '0' + samples_per_second: samp_rate + type: complex + vlen: '1' + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [328, 384.0] + rotation: 0 + state: enabled +- name: qtgui_freq_sink_x_0 + id: qtgui_freq_sink_x + parameters: + affinity: '' + alias: '' + alpha1: '1.0' + alpha10: '1.0' + alpha2: '1.0' + alpha3: '1.0' + alpha4: '1.0' + alpha5: '1.0' + alpha6: '1.0' + alpha7: '1.0' + alpha8: '1.0' + alpha9: '1.0' + autoscale: 'False' + average: '1.0' + axislabels: 'True' + bw: samp_rate + color1: '"blue"' + color10: '"dark blue"' + color2: '"red"' + color3: '"green"' + color4: '"black"' + color5: '"cyan"' + color6: '"magenta"' + color7: '"yellow"' + color8: '"dark red"' + color9: '"dark green"' + comment: '' + ctrlpanel: 'False' + fc: '0' + fftsize: '1024' + freqhalf: 'True' + grid: 'False' + gui_hint: '' + label: Relative Gain + label1: '' + label10: '''''' + label2: '''''' + label3: '''''' + label4: '''''' + label5: '''''' + label6: '''''' + label7: '''''' + label8: '''''' + label9: '''''' + legend: 'True' + maxoutbuf: '0' + minoutbuf: '0' + name: '""' + nconnections: '1' + norm_window: 'False' + showports: 'False' + tr_chan: '0' + tr_level: '0.0' + tr_mode: qtgui.TRIG_MODE_FREE + tr_tag: '""' + type: complex + units: dB + update_time: '0.10' + width1: '1' + width10: '1' + width2: '1' + width3: '1' + width4: '1' + width5: '1' + width6: '1' + width7: '1' + width8: '1' + width9: '1' + wintype: window.WIN_BLACKMAN_hARRIS + ymax: '10' + ymin: '-140' + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [928, 328.0] + rotation: 0 + state: enabled +- name: qtgui_time_sink_x_0 + id: qtgui_time_sink_x + parameters: + affinity: '' + alias: '' + alpha1: '1.0' + alpha10: '1.0' + alpha2: '1.0' + alpha3: '1.0' + alpha4: '1.0' + alpha5: '1.0' + alpha6: '1.0' + alpha7: '1.0' + alpha8: '1.0' + alpha9: '1.0' + autoscale: 'True' + axislabels: 'True' + color1: blue + color10: dark blue + color2: red + color3: green + color4: black + color5: cyan + color6: magenta + color7: yellow + color8: dark red + color9: dark green + comment: '' + ctrlpanel: 'False' + entags: 'True' + grid: 'False' + gui_hint: '' + label1: Signal 1 + label10: Signal 10 + label2: Signal 2 + label3: Signal 3 + label4: Signal 4 + label5: Signal 5 + label6: Signal 6 + label7: Signal 7 + label8: Signal 8 + label9: Signal 9 + legend: 'True' + marker1: '-1' + marker10: '-1' + marker2: '-1' + marker3: '-1' + marker4: '-1' + marker5: '-1' + marker6: '-1' + marker7: '-1' + marker8: '-1' + marker9: '-1' + name: Output sig + nconnections: '1' + size: '1024' + srate: samp_rate + stemplot: 'False' + style1: '1' + style10: '1' + style2: '1' + style3: '1' + style4: '1' + style5: '1' + style6: '1' + style7: '1' + style8: '1' + style9: '1' + tr_chan: '0' + tr_delay: '0' + tr_level: '0.0' + tr_mode: qtgui.TRIG_MODE_FREE + tr_slope: qtgui.TRIG_SLOPE_POS + tr_tag: '""' + type: complex + update_time: '0.10' + width1: '1' + width10: '1' + width2: '1' + width3: '1' + width4: '1' + width5: '1' + width6: '1' + width7: '1' + width8: '1' + width9: '1' + ylabel: Amplitude + ymax: '1' + ymin: '-1' + yunit: '""' + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [928, 192.0] + rotation: 0 + state: enabled +- name: qtgui_time_sink_x_1 + id: qtgui_time_sink_x + parameters: + affinity: '' + alias: '' + alpha1: '1.0' + alpha10: '1.0' + alpha2: '1.0' + alpha3: '1.0' + alpha4: '1.0' + alpha5: '1.0' + alpha6: '1.0' + alpha7: '1.0' + alpha8: '1.0' + alpha9: '1.0' + autoscale: 'True' + axislabels: 'True' + color1: blue + color10: dark blue + color2: red + color3: green + color4: black + color5: cyan + color6: magenta + color7: yellow + color8: dark red + color9: dark green + comment: '' + ctrlpanel: 'False' + entags: 'True' + grid: 'False' + gui_hint: '' + label1: Signal 1 + label10: Signal 10 + label2: Signal 2 + label3: Signal 3 + label4: Signal 4 + label5: Signal 5 + label6: Signal 6 + label7: Signal 7 + label8: Signal 8 + label9: Signal 9 + legend: 'True' + marker1: '-1' + marker10: '-1' + marker2: '-1' + marker3: '-1' + marker4: '-1' + marker5: '-1' + marker6: '-1' + marker7: '-1' + marker8: '-1' + marker9: '-1' + name: Noise + nconnections: '1' + size: '1024' + srate: samp_rate + stemplot: 'False' + style1: '1' + style10: '1' + style2: '1' + style3: '1' + style4: '1' + style5: '1' + style6: '1' + style7: '1' + style8: '1' + style9: '1' + tr_chan: '0' + tr_delay: '0' + tr_level: '0.0' + tr_mode: qtgui.TRIG_MODE_FREE + tr_slope: qtgui.TRIG_SLOPE_POS + tr_tag: '""' + type: complex + update_time: '0.10' + width1: '1' + width10: '1' + width2: '1' + width3: '1' + width4: '1' + width5: '1' + width6: '1' + width7: '1' + width8: '1' + width9: '1' + ylabel: Amplitude + ymax: '1' + ymin: '-1' + yunit: '""' + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [744, 104.0] + rotation: 0 + state: enabled +- name: qtgui_waterfall_sink_x_0 + id: qtgui_waterfall_sink_x + parameters: + affinity: '' + alias: '' + alpha1: '1.0' + alpha10: '1.0' + alpha2: '1.0' + alpha3: '1.0' + alpha4: '1.0' + alpha5: '1.0' + alpha6: '1.0' + alpha7: '1.0' + alpha8: '1.0' + alpha9: '1.0' + axislabels: 'True' + bw: samp_rate + color1: '0' + color10: '0' + color2: '0' + color3: '0' + color4: '0' + color5: '0' + color6: '0' + color7: '0' + color8: '0' + color9: '0' + comment: '' + fc: '0' + fftsize: '1024' + freqhalf: 'True' + grid: 'False' + gui_hint: '' + int_max: '10' + int_min: '-140' + label1: '' + label10: '' + label2: '' + label3: '' + label4: '' + label5: '' + label6: '' + label7: '' + label8: '' + label9: '' + legend: 'True' + maxoutbuf: '0' + minoutbuf: '0' + name: '""' + nconnections: '1' + showports: 'False' + type: complex + update_time: '0.10' + wintype: window.WIN_BLACKMAN_hARRIS + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [1104, 256.0] + rotation: 0 + state: enabled +- name: zeromq_rep_sink_0 + id: zeromq_rep_sink + parameters: + address: tcp://127.0.0.1:4446 + affinity: '' + alias: '' + bind: 'True' + comment: '' + hwm: '-1' + pass_tags: 'False' + timeout: '100' + type: complex + vlen: '1' + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [752, 432.0] + rotation: 0 + state: enabled +- name: zeromq_req_source_0 + id: zeromq_req_source + parameters: + address: tcp://127.0.0.1:4445 + affinity: '' + alias: '' + bind: 'False' + comment: '' + hwm: '-1' + maxoutbuf: '0' + minoutbuf: '0' + pass_tags: 'False' + timeout: '100' + type: complex + vlen: '1' + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [32, 240.0] + rotation: 0 + state: enabled + +connections: +- [analog_fastnoise_source_x_0, '0', blocks_add_xx_0, '0'] +- [analog_fastnoise_source_x_0, '0', qtgui_time_sink_x_1, '0'] +- [blocks_add_xx_0, '0', qtgui_freq_sink_x_0, '0'] +- [blocks_add_xx_0, '0', qtgui_time_sink_x_0, '0'] +- [blocks_add_xx_0, '0', qtgui_waterfall_sink_x_0, '0'] +- [blocks_add_xx_0, '0', zeromq_rep_sink_0, '0'] +- [blocks_throttle2_0_0, '0', blocks_add_xx_0, '1'] +- [zeromq_req_source_0, '0', blocks_throttle2_0_0, '0'] + +metadata: + file_format: 1 + grc_version: 3.10.9.2 diff --git a/doc/zmq_tutorial.md b/doc/zmq_tutorial.md new file mode 100644 index 0000000000..f6f8070a55 --- /dev/null +++ b/doc/zmq_tutorial.md @@ -0,0 +1,177 @@ +# ZMQ Radio Tutorial + +This tutorial provides instructions on how to use the ZeroMQ (ZMQ) radio implementation in OpenAirInterface. The ZMQ +radio allows for simulating the radio link over a network using ZMQ sockets, which is useful for development, testing, +and CI/CD without the need for physical SDR hardware. + +It currently supports two main scenarios: +1. Connecting an **OAI gNB** with an **OAI NR UE**. +2. Connecting an **OCUDU gNB** (Split 8) with an **OAI NR UE**. +3. Using GNU Radio companion to inject noise in DL between **OAI gNB** and **OAI NR UE**. + +## Prerequisites + +Before starting, ensure you have the following dependencies installed on your system: + +- **ZeroMQ development libraries**: + ```bash + sudo apt-get install libzmq3-dev + ``` +- **OAI Dependencies**: Ensure you have followed the standard OAI build instructions to install necessary system + dependencies. + + +## Compilation + +To use the ZMQ radio, you need to build the `oai_zmqdevif` library along with the softmodems. + +Configure the build: + +```bash +cmake ../ -DOAI_ZMQ=ON +``` + +Compile: + +```bash +cmake --build . --target nr-softmodem nr-uesoftmodem ldpc params_libconfig zmq_radio +``` + +## Scenario 1: OAI gNB <-> OAI NR UE + +In this scenario, both the gNB and the UE are running OAI code and communicate over ZMQ. + +### 1. Start the gNB + +Run the gNB with the `oai_zmqdevif` device name. Note the mapping of TX/RX ports. + +```bash +sudo ./nr-softmodem -O ../targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band78.fr1.106PRB.usrpb210.conf --gNBs.[0].min_rxtxtime 6 --device.name oai_zmqdevif --zmq.[0].tx_channels tcp://127.0.0.1:4556 --zmq.[0].rx_channels tcp://127.0.0.1:4557 +``` + +### 2. Start the UE + +Start the UE and connect it to the ports exposed by the gNB. Note that the UE's TX port should match the gNB's RX port, +and vice versa. + +```bash +sudo ./nr-uesoftmodem -r 106 --numerology 1 --band 78 -C 3619200000 --ssb 516 --device.name oai_zmqdevif --zmq.[0].tx_channels tcp://127.0.0.1:4557 --zmq.[0].rx_channels tcp://127.0.0.1:4556 +``` + +## Scenario 2: OCUDU gNB (Split 8) <-> OAI NR UE + +This scenario demonstrates interoperability between the OCUDU project's Split 8 gNB and the OAI NR UE. + +### 1. Start the Core Network + +Deploy the OAI 5G Core Network using Docker Compose. + +```bash +cd doc/tutorial_resources/oai-cn5g +docker compose up -d +``` + +### 2. Configure and Start OCUDU gNB + +Create a `config.yml` in your OCUDU directory with the following content: + +```yaml +cu_cp: + amf: + addrs: 192.168.70.132 + port: 38412 + bind_addrs: 0.0.0.0 + supported_tracking_areas: + - tac: 1 + plmn_list: + - plmn: "00101" + tai_slice_support_list: + - sst: 1 + +ru_sdr: + device_driver: zmq + device_args: tx_port=tcp://127.0.0.1:4558,rx_port=tcp://127.0.0.1:4556 + srate: 23.04 + tx_gain: 25 + rx_gain: 25 + +cell_cfg: + dl_arfcn: 632628 + band: 78 + channel_bandwidth_MHz: 20 + common_scs: 30 + plmn: "00101" + tac: 1 + pci: 1 + tdd_ul_dl_cfg: + dl_ul_tx_period: 5 + nof_dl_slots: 3 + nof_dl_symbols: 10 + nof_ul_slots: 1 + nof_ul_symbols: 2 + csi: + csi_rs_enabled: false + pucch: + formats: f0_and_f2 + nof_cell_csi_res: 0 + +log: + filename: gnb.log + all_level: debug + +pcap: + mac_enable: false + mac_filename: /tmp/gnb_mac.pcap + ngap_enable: false + ngap_filename: /tmp/gnb_ngap.pcap +``` + +Run the OCUDU gNB: + +```bash +sudo ./apps/gnb_split_8/gnb -c config.yml +``` + +### 3. Start OAI NR UE + +Connect the OAI NR UE to the OCUDU gNB via ZMQ. + +``` +sudo ./nr-uesoftmodem -r 51 -E --numerology 1 --band 78 -C 3489420000 --ssb 0 --uecap_file ../targets/PROJECTS/GENERIC-NR-5GC/CONF/uecap_ports1.xml --log_config.global_log_options level,nocolor,time --device.name oai_zmqdevif --zmq.[0].tx_channels tcp://127.0.0.1:4556 --zmq.[0].rx_channels tcp://127.0.0.1:4558 -O ../ci-scripts/conf_files/nrue.uicc.conf --uicc0.imsi 001010000000001 +``` + +> Once connected, you can perform standard network tests like `ping` or `iperf` between the UE and the core network. + +## Scenario 3: Inject noise using GNU radio companion + +In this scenario, you can inject noise between OAI gNB and OAI NR UE. An example GNU radio companion project +is added in `noise_injector.grc`. The program expects a ZMQ radio stream at tcp://127.0.0.1:4445 and outputs the + same samples with added noise at tcp://127.0.0.1:4446 + +Architecture +```mermaid +graph TD + nr_softmodem -- "DL - port 4445" --> noise_injector + noise_injector -- "DL+ noise - port 4446" --> nr_uesoftmodem + nr_uesoftmodem -- "UL - port 4557"--> nr_softmodem +``` + +### 1. Start the gNB + +``` +sudo ./nr-softmodem -O ../targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band78.fr1.106PRB.usrpb210.conf --gNBs.[0].min_rxtxtime 6 --device.name oai_zmqdevif --zmq.[0].tx_channels tcp://127.0.0.1:4445 --zmq.[0].rx_channels tcp://127.0.0.1:4557 +``` + +### 2. Start the UE + +``` +sudo ./nr-uesoftmodem -r 106 --numerology 1 --band 78 -C 3619200000 --ssb 516 --device.name oai_zmqdevif --zmq.[0].tx_channels tcp://127.0.0.1:4557 --zmq.[0].rx_channels tcp://127.0.0.1:4446 +``` + +### 3. Start the GNU radio companion program + +You should see a window with an input field labelled noise_amp and a frequency domain waterfall graph of the gNBs +downlink signal. + +Now you should be able to change the injected noise and see the UE loose DL connection. Use either the slider +or the input field to change noise amplitude.