19 KiB
Устройство программы
Процессы и кольца
Программа — четыре процесса (с АЦП 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, без видеокарты.