Open NTN Testbed: network-verified UE location over a LEO satellite
This is a 5G Standalone testbed in which the UE reaches the gNB through a low-Earth-orbit satellite. The LMF works out the UE's position from round trips to that satellite, and a set of scripts compares each fix against the emulated ground truth. The network locates the UE from the satellite link alone, and checks the position the UE reports, without GNSS and without a second ground station.
Network verification of UE location
Release 18 added a way for the network to check a UE's reported location without GNSS, introduced for regulatory reasons: network verification of UE location, TS 38.305 clause 8.10. The procedure is NR Multi-RTT, adapted to a different geometry. Terrestrial Multi-RTT needs several TRPs at separate locations at the same moment, and an NTN cell has only one. The standard therefore allows one TRP at different points in time. The satellite travels at about 7 km/s, so a single satellite sampled across its pass sees the UE from many directions, and the intersection of those ranges gives the UE's position.
The LMF receives each satellite's ephemeris, and the TRP it belongs to, from OAM rather than from the RAN. TS 38.305 clause 5.4.4 states: "For NTN, the LMF is configured by the OAM with satellite related information ... as well as the association between TRP(s) and satellite(s), the association between gNB and TRP(s)."
Components
| Repository | Branch | Contents |
|---|---|---|
| OCUDU-RAN | ntn | gNB: DL-PRS, gNB Rx-Tx, NRPPa TRP information, NTN channel emulation, configs, run and scoring scripts |
| OAI-RAN | rfsim_ocudu | nrUE: DL-PRS reception, UE Rx-Tx per TRP, LPP over NAS |
| oai-lmf | develop | LMF: LPP over N1, Release 18 codecs, the NTN solver |
| oai-amf | lpp-ntn | AMF: LPP routing between the UE and the LMF over N1 and N2 |
| oai-cn5g | not pinned | the rest of the 5G core: SMF, UPF, NRF, UDM/UDR/AUSF, MySQL |
Positioning needs two of the core network functions replaced. oai-lmf is a fork of EURECOM's oai-cn5g-lmf, and adds the Release 18 LPP and NRPPa codecs together with the NTN solver.
oai-amf is a fork of EURECOM's oai-cn5g-amf that carries LPP between the UE and the LMF. It tracks each LCS correlation identifier against its SUPI and LMF instance, so an uplink LPP message reaches the LMF that asked for it. It also checks the UE's 5GMM LPP capability and carries the LPP payload container on UL NAS transport. The stock AMF drops that container, so positioning does not work against an unmodified oaisoftwarealliance/oai-amf image, whatever the LMF is doing. The src/common-src submodule is patched on the same branch.
Both images replace their stock counterparts directly. The rest of the core is unchanged.
Prerequisites
- Ubuntu 22.04 or 24.04, x86-64, 8+ cores recommended
sudorights, Docker and Docker Compose- About 40 GB of disk: the regenerated Release 18 ASN.1 codecs are large
1. Get the sources
mkdir -p ~/ntn && cd ~/ntn
git clone -b ntn https://github.com/TOSSI-Foundation/OCUDU-RAN.git
git clone -b rfsim_ocudu https://github.com/TOSSI-Foundation/OAI-RAN.git OAI-RAN-rfsim
git clone https://github.com/TOSSI-Foundation/oai-lmf.git
git clone -b lpp-ntn --recurse-submodules https://github.com/TOSSI-Foundation/oai-amf.git
2. Build
gNB
cd ~/ntn/OCUDU-RAN
# dependencies: see the OCUDU user manual, https://docs.ocudu.org/user_manual/installation/
cmake -S . -B build_ntn -DCMAKE_BUILD_TYPE=Release -DENABLE_RFSIM=ON
make -C build_ntn -j"$(nproc)" gnb
The binary is produced at ~/ntn/OCUDU-RAN/build_ntn/apps/gnb/gnb. Build in build_ntn rather than build. The NTN tree links the rfsimulator statically, and a default build produces it as a plugin that the NTN configs cannot load.
UE
cd ~/ntn/OAI-RAN-rfsim/cmake_targets
./build_oai -I
./build_oai --nrUE -w SIMU
After any later change to the UE, build both targets:
cmake --build ran_build/build --target nr-uesoftmodem -j"$(nproc)"
cmake --build ran_build/build --target rfsimulator -j"$(nproc)"
rfsimulator is a separate plugin, and nr-uesoftmodem does not build it. Building only the modem leaves the previous radio binary in place, with nothing to indicate it.
LMF
cd ~/ntn/oai-lmf
docker build -f docker/Dockerfile.lmf.ubuntu -t oai-lmf:lpp-dev .
This takes about six minutes, as the Dockerfile builds with --clean inside. The solver also has a self-test that runs on the host without a container, in about two seconds:
cd ~/ntn/oai-lmf/src/lmf_app
g++ -std=c++17 -O2 -DLMF_NTN_NO_OAM lmf_ntn.cpp lmf_ntn_selftest.cpp -o /tmp/lmf_ntn_selftest && /tmp/lmf_ntn_selftest
It checks the propagator against the delay the gNB emulates for the same ephemeris, and the solver against a synthetic pass with and without noise.
AMF
cd ~/ntn/oai-amf
git submodule update --init --recursive
docker build -f docker/Dockerfile.amf.ubuntu -t oai-amf:lpp-dev .
The src/common-src submodule carries the NAS half of the LPP transport, so the clone needs --recurse-submodules, or the git submodule update above. Without it, LPP messages are dropped before the AMF sees them.
Both images should now be listed:
docker images | grep -E "oai-(amf|lmf):lpp-dev"
3. Core network
The core is stock OAI CN5G, with the AMF and the LMF replaced by the images built above. Nothing else changes.
Clone the core
cd ~
git clone https://gitlab.eurecom.fr/oai/cn5g/oai-cn5g-fed.git oai-cn5g
cd ~/oai-cn5g
mkdir -p conf/ntn logs/lmf
conf/ntn holds the OAM satellite file the LMF reads; logs/lmf collects one JSON line per fix.
Point the compose file at the TOSSI images
Two edits to ~/oai-cn5g/docker-compose.yaml. The AMF only needs its image swapped:
oai-amf:
container_name: "oai-amf"
image: oai-amf:lpp-dev # was oaisoftwarealliance/oai-amf:develop
The LMF needs the image, two extra mounts and the NTN environment:
oai-lmf:
container_name: "oai-lmf"
image: oai-lmf:lpp-dev # was oaisoftwarealliance/oai-lmf:develop
expose:
- 80/tcp
- 8080/tcp
volumes:
- ./conf/config.yaml:/openair-lmf/etc/config.yaml
- ./conf/ntn:/openair-lmf/etc/ntn # a DIRECTORY, not the file
- ./logs/lmf:/openair-lmf/positions
environment:
- TZ=Europe/Paris
- LMF_AUTO_SUPI=imsi-001010000000002 # position this UE with no AMF-triggered request
- LMF_AUTO_INTERVAL_S=5 # seconds between location sessions
- LMF_MEAS_ROUNDS=5 # measurement rounds per fix
- LMF_NTN_SATELLITES=/openair-lmf/etc/ntn/ntn_satellites.json
depends_on:
- oai-nrf
- oai-amf
networks:
public_net:
ipv4_address: 192.168.70.140
Configure the LMF
# ~/oai-cn5g/conf/config.yaml
lmf:
gnb_id_bits_count: 30 # must equal gnb_id_bit_length in configs/ntn/leo_nrppa.yml
num_gnb: 1 # 2 for the two-satellite scenario
trp_info_wait_ms: 10000
positioning_wait_ms: 10000
measurement_wait_ms: 10000
gnb_id_bits_count has to match the gNB. The OAI AMF's NGAP decoder handles only a 4-byte gNB ID and mis-keys shorter ones, so if the gNB advertises 22 bits, NRPPa addressed to the LMF does not arrive.
Bring it up
cd ~/oai-cn5g
docker compose up -d
until [ "$(docker compose ps --format '{{.Status}}' | grep -c healthy)" -ge 11 ]; do sleep 5; done
docker compose ps --format '{{.Name}} {{.Status}}'
After rebuilding either image, recreate that container so it picks up the new one:
docker compose up -d --force-recreate oai-lmf # or oai-amf
Check the swap took
docker compose images oai-amf oai-lmf
docker exec oai-lmf sh -c 'echo $LMF_NTN_SATELLITES; ls /openair-lmf/etc/ntn'
docker logs oai-amf 2>&1 | grep -i lpp | head
If docker logs oai-lmf shows TRP information and LPP capability exchanges but no NTN Multi-RTT round lines, the LMF is running but the AMF is not carrying LPP. Check that oai-amf is running the oai-amf:lpp-dev image.
4. Pass placement and ephemeris distribution
Both the emulated channel and SIB19 come from one ephemeris in the gNB config, so they cannot drift apart. The epoch is stamped immediately before launch:
sudo sed -i "s/epoch_timestamp: '[^']*'/epoch_timestamp: '$(date -u -d '+12 seconds' '+%Y-%m-%dT%H:%M:%S')'/" \
~/ntn/OCUDU-RAN/configs/ntn/leo_rfsim_gnb.yml
The LMF needs the same ephemeris through its OAM input. One script writes it from the gNB configuration, so the two cannot disagree:
~/ntn/OCUDU-RAN/scripts/lmf_ntn_oam.py \
~/ntn/OCUDU-RAN/configs/ntn/leo_rfsim_gnb.yml \
~/oai-cn5g/conf/ntn/ntn_satellites.json
Scenario 1: single satellite over one pass
This is the baseline configuration, and the case Release 18 specifies. It uses one gNB, one cell and one satellite. The LMF collects round trips as the satellite crosses, and solves for the UE's position.
Terminal 1, gNB. The gNB is the rfsimulator server here, so start it first:
sudo ~/ntn/OCUDU-RAN/build_ntn/apps/gnb/gnb \
-c ~/ntn/OCUDU-RAN/configs/ntn/leo_rfsim_gnb.yml \
-c ~/ntn/OCUDU-RAN/configs/ntn/leo_nrppa.yml
Terminal 2, UE:
sudo ~/ntn/OAI-RAN-rfsim/cmake_targets/ran_build/build/nr-uesoftmodem \
-O ~/ntn/OAI-RAN-rfsim/targets/PROJECTS/GENERIC-NR-5GC/CONF/ue.ntn.leo.rfsim.conf \
--band 256 -C 2185000000 --CO -190000000 -r 52 --numerology 0 --ssb 62 \
--rfsim --time-sync-I 0.1 --ntn-initial-time-drift -40
Alternatively, in a single command: scripts/run_leo_rfsim.sh with EXTRA_CFG=configs/ntn/leo_nrppa.yml.
Measured results
364 rounds over one pass, producing one fix approximately every 9 s. The ground truth is the North Pole, which is also the position the UE reports.
| Data | Fix error | 1-sigma (LMF) | Release 18 verdict (10 km) |
|---|---|---|---|
| 45 to 180 s | 1 to 56 km | 0.7 to 8.7 km | AMBIGUOUS, no verdict |
| 431 s | 27.1 m | 17 m | consistent |
| 646 s | 31.8 m | 10 m | consistent |
Round trips against the delay the gNB was emulating: +2.7 m, sd 13.2 m over 369 rounds.
The ambiguity comes from the geometry, and is not a fault in the measurement. Before the satellite passes the UE, the range profile does not yet show which side of the ground track the UE is on, and a mirrored position fits almost as well. The LMF reports AMBIGUOUS and gives no verdict, rather than picking one of the two.
Scenario 2: cross-plane satellite switch
A satellite switch (TS 38.331 satSwitchWithReSync-r18) hands the same cell from one satellite to the next. This is still one TRP at different points in time, but the arc now spans two orbital planes, which clears the mirror ambiguity much sooner.
cd ~/ntn/OCUDU-RAN
CFG=configs/ntn/leo_satswitch_xplane.yml EXTRA_CFG=configs/ntn/leo_nrppa.yml \
SWITCH_AT=120 DUR=620 scripts/run_leo_satswitch_rfsim.sh
The same run by hand. Both configs carry the epoch and the overlay carries the switch instant, so all three timestamps are stamped together. If they are out of step, the two orbits run on different clocks:
EPOCH=$(date -u -d '+12 seconds' '+%Y-%m-%dT%H:%M:%S') # T+0
SWITCH=$(date -u -d '+132 seconds' '+%Y-%m-%dT%H:%M:%S') # T+120, the switch
sudo sed -i "s/epoch_timestamp: '[^']*'/epoch_timestamp: '$EPOCH'/" \
~/ntn/OCUDU-RAN/configs/ntn/leo_rfsim_gnb.yml
sudo sed -i "s/epoch_timestamp: '[^']*'/epoch_timestamp: '$EPOCH'/;
s/t_service: '[^']*'/t_service: '$SWITCH'/;
s/t_service_start: '[^']*'/t_service_start: '$SWITCH'/" \
~/ntn/OCUDU-RAN/configs/ntn/leo_satswitch_xplane.yml
Then the OAM file, which now needs --switch-cfg so that it writes two satellites with servesFrom and servesUntil windows at the switch instant. The TRP-to-satellite association becomes a function of time:
~/ntn/OCUDU-RAN/scripts/lmf_ntn_oam.py \
~/ntn/OCUDU-RAN/configs/ntn/leo_rfsim_gnb.yml \
~/oai-cn5g/conf/ntn/ntn_satellites.json \
--switch-cfg ~/ntn/OCUDU-RAN/configs/ntn/leo_satswitch_xplane.yml
Terminal 1, gNB. Three configs this time: the link, the switch overlay and the positioning overlay.
sudo ~/ntn/OCUDU-RAN/build_ntn/apps/gnb/gnb \
-c ~/ntn/OCUDU-RAN/configs/ntn/leo_rfsim_gnb.yml \
-c ~/ntn/OCUDU-RAN/configs/ntn/leo_satswitch_xplane.yml \
-c ~/ntn/OCUDU-RAN/configs/ntn/leo_nrppa.yml
Terminal 2, UE. Unchanged from scenario 1:
sudo ~/ntn/OAI-RAN-rfsim/cmake_targets/ran_build/build/nr-uesoftmodem \
-O ~/ntn/OAI-RAN-rfsim/targets/PROJECTS/GENERIC-NR-5GC/CONF/ue.ntn.leo.rfsim.conf \
--band 256 -C 2185000000 --CO -190000000 -r 52 --numerology 0 --ssb 62 \
--rfsim --time-sync-I 0.1 --ntn-initial-time-drift -40
Terminal 3, tracker:
~/ntn/OCUDU-RAN/scripts/leo_tracker.py /tmp/gnb_leo_nrppa.log --ue /tmp/ue_leo_nrppa.log --port 8500
Check that the gNB armed the switch before the pass gets going. Without this line the switch is only advertised in SIB19, and the cell keeps flying satellite 1 past its horizon:
sudo grep -a "Sat-switch promotion scheduled" /tmp/gnb_leo_nrppa.log
sudo grep -a "Sat-switch promotion applied" /tmp/gnb_leo_nrppa.log # at t_service
Satellite 2 is satellite 1's state rotated 90 degrees about the Earth's axis. The reference location is the North Pole, which sits on that rotation axis, so the range and the UE's timing advance are the same at every moment and the link stays up across the switch. The two ground tracks still cross, so the mirror position across track 1 is not a solution for track 2. The same-plane leo_satswitch.yml does not work here, because both ground tracks lie on one line and the ambiguity remains.
This geometry has one side effect worth knowing about. Because the two elevations are equal to within 0.0000 degrees, the switch does not show up on the tracker's elevation chart: satellite 2 appears exactly where satellite 1 was, with no arc of its own. That is the geometry behaving as intended, not a broken plot.
Measured results
| One satellite | With the switch at T+120 s | |
|---|---|---|
| Ambiguity | 183 to 315 s before it cleared | never ambiguous |
| Error at 198 s | ~50 km (wrong side) | 12.1 m |
| Error at 619 s | 29 to 83 m | 14.3 m |
| 1-sigma at the end | 10 × 1 m | 2 × 1 m |
Both satellites contribute: 199 rounds from the first and 280 from the second. This is the best result on the testbed, and the configuration to use when a fast, unambiguous fix is required.
Scenario 3: concurrent dual-satellite operation
Two TRPs on two satellites over the same UE, so the LMF has two ranges per instant instead of one per instant along a pass. TS 38.305 has no NTN-specific procedure for this, since Release 18's NTN addition is the single-TRP case, but it needs none: clause 5.4.2 puts a TRP on board a satellite, 5.4.4 has OAM give the LMF the TRP-to-satellite association, and LPP carries up to nrMaxTRPs-r16 = 256 TRPs with the Release 18 NTN fields per TRP. Two satellites are two ordinary TRPs.
This scenario needs one extra step after each boot. The second gNB needs its own address, because GTP-U's port 2152 is fixed and two gNBs cannot both bind it on the same address:
sudo ip addr add 192.168.70.141/26 dev oai-cn5g # NOT .130-.140: those are the core's containers
sudo sed -i 's/^\( *num_gnb:\) *[0-9]*/\1 2/' ~/oai-cn5g/conf/config.yaml
cd ~/oai-cn5g && docker compose restart oai-lmf
Write both satellites into the OAM file, each carrying its own gNB's TRP:
~/ntn/OCUDU-RAN/scripts/lmf_ntn_oam.py \
~/ntn/OCUDU-RAN/configs/ntn/leo_rfsim_gnb.yml \
~/oai-cn5g/conf/ntn/ntn_satellites.json \
--add ~/ntn/OCUDU-RAN/configs/ntn/leo_dualsat_sat2.yml:412
Terminal 1, satellite 1's gNB. Start it before the UE:
sudo ~/ntn/OCUDU-RAN/build_ntn/apps/gnb/gnb \
-c ~/ntn/OCUDU-RAN/configs/ntn/leo_rfsim_gnb.yml \
-c ~/ntn/OCUDU-RAN/configs/ntn/leo_dualsat_sat1.yml
Terminal 2, the UE, running as the rfsimulator server, which is the only way it sees both cells at once:
sudo ~/ntn/OAI-RAN-rfsim/cmake_targets/ran_build/build/nr-uesoftmodem \
-O ~/ntn/OAI-RAN-rfsim/targets/PROJECTS/GENERIC-NR-5GC/CONF/ue.ntn.leo.rfsim.conf \
--band 256 -C 2185000000 --CO -190000000 -r 52 --numerology 0 --ssb 62 \
--rfsim --rfsimulator.[0].serveraddr server \
--time-sync-I 0.1 --ntn-initial-time-drift -40
Terminal 3, satellite 2's gNB, once the UE has an IP, about 190 s:
sudo ~/ntn/OCUDU-RAN/build_ntn/apps/gnb/gnb \
-c ~/ntn/OCUDU-RAN/configs/ntn/leo_rfsim_gnb.yml \
-c ~/ntn/OCUDU-RAN/configs/ntn/leo_dualsat_sat2.yml
Alternatively, the complete scenario: scripts/run_leo_dualsat_rfsim.sh.
Start the gNB before the UE, even though the UE is the rfsimulator server. In the other order, the UE runs on empty samples for a second or two before any cell exists, and then has to find an SSB in a stream it has already been pacing itself against, which makes the attach unreliable. With the gNB started first, the attach succeeded on four runs out of four.
Additional requirements for two satellites
The emulated delay has to be split between the two directions. The channel used to apply the whole round trip to the gNB's receive path. With a single satellite that looks the same as the correct behaviour, but it makes both downlinks arrive at the same time, so the difference between them is always zero whatever the geometry. Setting ntn_dl_share=0.5 in ru_sdr.device_args splits it:
gNB downlink delay = round trip × share advertised to the peer in every block header
gNB receive delay = round trip × (1 − share)
The round trip is unchanged, so every measurement of scenarios 1 and 2 is unchanged: the uplink still lands on the gNB's own clock and the UE's Rx-Tx is still its whole advance. Only when each gNB's downlink reaches the UE changes, which is the quantity two satellites need. The default is 0.
Each gNB counts frames from its own boot. Two gNBs started seconds apart number the same instant differently. That offset is signalled to the UE as nr-DL-PRS-SFN0-Offset (37.355 6.4.3), and it has to be applied in three places: where the UE looks for the neighbour's PRS, which sequence it generates (c_init carries the slot number, TS 38.211 7.4.1.7.2, an incorrect value yields zero correlation rather than weak correlation), and where the LMF compares two gNBs' measurement timestamps.
The ground tracks have to cross. leo_dualsat_sat2.yml is satellite 1's orbit rotated 90 degrees about the Earth's axis, with a 0.1 s lead so that the two ranges are not identical. The cyclic prefix limits how large that lead can be, at 4.69 µs for 15 kHz. A 0.25 s lead works out at ±5.77 µs, which puts the neighbour's PRS outside the prefix, and the UE then received it 39 dB down.
Verifying a run
cd ~/ntn/OCUDU-RAN
# the fix against the truth, and the Release 18 verdict on the UE's claim
scripts/ntn_fix_check.py --ue-conf ~/ntn/OAI-RAN-rfsim/targets/PROJECTS/GENERIC-NR-5GC/CONF/ue.ntn.leo.rfsim.conf
# every round against the round trip the gNB actually emulated
sudo scripts/ntn_multirtt_check.py /tmp/gnb_dualsat_sat1.log /tmp/gnb_dualsat_sat2.log
ntn_multirtt_check.py shows whether a disagreement comes from the measurement or from the model. For each satellite it reports the LMF's range against the emulated ground truth, and the orbit model against that same ground truth:
417 Multi-RTT rounds matched to the gNB's emulated round trip
RTT - truth: mean +0.058 us sd 1.061 us => range error mean +9 m, sd 159 m
aligned to the SRS's UL subframe: mean +0.025 us sd 0.413 us => range error mean +4 m, sd 62 m
LMF solver input, satellite 0: range - truth +0.2 m sd 40.0 m; orbit model - emulated truth +13.4 m sd 0.1 m
The two halves refer to different uplink subframes. The gNB's half refers to the SRS, and the UE's half to the subframe it transmits after the PRS, so the script moves both onto one subframe using the rate at which the round trip is changing. Without that correction the pair is 9 to 24 ms apart, which at 46 µs/s of drift gave a 0.8 µs bias, or 240 m of range.
Live tracker
~/ntn/OCUDU-RAN/scripts/leo_tracker.py /tmp/gnb_dualsat_sat1.log /tmp/gnb_dualsat_sat2.log \
--ue /tmp/ue_dualsat.log --port 8500
This serves a live page on http://127.0.0.1:8500. It shows a to-scale side view of every satellite, elevation against time, the measurement tiles (gNB Rx-Tx, UL-RTOA and DL-PRS ToA per TRP), and the fix itself. Each round's range is drawn as a circle of possible positions around the satellite's ground point, in an azimuthal equidistant projection, so the circles can be seen converging on the UE. Supply every gNB log; with a single log the tracker shows only one satellite.
lmf.num_gnb must match the number of gNBs in the scenario
Scenarios 1 and 2 run one gNB, and scenario 3 runs two. If num_gnb is left at 2 after a two-satellite run, every single-gNB scenario fails in a way that looks like a positioning bug but is not:
[error] trp information request: timeout after 10000ms waiting for 2 gnb responses, but received: 1
The LMF gives up on the location session before it ever asks for a measurement, so the tracker shows no Multi-RTT rounds, no NRPPa measurement responses and "no SRS measured". DL-PRS reception and LPP capability transfers carry on as normal, because they do not depend on this. The giveaway is that the gNB is receiving SRS, with the tracker's "SRS received" tile at 100%, while the LMF has no measurement responses.
grep -m1 num_gnb ~/oai-cn5g/conf/config.yaml
sudo sed -i 's/^\( *num_gnb:\) *[0-9]*/\1 1/' ~/oai-cn5g/conf/config.yaml
cd ~/oai-cn5g && docker compose restart oai-lmf
run_leo_dualsat_rfsim.sh sets the value to 2 and restores the previous one when it exits. Running the two-satellite scenario by hand leaves it at 2.
The UE must request the slice the gNB advertises
The configs/ntn/*_rfsim_gnb.yml files serve sst 1 / sd 1. If the UE asks for nssai_sst = 1 with no differentiator, it registers successfully and both LPP and DL-PRS work, but no PDU session is ever set up. This looks like an attach failure even though the radio link is healthy. ue.ntn.leo.rfsim.conf already carries the differentiator. For GEO, use ue.ntn.geo.rfsim.slice.conf, which is ue.ntn.geo.rfsim.conf with nssai_sd = 1 added.
Files
| Path | Purpose |
|---|---|
configs/ntn/leo_rfsim_gnb.yml | the LEO link: ephemeris, k_offset, channel emulation |
configs/ntn/leo_nrppa.yml | positioning overlay: SRS, DL-PRS, TRP geo coordinates, 30-bit gNB ID |
configs/ntn/leo_satswitch_xplane.yml | scenario 2: the cross-plane switch satellite |
configs/ntn/leo_dualsat_sat1.yml, leo_dualsat_sat2.yml | scenario 3: two cells, two satellites, one frequency layer |
scripts/lmf_ntn_oam.py | writes the LMF's OAM satellite file from the gNB config |
scripts/run_leo_rfsim.sh, run_leo_satswitch_rfsim.sh, run_leo_dualsat_rfsim.sh | the three scenarios |
scripts/ntn_fix_check.py | scores the LMF's fixes against the truth |
scripts/ntn_multirtt_check.py | scores every round against the emulated round trip |
scripts/leo_tracker.py | the live page |
HANDOFF_POSITIONING.md | the full record, including the negative results |
References
- TS 38.305, NG-RAN positioning: 4.3.11, 5.4.2, 5.4.4, 8.10 (Multi-RTT), 8.12 (DL-TDOA)
- TS 37.355, LPP:
NR-Multi-RTT-MeasList-r16,NR-NTN-UE-RxTxMeasurements-r18,NR-DL-PRS-AssistanceData-r16, 6.4.3 - TS 38.455, NRPPa: TRP Information, SFN Initialisation Time,
Search-window-information - TS 38.211, 7.4.1.7, DL-PRS generation and mapping
- TS 38.215, 5.1.30 UE Rx-Tx, 5.1.46 subframe offset, 5.1.47 DL timing drift
- TS 38.331,
satSwitchWithReSync-r18