# Соглашения в `.grc` Все флоуграфы репозитория повторяют один и тот же набор решений. Ниже — то, что нужно воспроизвести в новом флоуграфе, чтобы он корректно работал с диспетчером. ## Именование и режим генерации | | Запускаемые флоуграфы | Иерархические блоки | |---|---|---| | Каталог | `generic/`, `satellites/` | `test_flowgraphs/` | | `generate_options` | `no_gui` | `hb` | | `id` | `satnogs_<имя файла>` | `satnogs_<имя файла>` | | Результат сборки | `satnogs_<имя>.py` в `bin/` | `.py` + `.block.yml` в персистентном каталоге GNU Radio | `id` должен совпадать с именем файла: по нему `grcc` называет выходной скрипт, а диспетчер ищет этот скрипт в `PATH`. ## Общий набор параметров Почти каждый запускаемый флоуграф объявляет один и тот же набор блоков `parameter`. Копируйте его целиком из `generic/example_flowgraph.grc` или из ближайшего по смыслу декодера: ``` antenna, bb_freq, bw, dc_removal, decoded_data_file_path, dev_args, enable_iq_dump, file_path, gain, gain_mode, iq_file_path, lo_offset, other_settings, ppm, rigctl_host, rigctl_port, rx_freq, samp_rate_rx, soapy_rx_device, stream_args, tune_args, udp_dump_host, udp_dump_port, waterfall_file_path, zmq_base_port ``` Плюс, по необходимости, `baudrate` и `framing`. `zmq_base_port` (`intx`, 16887) обязателен: диспетчер передаёт его всегда, а `grcc` генерирует `parse_args()`, поэтому флоуграф без этого параметра завершится с кодом 2. Имена обязаны совпадать с аргументами `client_argument_parser()` — см. [](architecture.md#контракт-2-имена-аргументов). ## Доплеровская компенсация Источник сдвига объявляется переменной: ```python variable_rigctl = satnogs.rigctl_doppler_correction.make(rx_freq, Hamlib.RIG_MODEL_NETRIGCTL) ``` Адрес `rigctld` нельзя задать параметром блока — он подставляется фрагментом кода. Блок `snippet` в секции `main_after_init`: ```python self.variable_rigctl.set_param("rig_pathname", str(self.rigctl_host) + ":" + str(self.rigctl_port)) ``` Этот фрагмент дословно скопирован во все флоуграфы. Забыть его — значит получить попытку подключения к адресу по умолчанию. Приёмник настраивается на `rx_freq - lo_offset`, обратный сдвиг делает сам блок `satnogs_doppler_compensation`. ## Децимация и частоты Стандартная формула: ```python audio_samp_rate = 48000 decimation = max(4, satnogs.find_decimation(baudrate, 2, audio_samp_rate)) ``` Для флоуграфов с фазовой манипуляцией добавляется число отсчётов на символ: ```python sps = 4 decimation = max(4, satnogs.find_decimation(baudrate, 2, audio_samp_rate, sps)) ``` Нижняя граница `max(4, …)` обязательна: дальше по тракту встречается целочисленное деление `decimation // 2` и `decimation // sps`, и при малой децимации оно даёт ноль. Именно это чинил коммит «Fix decimation for USP». Выходная частота доплер-компенсации (`out_samp_rate`) различается по семействам: | Семейство | `out_samp_rate` | Примеры | |---|---|---| | Аудио-режимы | `audio_samp_rate` = 48000 | `fm.grc` | | Построчная развёртка | `4*4160*4` = 66560 | `noaa_apt_decoder.grc`, `sstv_pd120_demod.grc` | | Режимы с бодрейтом | `baudrate × decimation` | `fsk_ax25.grc`, `bpsk.grc`, Geoscan, USP | `audio_samp_rate = 48000` объявлен и в APT, и в SSTV_PD120, но уходит только в последний ресемплер аудио (`rrate: audio_samp_rate / (4*4160*4)`). Сама доплер-компенсация идёт на 66560. Эта же частота уходит в водопад, IQ-дамп и UDP-поток. Поэтому **полоса и разрешение водопада зависят от режима** (БПФ везде на 1024 точки: бин от 46.9 Гц при 48 кГц до 195 Гц при 200 кГц у PHASMA), а частоту дискретизации IQ-файла нужно считать самому: в имени файла её нет. Геометрию водопад пишет в заголовок `.dat`, и клиент с 2026-09-12 отдаёт её порталу вместе с нормированным на бин уровнем шума (`client_metadata.signal.noise_dbhz`) — сравнивать `noise_db` между режимами напрямую нельзя. ## Три обязательных ответвления Каждый запускаемый флоуграф обязан отдавать: Все три адреса выводятся из одного параметра `zmq_base_port` — литеральных `tcp://127.0.0.1:1688x` в `.grc` быть не должно: **Аудио, `zmq_base_port + 2`.** `zeromq_pub_sink` типа `float`, адрес `'"tcp://127.0.0.1:" + str(zmq_base_port + 2)'`. Подписчик жёстко пишет 48000 Гц моно — тракт обязан привести звук именно к этой частоте, обычно `pfb_arb_resampler`. Рассогласование ничем не проверяется. **IQ, `zmq_base_port + 1`.** `blocks_complex_to_interleaved_short` с масштабом `16384` и `vlen=2`, дальше `zeromq_pub_sink` типа `short`, адрес `'"tcp://127.0.0.1:" + str(zmq_base_port + 1)'`. **Кадры, `zmq_base_port`.** Только для флоуграфов с декодером: `zeromq_pub_msg_sink`, адрес `'"tcp://127.0.0.1:" + str(zmq_base_port)'`. Плюс водопад (`satnogs_waterfall_sink`, БПФ 1024, 10 строк в секунду) и `network_udp_sink` (complex, полезная нагрузка 1472 байта) на `udp_dump_host:udp_dump_port`. :::{note} Смещения не взаимозаменяемы: сток типа `float` обязан быть на `+2`, типа `short` — на `+1`. Перепутать их значит отправить аудио подписчику IQ. Это проверяет `tests/test_flowgraphs.py`. ::: ## Рудиментарные параметры Четыре параметра объявлены почти во всех флоуграфах, но **ни к чему внутри не подключены**: - `decoded_data_file_path` - `file_path` - `iq_file_path` - `enable_iq_dump` Пятый, `doppler_correction_per_sec`, снят 2026-09-12: его не читал ни один блок, а диспетчер принимает и обнуляет его ради старых клиентов, которые продолжают слать `--doppler-correction-per-sec`. Запись файлов давно переехала в процессы-подписчики, а `enable_iq_dump` обрабатывает диспетчер. Параметры остались только затем, чтобы флоуграф не упал, когда диспетчер передаст их в общем цикле `--ключ=значение`. Не документируйте их как рабочие и не пытайтесь чинить «неработающий» `--iq-file-path` внутри `.grc` — он работает, просто в другом процессе. Единственный путь, который действительно обрабатывает флоуграф, — `waterfall_file_path`. ## Общий фронтенд Приёмный тракт до демодулятора одинаков во всех флоуграфах: `soapy_source`, `satnogs_doppler_compensation`, `satnogs_waterfall_sink`, IQ в ZMQ (`blocks_complex_to_interleaved_short` + `zeromq_pub_sink` типа `short`) и `network_udp_sink`. **Источник истины — `generic/fm.grc`.** Правка параметра тракта — это правка в нём и одна команда: ```sh python3 tools/sync_frontend.py # разложить по остальным графам python3 tools/sync_frontend.py --check # только сравнить (так делает тест) ``` Скрипт переписывает секцию `parameters:` каждого фронтенд-блока, который в целевом графе уже есть; блоки и соединения он не добавляет и не удаляет, YAML не переформатирует — дифф построчный. У каждого графа свои остаются `out_samp_rate` доплер-компенсации и `samp_rate` водопада (полоса режима) и `compensate` у `iq_receiver`. Ссылка на параметр, которого в целевом графе нет, — ошибка проверки: `grcc` упал бы на сборке. `test_frontend_matches_golden` в `tests/test_flowgraphs.py` держит графы в согласии с золотым. Из `soapy_source` при этом вычищены 16 полей второго канала (`nchan: '1'`, в кодоген они не попадали, GRC подставляет дефолты `block.yml`); поля усиления под конкретные `devname` оставлены — при `devname: custom` они тоже мертвы, но трогать их без прогона на железе незачем. Остальная копипаста живёт за фронтендом, и её при изменении по-прежнему надо править во всех файлах: | Что | Где | |---|---| | фрагмент `rig_pathname` | все флоуграфы | | `audio_samp_rate = 48000` | каждый `.grc` | | формула `decimation` | каждый `.grc` с бодрейтом | | константы контура ЧМн (задержка 512, окно 1024, масштаб 1/1024) | все ЧМн-декодеры | | словарь `available_framings` | по-разному в трёх файлах: 1, 3 и 6 записей | ## Проверка после правки ```sh grcc satellites/my_decoder.grc -o /tmp # генерация без полной сборки python3 /tmp/satnogs_my_decoder.py --help # список параметров ``` Второй командой удобно сверить, что набор параметров совпадает с тем, что шлёт диспетчер.