Files
2026-10-16 18:35:02 +03:00

19 KiB
Raw Permalink Blame History

Устройство программы

Процессы и кольца

Программа — четыре процесса (с АЦП E-502 — пять: добавляется коллектор), связанные кольцевыми буферами в разделяемой памяти POSIX:

                   raw                    preprocessed                   results
железо ─► процесс сбора ─────► data_preprocessor ─────────► data_processor ─────► окно
               │                      │                          │
               │ raw_tap              │ preprocessed_tap         └─► TCP-локатор
               └─────► окно           └─────► окно
Процесс Что делает Код
окно программы (Python, PyQt6) запускает и перезапускает остальные процессы, читает результаты и копии, рисует, обслуживает веб-интерфейс и GPIO-кнопку python_app/gui/
процесс сбора (Python) управляет железом, публикует коллекции в raw python_app/scripts/matrix_raw_producer.py, rph_radar_raw_producer.py
препроцессор (C++) калибровка, вычитание референса, режекторный фильтр, калибровка по объекту data_acq_and_processing/preprocessing/
процессор (C++) режимы обработки, локатор data_acq_and_processing/processing/

Процесс сбора выбирается по radar.model в python_app/orchestration/producer_selection.py; той же функцией пользуются окно и start.sh --producer-only. У радиофотонного радара с АЦП E-502 процесс сбора запускает ещё и C++-коллектор rph_radar_collector, который передаёт развёртки через псевдотерминал.

Кольца работают по правилу «побеждает новое»: писатель никогда не ждёт читателя, при переполнении затирается самый старый непрочитанный кадр, а читатель, которого обогнали во время чтения, продолжает с самого свежего. Окно читает только копии raw_tap и preprocessed_tap: если бы оно читало основные кольца, оно отбирало бы кадры у препроцессора и процессора. Пустое имя копии отключает её.

Раскладка кольца — RDRRING2, версия 1: заголовок 64 байта, у каждого слота заголовок 16 байт. Код: Python — python_app/orchestration/shm/ring_layout.py, C++ — common_cpp/ipc/ (ShmRing). Владелец заменяет несовместимый сегмент через unlink, не обрезая отображения читателей.

Форматы кадров

