From 8c85acbba66e3023a416dd82f1a6d0c82e6c1925 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Tue, 23 Sep 2025 11:59:48 +0200 Subject: [PATCH 01/11] Add script to control attenuation of Mini-Circuits RC*DAT --- ci-scripts/attenuatorctl.py | 175 ++++++++++++++++++++++++++++++++++++ 1 file changed, 175 insertions(+) create mode 100644 ci-scripts/attenuatorctl.py diff --git a/ci-scripts/attenuatorctl.py b/ci-scripts/attenuatorctl.py new file mode 100644 index 0000000000..e6f8c01837 --- /dev/null +++ b/ci-scripts/attenuatorctl.py @@ -0,0 +1,175 @@ +#!/usr/bin/env python3 + +import argparse +import logging +import itertools +import usb.core +import usb.util +import sys +import time + +logging.basicConfig( + level=logging.DEBUG, + stream=sys.stdout, + format="%(message)s" +) + +# helper class parsing command line options for channels and attenuators +class ValidateChAtt(argparse.Action): + def _parse_channels(s): + if len(s.split("-")) == 2: + s, e = [int(x) for x in s.split("-")] + chs = [x for x in range(s, e+1)] + if len(chs) == 0: + raise Exception(f"empty range") + elif len(s.split(",")) > 1: + chs = [int(x) for x in s.split(",")] + elif s.isdigit(): + chs = [int(s)] + else: + raise Exception(f"could not parse channel expression") + for c in chs: + if not 1 <= c <= 4: + raise Exception(f"channel number must be within [1,4], but have {c}") + return chs + + def __call__(self, parse, args, values, option_string=None): + ch, att = values + try: + chs = ValidateChAtt._parse_channels(ch) + except Exception as e: + parse.exit(1, f"while parsing channel expression {ch}: {e}\n") + if not att.isdigit(): + parse.exit(1, f'expected number for attenuation, but got {att}\n') + if not 0 <= int(att) <= 63: + parse.exit(1, f'attenuation must be within [0,63], but is {att}\n') + opts = getattr(args, self.dest) or {} + for c in chs: + opts[c] = int(att) + setattr(args, self.dest, opts) + +def _parse_args() -> argparse.Namespace: + epilog = ''' +[CHs] can have the following forms: + (1) single channel number w (within [1-4]) + (2) a range of channels in the form x-y (x <= y) + (3) a list of channels in the form a,b,c +[ATT] must be within [0,63] (dB) + +--set and --reach are mutually exclusive + +Examples: +- reset all channels to 0: -s 1-4 0 +- set channels 3 and 4 to attenuation 60: -s 3,4 60 +- perform attenuation sweep during 10s, reaching ch. 1+2 to 60: -r 1,2 60 -d 10 +''' + parser = argparse.ArgumentParser(description='Mini-Circuit RC*DAT attenuator controller', + epilog=epilog, + formatter_class=argparse.RawDescriptionHelpFormatter) + + parser.add_argument('--info', '-i', action='store_true', default=False, + help='Get Infos for all connected Mini-Circuits RC*DAT',) + parser.add_argument('--set', '-s', nargs=2, metavar=("[CHs]","[ATT]"), + action=ValidateChAtt, help="Set a fixed attenuation ATT on channels CHs") + parser.add_argument('--reach', '-r', nargs=2, metavar=("[CHs]","[ATT]"), + action=ValidateChAtt, help="Perform an \"attenuation sweep\": reach attenuation ATT on channels CHs") + parser.add_argument('--duration', '-d', action='store', type=float, default=5.0, + help='DURATION for attenuation sweep to --reach given attenuation (Default: 5)',) + parser.add_argument('--progress', '-p', action='store_true', default=False, + help='If provided, will show progress during attenuation sweep',) + return parser.parse_args() + +def _exec(dev, cmd): + dev.write(1, cmd) + ans = dev.read(0x81,64) + # take from ans(wer) while valid bytes + sn = itertools.takewhile(lambda x: x < 255 and x > 0, ans) + # turn bytes into characters and accumulate into string + return "".join(map(chr, sn)) + +def devId(dev): + return f"{dev.idVendor:04x}:{dev.idProduct:04x}" + +def _info(dev): + ident = devId(dev) + SerialN = _exec(dev, "*:SN?") + ModelN = _exec(dev, "*:MN?") + Fw = _exec(dev, "*:FIRMWARE?") + + logging.info(f'{ident}: model is {ModelN}') + logging.info(f'{ident}: serial number is {SerialN}') + logging.info(f'{ident}: FW version is {Fw}') + +def _get_attenuation(dev): + ident = devId(dev) + att = _exec(dev, "*:ATT?") + # remove leading "*" and split by channels + return [float(n) for n in att[1:].split(" ")] + +def _set_attenuation(dev, ch_att): + ident = devId(dev) + for ch, att in ch_att: + #logging.debug(f"{ident}: Set attenuation channel {ch}: {att} dB") + att = round(4 * att) / 4 # round to closest 0.25dB + resp = _exec(dev, f"*:CHAN:{ch}:SETATT:{att};" ) + +def _continuous_set_attenuation(dev, ch_att_end, duration, show_progress=False, step_duration=0.25): + ident = devId(dev) + + current = _get_attenuation(dev) + steps = duration / step_duration + ch_att_step = [(ch, (att_end - current[ch - 1]) / steps) for ch, att_end in ch_att_end] + logging.info(f"{ident}: sweep attenuation during {duration} s in {steps} steps: increments {ch_att_step}") + for i in range(1, int(steps)+1): + next_att = [(ch, current[ch - 1] + i * att_step) for ch, att_step in ch_att_step] + _set_attenuation(dev, next_att) + if show_progress: + logging.debug(f"{ident}: attenuation for channel(s): {next_att}") + time.sleep(step_duration) + _set_attenuation(dev, ch_att_end) + +def _get_usb_devs(idVendor, idProduct): + devs = [] + for dev in usb.core.find(idVendor=idVendor, idProduct=idProduct, find_all=True): + for configuration in dev: + for interface in configuration: + ifnum = interface.bInterfaceNumber + if not dev.is_kernel_driver_active(ifnum): + continue + try: + dev.detach_kernel_driver(ifnum) + except e: + pass + devs.append(dev) + return devs + +if __name__ == '__main__': + # Parse the arguments + args = _parse_args() + if args.set and args.reach: + logging.error("--set and --reach are mutually exclusive") + sys.exit(1) + + # 20ce:0023 is Mini-Circuit RC*DAT device + devs = _get_usb_devs(idVendor=0x20ce, idProduct=0x0023) + if len(devs) == 0: + logging.error('no Mini-Circuits RC*DAT device detected') + sys.exit(-1) + if len(devs) > 1: + logging.error('more than one Mini-Circuits RC*DAT device detected') + sys.exit(-1) + dev = devs[0] + + if args.info: + _info(dev) + + if args.set: + _set_attenuation(dev, [(k,v) for k,v in args.set.items()]) + + if args.reach: + _continuous_set_attenuation(dev, [(k,v) for k,v in args.reach.items()], args.duration, args.progress) + + ident = devId(dev) + for i, a in enumerate(_get_attenuation(dev)): + logging.info(f"{ident}: Attenuation channel {i+1}: {a} dB") + sys.exit(0) From f93a71977fccf561a9a6d8143190d1c5c141faa8 Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Fri, 10 Oct 2025 17:46:14 +0200 Subject: [PATCH 02/11] Add band validation in neighbor cell configuration - Add band mismatch check in valid_du_in_neighbour_configs function - Ensures neighbor cell band matches serving cell band - Prevents invalid neighbor configurations for handover --- openair2/RRC/NR/rrc_gNB_du.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/openair2/RRC/NR/rrc_gNB_du.c b/openair2/RRC/NR/rrc_gNB_du.c index edf6f19164..246df60626 100644 --- a/openair2/RRC/NR/rrc_gNB_du.c +++ b/openair2/RRC/NR/rrc_gNB_du.c @@ -257,6 +257,11 @@ static bool valid_du_in_neighbour_configs(const seq_arr_t *neighbour_cell_config nc->absoluteFrequencySSB); return false; } + // Check neighbour band + if (get_dl_band(cell) != nc->band) { + LOG_W(NR_RRC, "Cell %ld in neighbour config: Band mismatch (%d vs %d)\n", cell->nr_cellid, get_dl_band(cell), nc->band); + return false; + } if (get_ssb_scs(cell) != nc->subcarrierSpacing) { LOG_W(NR_RRC, "Cell %ld in neighbour config: SCS mismatch (%d vs %d)\n", From 0ba9a9758b302816cd35a9ce62d7839351d86055 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Mon, 13 Oct 2025 09:43:44 +0200 Subject: [PATCH 03/11] CI: expand %%workspace%% token - main.py: replace %%workspace%% with CONTAINERS.eNBSourceCodePath in Custom_Command and Custom_Script Co-authored-by: Guido Casati --- ci-scripts/main.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ci-scripts/main.py b/ci-scripts/main.py index 3e7be396cc..ac4ac2c3be 100644 --- a/ci-scripts/main.py +++ b/ci-scripts/main.py @@ -321,11 +321,15 @@ def ExecuteActionWithParam(action, ctx): # Change all execution targets to localhost node = 'localhost' command = test.findtext('command') + # Allow referencing repository workspace path in XML via %%workspace%% + command = command.replace("%%workspace%%", CONTAINERS.eNBSourceCodePath) success = cls_oaicitest.Custom_Command(HTML, node, command) elif action == 'Custom_Script': node = test.findtext('node') script = test.findtext('script') + # Allow referencing repository workspace path in XML via %%workspace%% + script = script.replace("%%workspace%%", CONTAINERS.eNBSourceCodePath) success = cls_oaicitest.Custom_Script(HTML, node, script) elif action == 'Pull_Cluster_Image': From 42a54a01a037451c5a32f9b02a8c7519d466a09c Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Thu, 9 Oct 2025 16:38:38 +0200 Subject: [PATCH 04/11] USRP: move time/clock source setting to later Doing this before using set_master_clock_rate() on the B200 seems to invalidate synchronization setting. [HW] USRP clock set to 1759836386.000000 sec [...] [HW] current pps at 3.286750, starting streaming at 4.286750 In other words, the current time setting was reset. With this change now, we see [HW] USRP clock set to 1759929873.000000 sec [...] [HW] current pps at 1759929873.000000, starting streaming at 1759929874.000000 This is because changing clock sources (I assume setting the master clock rate impacts the clock) can result in changing the time. From the documentation of [1]: > Note: When changing clock sources, a previously set time will most > likely be lost. It is recommended to set the time after changing the > clock source. Otherwise, an unexpected time may line up with future PPS > edges. [1] https://files.ettus.com/manual/classuhd_1_1usrp_1_1multi__usrp.html#a99254abfa5259b70a020e667eee619b9 --- radio/USRP/usrp_lib.cpp | 139 ++++++++++++++++++---------------------- 1 file changed, 64 insertions(+), 75 deletions(-) diff --git a/radio/USRP/usrp_lib.cpp b/radio/USRP/usrp_lib.cpp index 6dde70b2eb..79c58b59f4 100644 --- a/radio/USRP/usrp_lib.cpp +++ b/radio/USRP/usrp_lib.cpp @@ -1161,79 +1161,6 @@ extern "C" { s->usrp = uhd::usrp::multi_usrp::make(args); - if (args.find("clock_source")==std::string::npos) { - if (openair0_cfg[0].clock_source == internal) { - s->usrp->set_clock_source("internal"); - LOG_I(HW,"Setting clock source to internal\n"); - } - else if (openair0_cfg[0].clock_source == external ) { - s->usrp->set_clock_source("external"); - LOG_I(HW,"Setting clock source to external\n"); - } - else if (openair0_cfg[0].clock_source==gpsdo) { - s->usrp->set_clock_source("gpsdo"); - LOG_I(HW,"Setting clock source to gpsdo\n"); - } - else { - LOG_W(HW,"Clock source set neither in usrp_args nor on command line, using default!\n"); - } - } - else { - if (openair0_cfg[0].clock_source != unset) { - LOG_W(HW,"Clock source set in both usrp_args and in clock_source, ingnoring the latter!\n"); - } - } - - if (args.find("time_source")==std::string::npos) { - if (openair0_cfg[0].time_source == internal) { - s->usrp->set_time_source("internal"); - LOG_I(HW,"Setting time source to internal\n"); - } - else if (openair0_cfg[0].time_source == external ) { - s->usrp->set_time_source("external"); - LOG_I(HW,"Setting time source to external\n"); - } - else if (openair0_cfg[0].time_source==gpsdo) { - s->usrp->set_time_source("gpsdo"); - LOG_I(HW,"Setting time source to gpsdo\n"); - } - else { - LOG_W(HW,"Time source set neither in usrp_args nor on command line, using default!\n"); - } - } - else { - if (openair0_cfg[0].time_source != unset) { - LOG_W(HW, "Time source set in both usrp_args and in openair0_cfg[0].time_source, ignoring the latter!\n"); - } - } - - - if (s->usrp->get_clock_source(0) == "gpsdo") { - s->use_gps = 1; - - if (sync_to_gps(device)==EXIT_SUCCESS) { - LOG_I(HW,"USRP synced with GPS!\n"); - } else { - LOG_I(HW,"USRP fails to sync with GPS. Exiting.\n"); - exit(EXIT_FAILURE); - } - } else { - if (s->usrp->get_time_source(0) == "external") { - usrp_sync_pps(s); - } else { - s->usrp->set_time_next_pps(uhd::time_spec_t(0.0)); - } - - if (s->usrp->get_clock_source(0) == "external") { - if (check_ref_locked(s,0)) { - LOG_I(HW,"USRP locked to external reference!\n"); - } else { - LOG_I(HW,"Failed to lock to external reference. Exiting.\n"); - exit(EXIT_FAILURE); - } - } - } - if (device->type==USRP_X300_DEV) { openair0_cfg[0].rx_gain_calib_table = calib_table_x310; std::cerr << "-- Using calibration table: calib_table_x310" << std::endl; @@ -1470,8 +1397,70 @@ extern "C" { } } - //s->usrp->set_clock_source("external"); - //s->usrp->set_time_source("external"); + if (args.find("clock_source") == std::string::npos) { + if (openair0_cfg[0].clock_source == internal) { + s->usrp->set_clock_source("internal"); + LOG_I(HW, "Setting clock source to internal\n"); + } else if (openair0_cfg[0].clock_source == external) { + s->usrp->set_clock_source("external"); + LOG_I(HW, "Setting clock source to external\n"); + } else if (openair0_cfg[0].clock_source == gpsdo) { + s->usrp->set_clock_source("gpsdo"); + LOG_I(HW, "Setting clock source to gpsdo\n"); + } else { + LOG_W(HW, "Clock source set neither in usrp_args nor on command line, using default!\n"); + } + } else { + if (openair0_cfg[0].clock_source != unset) { + LOG_W(HW, "Clock source set in both usrp_args and in clock_source, ingnoring the latter!\n"); + } + } + + if (args.find("time_source") == std::string::npos) { + if (openair0_cfg[0].time_source == internal) { + s->usrp->set_time_source("internal"); + LOG_I(HW, "Setting time source to internal\n"); + } else if (openair0_cfg[0].time_source == external) { + s->usrp->set_time_source("external"); + LOG_I(HW, "Setting time source to external\n"); + } else if (openair0_cfg[0].time_source == gpsdo) { + s->usrp->set_time_source("gpsdo"); + LOG_I(HW, "Setting time source to gpsdo\n"); + } else { + LOG_W(HW, "Time source set neither in usrp_args nor on command line, using default!\n"); + } + } else { + if (openair0_cfg[0].time_source != unset) { + LOG_W(HW, "Time source set in both usrp_args and in openair0_cfg[0].time_source, ignoring the latter!\n"); + } + } + + if (s->usrp->get_clock_source(0) == "gpsdo") { + s->use_gps = 1; + + if (sync_to_gps(device) == EXIT_SUCCESS) { + LOG_I(HW, "USRP synced with GPS!\n"); + } else { + LOG_I(HW, "USRP fails to sync with GPS. Exiting.\n"); + exit(EXIT_FAILURE); + } + } else { + if (s->usrp->get_time_source(0) == "external") { + usrp_sync_pps(s); + } else { + s->usrp->set_time_next_pps(uhd::time_spec_t(0.0)); + } + + if (s->usrp->get_clock_source(0) == "external") { + if (check_ref_locked(s, 0)) { + LOG_I(HW, "USRP locked to external reference!\n"); + } else { + LOG_I(HW, "Failed to lock to external reference. Exiting.\n"); + exit(EXIT_FAILURE); + } + } + } + // display USRP settings LOG_I(HW,"Actual master clock: %fMHz...\n",s->usrp->get_master_clock_rate()/1e6); LOG_I(HW,"Actual clock source %s...\n",s->usrp->get_clock_source(0).c_str()); From e375a13aa5f2dc651b18dd03a13965db36d8a870 Mon Sep 17 00:00:00 2001 From: Reem Bahsoun Date: Wed, 15 Oct 2025 20:08:49 +0200 Subject: [PATCH 05/11] CI infra: rename UE name to reflect PLMN --- ci-scripts/ci_infra.yaml | 2 +- .../xml_files/container_sa_f1_ho_b210_quectel.xml | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/ci-scripts/ci_infra.yaml b/ci-scripts/ci_infra.yaml index 85dbd10180..837f1edb82 100644 --- a/ci-scripts/ci_infra.yaml +++ b/ci-scripts/ci_infra.yaml @@ -85,7 +85,7 @@ oc-cn5g-20897: Undeploy: "! scripts/oc-cn5g-undeploy.sh /opt/oai-cn5g-fed-develop-2025-jan oaicicd-core-for-fhi72" LogCollect: "! scripts/oc-cn5g-logcollect.sh /opt/oai-cn5g-fed-develop-2025-jan oaicicd-core-for-fhi72 %%log_dir%%" -oc-cn5g-00102-ho: +oc-cn5g-00103-ho: Host: groot NetworkScript: echo "inet 172.21.6.111" RunIperf3Server: False diff --git a/ci-scripts/xml_files/container_sa_f1_ho_b210_quectel.xml b/ci-scripts/xml_files/container_sa_f1_ho_b210_quectel.xml index 814346b5f3..81e3745167 100644 --- a/ci-scripts/xml_files/container_sa_f1_ho_b210_quectel.xml +++ b/ci-scripts/xml_files/container_sa_f1_ho_b210_quectel.xml @@ -58,7 +58,7 @@ DeployCoreNetwork Initialize 5G Core - oc-cn5g-00102-ho + oc-cn5g-00103-ho @@ -113,7 +113,7 @@ Ping Ping: 100pings in 20sec raspix - oc-cn5g-00102-ho + oc-cn5g-00103-ho -c 100 -i 0.2 5 @@ -143,7 +143,7 @@ Ping Ping: 50pings in 10sec raspix - oc-cn5g-00102-ho + oc-cn5g-00103-ho -c 50 -i 0.2 5 @@ -166,7 +166,7 @@ Ping Ping: 50pings in 10sec raspix - oc-cn5g-00102-ho + oc-cn5g-00103-ho -c 50 -i 0.2 5 @@ -189,7 +189,7 @@ Ping Ping: 50pings in 10sec raspix - oc-cn5g-00102-ho + oc-cn5g-00103-ho -c 50 -i 0.2 5 @@ -244,7 +244,7 @@ UndeployCoreNetwork true Terminate 5G Core - oc-cn5g-00102-ho + oc-cn5g-00103-ho From 464762e52be9db5809edbee693a145e126d2d555 Mon Sep 17 00:00:00 2001 From: Reem Bahsoun Date: Wed, 15 Oct 2025 20:09:38 +0200 Subject: [PATCH 06/11] Fix: PCI on different SSB position --- .../conf_files/gnb-du.sa.band78.51prb.usrpb210.ho-pci1.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci-scripts/conf_files/gnb-du.sa.band78.51prb.usrpb210.ho-pci1.conf b/ci-scripts/conf_files/gnb-du.sa.band78.51prb.usrpb210.ho-pci1.conf index 3ba7e17b46..ae94203fd0 100644 --- a/ci-scripts/conf_files/gnb-du.sa.band78.51prb.usrpb210.ho-pci1.conf +++ b/ci-scripts/conf_files/gnb-du.sa.band78.51prb.usrpb210.ho-pci1.conf @@ -108,7 +108,7 @@ gNBs = hoppingId = 40; p0_nominal = -90; # ssb_PositionsInBurs_BitmapPR - ssb_PositionsInBurst_Bitmap = 1; + ssb_PositionsInBurst_Bitmap = 2; # ssb_periodicityServingCell # 0 = ms5, 1=ms10, 2=ms20, 3=ms40, 4=ms80, 5=ms160, 6=spare2, 7=spare1 From c9b704ed64ca4cf0bb1b0017fad66bef22b7d5a9 Mon Sep 17 00:00:00 2001 From: Reem Bahsoun Date: Wed, 15 Oct 2025 20:10:42 +0200 Subject: [PATCH 07/11] Rename services in docker-compose for clarity Harmonize with other files and use dashes. --- ci-scripts/xml_files/container_sa_f1_ho_b210_quectel.xml | 6 +++--- ci-scripts/yaml_files/5g_sa_f1_b210_ho/docker-compose.yml | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/ci-scripts/xml_files/container_sa_f1_ho_b210_quectel.xml b/ci-scripts/xml_files/container_sa_f1_ho_b210_quectel.xml index 81e3745167..f4b3dfc344 100644 --- a/ci-scripts/xml_files/container_sa_f1_ho_b210_quectel.xml +++ b/ci-scripts/xml_files/container_sa_f1_ho_b210_quectel.xml @@ -92,14 +92,14 @@ Deploy gNB-CU in a container ci-scripts/yaml_files/5g_sa_f1_b210_ho groot - oai_cu + oai-cu Deploy_Object Deploy gNB-DU0 (TDD/Band77/20MHz/B210) in a container ci-scripts/yaml_files/5g_sa_f1_b210_ho - oai_du0 + oai-du0 groot @@ -123,7 +123,7 @@ Deploy gNB-DU1 (TDD/Band77/20MHz/B210) in a container ci-scripts/yaml_files/5g_sa_f1_b210_ho rocket - oai_du1 + oai-du1 diff --git a/ci-scripts/yaml_files/5g_sa_f1_b210_ho/docker-compose.yml b/ci-scripts/yaml_files/5g_sa_f1_b210_ho/docker-compose.yml index 24a3055701..c1c3db1607 100644 --- a/ci-scripts/yaml_files/5g_sa_f1_b210_ho/docker-compose.yml +++ b/ci-scripts/yaml_files/5g_sa_f1_b210_ho/docker-compose.yml @@ -1,5 +1,5 @@ services: - oai_cu: + oai-cu: image: ${REGISTRY:-oaisoftwarealliance}/${GNB_IMG:-oai-gnb}:${TAG:-develop} container_name: oai-cu cap_drop: @@ -25,7 +25,7 @@ services: timeout: 5s retries: 5 - oai_du0: + oai-du0: image: ${REGISTRY:-oaisoftwarealliance}/${GNB_IMG:-oai-gnb}:${TAG:-develop} container_name: oai-du0 cap_drop: @@ -57,7 +57,7 @@ services: timeout: 5s retries: 5 - oai_du1: + oai-du1: image: ${REGISTRY:-oaisoftwarealliance}/${GNB_IMG:-oai-gnb}:${TAG:-develop} container_name: oai-du1 cap_drop: From d8e9a30fad442c51be332bc24dcd6dc1cbc29df7 Mon Sep 17 00:00:00 2001 From: Reem Bahsoun Date: Thu, 18 Sep 2025 11:40:21 +0200 Subject: [PATCH 08/11] Add attenuator commands to the XML file --- .../container_sa_f1_ho_b210_quectel.xml | 30 ++++++++++++------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/ci-scripts/xml_files/container_sa_f1_ho_b210_quectel.xml b/ci-scripts/xml_files/container_sa_f1_ho_b210_quectel.xml index f4b3dfc344..978691e534 100644 --- a/ci-scripts/xml_files/container_sa_f1_ho_b210_quectel.xml +++ b/ci-scripts/xml_files/container_sa_f1_ho_b210_quectel.xml @@ -30,6 +30,7 @@ 000003 000004 000005 + 000027 000006 000007 000008 @@ -87,6 +88,13 @@ rocket + + Custom_Command + Set attenuator to 0 for DU0 and 17 for DU1 + rocket + python3 %%workspace%%/ci-scripts/attenuatorctl.py -r 1,2 0 -r 3,4 17 -d 5 + + Deploy_Object Deploy gNB-CU in a container @@ -134,9 +142,9 @@ Custom_Command - Trigger Handover - groot - echo ci trigger_f1_ho | ncat 127.0.0.1 9090 + Attenuate DU0 by 15 and remove the attenuation on DU1 to trigger Handover + rocket + python3 %%workspace%%/ci-scripts/attenuatorctl.py -r 1,2 15 -r 3,4 0 -d 5 @@ -156,10 +164,10 @@ - Custom_Command - Trigger Handover - groot - echo ci trigger_f1_ho | ncat 127.0.0.1 9090 + Custom_Command + Attenuate DU1 by 17dB and remove attenuation on DU0 to trigger Handover + rocket + python3 %%workspace%%/ci-scripts/attenuatorctl.py -r 1,2 0 -r 3,4 17 -d 5 @@ -179,10 +187,10 @@ - Custom_Command - Trigger Handover - groot - echo ci trigger_f1_ho | ncat 127.0.0.1 9090 + Custom_Command + Attenuate DU0 by 15 and remove the attenuation on DU1 to trigger Handover + rocket + python3 %%workspace%%/ci-scripts/attenuatorctl.py -r 1,2 15 -r 3,4 0 -d 5 From e2075b57cacb3167e87b32e62c7c852c75cddf91 Mon Sep 17 00:00:00 2001 From: Reem Bahsoun Date: Wed, 15 Oct 2025 20:12:07 +0200 Subject: [PATCH 09/11] Clean up F1 HO config files Harmonize gNB names with other files, remove control ports (not used), put sl_ahead in right place. --- ci-scripts/conf_files/gnb-cu.sa.f1.ho.conf | 4 ++-- .../gnb-du.sa.band78.51prb.usrpb210.ho-pci0.conf | 10 ++++------ .../gnb-du.sa.band78.51prb.usrpb210.ho-pci1.conf | 10 ++++------ 3 files changed, 10 insertions(+), 14 deletions(-) diff --git a/ci-scripts/conf_files/gnb-cu.sa.f1.ho.conf b/ci-scripts/conf_files/gnb-cu.sa.f1.ho.conf index 8159eff571..f16a94b472 100644 --- a/ci-scripts/conf_files/gnb-cu.sa.f1.ho.conf +++ b/ci-scripts/conf_files/gnb-cu.sa.f1.ho.conf @@ -1,4 +1,4 @@ -Active_gNBs = ( "oai-cu"); +Active_gNBs = ( "CU-OAI"); # Asn1_verbosity, choice in: none, info, annoying Asn1_verbosity = "none"; @@ -10,7 +10,7 @@ gNBs = # cell_type = "CELL_MACRO_GNB"; - gNB_name = "oai-cu"; + gNB_name = "CU-OAI"; // Tracking area code, 0x0000 and 0xfffe are reserved values tracking_area_code = 1; diff --git a/ci-scripts/conf_files/gnb-du.sa.band78.51prb.usrpb210.ho-pci0.conf b/ci-scripts/conf_files/gnb-du.sa.band78.51prb.usrpb210.ho-pci0.conf index eb52e68566..df225e84c2 100644 --- a/ci-scripts/conf_files/gnb-du.sa.band78.51prb.usrpb210.ho-pci0.conf +++ b/ci-scripts/conf_files/gnb-du.sa.band78.51prb.usrpb210.ho-pci0.conf @@ -1,4 +1,4 @@ -Active_gNBs = ( "oai-du0"); +Active_gNBs = ( "DU0-OAI"); # Asn1_verbosity, choice in: none, info, annoying Asn1_verbosity = "none"; @@ -8,7 +8,7 @@ gNBs = ////////// Identification parameters: gNB_ID = 0xe88; gNB_DU_ID = 0xe00; - gNB_name = "oai-du0"; + gNB_name = "DU0-OAI"; // Tracking area code, 0x0000 and 0xfffe are reserved values tracking_area_code = 1 ; @@ -107,7 +107,7 @@ gNBs = pucchGroupHopping = 0; hoppingId = 40; p0_nominal = -90; -# ssb_PositionsInBurs_BitmapPR + ssb_PositionsInBurst_Bitmap = 1; # ssb_periodicityServingCell @@ -159,9 +159,7 @@ MACRLCs = ( tr_n_preference = "f1"; local_n_address = "172.21.19.98"; remote_n_address = "172.21.19.98"; - local_n_portc = 500; local_n_portd = 2154; - remote_n_portc = 501; remote_n_portd = 2153; pusch_TargetSNRx10 = 200; pucch_TargetSNRx10 = 230; @@ -176,7 +174,6 @@ L1s = ( prach_dtx_threshold = 100; pucch0_dtx_threshold = 10; max_ldpc_iterations = 20; - sl_ahead = 3; ofdm_offset_divisor = 8; #set this to UINT_MAX for offset 0 } ); @@ -188,6 +185,7 @@ RUs = ( nb_rx = 1 att_tx = 0; att_rx = 0; + sl_ahead = 3; bands = [77]; max_pdschReferenceSignalPower = -27; max_rxgain = 114; diff --git a/ci-scripts/conf_files/gnb-du.sa.band78.51prb.usrpb210.ho-pci1.conf b/ci-scripts/conf_files/gnb-du.sa.band78.51prb.usrpb210.ho-pci1.conf index ae94203fd0..14ec8b1e82 100644 --- a/ci-scripts/conf_files/gnb-du.sa.band78.51prb.usrpb210.ho-pci1.conf +++ b/ci-scripts/conf_files/gnb-du.sa.band78.51prb.usrpb210.ho-pci1.conf @@ -1,4 +1,4 @@ -Active_gNBs = ( "oai-du1"); +Active_gNBs = ( "DU1-OAI"); # Asn1_verbosity, choice in: none, info, annoying Asn1_verbosity = "none"; @@ -8,7 +8,7 @@ gNBs = ////////// Identification parameters: gNB_ID = 0xe88; gNB_DU_ID = 0xe01; - gNB_name = "oai-du1"; + gNB_name = "DU1-OAI"; // Tracking area code, 0x0000 and 0xfffe are reserved values tracking_area_code = 1 ; @@ -107,7 +107,7 @@ gNBs = pucchGroupHopping = 0; hoppingId = 40; p0_nominal = -90; -# ssb_PositionsInBurs_BitmapPR + ssb_PositionsInBurst_Bitmap = 2; # ssb_periodicityServingCell @@ -159,9 +159,7 @@ MACRLCs = ( tr_n_preference = "f1"; local_n_address = "172.21.19.99"; remote_n_address = "172.21.19.98"; - local_n_portc = 500; local_n_portd = 2154; - remote_n_portc = 501; remote_n_portd = 2153; pusch_TargetSNRx10 = 200; pucch_TargetSNRx10 = 230; @@ -176,7 +174,6 @@ L1s = ( prach_dtx_threshold = 100; pucch0_dtx_threshold = 10; max_ldpc_iterations = 20; - sl_ahead = 3; ofdm_offset_divisor = 8; #set this to UINT_MAX for offset 0 } ); @@ -188,6 +185,7 @@ RUs = ( nb_rx = 1 att_tx = 0; att_rx = 0; + sl_ahead = 3; bands = [77]; max_pdschReferenceSignalPower = -27; max_rxgain = 114; From 9803fb29f66855fee25c0f226a53449bee6ce755 Mon Sep 17 00:00:00 2001 From: Reem Bahsoun Date: Wed, 15 Oct 2025 20:11:16 +0200 Subject: [PATCH 10/11] Reorganize XML steps for F1 HO Number linearly for easier navigation in HTML reports, and start DU1 before connecting the UE. Reset attenuation on all channels to 0. --- .../container_sa_f1_ho_b210_quectel.xml | 69 +++++++++++-------- 1 file changed, 39 insertions(+), 30 deletions(-) diff --git a/ci-scripts/xml_files/container_sa_f1_ho_b210_quectel.xml b/ci-scripts/xml_files/container_sa_f1_ho_b210_quectel.xml index 978691e534..77b2312409 100644 --- a/ci-scripts/xml_files/container_sa_f1_ho_b210_quectel.xml +++ b/ci-scripts/xml_files/container_sa_f1_ho_b210_quectel.xml @@ -30,7 +30,6 @@ 000003 000004 000005 - 000027 000006 000007 000008 @@ -52,6 +51,8 @@ 000024 000025 000026 + 000027 + 000028 @@ -88,14 +89,14 @@ rocket - + Custom_Command Set attenuator to 0 for DU0 and 17 for DU1 rocket python3 %%workspace%%/ci-scripts/attenuatorctl.py -r 1,2 0 -r 3,4 17 -d 5 - + Deploy_Object Deploy gNB-CU in a container ci-scripts/yaml_files/5g_sa_f1_b210_ho @@ -103,7 +104,7 @@ oai-cu - + Deploy_Object Deploy gNB-DU0 (TDD/Band77/20MHz/B210) in a container ci-scripts/yaml_files/5g_sa_f1_b210_ho @@ -111,13 +112,21 @@ groot - + + Deploy_Object + Deploy gNB-DU1 (TDD/Band77/20MHz/B210) in a container + ci-scripts/yaml_files/5g_sa_f1_b210_ho + rocket + oai-du1 + + + Attach_UE Attach Quectel raspix - + Ping Ping: 100pings in 20sec raspix @@ -126,28 +135,20 @@ 5 - - Deploy_Object - Deploy gNB-DU1 (TDD/Band77/20MHz/B210) in a container - ci-scripts/yaml_files/5g_sa_f1_b210_ho - rocket - oai-du1 - - - + IdleSleep Sleep 5 - + Custom_Command Attenuate DU0 by 15 and remove the attenuation on DU1 to trigger Handover rocket python3 %%workspace%%/ci-scripts/attenuatorctl.py -r 1,2 15 -r 3,4 0 -d 5 - + Ping Ping: 50pings in 10sec raspix @@ -156,21 +157,21 @@ 5 - + Custom_Command UE connected to DU1? groot echo ci fetch_du_by_ue_id 1 | ncat 127.0.0.1 9090 | grep "3585" - + Custom_Command Attenuate DU1 by 17dB and remove attenuation on DU0 to trigger Handover rocket python3 %%workspace%%/ci-scripts/attenuatorctl.py -r 1,2 0 -r 3,4 17 -d 5 - + Ping Ping: 50pings in 10sec raspix @@ -179,21 +180,21 @@ 5 - + Custom_Command UE connected to DU0? groot echo ci fetch_du_by_ue_id 1 | ncat 127.0.0.1 9090 | grep "3584" - + Custom_Command Attenuate DU0 by 15 and remove the attenuation on DU1 to trigger Handover rocket python3 %%workspace%%/ci-scripts/attenuatorctl.py -r 1,2 15 -r 3,4 0 -d 5 - + Ping Ping: 50pings in 10sec raspix @@ -202,21 +203,21 @@ 5 - + Custom_Command UE connected to DU1? groot echo ci fetch_du_by_ue_id 1 | ncat 127.0.0.1 9090 | grep "3585" - + Detach_UE true Detach Quectel raspix - + Undeploy_Object true Undeploy CU-DU0 @@ -224,7 +225,7 @@ ci-scripts/yaml_files/5g_sa_f1_b210_ho - + Undeploy_Object true Undeploy DU1 @@ -232,7 +233,15 @@ ci-scripts/yaml_files/5g_sa_f1_b210_ho - + + Custom_Command + Reset all channels on the attenuator to 0 + rocket + python3 %%workspace%%/ci-scripts/attenuatorctl.py -s 1-4 0 + true + + + Clean_Test_Server_Images true Clean Test Images on Test Server @@ -240,7 +249,7 @@ oai-gnb - + Clean_Test_Server_Images true Clean Test Images on Test Server @@ -248,7 +257,7 @@ oai-gnb - + UndeployCoreNetwork true Terminate 5G Core From 5f162c436ef23b6448dda1746229144dcd0231cc Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Thu, 16 Oct 2025 11:36:04 +0200 Subject: [PATCH 11/11] Add documentation for CI Handover Setup --- doc/TESTBenches.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/doc/TESTBenches.md b/doc/TESTBenches.md index e7564b8678..8674103a1a 100644 --- a/doc/TESTBenches.md +++ b/doc/TESTBenches.md @@ -24,6 +24,9 @@ | gracehopper1-oai | Gracehopper1 | gNB (n78, PNF/Nvidia CUBB + VNF) | Foxconn RU, _Nvidia Aerial integration_ | | gracehopper3-oai | Gracehopper3 | build | -- | | acamas | Acamas | 4G and 5G rfsimulator| -- | +| groot | Groot | gNB (n77) | B210 (30AD30F) | +| rocket | Rocket | gNB (n77) | B210 (31F8010) | +| raspix | Raspix | COTS UE | Quectel RM520N | Note: The available resources, and their current usage, is indicated here: - [Lockable resources of jenkins-oai](https://jenkins-oai.eurecom.fr/lockable-resources/): @@ -226,6 +229,11 @@ information on how the images are built. - cacofonix + FHI72 + Metanoia (gNB), up2 (Quectel RM520N UE), OAI CN5G - OpenShift cluster for CN deployment - FHI 7.2 testing with 100 MHz bandwidth, 2 layers in DL +- [RAN-SA-Handover-CN5G](https://jenkins-oai.eurecom.fr/job/RAN-SA-Handover-CN5G/) + ~5G-NR + - groot (CU+DU0) + B210, rocket (DU1) + B210, raspix (Quectel RM520N UE), OAI CN5G + - OpenShift cluster for CN deployment + - Attenuator (mini circuits RC4DAT-6G-60) - controlled from rocket ### RAN-CI-NSA-Trigger