Скрипты и декодеры¶
Содержимое scripts/ копируется в /usr/local/bin образа и делается
исполняемым. Это точки расширения станции: всё, что не влезает в основной
конвейер — дополнительные декодеры, обзор диапазона, управление реле — живёт
здесь, а не в Python-коде клиента.
Важно
Новый декодер подключают сюда, а не в src/. Клиент вызывает всего два
скрипта, satnogs-pre и satnogs-post, а те уже разветвляются дальше.
Два хука¶
Клиент запускает их до и после прохода с одинаковым набором аргументов:
satnogs-pre {{ID}} {{FREQ}} {{TLE}} {{TIMESTAMP}} {{BAUD}} {{SCRIPT_NAME}}
satnogs-post {{ID}} {{FREQ}} {{TLE}} {{TIMESTAMP}} {{BAUD}} {{SCRIPT_NAME}}
|
|
|
|
|
|
|---|---|---|---|---|---|
ID наблюдения |
Частота, Гц |
TLE |
Метка времени |
Скорость |
Имя скрипта графа |
Отключаются переменными OBSERVATION__RUN_PRE_OBSERVATION_SCRIPT и
OBSERVATION__RUN_POST_OBSERVATION_SCRIPT; выключение pre-хука отключает все
внешние декодеры разом.
satnogs-pre — останавливает обзор диапазона (SDR нужен для приёма),
запускает grsat.py и meteor.sh, при GPIO_ENABLE дёргает gpio.py
с частотой прохода.
satnogs-post — останавливает те же декодеры, запускает декодирование SSTV,
переименовывает IQ-дамп, возвращает обзор диапазона и паркует ротатор.
Обратите внимание на порядок в satnogs-post: bandscan.sh start идёт после
остановки декодеров, потому что обзор снова займёт приёмник.
Примечание
Фоновый процесс, запущенный из хука, наследует его stdout и продолжает
писать в тот же пайп после того, как сам хук завершился, — так делает
bandscan.sh start, живущий до bandscan.sh stop следующего прохода. Клиент
это учитывает: пайп закрывает поток-читатель, а не вызывающий, иначе уборка
после satnogs-post ждала бы смерти обзора диапазона и откладывала бы
построение водопада и выгрузку данных до следующего прохода. Ждать вывода
клиент готов не дольше subprocess_log.JOIN_TIMEOUT_IN_SECONDS, дальше
чтение продолжается в фоне. Если вашему скрипту нужен собственный лог,
перенаправляйте вывод фонового процесса в файл — как делает grsat.py.
Декодирование телеметрии¶
grsat.py¶
Главный скрипт декодирования — обёртка над gr_satellites, работающая
параллельно основному потоковому графу. Собирает декодированные кадры, пишет
JSON-телеметрию в директорию наблюдения (её подхватит наблюдатель файловой
системы и отправит на портал), а также передаёт кадры декодерам изображений и
SSTV.
Режимы вызова: start, stop, stream, sstv.
start запускает gr_satellites только если клиент выбрал его декодером
прохода: переменная SONIKS_DECODER (gr-satellites либо satnogs)
выставляется клиентом в окружение satnogs-pre/satnogs-post на каждый
проход по наличию satyaml для NORAD (sat_data.decoder_for(), Фаза 3.2
дорожной карты сети). Без satyaml gr_satellites раньше запускался на каждом
проходе и умирал молча — его вывод идёт в DEVNULL. Без переменной (ручной
запуск) скрипт ведёт себя как прежде. stop и sstv от выбора не зависят:
первый терпит отсутствие pid- и kiss-файлов, второй — отдельный декодер
картинок.
Кадры уходят потоком, во время прохода (Фаза 3.1 дорожной карты сети).
start запускает gr_satellites с двумя выходами KISS: файлом --kiss_out и
TCP-сервером --kiss_server на порту 8100, — и фоном grsat.py stream. Тот
подключается к серверу, пишет каждый кадр файлом data_* сразу, как он
пришёл, и выходит по EOF, когда stop гасит gr_satellites. Файлы подхватывает
наблюдатель файловой системы клиента — кадр оказывается на портале через
OBSERVATION__BATCH_DELAY, а не после прохода.
stop после этого разбирает --kiss_out целиком, как и раньше: это
страховка на случай, если поток не подключился или умер. Кадры, уже записанные
потоком, запишутся второй раз под суффиксом _g1, но портал отсеивает повтор
по sha256 содержимого внутри наблюдения — ответ 200 без записи. Сам
--kiss_out читать по мере записи нельзя: GNU Radio file_sink буферизует и
сбрасывает хвост только при закрытии, а SIGKILL его теряет.
--start_time не передаётся намеренно: он для записей, проигрываемых с
--throttle. На живом UDP метка кадра считалась бы от момента сборки графа
gr_satellites и уезжала назад на время подготовки прохода; без него метка —
реальное UTC часов станции.
Переменные: GRSAT_LOG_LEVEL, GRSAT_KEEPLOGS, GRSAT_ZMQ_PORT;
SONIKS_DECODER ставит клиент, в .env её не задают.
sat.cfg¶
Per-NORAD переопределения аргументов gr_satellites — по одной строке на
аппарат:
35933 --clk_bw 0.3
46276 --disable_dc_block --deviation 500 --clk_bw 0.15
Здесь место точечным исправлениям для конкретных спутников, у которых стандартные параметры демодуляции не работают.
Файл живёт в soniks-satyaml вместе с satyaml: это данные спутников, и меняются они теми же правками. На станцию он приезжает sat-data bundle’ом с портала, см. Поддерживаемые спутники; в образ попадает через сабмодуль — как запас на первый старт, до первого похода за bundle.
gr_satellites читает его строго из ~/.gr_satellites/sat.cfg, а $HOME
станции (/var/lib/soniks-client) перекрыт именованным томом. Кладут файл туда
двое: grsat.py копирует его из образа, если в томе пусто, а sync_sat_data()
обновляет из bundle. Правки оператора не затираются: bundle переписывает
файл, только если тот совпадает с прошлым доставленным, иначе оставляет как
есть и пишет предупреждение в лог. Чтобы принять версию из bundle, удалите файл
в томе и перезапустите контейнер.
imagedecode.py¶
Сборка изображений из телеметрических кадров. Класс ImageDecode выбирает
декодер по NORAD ID; каждый декодер объявляет свой список supported_norad:
Декодер |
Аппараты |
|---|---|
|
Geoscan-Edelveis, StratoSat |
|
Geoscan |
|
Sputnix |
|
MARINA |
|
CAS-5A |
|
Lucky-7, Sharjahsat-1. Подключены к диспетчеру, но помечены в коде |
Добавление аппарата к существующему формату — это дописать его NORAD ID в
supported_norad нужного класса. Новый формат — новый подкласс плюс ветка
выбора.
sstv_wrapper.py¶
Декодирование SSTV для захардкоженного списка NORAD ID, обёртка над CLI sstv.
Вызывается из satnogs-post через grsat.py sstv.
Сам декодер — пакет из сабмодуля sstv/, в образ ставится из него же: версию
фиксирует указатель сабмодуля в этом репозитории. Правка декодера — коммит в
сабмодуль, затем коммит здесь с новым указателем.
Как декодируется изображение (после поиска заголовка и VIS):
частота — ЧМ-дискриминатор: полоса 1000–2500 Гц, аналитический сигнал, разность фаз соседних отсчётов. Прежняя оценка по пику короткого БПФ ошибалась в яркости на десятки ступеней даже на чистом тоне;
синхро — одна прямая по синхроимпульсам всех строк с отсевом выбросов: строка с синхро в шуме не сдвигается, дрейф часов правится наклоном;
сглаживание — окно Ханна, которое удлиняется (до ×4) по шуму, измеренному внутри синхроимпульсов. Robot 36 выбирает фазу цвета голосованием по разделителям строк.
В лог (GRSAT_KEEPLOGS) декодер пишет строку Sync: … — число строк с чистым
синхро, дрейф в ppm, джиттер, шум и выбранное окно: по ней видно, что было с
сигналом.
Внешние конвейеры¶
meteor.sh¶
Демодуляция и декодирование METEOR 2-3 / 2-4 из файла IQ-дампа (не из UDP).
Работает только при FLOWGRAPH__ENABLE_IQ_DUMP=True. Список аппаратов —
METEOR_NORAD (по умолчанию 57166 59051).
bandscan.sh¶
Широкополосный обзор диапазона в простое, через rx_sdr и rffft из
strf. Запускается и останавливается хуками,
чтобы не конкурировать за приёмник. Число каналов БПФ подбирается по частоте
автоматически: 20 для VHF, 50 для UHF, 100 для S-диапазона.
Требует BANDSCAN_FREQ и реального диска под BANDSCAN_DIR — по умолчанию
там /srv/bandscan, и писать туда в tmpfs — плохая идея.
Вспомогательные¶
Скрипт |
Что делает |
|---|---|
|
Точка входа контейнера: |
|
Переименовывает IQ-дамп в |
|
Считает частоту дискретизации IQ по скорости и имени скрипта графа. Портирован из satnogs. |
|
Отправляет ротатору |
|
Управление реле (МШУ, выбор антенны, PA, ротатор) через MCP2221 в зависимости от частоты. Исключён из проверки ruff. |
|
Разбор KISS gr_satellites: кадры и метки времени. Общий для |
|
Ручной прогон потокового графа — проверка SDR + GNU Radio без ожидания прохода. Вызывается как |
Ещё две вещи в scripts/ — не скрипты. libresdr/usrp_b210_fpga.bin —
прошивка FPGA для LibreSDR: при сборке образа она подменяет штатную в
/usr/share/uhd, а из /usr/local/bin удаляется.
gnuradio/vmcircbuf_default_factory задаёт реализацию кольцевого буфера GNU
Radio, но лежит не по назначению: COPY scripts/* в Dockerfile разворачивает
поддиректории плоско, поэтому файл оказывается прямо в /usr/local/bin/.
GNU Radio по этому пути его не читает — то есть сейчас настройка ни на что не
влияет. Куда именно его класть, проверяется только запущенным GNU Radio; пункт
открыт в дорожной карте.
Соглашения¶
Скрипты запускаются от пользователя
9999, не от root.Рабочая директория наблюдения —
$PATHS__BASE/$PATHS__OUTPUT_DIR(в контейнере/var/lib/soniks-client/data/output). Всё, что положено туда с префиксомdata_, будет выгружено на портал.Скрипты читают переменные окружения напрямую, минуя модель настроек. Они не валидируются на старте: опечатка не выдаст ошибку, функция просто молча не включится.
Признак «включено» — регулярное выражение
(TRUE|YES|1)без учёта регистра.Имена переменных с префиксом секции сверяются с полями
Settingsавтотестомtests/test_script_env_names.py: потерянный префикс или опечатка роняют тест, а не «просто молча не работают».