GNU Radio SDR Skill
Status: Active Trit: -1 (MINUS - signal consumption/analysis) Seed: 3800 (RF frequency reference) Color: #4A90D9 (radio blue)
"Flowgraphs are signal poems. Blocks are the words."
Overview
GNU Radio is a free, open-source software development toolkit for signal processing. It provides:
- Flowgraphs: Visual signal processing chains
- Blocks: Modular DSP components (filters, modulators, decoders)
- Python Integration: Embedded Python blocks for custom processing
- Hardware Support: RTL-SDR, HackRF, USRP, PlutoSDR, and more
Architecture
┌─────────────────────────────────────────────────────────────────┐
│ GNU RADIO FLOWGRAPH │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ Source │───▶│ Filter │───▶│ Demod │───▶│ Sink │ │
│ │ (SDR) │ │ (LPF) │ │ (FM) │ │ (Audio) │ │
│ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │
│ │ │ │ │ │
│ ▼ ▼ ▼ ▼ │
│ [complex] [complex] [float] [float] │
│ trit: +1 trit: 0 trit: -1 trit: +1 │
│ │
│ GF(3) Conservation: +1 + 0 + (-1) + (+1) ≡ 1 (mod 3) │
└─────────────────────────────────────────────────────────────────┘
Installation
# macOS with Flox
flox install gnuradio
# macOS with Homebrew
brew install gnuradio
# Ubuntu/Debian
sudo apt install gnuradio
# From source (GR4)
git clone https://github.com/gnuradio/gnuradio.git
cd gnuradio && mkdir build && cd build
cmake .. && make -j$(nproc) && sudo make install
Block Types
| Type | Purpose | GF(3) Role | |------|---------|------------| | Source | Generate/receive signals | +1 (PLUS) | | Sync | Sample-rate processing | 0 (ERGODIC) | | Sink | Output/consume signals | -1 (MINUS) | | Hier | Hierarchical sub-flowgraph | 0 (ERGODIC) |
Embedded Python Block
"""
Embedded Python Block - GF(3) Trit Tagger
Tags samples with spatial trit based on frequency
"""
import numpy as np
from gnuradio import gr
class blk(gr.sync_block):
"""GF(3) Trit Tagger for RF signals"""
def __init__(self, center_freq=100e6):
gr.sync_block.__init__(
self,
name='GF3 Trit Tagger',
in_sig=[np.complex64],
out_sig=[np.complex64]
)
self.center_freq = center_freq
self.trit = self._freq_to_trit(center_freq)
def _freq_to_trit(self, freq):
"""Map frequency to GF(3) trit"""
# Frequency bands: VHF=+1, UHF=0, SHF=-1
if freq < 300e6: # VHF
return 1
elif freq < 3e9: # UHF
return 0
else: # SHF+
return -1
def work(self, input_items, output_items):
# Tag with trit metadata
self.add_item_tag(
0, # output port
self.nitems_written(0),
pmt.intern("trit"),
pmt.from_long(self.trit)
)
output_items[0][:] = input_items[0]
return len(output_items[0])
Flowgraph Examples
FM Radio Receiver
#!/usr/bin/env python3
# FM Radio with GF(3) tagging
from gnuradio import gr, blocks, analog, audio, filter
from gnuradio.filter import firdes
import osmosdr
class fm_radio(gr.top_block):
def __init__(self, freq=100.1e6):
gr.top_block.__init__(self, "FM Radio")
# Source: RTL-SDR (+1 trit - generation)
self.source = osmosdr.source(args="rtl=0")
self.source.set_sample_rate(2.4e6)
self.source.set_center_freq(freq)
self.source.set_gain(40)
# Filter: Low-pass (0 trit - processing)
self.lpf = filter.fir_filter_ccf(
10, # decimation
firdes.low_pass(1, 2.4e6, 100e3, 10e3)
)
# Demodulator: WBFM (-1 trit - extraction)
self.demod = analog.wbfm_receive(
quad_rate=240e3,
audio_decimation=5
)
# Sink: Audio (+1 trit - output)
self.audio_sink = audio.sink(48000)
# Connect flowgraph
self.connect(self.source, self.lpf, self.demod, self.audio_sink)
if __name__ == '__main__':
tb = fm_radio(freq=100.1e6)
tb.start()
input('Press Enter to stop...')
tb.stop()
tb.wait()
Spectrum Analyzer
#!/usr/bin/env python3
# Spectrum analyzer with WebSocket output
from gnuradio import gr, blocks, fft
from gnuradio.fft import window
import numpy as np
import json
class spectrum_analyzer(gr.top_block):
def __init__(self, center_freq=100e6, samp_rate=2.4e6):
gr.top_block.__init__(self, "Spectrum Analyzer")
self.fft_size = 1024
# Source
self.source = osmosdr.source(args="rtl=0")
self.source.set_sample_rate(samp_rate)
self.source.set_center_freq(center_freq)
# FFT
self.fft = fft.fft_vcc(
self.fft_size,
True, # forward
window.blackmanharris(self.fft_size),
True # shift
)
# Stream to vector
self.s2v = blocks.stream_to_vector(
gr.sizeof_gr_complex,
self.fft_size
)
# Magnitude squared
self.mag = blocks.complex_to_mag_squared(self.fft_size)
# Python sink for WebSocket
self.sink = blocks.probe_signal_vf(self.fft_size)
self.connect(self.source, self.s2v, self.fft, self.mag, self.sink)
def get_spectrum(self):
"""Get current spectrum as JSON"""
data = self.sink.level()
return json.dumps({
'spectrum': data.tolist(),
'trit': 0, # ERGODIC - measurement
'fft_size': self.fft_size
})
Integration with Muchas Radio
# muchas_radio_sdr.py
# Bridge GNU Radio to MPD streaming
from gnuradio import gr, audio
import subprocess
import os
class RadioToMPD(gr.top_block):
"""Stream GNU Radio audio to MPD via FIFO"""
def __init__(self, samp_rate=48000):
gr.top_block.__init__(self, "Radio to MPD")
# Create FIFO for MPD input
self.fifo_path = "/tmp/gnuradio_audio.fifo"
if not os.path.exists(self.fifo_path):
os.mkfifo(self.fifo_path)
# Audio source (from demodulator)
self.audio_source = audio.source(samp_rate)
# File sink to FIFO
self.file_sink = blocks.file_sink(
gr.sizeof_float,
self.fifo_path,
False # don't append
)
self.connect(self.audio_source, self.file_sink)
def start_mpd_stream(self):
"""Tell MPD to play from FIFO"""
subprocess.run([
"mpc", "add", f"file://{self.fifo_path}"
])
subprocess.run(["mpc", "play"])
GF(3) Signal Triads
┌─────────────────────────────────────────────────────────────────┐
│ GF(3) SIGNAL TRIADS │
├─────────────────────────────────────────────────────────────────┤
│ │
│ RF Triad: │
│ Source (+1) ⊗ Channel (0) ⊗ Sink (-1) = 0 ✓ │
│ │
│ Frequency Triad: │
│ VHF (+1) ⊗ UHF (0) ⊗ SHF (-1) = 0 ✓ │
│ │
│ Processing Triad: │
│ Modulate (+1) ⊗ Filter (0) ⊗ Demodulate (-1) = 0 ✓ │
│ │
│ Audio Triad: │
│ Capture (+1) ⊗ Process (0) ⊗ Playback (-1) = 0 ✓ │
│ │
└─────────────────────────────────────────────────────────────────┘
Hardware Support
| Device | Interface | Freq Range | Sample Rate | |--------|-----------|------------|-------------| | RTL-SDR | USB | 24-1766 MHz | 2.4 MS/s | | HackRF | USB | 1-6000 MHz | 20 MS/s | | USRP | USB/Eth | DC-6 GHz | 200 MS/s | | PlutoSDR | USB | 70-6000 MHz | 61.44 MS/s | | LimeSDR | USB | 100k-3.8 GHz | 61.44 MS/s |
Commands
# Launch GNU Radio Companion (GUI)
gnuradio-companion
# Run flowgraph from command line
python3 my_flowgraph.py
# List available blocks
gr_modtool info
# Create new OOT module
gr_modtool newmod my_blocks
# Add Python block to module
gr_modtool add -t sync -l python my_block
# Install OOT module
cd build && cmake .. && make && sudo make install
GR4 (Next Generation)
GNU Radio 4.0 brings:
- C++20 with concepts and ranges
- Reflection-based block definitions
- Graph-based scheduling
- Better performance via SIMD
// GR4 Block example
template<typename T>
struct Multiply : gr::Block<Multiply<T>> {
gr::PortIn<T> in;
gr::PortOut<T> out;
float factor = 1.0f;
gr::work::Status processBulk(auto& ins, auto& outs) {
std::ranges::transform(ins, outs.begin(),
[this](auto x) { return x * factor; });
return gr::work::Status::OK;
}
};
Related Skills
| Skill | Connection |
|-------|------------|
| muchas-radio | Audio streaming integration |
| whitehole-audio | Audio loopback driver |
| gay-mcp | GF(3) trit coloring |
| pluscode-zig | Location-based frequency allocation |
| fokker-planck-analyzer | Signal diffusion analysis |
Environment Variables
# GNU Radio paths
GR_CONF_CONTROLPORT_ON=True
GR_CONF_CONTROLPORT_EDGES_LIST=True
PYTHONPATH=/usr/local/lib/python3/dist-packages:$PYTHONPATH
# SDR device
SOAPY_SDR_ROOT=/usr/local
RTL_TCP_SERVER=127.0.0.1:1234
# GF(3) config
GR_TRIT_TAGGING=true
GR_TRIT_SOURCE=pluscode
Skill Name: gnu-radio Type: SDR / Signal Processing / Python Trit: -1 (MINUS - signal consumption) Seed: 3800 Key Insight: Flowgraphs are compositional signal processing with GF(3) conservation Repository: github.com/gnuradio/gnuradio
Autopoietic Marginalia
The interaction IS the skill improving itself.
Every use of this skill is an opportunity for worlding:
- MEMORY (-1): Record what was learned
- REMEMBERING (0): Connect patterns to other skills
- WORLDING (+1): Evolve the skill based on use
Add Interaction Exemplars here as the skill is used.