Skip to main content

Remote RF

A python API to remotely access signal centric hardware.

Courtesy of Wireless Lab @ UCLA. - Ethan Ge

Prerequisites

  • Python 3.10: This package works in Python 3.10+. If you don’t have Python installed, you can download it from the official Python website.

To check your current Python version, open a terminal and run:

python --version
  • UCLA VPN: Please ensure that you are connected to the UCLA VPN. You can download and configure the VPN client from the following link: UCLA VPN Client Download. If you’re not connected to the VPN, you will not have access to the lab servers.

Installation

Use the package manager pip to install remoterf. It is recommended that you install this package within a virtual environment.

python3 -m venv venv        # Create virtual environment
source venv/bin/activate    # Activate virtual environment

pip install remoterf        # Install remoterf

The package was named remoteRF before 2.2.0. Old code that does import remoteRF or from remoteRF.drivers... import ... still runs, with a one-time warning; change it to remoterf.

NI USRP-2901, Ettus B205-mini, B200-family, and USRP N210

The packaged client natively includes the shared USRP schema for the qualified NI USRP-2901, Ettus Research USRP B205-mini, the broader USB/B200 family, and the Ethernet-connected N210. No custom client driver is required. The B205-mini uses the same native-style API and reports its live 1 RX/1 TX channel topology through remoterf_capabilities.

After reserving a server-local usrp inventory entry, fetch or refresh its Dynamic v2 package with the reservation token:

from remoterf.drivers import ensure_driver

ensure_driver(token="reservation-token")

Both supported initialization forms then use the same generated runtime:

from remoterf.drivers.usrp import uhd

with uhd.usrp.MultiUSRP(token="reservation-token") as usrp:
    print(usrp.remoterf_capabilities)
    usrp.set_rx_rate(1e6)
from remoterf.drivers.usrp import MultiUSRP

usrp = MultiUSRP("reservation-token")
try:
    print(usrp.get_pp_string())
finally:
    usrp.close()

Streaming uses NumPy buffers and native-like UHD value objects. Only the leading region reported by native UHD is changed on a partial receive:

import numpy as np
from remoterf.drivers.usrp import uhd

with uhd.usrp.MultiUSRP(token="reservation-token") as usrp:
    stream_args = uhd.usrp.StreamArgs("fc32", "sc16")
    stream_args.channels = [0]
    with usrp.get_rx_stream(stream_args) as rx:
        samples = np.empty(4096, dtype=np.complex64)
        metadata = uhd.types.RXMetadata()
        count = rx.recv(samples, metadata, 0.25)
        valid_samples = samples[:count]

The generated package includes native-like namespaces, aliases, overload stubs, deterministic close(), context managers, opaque session-bound handles, and typed errors in remoterf.core.v2_errors. Streamers are never silently recreated after a disconnect; reopen a new device session and configure a new stream explicitly.

Methods marked deferred in the fetched schema remain available through generic native dispatch, but are not release-qualified as exact UHD overload parity until the server's UHD 4.10 target-introspection and hardware differential gates pass.

RTL-SDR

RTL-SDR clients are generated from the server's rtl_sdr schema. The machine running client code does not need librtlsdr or PyRtlSdr:

from remoterf.drivers import ensure_driver

token = "reservation-token"
ensure_driver(token=token)

from remoterf.drivers.rtl_sdr import RtlSdr

sdr = RtlSdr(token)
sdr.sample_rate = 2_048_000
sdr.center_freq = 100_000_000
sdr.gain = "auto"
sdr.agc_mode = True

samples = sdr.read_samples(16_384)  # NumPy complex64
raw_iq = sdr.read_bytes(4096)       # packed uint8 I/Q bytes

The server owns the USB handle for its lifetime, so the generated client does not close the physical radio. read_samples and read_bytes are bounded to a 4 MiB response. Call them repeatedly for longer captures. Native asynchronous callbacks are not transported remotely.

Through the RemoteRF Global relay one reply is capped at 768 KiB. A lab on a current Server keeps a larger reply (up to 4 MiB) and the client fetches it in pages; an older lab refuses it with response_too_large, so keep reads there to about 98k complex64 samples (read_samples(65536) is safe). LAN and direct paths are not capped.

When a Tailscale address differs from the IP or DNS identity in the server certificate, keep TLS verification enabled and set the expected identity:

export REMOTERF_ADDR=rrf2.example.ts.net:61005
export REMOTERF_TLS_SERVER_NAME=certificate-name.example

TI mmWave radar

TI radar clients are generated from the server's ti_mmwave schema. Only the machine physically connected to the radar needs pyserial or a CP210x driver. The initial parser profile supports xWR68xx mmWave SDK 3 out-of-box TLV output.

from remoterf.drivers import ensure_driver

token = "reservation-token"
ensure_driver(token=token)

from remoterf.drivers.ti_mmwave import TiMmWave, decode_frame

radar = TiMmWave(token)
print(radar.device_info)
print(radar.query_version())

radar.apply_config(open("profile.cfg").read(), start=True)
try:
    packet = radar.read_frame(timeout=1.0)
    frame = decode_frame(packet)
    print(frame.header.frame_number, frame.points, frame.side_info)
finally:
    radar.stop()

read_frame() returns one complete binary UART packet. Decoding is client-local and retains unknown TLVs as raw bytes, allowing custom firmware to be captured before a dedicated parser profile is added. stream_stats reports dropped frames, queued frames, resynchronization bytes, invalid headers, and reader health. Remote firmware flashing is deliberately unsupported.

If pip install doesn't work, you can clone the source directly from github.

RemoteRF Global v0 / Remote Internet Access

By default, RemoteRF assumes you're on the same network as the RemoteRF Server, and you configure the client with that server's address:

