gr-mcp/docs/grc-runtime-communication.md
Ryan Malloy 212832e7e4 feat: expose protocol analysis, OOT export tools; harden for release
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
2026-02-20 13:17:11 -07:00

243 lines
8.5 KiB
Markdown

# 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
1. Add `XMLRPC Server` block to flowgraph
2. GRC variables automatically become `set_X()` / `get_X()` methods
3. Connect with any XML-RPC client (Python, C++, curl, etc.)
#### Client Example
```python
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:**
```ini
[ControlPort]
on = True
edges_list = True
[thrift]
port = 9090
nthreads = 2
```
#### Client Example
```python
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
```bash
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 guide
- `gr-blocks/examples/xmlrpc/` - XML-RPC usage examples
- `docs/usage-manual/(exported from wiki) Performance Counters.txt` - Performance monitoring