Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Introduction

FutureSDR is a software-defined radio (SDR) runtime written in Rust with a focus on portability, performance, and developer ergonomics.

Main Features

  • Platform support: FutureSDR runs on Linux, Windows, macOS, Android, and on the web. Support for both native and browser targets allows you to reuse the same signal-processing code across desktop, embedded, and WebAssembly deployments.
  • Accelerators: FutureSDR integrates with accelerators through custom buffers that provide direct access to accelerator memory (e.g., DMA buffers, GPU staging buffers, machine-learning tensors). Developers can implement their own buffers or reuse existing ones for Xilinx Zynq DMA, Vulkan GPU, and Burn, a Rust machine-learning framework.
  • Custom Schedulers: FutureSDR uses an async runtime that schedules data-processing workloads as user-space tasks. This architecture lets you plug in different scheduling strategies to match your latency and throughput goals.

Core Concepts

While FutureSDR’s implementation differs from other SDR frameworks, the core abstractions remain familiar. It supports Blocks that implement stream-based or message-based data processing. These blocks can be combined into a Flowgraph and launched on a Runtime that is driven by a Scheduler.

Installation

Compiling and running FutureSDR applications requires at least a Rust toolchain. The sections below walk you through setting up Rust and the additional tooling needed for building native binaries and the web user interface.

Install Rust

To install Rust, follow the official instructions.

FutureSDR works with both the stable and nightly toolchains. The nightly compiler enables a few performance optimizations and is required when you build or modify the web UI, since it uses Leptos, which provides an ergonomic syntax behind a nightly feature flag.

Info

We recommend using the nightly Rust toolchain.

You can switch to nightly globally:

rustup toolchain install nightly
rustup default nightly

or only for your FutureSDR project:

rustup toolchain install nightly
cd <into your project or FutureSDR>
rustup override set nightly

Web GUI and Web SDR Applications

FutureSDR ships with pre-compiled web UIs, so you can use them without extra tooling. If you want to extend or adapt the web UIs, install the wasm32-unknown-unknown target:

rustup target add wasm32-unknown-unknown

Install Trunk, a build and packaging tool for Rust WebAssembly projects, with Cargo or one of the other options listed in their documentation:

cargo install --locked trunk

Linux (Ubuntu)

  • Clone the FutureSDR repository
    git clone https://github.com/FutureSDR/FutureSDR.git
  • Optionally, install SoapySDR
    sudo apt install -y libsoapysdr-dev soapysdr-module-all soapysdr-tools
  • Check if your setup is working by running cargo build in the FutureSDR directory.

macOS

These instructions assume that you use Homebrew as your package manager.

  • Clone the FutureSDR repository
    git clone https://github.com/FutureSDR/FutureSDR.git
  • Optionally, install SoapySDR
    brew install soapysdr
  • Additional drivers are available in the Pothos Homebrew tap.
  • Check if your setup is working by running cargo build in the FutureSDR directory.

Windows

  • Install Visual Studio C++ Community Edition (required components: Win10 SDK and VC++).

    Visual Studio does not add its binaries and libraries to the PATH. Instead, it offers various terminal environments, configured for a given toolchain. Please use the native toolchain for your system to build FutureSDR, e.g., x64 Native Tools Command Prompt for VS 2022.

For SoapySDR hardware drivers:

  • PothosSDR for pre-built SDR drivers. The installer offers to add the libraries to your PATH. Make sure to check this option.
  • Install bindgen dependencies.
  • Run volk_profile on the command line.

PothosSDR comes with many SoapySDR modules. Some of them require further software and services, which can cause issues when scanning for available devices. If you run into this issue, either (1) use a filter to specify the driver manually or (2) move the problematic library to a backup folder outside the search path. The libraries are, by default, at C:\Program Files\PothosSDR\lib\SoapySDR\modules0.8. If, for example, SDRplay or UHD causes issues, move sdrPlaySupport.dll or uhdSupport.dll to a backup folder.

  • Check if your setup is working by running cargo build in the FutureSDR directory.

Running Flowgraphs

Configuration

FutureSDR offers runtime options that can be configured through a config.toml or environment variables.

It will search for a global user config at ~/.config/futuresdr/config.toml, a project config.toml in the current directory, and environment variables. The user config has the lowest precedence, while environment variables have the highest precedence.