remoterf --config --addr 192.168.1.20:12321

For deployments exposed through the initial RemoteRF Global gateway, the same workflow works with a DNS hostname instead of a LAN IP:

remoterf --config --addr ucla.global.remoterf.net:12321

The client does not know or care whether ucla.global.remoterf.net is a directly reachable RemoteRF Server, a LAN server, or a RemoteRF Global v0 transparent TCP gateway forwarding to a RemoteRF Server elsewhere. From the client's perspective, it's simply a RemoteRF Server endpoint identified by host:port — an IPv4 address, an IPv6 address (bracketed, e.g. [2001:db8::1]:12321), or a normal DNS hostname all work the same way. Certificate bootstrap (host:cert_port) and the gRPC connection (host:port) are both made against whatever hostname you configure — the client stores and uses that hostname as-is and never resolves it to an IP before saving your configuration, so DNS/IP changes on the server side don't require reconfiguration.

TLS note: RemoteRF Global v0 does not weaken certificate verification. The RemoteRF Server's certificate must include the hostname you connect with (e.g. ucla.global.remoterf.net) in its Subject Alternative Name (SAN) for standard TLS hostname validation to succeed — this is a server-side certificate requirement, not a client setting. If you're instead connecting through something like Tailscale, where the address you dial differs from the identity in the certificate, use the existing REMOTERF_TLS_SERVER_NAME override described above; it still requires that name to be present in the certificate.

v0 is intentionally minimal — it does not include RemoteRF Global accounts, deployment discovery/selection, federation, NAT traversal, direct P2P, automatic routing, or global resource IDs. Those are future versions. The client-side software behaves identically whether you point it at a LAN server or at a RemoteRF Global v0 endpoint.

RemoteRF Global v2 Phase-1 development boundary

The disposable Global v1 account-selection and deployment-session client was removed at Gate C. The normal CLI supports direct/native gRPC accounts and clean HTTPS deployment-home accounts; neither path treats a broker identity, username, email, or home token as destination credentials.

Gate G adds permission visibility to the ordinary perms command. It renders HOME-local groups plus current destination-signed federated groups with their contract/version/state and retrieval time. An unreachable destination is explicitly labeled as unsigned transport status, never as an authoritative empty group result. This works when the HOME has zero devices.

Phase 1 still grants no federated device discovery, reservation, lease, session, streaming, RX, TX, or radio access. Production remains unchanged. See deployment accounts and identity boundary.

Metadata

Release files for remoterf 2.6.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for remoterf 2.6.0
File Size Uploaded
remoterf-2.6.0.tar.gz 242.9 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for remoterf 2.6.0
File Interpreter ABI Platform
remoterf-2.6.0-py3-none-any.whl Python 3 none any Details

Total release size: 446.3 kB

Release files / remoterf-2.6.0.tar.gz

Download URL remoterf-2.6.0.tar.gz
Size 242.9 kB
Tags Source
SHA-256 checksum
How to use checksums
624ff225168b9224a314ca3d8be5145c02940b7462e7e6d5f2825f5b1c70bdaa
BLAKE2b-256 checksum
How to use checksums
5db74d22485a9439d5a49bef8a7bad7d7d6f0424955cc62d82c115af3173c1da
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/5.1.0 CPython/3.12.2

Release files / remoterf-2.6.0-py3-none-any.whl

Download URL remoterf-2.6.0-py3-none-any.whl
Size 203.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
43dfd53fa00fbd4b4fac1dde268b450536d6ef4b716e553910190067626a5407
BLAKE2b-256 checksum
How to use checksums
6498bd68292e11387f29af5ec7f22dd72ce7ce935c0d09e3611c6a186b49454c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/5.1.0 CPython/3.12.2

Release history Release notifications | RSS feed

2.11.0

2 release files

2.10.0

1 release file

2.9.0

1 release file

2.7.2

1 release file

2.7.1

1 release file

2.7.0

1 release file

2.6.3

1 release file

2.6.2

2 release files

2.6.1

2 release files

This release

2.6.0 This release

2 release files

2.5.2

2 release files

2.5.1

2 release files

2.5.0

2 release files

2.4.0

2 release files

2.3.1

2 release files

2.3.0

2 release files

2.2.5

2 release files

2.2.4

2 release files

2.2.3

2 release files

2.2.2

2 release files

2.2.1

2 release files

2.2.0

2 release files

2.1.9

2 release files

2.1.8

1 release file

2.1.7

1 release file

2.1.6

1 release file

2.1.5

1 release file

2.1.4

1 release file

2.1.3

1 release file

2.1.2

1 release file

2.1.1

2 release files

2.1.0

2 release files

2.0.15

2 release files

2.0.14

2 release files

2.0.13

2 release files

2.0.12

2 release files

2.0.11

2 release files

2.0.10

2 release files

2.0.9

2 release files

2.0.8

2 release files

2.0.7

2 release files

2.0.4

2 release files

2.0.3

2 release files

2.0.2

2 release files

2.0.1

2 release files

2.0.0

2 release files

1.1.8

2 release files

1.1.7

2 release files

1.1.6

2 release files

1.1.5

2 release files

1.1.4

2 release files

1.1.3

2 release files

1.1.2

2 release files

1.1.1

2 release files

1.1.0

2 release files

1.0.10

1 release file

1.0.9

2 release files

1.0.8

2 release files

1.0.7

2 release files

1.0.6

2 release files

1.0.5

2 release files

1.0.4

2 release files

0.0.6

2 release files

0.0.5

2 release files

0.0.4

2 release files

0.0.3

2 release files

0.0.2

2 release files

0.0.1

2 release files

0.0.0

1 release file

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page