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.
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 buildin 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 buildin 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_profileon 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 buildin 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 inboxbuffer_size: default minimum size of a stream buffer in bytesstack_size: stack size (in bytes) for all threadsslab_reserved: number of items a Slab buffer copies into the next bufferlog_level: one ofoff,info,warn,error,debug, ortracectrlport_enable: whether control port should be enabled (trueorfalse)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
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
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
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.
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 defaulttracing_max_level_debugandtracing_release_max_level_infoare enabledaaronia_http: drivers for Aaronia HTTP servers, usable through Seifyaudio: read/write audio files and interface speakers/micburn: buffers using Burn tensorsflow_scheduler: enable the Flow Schedulerhackrf: 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 abstractionseify_dummy: enable dummy driver for Seify for use in unit testssoapy: enable SoapySDR driver for Seifytracing_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 blockswgpu: enable WGPU buffers and blockszeromq: enable ZeroMQ source and sinkzynq: 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
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.