The available options are:

  • queue_size: number of messages that fit into a block’s inbox
  • buffer_size: default minimum size of a stream buffer in bytes
  • stack_size: stack size (in bytes) for all threads
  • slab_reserved: number of items a Slab buffer copies into the next buffer
  • log_level: one of off, info, warn, error, debug, or trace
  • ctrlport_enable: whether control port should be enabled (true or false)
  • ctrlport_bind: endpoint that the control-port web server should bind to (e.g., 127.0.0.1:1337)
  • frontend_path: path to a web UI that is served as the root URL of the control-port server

An example config.toml:

log_level = "debug"
buffer_size = 32768
queue_size = 8192
ctrlport_enable = true
ctrlport_bind = "127.0.0.1:1337"

Alternatively, pass these options through environment variables. Each key uses the prefix FUTURESDR_ and is uppercased:

export FUTURESDR_CTRLPORT_ENABLE="true"
export FUTURESDR_CTRLPORT_BIND="0.0.0.0:1337"

Rust Features

Some examples use Cargo features to selectively enable functionality such as SDR drivers or GPU backends. Check the [features] section in an example’s Cargo.toml for the full list of supported flags.

[features]
default = ["soapy"]
aaronia_http = ["futuresdr/aaronia_http"]
soapy = ["futuresdr/soapy"]

In this example soapy is enabled by default, and the Aaronia HTTP driver can be enabled by adding the corresponding feature.

cargo run --release --bin rx --features=aaronia_http

Disable default features with:

cargo run --release --bin rx --no-default-features

Log and Debug Messages

FutureSDR uses the tracing library for log and debug messages. Applications can set their own handler for log messages, otherwise FutureSDR will set EnvFilter as default handler. If the application uses a custom handler, the logging-related configuration of FutureSDR will not be considered, and you'd have to check with the documentation of the application for information about logging.

If no log handler is set when a flowgraph is launched on a runtime, FutureSDR will set EnvFilter. There are extensive configuration options to configure logging per module through environment variables. Please see the documentation.

Some examples:

# set log level to warn
FUTURESDR_LOG=warn cargo run --bin rx

# disable log messages from lora::frame_sync module
FUTURESDR_LOG=lora::frame_sync=off cargo run --bin rx

# set default log level to info but disable messages from lora::decoder
FUTURESDR_LOG=info,lora::decoder=off cargo run --release --bin rx

Warning

By default, FutureSDR sets feature flags that disable tracing level log messages in debug mode and everything more detailed than info in release mode. This is a compile time filter!

Also, these flags are transitive! If you want more detailed logs in your application, disable default features for the FutureSDR dependency.

[dependencies]
futuresdr = { version = ..., default-features=false, features = ["foo", "bar"] }

Command Line Arguments

Most examples allow passing command line arguments. When running the application with cargo, use -- to separate Cargo’s arguments from the application’s arguments.

To check which arguments are available, pass the -h/--help flag.

$ cargo run --release -- -h
Usage: fm-receiver [OPTIONS]

Options:
  -g, --gain <GAIN>              Gain to apply to the seify source [default: 30]
  -f, --frequency <FREQUENCY>    Center frequency [default: 100000000]
  -r, --rate <RATE>              Sample rate [default: 1000000]
  -a, --args <ARGS>              Seify args [default: ]
      --audio-mult <AUDIO_MULT>  Multiplier for intermedia sample rate
      --audio-rate <AUDIO_RATE>  Audio Rate
  -h, --help                     Print help

Important

When running applications with cargo, use -- to separate command line parameters of cargo and the application.

cargo run --release --bin foo -- --sample_rate 3e6

SDR Device Selection and Configuration

Most example applications support an -a/--argument command line option that is passed to the SDR hardware drivers. The argument can be used to pass additional options, select the hardware driver, or specify the SDR, if more than one is connected.

Driver selection can be necessary in more cases than one might expect. FutureSDR uses Seify as SDR hardware abstraction layer, which usually defaults to using Soapy drivers under the hood. Many distributions ship a bundle of Soapy drivers that include an audio driver, which enumerates your sound card as SDR. You can run SoapySDR --probe to see what is detected.

If Seify selects the wrong device, specify the device argument to select the correct one by defining the driver (e.g., -a soapy_driver=rtlsdr) and optionally the device index (e.g., -a soapy_driver=rtlsdr,index=1) or any other identifier supported by the driver (e.g., serial number, IP address, or USB device ID). See the driver documentation for information about what is supported.

A complete command could be

cargo run --release --bin receiver -- -a soapy_driver=rtlsdr

Important

