Agent Skills: GNU Radio SDR Skill

GNU Radio SDR toolkit for signal processing flowgraphs with Python blocks

UncategorizedID: plurigrid/asi/gnu-radio

Install this agent skill to your local

pnpm dlx add-skill https://github.com/plurigrid/asi/tree/HEAD/skills/gnu-radio

Skill Files

Browse the full folder contents for gnu-radio.

Download Skill

Loading file tree…

skills/gnu-radio/SKILL.md

Skill Metadata

Name
gnu-radio
Description
GNU Radio SDR toolkit for signal processing flowgraphs with Python blocks

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.