Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/EttusResearch/uhd/llms.txt

Use this file to discover all available pages before exploring further.

UHD provides a Python API that closely mirrors the C++ multi_usrp interface. The bindings are generated with PyBind11 and expose all major hardware-control functions under PEP 8–compatible names. The Python API is particularly convenient for interactive work, rapid prototyping, post-processing scripts, and Jupyter Notebook environments.
The Python API mirrors the C++ API in both behavior and semantics. The C++ reference documentation applies directly — simply convert snake_case C++ names to CamelCase class names as described below.

Installation

Enable ENABLE_PYTHON_API when configuring UHD from source:
cmake -DENABLE_PYTHON_API=ON \
      -DPYTHON_INCLUDE_DIR=$(python3 -c "import sysconfig; print(sysconfig.get_path('include'))") \
      ..
make -j$(nproc)
sudo make install
Python header files are required. Install them with:
# Debian/Ubuntu
sudo apt install python3-dev

# Fedora/RHEL
sudo dnf install python3-devel
Python 2 is not supported. UHD 4.0.0 and later require Python 3.

Naming Conventions

Python API names follow PEP 8. The mapping from C++ to Python is consistent:
C++Python
uhd::usrp::multi_usrpuhd.usrp.MultiUSRP
uhd::stream_args_tuhd.usrp.StreamArgs
uhd::rx_metadata_tuhd.types.RXMetadata
uhd::tx_metadata_tuhd.types.TXMetadata
uhd::stream_cmd_tuhd.types.StreamCMD
uhd::stream_cmd_t::stream_mode_tuhd.types.StreamMode
uhd::time_spec_tuhd.types.TimeSpec
uhd::tune_request_tuhd.types.TuneRequest
Method names remain snake_case (e.g., set_rx_freq, get_rx_gain) and are identical to their C++ counterparts.

uhd.usrp.MultiUSRP

The central class for hardware control. Wraps uhd::usrp::multi_usrp.
import uhd

# Open device (empty string = auto-detect)
usrp = uhd.usrp.MultiUSRP("")

# Target a specific device
usrp = uhd.usrp.MultiUSRP("type=b200")
usrp = uhd.usrp.MultiUSRP("addr=192.168.10.2")
All configuration methods from C++ are available with identical signatures:
usrp.set_rx_rate(1e6)
usrp.set_rx_freq(uhd.types.TuneRequest(2.4e9), 0)
usrp.set_rx_gain(40.0, 0)
usrp.set_rx_bandwidth(1e6, 0)
usrp.set_clock_source("internal")
usrp.set_time_source("internal")

actual_freq = usrp.get_rx_freq(0)
actual_gain = usrp.get_rx_gain(0)

uhd.usrp.StreamArgs

import uhd

# Create stream args (cpu_format, otw_format)
stream_args = uhd.usrp.StreamArgs("fc32", "sc16")
stream_args.channels = [0]
stream_args.args     = ""          # optional key=val string

rx_stream = usrp.get_rx_stream(stream_args)
tx_stream = usrp.get_tx_stream(stream_args)

uhd.types.RXMetadata

Returned by rx_stream.recv(). Provides timing, error, and burst information.
md = uhd.types.RXMetadata()

# After recv():
if md.error_code != uhd.types.RXMetadataErrorCode.none:
    print("RX error:", md.strerror())

if md.has_time_spec:
    print("Sample timestamp:", md.time_spec.get_real_secs(), "s")

RX Error Codes

Python EnumDescription
RXMetadataErrorCode.noneNo error
RXMetadataErrorCode.timeoutNo data received within timeout
RXMetadataErrorCode.late_commandCommand issued in the past
RXMetadataErrorCode.broken_chainExpected follow-on command missing
RXMetadataErrorCode.overflowFIFO overflowed or packet dropped
RXMetadataErrorCode.alignmentMulti-channel time alignment failed
RXMetadataErrorCode.bad_packetReceived packet could not be parsed

uhd.types.TXMetadata

Passed to tx_stream.send().
md = uhd.types.TXMetadata()
md.has_time_spec  = False
md.start_of_burst = True
md.end_of_burst   = True

uhd.types.StreamCMD and StreamMode

Used to start and stop reception.
import uhd

# Start continuous streaming
stream_cmd = uhd.types.StreamCMD(uhd.types.StreamMode.start_cont)
stream_cmd.stream_now = True
rx_stream.issue_stream_cmd(stream_cmd)

# Stop
stop_cmd = uhd.types.StreamCMD(uhd.types.StreamMode.stop_cont)
rx_stream.issue_stream_cmd(stop_cmd)

# Fixed number of samples, timed start
timed_cmd = uhd.types.StreamCMD(uhd.types.StreamMode.num_done)
timed_cmd.num_samps  = 10000
timed_cmd.stream_now = False
timed_cmd.time_spec  = uhd.types.TimeSpec(2.0)
rx_stream.issue_stream_cmd(timed_cmd)
Python EnumC++ EquivalentDescription
StreamMode.start_contSTREAM_MODE_START_CONTINUOUSStream indefinitely
StreamMode.stop_contSTREAM_MODE_STOP_CONTINUOUSStop continuous stream
StreamMode.num_doneSTREAM_MODE_NUM_SAMPS_AND_DONEStream N samples and stop
StreamMode.num_moreSTREAM_MODE_NUM_SAMPS_AND_MOREStream N samples, expect more