Seify will forward all arguments to Soapy. Only the driver argument has to be prefixed to soapy_driver to differentiate it from Seify driver selection.

Important

Soapy might select the wrong device even if only one SDR is plugged into your PC. Use the -a/--argument to select the Soapy driver, e.g., -a soapy_driver=rtlsdr.

Flowgraph Interaction

It is possible to interact with a running flowgraph through the control port REST API, which can be used as the base for arbitrary UIs using web technology or any other GUI framework.

REST API

Control port provides a REST API to expose the flowgraph structure and enable remote interaction. It is enabled by default, but you can configure it explicitly through the configuration, for example:

ctrlport_enable = true
ctrlport_bind = "127.0.0.1:1337"

To allow remote hosts to access control port, bind it to a public interface or an unrestricted address:

ctrlport_enable = true
ctrlport_bind = "0.0.0.0:1337"

Alternatively, configure control port through environment variables, which always take precedence:

export FUTURESDR_CTRLPORT_ENABLE="true"
export FUTURESDR_CTRLPORT_BIND="0.0.0.0:1337"

Control port can be accessed with a browser or programmatically (e.g., using curl, the Python requests library, etc.). FutureSDR also provides a support library to ease remote interaction from Rust.

To get a JSON description of the first flowgraph executed on a runtime, open 127.0.0.1:1337/api/fg/0/ in your browser or use curl:

curl http://127.0.0.1:1337/api/fg/0/ | jq
{
  "blocks": [
    {
      "id": 0,
      "type_name": "Encoder",
      "instance_name": "Encoder-0",
      "stream_inputs": [],
      "stream_outputs": [
        "output"
      ],
      "message_inputs": [
        "tx"
      ],
      "message_outputs": [],
      "blocking": false
    },
    {
      "id": 1,
      "type_name": "Mac",
      "instance_name": "Mac-1",
      "stream_inputs": [],
      "stream_outputs": [],
      "message_inputs": [
        "tx"
      ],
      "message_outputs": [
        "tx"
      ],
      "blocking": false
    },
  ],
  "stream_edges": [
    [
      0,
      "output",
      2,
      "input"
    ],
  ],
  "message_edges": [
    [
      1,
      "tx",
      0,
      "tx"
    ],
  ]
}

It is also possible to get information about a particular block.

curl http://127.0.0.1:1337/api/fg/0/block/0/ | jq
{
  "id": 0,
  "type_name": "Encoder",
  "instance_name": "Encoder-0",
  "stream_inputs": [],
  "stream_outputs": [
    "output"
  ],
  "message_inputs": [
    "tx"
  ],
  "message_outputs": [],
  "blocking": false
}

All message handlers of a block are exposed automatically through the REST API. Assuming block 0 is the SDR source or sink, you can set the frequency by posting a JSON-serialized PMT to the corresponding message handler:

curl -X POST -H "Content-Type: application/json" -d '{ "U32": 123 }'  http://127.0.0.1:1337/api/fg/0/block/0/call/freq/

Here are some more examples of serialized PMTs:

{ "U32": 123 }
{ "U64": 5}
{ "F32": 123 }
{ "Bool": true }
{ "VecU64": [ 1, 2, 3] }
"Ok"
"Null"
{ "String": "foo" }

Web UI

FutureSDR comes with a minimal, work-in-progress web UI, implemented in the prophecy crate. It comes pre-compiled at crates/prophecy/dist. When FutureSDR is started with control port enabled, you can specify the frontend_path configuration option to serve a custom frontend at the root path of the control-port URL (e.g., 127.0.0.1:1337).

Using the REST API, it is straightforward to build custom UIs.

  • A web UI served by an independent server
  • A web UI served through FutureSDR control port (see the WLAN and ADS-B examples)
  • A UI using arbitrary technology (GTK, Qt, etc.) running as a separate process (see the Egui example)

Project Creation

To create a Rust crate that uses FutureSDR initialize the crate and add FutureSDR as a dependency.

cargo init my_project
cd my_project

Edit the Cargo.toml to add the dependency. There are several options:

Use a specific version (stable, but code might be outdated due to irregular release cycles)

[dependencies]
futuresdr = { version = "0.0.39" }

Track the main branch (unstable but always up-to-date)

[dependencies]
futuresdr = { git = "https://github.com/FutureSDR/FutureSDR.git", branch = "main" }

Use a specific commit (potentially best of both worlds)

