Skip to content

Quickstart

The bindings run the supported UE and gNB owners in one Python process from one terminal. Private child processes isolate selected OAI state and failure paths, but the package starts, checks, stops, and reaps them for you.

"""Run a persistent OAI full-PHY link from one Python process."""

from oai_python.bindings.nr.phy.link import NrPhyLink

payload = bytes(range(64))
with NrPhyLink() as link:
    slot = link.step(downlink_payload=payload)
    link.run_slots(100)

(received,) = slot.transmissions
assert received.crc_ok and received.decoded_payload == payload
print(f"decoded {len(received.decoded_payload)} downlink bytes")

Expected output:

decoded 64 downlink bytes

step() advances one explicit slot. run_slots(count) advances as many more slots as requested without returning to Python for every slot. The owner and its OAI state remain alive until close() or the end of the with block.

Choose the right owner

Owner PHY work MAC work Users Best for
NrPhyLink Real reviewed SISO TX/RX chain None Link endpoints Link-level and waveform algorithms
NrPhyStack Real supported PRACH, PDCCH, PDSCH, PUSCH, and PUCCH paths OAI grants and state 1 UE Causal access and traffic through MAC plus PHY
UeGnbLink Abstracted as copied FAPI outcomes Persistent OAI UE/gNB MAC 1โ€“16 UEs Fast scheduler and protocol experiments

Granular classes under oai_python.bindings.nr.phy call individual OAI blocks without requiring any MAC owner. PHY is a first-class mode, not an internal detail of the MAC simulation.

Run UE and gNB together

For a complete one-UE supported stack:

"""Attach one UE and exchange SDUs through the complete PHY stack mode."""

from oai_python.bindings.nr.phy.stack import NrPhyStack


def main() -> None:
    with NrPhyStack(history_limit=64) as simulation:
        simulation.start()
        attached = simulation.run_until_connected(140)
        assert attached.terminal_state == "connected"

        simulation.enqueue_downlink_sdu(0, b"hello from the gNB")
        simulation.enqueue_uplink_sdu(0, b"hello from the UE")
        for _ in range(500):
            simulation.step()
            result = simulation.result()
            if result.downlink_sdus and result.uplink_sdus:
                break
        else:
            raise AssertionError("full-PHY stack traffic did not complete")

    print(f"connected RNTI: {result.connected_rntis[0]:#06x}")
    print(f"downlink SDU: {result.downlink_sdus[-1]!r}")
    print(f"uplink SDU: {result.uplink_sdus[-1]!r}")


if __name__ == "__main__":
    main()

The exact RNTI is deterministic for a given configuration. Successful output reports one connected RNTI and the two decoded payloads:

connected RNTI: 0x....
downlink SDU: b'hello from the gNB'
uplink SDU: b'hello from the UE'

Attach multiple UEs

Use UeGnbLink(ue_count=4) for four UE MAC owners attached to one gNB owner. Each delivery includes both ue_id and rnti, so results cannot silently lose user attribution. Supported ue_count values are 1 through 16.

"""Run four UEs through the abstract MAC/FAPI link mode."""

from oai_python.bindings.nr.mac.link import UeGnbLink


def main() -> None:
    with UeGnbLink(ue_count=4, history_limit=64) as simulation:
        simulation.start()
        attached = simulation.run_until_connected(1_000)
        assert attached.terminal_state == "connected"

        for ue_id in range(4):
            simulation.enqueue_downlink_sdu(ue_id, f"downlink-{ue_id}".encode())
            simulation.enqueue_uplink_sdu(ue_id, f"uplink-{ue_id}".encode())

        for _ in range(1_000):
            simulation.step()
            result = simulation.result()
            if len(result.downlink_sdus) == len(result.uplink_sdus) == 4:
                break
        else:
            raise AssertionError("MAC/FAPI traffic did not complete")

    for delivery in result.deliveries:
        print(
            f"UE {delivery.ue_id} RNTI {delivery.rnti:#06x} "
            f"{delivery.direction} SDU: {delivery.payload!r}"
        )


if __name__ == "__main__":
    main()

Expected output contains eight attributed deliveries: uplink and downlink for each of UE 0 through UE 3.

Cleanup and errors

Prefer context managers. Calling close() twice is safe, and using an owner after close raises a Python exception. Timeouts and child-process failures are reported as exceptions rather than leaving a worker behind. For a support report, run oai-python-diagnose --json and include the redacted output.