Кадр Где Содержимое
RAW3 raw, raw_tap, файлы наборов python_app/runtime/*_bundle.bin коллекция: окно съёма, затем для каждой трассы пара, окно съёма, частоты float32 и S21 complex64. Размер — 44 + пары × (28 + 12 × точки) байт
PRP3 preprocessed, preprocessed_tap то же после предобработки
RSL3 results заголовок 64 байта (номер коллекции, метка стадии, время обработки, settings_revision, окно съёма, использованная скорость, число результатов) и результаты: трасса, число, изображение или таблица, для всей коллекции и для пар

Окно съёма обязательно; старые форматы RAW2, PRP2 и RSL2 отвергаются. Python и C++ обновляются только вместе. Подробное описание полей — §6 librevna_protocol.md.

Идентичность и порядок коллекций

  • Коллекция определяется парой (collection_id, capture_start_ns). Номер после Stop и Start снова начинается с 1, поэтому одного номера мало.
  • Окно съёма ставит процесс сбора по часам CLOCK_MONOTONIC; дальше оно не меняется. У каждой трассы своё окно: при переключении пар окно коллекции про отдельную пару ничего не говорит.
  • История упорядочена по capture_start_ns. monotonic_ns — диагностическая метка стадии.
  • Сопоставление стадий, Single Capture, Remove Last Measurement, кэш 3D SAR и фильтр приближения GPR используют точный ключ (python_app/models/collection_identity.py), без эвристик по близости времён.

Результаты штатно приходят не по порядку: процессор пересчитывает сохранённые коллекции при смене настроек, а остановленное окно в режиме B-scan отправляет свои коллекции обратно в preprocessed. Поэтому:

  • коллекция не новее последней принятой обрабатывается и публикуется, но не попадает в историю процессора и не уходит локатору;
  • при новой ревизии процессор пересчитывает новейшую коллекцию, в B-scan — всю историю; локатору уходит только новейшая;
  • на время такого пересчёта окно пишет reprocess_current_result: false, чтобы процессор не пересчитывал параллельно;
  • история окна вставляет результат по времени съёма; на диск пишутся только новые коллекции;
  • удалённые коллекции (history_clear_before_ns, history_removed) отбрасываются и процессором, и окном, даже если их результаты придут позже.

Окно программы

gui/app_window.py только создаёт контроллеры, связывает их и закрывает окно. Раскладку строит gui/window_view.py; секции gui/controllers/sections/ — наборы виджетов, сигналы подключают контроллеры. Миксинов и фасадов нет.

Контроллер (gui/controllers/) Отвечает за
profile_controller профиль: выбор при запуске, загрузка, сохранение, профиль рядом с данными
config_controller перевод полей окна в модели и обратно по метаданным; состояния не хранит
radar_settings_controller развёртка, АЦП и лазер, пары сбора, пределы прибора
live_settings_controller режим обработки, ревизия, live-файл, пакет настроек из веба
pipeline_controller процессы, кольца, опрос и восстановление, Single Capture, приостановка, пересчёт B-scan
history_controller истории стадий, удаления
plot_controller и renderers/ графики, выбор пары B-scan, проверка ревизии, счётчик изменений картинки для веба
snapshot_controller, recording_controller сохранение данных, очистка и удаление, запись на диск
preprocess_controller наборы, окно предобработки, съём, нейтральные наборы, временный референс
object_calibration_controller, object_calibration_capture калибровка по объекту
web_controller веб-сервер, очередь действий, снимки состояния и картинки
control_button_controller, log_controller GPIO-кнопка; журнал и окна ошибок

Поля настроек описаны один раз — метаданными dataclass-моделей в python_app/models/gui_profile_schema.py (их разворачивает setting_fields.py). Из этого описания берутся ключ профиля, виджет, ключ live-файла, поле веб-формы и проверка при чтении. Подписи веб-формы берутся из подписей полей окна. Qt в моделях и кодеках нет. Оформление окна — gui/theme.py, цвета серий и маркеров — gui/controllers/renderers/common.py, оформление веба — webui/static/style.css.

Правила жизненного цикла

  • Приостановка (gui/runtime/pipeline_pause.py). Конвейер берёт владение до остановки и дочитывания колец, потому что дочитывание прокачивает события Qt. Асинхронный съём держит приостановку до конца. Автоперезапуск при удержанной приостановке запрещён; продолжается только то, что шло до неё.
  • Stop и закрытие окна. Пользовательский Stop сразу увеличивает счётчик остановок и отменяет ожидающие продолжения; техническая остановка приостановку не отменяет. Закрытие сразу запрещает новые запуски; если идёт дочитывание, его владелец заканчивает и вызывает отложенное закрытие.
  • Start проверяет всё до того, как что-то остановить. Отказ не трогает работающий процессор, локатор и историю.
  • Ошибка отрисовки не останавливает сбор: она пишется один раз и видна в строке состояния. Сбор останавливают только ошибки чтения колец.
  • Показывается новейшая коллекция: опрос рисует последний результат истории, даже если последним пришёл пересчёт более старой.
  • Графики меняются только через PlotController. Он считает изменения картинки, и веб снимает кадр по этому счётчику. Сигнал QGraphicsScene.changed не подключать: подписка переводит Qt в старый режим обновления, при котором на экране остаются следы прежних кривых (это проверяет тест test_redrawn_views_leave_no_stale_pixels_on_screen).
  • Без экрана и при нажатии GPIO-кнопки окна ошибок не показываются; приостановка освобождается до показа ошибки.
  • Одно действие — один код для кнопки окна, веба и GPIO, с общей проверкой занятости. Веб-поток трогает Qt только через сигналы с очередью и снимки состояния.
  • Синхронный съём (калибровка, референс, нейтральные наборы) идёт в потоке окна при приостановленном сборе; лишняя развёртка внутри такого нажатия — это зависшее окно. Съём для калибровки по объекту асинхронный, через обычный конвейер, а расчёт — в рабочем потоке без Qt.

Состав кода

Каталог Что
python_app/gui/ окно, диалоги, контроллеры, отрисовка
python_app/hardware_full/ сервисы приборов: librevna_service.py, multi_device_service.py, matrix_radar_service.py, switched_matrix_radar_service.py, point_target_mock_service.py (симулятор), switch_service.py
python_app/hardware_full/librevna_driver/ USB-драйвер одиночного LibreVNA, протокол версии 14
python_app/hardware_full/librevna_multi_device_driver/ синхронная многоприборная развёртка (cycle_collection.py — один цикл)
python_app/hardware_full/rph_radar/, laser_control/ радиофотонный радар: коллектор и плата RFG, плата лазеров
python_app/hardware_full/switch_drivers/ ВЧ-ключи H7992 через GPIO (gpio_uapi.py — ioctl ядра без внешних пакетов)
python_app/models/ профиль: схема, чтение и запись, проверки; модель данных (dataset_model.py)
python_app/orchestration/ запуск процессов (process_supervisor.py, restart_policy.py), рабочие файлы, кольца (shm/)
python_app/storage/npz/ наборы и наборы данных (store.py, serialize.py, snapshot_numpy.py, vna_history_json.py)
python_app/webui/ веб-сервер FastAPI в процессе окна, страница static/
python_app/workflows/ сценарии съёма и калибровки по объекту
python_app/scripts/ процессы сбора и утилиты
python_app/tests/ тесты Python
data_acq_and_processing/common_cpp/ общий C++: конфигурация (config/), кадры и кольца (ipc/), драйвер H7992 (hardware/)
data_acq_and_processing/preprocessing/ препроцессор и загрузка наборов
data_acq_and_processing/processing/ процессор, режимы (processors/src/), локатор (locator/)
data_acq_and_processing/rph_radar_collector/ коллектор L-Card E-502
data_acq_and_processing/tests/ тесты C++ и эталоны 3D SAR и GPR
data_acq_and_processing/third_party/ nlohmann/json и Eigen
deploy/ служба и обновление с флешки
device_firmware/ прошивки и описания протоколов

Где что править

Задача Куда смотреть
ключи профиля и их проверка python_app/models/run_config_schema.py, run_config_codec.py, run_config_validation.py; то же на стороне C++ — data_acq_and_processing/common_cpp/config/
поля окна, live-файла и веб-формы python_app/models/gui_profile_schema.py, секции gui/controllers/sections/, C++ — processing/data_processor/src/processing_live_config.cpp
формат кадров синхронно: C++ common_cpp/ipc/ (shared_types.hpp/.cpp), Python-писатель storage/npz/serialize.py, Python-читатель orchestration/shm/decoder.py, описание — §6 librevna_protocol.md
модель данных python_app/models/dataset_model.py
драйверы приборов python_app/hardware_full/
запуск процессов orchestration/process_supervisor.py, restart_policy.py; из окна — pipeline_controller.py
хранение данных python_app/storage/npz/
режимы обработки data_acq_and_processing/processing/processors/src/: bscan, gpr, passthrough, sar3d; общий код GPR и 3D SAR — backprojection.hpp; отрисовка — gui/controllers/renderers/
3D SAR sar3d_processor.cpp (алгоритм), sar3d_clean.{hpp,cpp} (фон кадра, CLEAN, рамки), sar3d_gpu.{hpp,cpp} (CUDA), renderers/sar3d.py
сценарии съёма gui/controllers/preprocess_controller.py, python_app/workflows/
веб-интерфейс python_app/webui/, мост к окну — gui/controllers/web_controller.py

Изменив поведение, поправьте главу документации, где оно описано: ключи профиля — configuration.md, поля окна — desktop.md и processing.md, форматы данных — data.md, кадры колец и USB-протокол — librevna_protocol.md, переключение стимула — per_point_switching.md.

3D SAR и эталон

Алгоритм 3D SAR — порт исследовательского ноутбука Multi_depthnorm_clean.ipynb (ячейки 1 и 5; хранится вне репозитория). Комментарии в sar3d_processor.cpp и sar3d_clean.cpp называют функции и константы ноутбука, поля окна — ровно его имена. Величины ноутбука, не вынесенные в окно, — константы в начале этих файлов.

Решения CLEAN и рамок зависят от точных равенств (углы сетки лежат на границе окна дальности, полуразмеры окон кратны шагам), поэтому граница на видеокарте считается в double без FMA (__dmul_rn, __dadd_rn), а хост-код 3D SAR собирается с -ffp-contract=off (SAR3D_HOST_FLAGS в Makefile). Иначе на машинах с FMA (aarch64, x86 с -mfma) результат расходился бы с ноутбуком.

Сверка с ноутбуком автоматизирована (глава Разработка): генератор выполняет неизменённые функции ноутбука и пишет эталон sar3d_reference.npz, тест гоняет настоящий CUDA-бэкенд и сравнивает с ним. Без видеокарты проверяются только предупреждение и правила рамок, поэтому после правки 3D SAR сверку нужно прогнать на настоящей карте. GPR сверяется с gpr_reference.npz на CPU, без видеокарты.