Architecture
This page explains how gr-satellites works internally: how the gr_satellites command turns a SatYAML file into a running GNU Radio flowgraph, how frames flow from the antenna to the data sinks, and how options and metadata are passed along. It is meant for contributors and for users who want to understand why the tool behaves as it does. The core of it is python/core/gr_satellites_flowgraph.py and apps/gr_satellites.
Layers of the Project
| Layer | Where | Role |
|---|---|---|
| Command line tool | apps/gr_satellites |
Argument parsing, input sources, top block. |
| Core flowgraph | python/core/gr_satellites_flowgraph.py |
Builds the decoder from a SatYAML file. |
| SatYAML files | python/satyaml/*.yml |
One description per satellite. |
| Components | python/components/ |
Demodulators, deframers, transports, data sinks, data sources. |
| Hierarchical helpers | python/hier/ |
Reusable sub-flowgraphs: syncword-to-PDU, CCSDS descrambler and Viterbi, PN9 scramblers, RMS AGC. |
| Low-level blocks | lib/ (C++), python/*.py |
FEC decoders (Reed-Solomon, Viterbi, Golay, RA), HDLC, KISS, NRZ-I, CRCs, submitters... |
| Telemetry and file formats | python/telemetry/, python/filereceiver/ |
construct definitions and file reassembly. |
| GRC definitions | grc/ |
YAML block definitions for GNU Radio Companion. |
From Command Line to Flowgraph
When you run gr_satellites FUNcube-1 --wavfile ao73.wav:
- Early exits. If the first argument is
--versionor--list_satellites, it is handled and the program exits. If there are no arguments or the first starts with-, the usage is printed (status 1). - Satellite resolution.
sys.argv[1]becomes{'file': ...},{'norad': ...}or{'name': ...}andyamlfiles.open_satyaml()loads the file (by path, or by scanning all bundled files). - Option registration.
gr_satellites_flowgraph.add_options()creates thedemodulation,deframinganddata sinkargument groups and calls the class methodadd_options()of every component referenced by the SatYAML file: one per data entry type, and one demodulator and deframer per transmitter. The parser usesconflict_handler='resolve', so a repeated option replaces the earlier one. - Parsing. If
--satcfgis present, the NORAD ID's line fromsat.cfgis prepended. Arguments are parsed strictly, or withparse_known_args()under--ignore_unknown_args.check_options()enforces--samp_ratewhere needed. - Configuration.
open_config()loads~/.gr_satellites/config.ini, creating it if missing. - Input.
setup_input()creates the source blocks for the chosen input (see below). - Sample rate.
--samp_rateif given, else the WAV header rate. - Decoder. A
gr_satellites_flowgraphis created with the options namespace and the config. - Connection. For sample inputs: source → (Throttle if
--throttle) → Multiply Const (--input_gain) → decoder. For--kiss_in: KISS File Source message port → decoderinport. - Run. Signal handlers for SIGINT/SIGTERM are installed;
tb.start()andtb.wait().
Input source construction
| Option | Blocks |
|---|---|
--wavfile |
WAV File Source; with --iq, Float To Complex from channels 0 and 1. |
--rawfile |
File Source of float or complex. |
--rawint16 |
File Source of short → Short To Float (scale 32767) → with --iq, Deinterleave + Float To Complex. |
--audio |
Audio Source (with --iq, two channels to Float To Complex). |
--udp |
Network UDP Source (payload 1472 bytes) of short (then as --rawint16) or, with --udp_raw, float/complex. |
--sigmf |
Metadata checks, then the --rawint16 path with IQ forced. |
--kiss_in |
kiss_file_source (File Source → KISS to PDU). |
Inside gr_satellites_flowgraph
The decoder is a hierarchical block. Its constructor:
- Parses string options (GRC case) with a parser built by
add_options(). - Loads the SatYAML file.
- In GRC mode (
grc_block=True), registers theoutmessage port and skips all data sinks. - Otherwise builds the data sinks:
- one per
dataentry (_init_datasink), except telemetry andunknownentries when--hexdumpis set; - the additional data sinks (
_init_additional_datasinks): KISS file, KISS server, ZMQ PUB, telemetry submitters (SatNOGS plustelemetry_servers, if enabled byGR_SATELLITES_SUBMIT_TLMorsubmit_tlm), and the hex dump sink for--hexdump; - one transport per
transportsentry (_init_transport), connected to its data sinks unless--hexdump.
- one per
- For PDU input, connects the
inport to all data sinks and additional data sinks. Transports are not used. - For sample input, for each transmitter (
_init_demodulator_deframer):- picks the demodulator class from
_demodulator_hooks[modulation], passingbaudrate,samp_rate,iq,dump_path, the options, and anydeviation,fm_deviation,af_carrierfrom the SatYAML entry; - picks the deframer class from
_deframer_hooks[framing], passing the options and anyframe size,precoding,RS basis,RS interleaving,convolutional,scrambler,callsign(translated to Python argument names); - connects input → demodulator → deframer;
- adds a
pdu_add_metatagger that insertstransmitter: <name>into each PDU's metadata; - connects the tagger to the
outport (GRC mode), or to all additional data sinks, the transmitter'sdatasinks and itstransports; - for each
additional_dataentry, connects that deframer port through its own tagger to the named data sink only.
- picks the demodulator class from
The hooks are dictionaries of classes. Variants are created with set_options(), which derives a class with some constructor arguments fixed (for example 'AX.25 G3RUH' is ax25_deframer with g3ruh_scrambler=True, and 'DBPSK' is bpsk_demodulator with differential=True).
Frame flow for a satellite with a transport
┌─► KISS file / KISS server / ZMQ / submitters / hexdump
samples ─► demod ─► deframer ─► tagger ─┤
├─► data sinks listed in transmitter 'data'
└─► transport ─► data sinks listed in transport 'data'
Two consequences follow, both visible to users:
- KISS output, ZMQ, hex dump and submission always see deframer output, never transport output.
- With
--kiss_in, frames reach the data sinks without passing through transports.
Options Flow
All components share one argparse namespace in the command line tool. Each component accesses self.options.<name>, and explicit constructor arguments (from SatYAML fields or from GRC parameters) take precedence over the namespace. Components inherit options_block, which, when given None or a string instead of a namespace, builds its own parser from its add_options() and parses the string. That is what makes the Command line options parameter of every GRC block work.
Deframer Building Blocks
Most deframers are built from a few shared pieces:
| Piece | Purpose |
|---|---|
digital.binary_slicer_fb |
Soft symbols to bits. |
sync_to_pdu, sync_to_pdu_packed, sync_to_pdu_soft (python/hier/) |
Correlate a syncword with a bit-error threshold and cut fixed-length packets into PDUs. |
distributed_syncframe_soft |
Distributed syncwords (AO-40 FEC). |
hdlc_deframer |
HDLC flags, bit de-stuffing and FCS check (AX.25 family). |
nrzi_decode, G3RUH descrambler_bb, ccsds_descrambler, PN9 scramblers |
Line coding and scrambling. |
decode_rs, ccsds_viterbi, fec.cc_decoder, Golay, RA and BCH decoders |
Forward error correction. |
crc16_arc, crc_check and others in crcs.py |
Integrity checks. |
New deframers are usually a short hierarchical block chaining these, plus an add_options() class method for their syncword threshold and verbosity flags.