[dependencies]
futuresdr = { git = "https://github.com/FutureSDR/FutureSDR.git", rev = "7afd76c6d768ebc6432e705efe13e73543d33668" }

Use a local working tree (if you work on FutureSDR in parallel)

[dependencies]
futuresdr = { path = "../FutureSDR" }

Features

FutureSDR supports several features that you may want to enable.

  • default: by default tracing_max_level_debug and tracing_release_max_level_info are enabled
  • aaronia_http: drivers for Aaronia HTTP servers, usable through Seify
  • audio: read/write audio files and interface speakers/mic
  • burn: buffers using Burn tensors
  • flow_scheduler: enable the Flow Scheduler
  • hackrf: enable Rust HackRF driver for Seify (unstable, not recommended)
  • rtlsdr: enable Rust RTL SDR driver for Seify (unstable, not recommended)
  • seify: enable Seify SDR hardware abstraction
  • seify_dummy: enable dummy driver for Seify for use in unit tests
  • soapy: enable SoapySDR driver for Seify
  • tracing_max_level_debug: disable tracing messages in debug mode (compile-time filter)
  • tracing_release_max_level_info: disable debug and tracing messages in release mode (compile-time filter)
  • vulkan: enable Vulkan buffers and blocks
  • wgpu: enable WGPU buffers and blocks
  • zeromq: enable ZeroMQ source and sink
  • zynq: enable Xilinx Zynq DMA buffers

For example:

[dependencies]
futuresdr = { version = "0.0.39", default-features = false, features = ["audio", "seify"] }

Minimal Example

To test if everything is working, you can paste the following minimal example in src/main.rs and execute it with cargo run.

use futuresdr::blocks::Head;
use futuresdr::blocks::NullSink;
use futuresdr::blocks::NullSource;
use futuresdr::prelude::*;

fn main() -> Result<()> {
    let mut fg = Flowgraph::new();

    let src = NullSource::<u8>::new();
    let head = Head::<u8>::new(123);
    let snk = NullSink::<u8>::new();

    connect!(fg, src > head > snk);

    Runtime::new().run(fg)?;

    Ok(())
}

Runtime

A FutureSDR Runtime has a Scheduler associated with it and can run one or multiple Flowgraphs.

Running a Flowgraph

In the simplest case, you can construct a runtime with the default scheduler (Smol), execute a flowgraph and wait for its completion.

let mut fg = Flowgraph::new();
// set up the flowgraph

Runtime::new().run(fg)?;

The run() method takes ownership of the flowgraph and returns it after completion.

Starting a Flowgraph

In most cases, you may want to start the flowgraph and continue instead of blocking and waiting for its completion, in which case one would use the start or start_sync methods. The former is for use in async contexts.

let mut fg = Flowgraph::new();
// set up the flowgraph

let rt = Runtime::new();
let let (task_handle, flowgraph_handle) = rt.start_sync(fg)?;

The task_handle can be used to await completion of the flowgraph and getting ownership back afterwards (similar to run()). The flowgraph_handle can be used to interact with the flowgraph (e.g., query its structure or send PMTs to blocks).

Selecting a Scheduler

To use a different scheduler or change its configuration, you can specify it when constructing the runtime.

let mut fg = Flowgraph::new();
// set up the flowgraph

let rt = Runtime::with_scheduler(FlowScheduler::new());
rt.run(fg)?;

Runtime Handle

It is possible to get a RuntimeHandle to interact with the runtime from different contexts (e.g., other threads or closures). The runtime handle can be passed around easily, since it is cloneable and implements the Send trait. Using the handle, it is possible to launch flowgraphs or query the flowgraphs that are currently executed.

let rt = Runtime::new();
let handle = rt.handle();

async_io::block_on(async move {
    let mut fg = Flowgraph::new();
    // set up the flowgraph

    let _ = handle.start(fg).await;
});

Scheduler

Smol

Flow

WebAssembly

Flowgraph

Flowgraph Handle

Block

Buffers

Logging

Warning

By default, FutureSDR sets feature flags that disable tracing level log messages in debug mode and everything more detailed than info in release mode. This is a compile time filter!

Also, these flags are transitive! If you want more detailed logs in your application, disable default features for the FutureSDR dependency.

[dependencies]
futuresdr = { version = ..., default-features=false, features = ["foo", "bar"] }

Tracing macros from prelude.

Custom log handler or default from futuresdr::runtime::init().

Android

WebAssembly

Web UI

Seify

Performance Measurement

Disable logging at compile time with feature flags.