Wire up protocol analysis (parse_protocol_spec, generate_decoder_chain, get_missing_oot_modules), signal analysis (analyze_iq_file), and OOT export (generate_oot_skeleton, export_block_to_oot, export_from_flowgraph) as MCP tools with integration tests. Security fixes from Hamilton review: - Remove `from __future__ import annotations` from tool registration files (breaks FastMCP schema generation) - Add blocklist guard to evaluate_expression (was unsandboxed eval) - Replace string interpolation with base64 encoding in Docker test harness (prevents code injection) - Add try/finally cleanup for temp files and Docker containers - Replace assert with proper ValueError in flowgraph block creation - Log OOT auto-discovery failures instead of swallowing silently Packaging: - Move entry point to src/gnuradio_mcp/server.py with script entry point (uv run gnuradio-mcp) - Add PyPI metadata (authors, license, classifiers, urls) - Add MIT LICENSE file - Rewrite README for current feature set (80+ tools) - Document single-session limitation
8.5 KiB
GRC Runtime Communication with Flowgraph Processes
This document explains how GNU Radio Companion (GRC) communicates with running flowgraph processes and the two mechanisms available for runtime control.
Key Insight: GRC is a Code Generator, Not a Runtime Controller
GRC runs flowgraphs as completely separate subprocesses via subprocess.Popen(). It does not have built-in runtime control capabilities.
+--------------------+ subprocess.Popen() +---------------------+
| GNU Radio | -----------------------------------> | Generated Python |
| Companion (GRC) | | Flowgraph Script |
| | <----------------------------------- | |
| (Qt/GTK GUI) | stdout/stderr pipe | (gr.top_block) |
+--------------------+ +---------------------+
The generated Python script runs independently. To control parameters at runtime, you must use one of the two communication mechanisms described below.
GRC Execution Flow
.grc file (YAML)
|
v Platform.load_and_generate_flow_graph()
Generator (Mako templates)
|
v generator.write()
Python script (with set_*/get_* methods)
|
v ExecFlowGraphThread -> subprocess.Popen()
Running flowgraph process
|
v stdout/stderr piped back to GRC console
Key GRC Execution Files
| File | Purpose |
|---|---|
grc/main.py |
Entry point |
grc/gui_qt/components/executor.py |
ExecFlowGraphThread subprocess launcher |
grc/core/platform.py |
Block registry, flowgraph loading |
grc/core/generator/Generator.py |
Generator factory |
grc/workflows/common.py |
Base generator classes |
grc/workflows/python_nogui/flow_graph_nogui.py.mako |
Mako template for Python |
Two Runtime Control Mechanisms
1. XML-RPC Server (Simple, HTTP-based)
A block-based approach - add the xmlrpc_server block to your flowgraph to expose GRC variables over HTTP.
| Aspect | Details |
|---|---|
| Protocol | HTTP (XML-RPC) |
| Default Port | 8080 |
| Setup | Add XMLRPC Server block to flowgraph |
| Naming | set_varname() / get_varname() |
| Type Support | Basic Python types |
How It Works
- Add
XMLRPC Serverblock to flowgraph - GRC variables automatically become
set_X()/get_X()methods - Connect with any XML-RPC client (Python, C++, curl, etc.)
Client Example
import xmlrpc.client
# Connect to running flowgraph
server = xmlrpc.client.ServerProxy('http://localhost:8080')
# Read and write variables
print(server.get_freq()) # Read a variable
server.set_freq(145.5e6) # Set a variable
# Flowgraph control
server.stop() # Stop flowgraph
server.start() # Start flowgraph
server.lock() # Lock flowgraph for modifications
server.unlock() # Unlock flowgraph
Key Files
| File | Purpose |
|---|---|
gr-blocks/grc/xmlrpc_server.block.yml |
Server block definition |
gr-blocks/grc/xmlrpc_client.block.yml |
Client block definition |
gr-blocks/examples/xmlrpc/ |
Example flowgraphs |
2. ControlPort/Thrift (Advanced, Binary)
A configuration-based approach - blocks register their parameters via setup_rpc() in C++ code. See docs/doxygen/other/ctrlport.dox for detailed block implementation.
| Aspect | Details |
|---|---|
| Protocol | Thrift Binary TCP |
| Default Port | 9090 |
| Setup | Enable in config, blocks call setup_rpc() |
| Naming | block_alias::varname |
| Type Support | Rich (complex, vectors, PMT types) |
| Metadata | Units, min/max, display hints |
Architecture
+------------------------------------------------------------------+
| Running Flowgraph Process |
+-----------------------------------------------------------------+
| Block A Block B |
| +------------------+ +------------------+ |
| | setup_rpc() { | | setup_rpc() { | |
| | add_rpc_var( | | add_rpc_var( | |
| | "gain", | | "freq", | |
| | &get_gain, | | &get_freq, | |
| | &set_gain); | | &set_freq); | |
| | } | | } | |
| +--------+---------+ +--------+---------+ |
| | | |
| v v |
| +----------------------------------------------------------+ |
| | rpcserver_thrift (port 9090) | |
| | +-----------------+ +-----------------+ | |
| | | setcallbackmap | | getcallbackmap | | |
| | | "blockA::gain" | | "blockA::gain" | | |
| | | "blockB::freq" | | "blockB::freq" | | |
| | +-----------------+ +-----------------+ | |
| +----------------------------------------------------------+ |
+------------------------------------------------------------------+
^
| Thrift Binary Protocol (TCP)
v
+------------------------------------------------------------------+
| Python Client |
| from gnuradio.ctrlport import GNURadioControlPortClient |
| |
| client = GNURadioControlPortClient(host='localhost', port=9090) |
| knobs = client.getKnobs(['blockA::gain', 'blockB::freq']) |
| client.setKnobs({'blockA::gain': 2.5}) |
+------------------------------------------------------------------+
Enabling ControlPort
~/.gnuradio/config.conf:
[ControlPort]
on = True
edges_list = True
[thrift]
port = 9090
nthreads = 2
Client Example
from gnuradio.ctrlport.GNURadioControlPortClient import GNURadioControlPortClient
# Connect to running flowgraph
client = GNURadioControlPortClient(host='localhost', port=9090)
# Get knobs (read values)
knobs = client.getKnobs(['analog_sig_source_0::frequency'])
print(knobs)
# Set knobs (write values)
client.setKnobs({'analog_sig_source_0::frequency': 1500.0})
# Regex-based retrieval - get all frequency knobs
all_freq_knobs = client.getRe(['.*::frequency'])
# Get metadata (units, min, max, description)
props = client.properties(['analog_sig_source_0::frequency'])
print(props['analog_sig_source_0::frequency'].units)
print(props['analog_sig_source_0::frequency'].min)
GUI Monitoring Tools
- gr-ctrlport-monitor - Real-time variable inspection
- gr-perf-monitorx - Performance profiling visualization
gr-ctrlport-monitor localhost 9090
gr-perf-monitorx localhost 9090
Key Files
| File | Purpose |
|---|---|
gnuradio-runtime/lib/controlport/thrift/gnuradio.thrift |
Thrift IDL definition |
gnuradio-runtime/include/gnuradio/rpcserver_thrift.h |
Server implementation |
gnuradio-runtime/include/gnuradio/rpcregisterhelpers.h |
Registration templates |
gnuradio-runtime/python/gnuradio/ctrlport/GNURadioControlPortClient.py |
Python client |
gnuradio-runtime/python/gnuradio/ctrlport/RPCConnectionThrift.py |
Thrift connection |
Comparison: XML-RPC vs ControlPort
| Feature | XML-RPC | ControlPort/Thrift |
|---|---|---|
| Setup | Add block to flowgraph | Enable in config.conf |
| Protocol | HTTP | Binary TCP |
| Performance | Slower (text-based) | Faster (binary) |
| Type support | Basic Python types | Complex, vectors, PMT |
| Metadata | None | Units, min/max, hints |
| Tooling | Any HTTP client | Specialized monitors |
| Use case | Simple control | Performance monitoring |
When to Use Each
Use XML-RPC when:
- You need quick, simple remote control
- Integration with web applications
- Language-agnostic client access
- Minimal configuration
Use ControlPort when:
- You need performance monitoring
- Working with complex data types
- Block-level control granularity
- Need metadata about parameters
Related Documentation
docs/doxygen/other/ctrlport.dox- Detailed ControlPort block implementation guidegr-blocks/examples/xmlrpc/- XML-RPC usage examplesdocs/usage-manual/(exported from wiki) Performance Counters.txt- Performance monitoring