Convenience Methods (Python-only)

These helper methods are not available in the C++ API and are unique to the Python bindings. They are optimized for one-shot data acquisition and transmission.

recv_num_samps()

Receive a fixed number of samples and return a NumPy array.
import uhd
import numpy as np

usrp = uhd.usrp.MultiUSRP("type=b200")

samps = usrp.recv_num_samps(
    num_samps  = int(1e6),   # 1M samples
    freq       = 2.4e9,      # 2.4 GHz
    rate       = 1e6,        # 1 Msps
    channels   = [0],        # channel 0
    gain       = 40,         # 40 dB
)
# samps is a numpy array of shape (1, 1_000_000) with dtype complex64
samps.tofile("iq_samples.dat")

send_waveform()

Transmit a pre-built NumPy array as a single burst.
import uhd
import numpy as np

usrp = uhd.usrp.MultiUSRP("type=b200")

# Build a 100 kHz tone
rate = 1e6
N    = int(rate)
t    = np.arange(N) / rate
waveform = np.exp(1j * 2 * np.pi * 100e3 * t).astype(np.complex64)

usrp.send_waveform(
    waveform_proto = waveform,
    duration       = 1.0,      # transmit for 1 second
    freq           = 915e6,
    rate           = rate,
    channels       = [0],
    gain           = 60,
)

Full RX Streaming Example

import uhd
import numpy as np

def main():
    # 1. Open device and configure
    usrp = uhd.usrp.MultiUSRP("type=b200")
    usrp.set_rx_rate(1e6)
    usrp.set_rx_freq(uhd.types.TuneRequest(915e6), 0)
    usrp.set_rx_gain(40, 0)
    usrp.set_rx_bandwidth(1e6, 0)

    # 2. Create streamer
    st_args = uhd.usrp.StreamArgs("fc32", "sc16")
    st_args.channels = [0]
    rx_stream = usrp.get_rx_stream(st_args)

    # 3. Allocate buffer and issue stream command
    max_samps = rx_stream.get_max_num_samps()
    buf = np.zeros(max_samps, dtype=np.complex64)
    metadata = uhd.types.RXMetadata()

    cmd = uhd.types.StreamCMD(uhd.types.StreamMode.start_cont)
    cmd.stream_now = True
    rx_stream.issue_stream_cmd(cmd)

    # 4. Receive loop
    received = []
    for _ in range(100):
        n = rx_stream.recv(buf, metadata)
        if metadata.error_code != uhd.types.RXMetadataErrorCode.none:
            print("Error:", metadata.strerror())
            break
        received.append(buf[:n].copy())

    # 5. Stop streaming
    stop = uhd.types.StreamCMD(uhd.types.StreamMode.stop_cont)
    rx_stream.issue_stream_cmd(stop)

    samples = np.concatenate(received)
    print(f"Total samples received: {len(samples)}")
    return samples

if __name__ == "__main__":
    main()

Full TX Streaming Example

import uhd
import numpy as np

def transmit_tone(freq_hz=915e6, tone_offset=100e3, rate=1e6, gain=60, duration=5.0):
    usrp = uhd.usrp.MultiUSRP("type=b200")
    usrp.set_tx_rate(rate)
    usrp.set_tx_freq(uhd.types.TuneRequest(freq_hz), 0)
    usrp.set_tx_gain(gain, 0)

    st_args = uhd.usrp.StreamArgs("fc32", "sc16")
    st_args.channels = [0]
    tx_stream = usrp.get_tx_stream(st_args)

    # Build one period of a tone
    max_samps = tx_stream.get_max_num_samps()
    t = np.arange(max_samps) / rate
    buf = np.exp(1j * 2 * np.pi * tone_offset * t).astype(np.complex64)

    # Send in a loop
    metadata = uhd.types.TXMetadata()
    metadata.start_of_burst = True
    metadata.end_of_burst   = False
    metadata.has_time_spec  = False

    import time
    end_time = time.time() + duration
    while time.time() < end_time:
        tx_stream.send(buf, metadata)
        metadata.start_of_burst = False

    # End-of-burst marker
    eob_md = uhd.types.TXMetadata()
    eob_md.end_of_burst = True
    tx_stream.send(np.zeros(1, dtype=np.complex64), eob_md)

if __name__ == "__main__":
    transmit_tone()

Thread Safety and the GIL

The following UHD Python API functions release the Python Global Interpreter Lock (GIL) during their execution for performance:
  • rx_streamer.recv()
  • tx_streamer.send()
  • tx_streamer.recv_async_msg()
  • uhd.find()
Because these functions access Python objects (NumPy buffers) without holding the GIL, you must ensure that no other Python thread accesses those buffers concurrently. In multi-threaded applications, use a threading.Lock or other synchronization primitive to protect shared buffers.
In single-threaded applications, no additional synchronization is required.

Build docs developers (and LLMs) love