added docs

This commit is contained in:
Ayzen
2026-10-16 18:35:02 +03:00
parent b88d704126
commit ad2b3e555a
19 changed files with 3719 additions and 663 deletions
+226
View File
@@ -0,0 +1,226 @@
# Веб-интерфейс, кнопка и локатор
Кроме окна, с программой работают из браузера, GPIO-кнопкой и через TCP-локатор: он
отдаёт внешним программам найденные объекты и принимает скорость радара. Без экрана
(раздел «Работа без экрана») других способов нет.
## Веб-интерфейс
Веб-сервер работает внутри программы, и с окном, и без экрана. Откройте в браузере
`http://<адрес машины>:8080/`. Порт меняется переменной окружения
`RADAR_SYSTEM_WEBUI_PORT`; неверное значение заменяется на 8080. Если порт занят,
программа работает без веб-интерфейса, а ошибка (`address already in use`) видна
только в выводе процесса. Если сервер не смог запуститься по другой причине, в журнале
появляется `Failed to start web UI`.
> **Внимание.** У веб-интерфейса нет пароля и шифрования: любой, кто достаёт до
> порта по сети, управляет радаром. По адресам `/docs`, `/redoc` и `/openapi.json`
> открыто описание API. Подключайте машину только к доверенной сети.
### Страница
Верхняя строка — кнопки управления: **Start**, **Single Capture**, **Stop**,
**Capture Temporary Reference**, **Remove Last Measurement**. Они делают то же, что
одноимённые кнопки окна (глава [Окно программы](desktop.md)).
**Processor Output** — картинка с графиков окна программы. Масштабировать её в
браузере нельзя: она повторяет то, что показано в окне. В режиме B-scan над ней
список пар (**Input i / Output o**) и кнопка **Next Combination**, в режиме 3D SAR —
строка состояния 3D SAR.
Боковая панель:
| Блок | Элемент | Что делает |
| --- | --- | --- |
| **Config Profile** | список и **Load** | загружает профиль из каталога `run_configs/`, как **Load Config…**; доступна при остановленном сборе. Список читается при открытии страницы; новые файлы видны после её перезагрузки |
| | `current: <файл>` | текущий профиль |
| **Save / Record Dataset** | **Save path**, **Name (optional)** | каталог и имя набора данных; при открытии страницы берутся из окна, а сохранение и запись из браузера переписывают ими **Path** и **Name** окна (запись — и **Sweeps to record**) |
| | **Save Last 1000** | сохраняет до 1000 последних коллекций каждой стадии, как одноимённая кнопка окна |
| | **Sweeps to record** и **Start + Record to Disk** | запускает сбор и записывает на диск заданное число следующих коллекций; ниже — ход записи `recording N / M sweep(s)…` |
| **Processor Settings** | форма | настройки текущего режима обработки (ниже); заголовок сворачивает форму |
| | таблицы **Tx geometry**, **Rx geometry** | геометрия антенн в режимах GPR и 3D SAR: **−** удаляет антенну, **Add Antenna** добавляет следующую свободную |
| | **Apply** | отправляет изменённые поля |
| | **Clear Runtime History** | очищает историю, как одноимённая кнопка окна |
Нижняя строка: `running` — идёт ли сбор, `processor` — работает ли процессор,
`ring` — имя кольца результатов, `scans` — сколько коллекций в истории каждой стадии,
и признак картинки: `live` — кадры приходят, `stale` — новых кадров нет дольше 2 с
(так бывает и тогда, когда картинка просто не меняется), `no data` — кадров ещё не
было. Сообщения об успехе и ошибках всплывают на 2,6 с; ошибки — красным.
Если окно браузера уже 760 пикселей, панель уходит под картинку.
### Как выполняются действия
Каждая кнопка вызывает тот же код, что и кнопка окна, с теми же проверками. Действия
из нескольких браузеров выполняются по очереди. Если окно программы не начало
действие за 30 с, запрос отменяется
(`The desktop did not start the action in time; the request was cancelled`). Если
начало, но не закончило, браузер получает
`The desktop action is still running; check its status before retrying`, а действие
доводится до конца. Ошибки, которые возникают позже (например, на середине
однократного съёма), видны только в журнале.
### Форма настроек
Форма строится из полей окна: те же подписи, пределы и группы. В ней поле **Mode** и
поля текущего режима; смена режима сразу перестраивает форму. **Apply** отправляет
только изменённые поля, поэтому два человека, правящие разные поля, не мешают друг
другу. Порядок полей может отличаться от окна. Значения проверяются так же, как в
окне: вне пределов — `Numeric setting is outside its allowed range`, неизвестный
вариант — `Unknown setting option: …`; при ошибке не применяется ни одно поле из
отправленных. Дробное число округляется до точности поля окна. Изменения из окна
приходят в форму раз в секунду.
Только в окне программы:
- **Apply Radar**, **Load Config…** из произвольного места, **Save Config…**;
- окно **Preprocessing…**: съём и выбор наборов, калибровка по объекту;
- **Save S21 JSON**;
- секции **Radar** и **Switches**: развёртка, лазер, ключи, пары сбора;
- **Show magnitude**, **Show phase**, **Unwrap phase**, **Scans to show (stopped)**;
- журнал и его уровень, строка состояния;
- масштаб и сдвиг графиков мышью.
### Картинка и обновление
Программа снимает картинку графиков, только когда подключён хотя бы один браузер и
изображение изменилось, и не чаще 5 раз в секунду. Каждый браузер получает самую
свежую картинку, состояние и настройки; медленное соединение пропускает
промежуточные кадры. Пока окно занято синхронным съёмом (калибровка, референс),
картинка не обновляется. Браузер переподключается сам через 1,5 с после обрыва.
### API
Страница работает через HTTP API, которым можно пользоваться и из своих программ.
Тело запросов и ответов — JSON; ошибка действия — код 400 и
`{"detail": "<сообщение>"}`. Тело неверного вида (поле не того типа, нет обязательного
поля) даёт код 422 со списком ошибок в `detail`.
| Запрос | Тело | Что делает |
| --- | --- | --- |
| `GET /api/status` | — | состояние (то же, что в нижней строке страницы) |
| `POST /api/start` | — | **Start** |
| `POST /api/single_capture` | — | **Single Capture** |
| `POST /api/stop` | — | **Stop** |
| `POST /api/tmp_reference` | — | **Capture Temporary Reference** |
| `POST /api/remove_last` | — | **Remove Last Measurement** |
| `POST /api/clear_history` | — | **Clear Runtime History** |
| `GET /api/configs` | — | список профилей в `run_configs/` |
| `POST /api/load_config` | `{"name": "<файл>.json"}` | загрузить профиль |
| `POST /api/save_dataset` | `{"path": "…", "name": "…"}` | **Save Last 1000** |
| `POST /api/start_recording` | `{"path": "…", "name": "…", "count": N}` | **Start + Record to Disk**, N от 1 до 1 000 000 |
| `GET /api/live_settings` | — | описание полей формы |
| `POST /api/live_settings` | `{"<поле>": значение, …}` | применить настройки. Имена полей — из описания формы; у настроек обработки они совпадают с ключами `processing_live.json` |
| `POST /api/display_combo` | `{"input_pos": i, "output_pos": o}` | выбрать пару B-scan |
| `POST /api/next_combo` | — | **Next Combination** |
| `WS /ws` | — | поток сообщений `frame` (картинка PNG в base64), `status`, `settings` |
## GPIO-кнопка
Кнопка, подключённая к GPIO компьютера, снимает временный референс — то же, что
**Capture Temporary Reference**. Она настраивается в разделе `control_button`
профиля (глава [Файл конфигурации](configuration.md)) и читается один раз при запуске
программы.
Схема по умолчанию: кнопка между линией GPIO и землёй, внутренняя подтяжка к питанию,
нажатие — спад уровня, по одному действию на нажатие. Индикации на самой кнопке нет;
результат виден в журнале (`GPIO control button pressed: capturing tmp reference`, затем
`Tmp S21 Reference captured: …`) и на графике pass-through.
Нажатие, пришедшее, пока программа останавливает сбор перед съёмом, отбрасывается
(`Tmp reference capture already in progress; ignoring duplicate request`). Нажатие во
время самого съёма ждёт его конца и запускает ещё один съём. Ошибки съёма только
записываются в журнал, окон с ошибкой кнопка не
открывает. Если настройки неполные, кнопка не включается
(`GPIO control button configuration is incomplete or unsupported; watcher not started`).
Если линию не удалось открыть — `Failed to start GPIO control button watcher`; если
наблюдение за линией прервалось — `GPIO control button watcher stopped: …`. В обоих
случаях программа работает дальше без кнопки до перезапуска.
## TCP-локатор
Локатор — TCP-сервер внутри процессора. Он отдаёт подключившимся программам объекты,
найденные режимом GPR, и принимает от них скорость радара для поправки на движение.
Настройки — раздел `run.locator_server` профиля; по умолчанию порт 8888 на всех
интерфейсах. Сервер работает, пока работает процессор: от первого **Start** до
закрытия программы или перезапуска процессора. **Stop**, временный референс и
пересчёт B-scan процессор и локатор не останавливают.
Обмен в обе стороны идёт пакетами: заголовок из 8 байт — номер устройства и длина
данных (оба uint32, младший байт первым), затем текст JSON в UTF-8.
Пакет от радара:
```text
03 00 00 00 71 00 00 00
{"obs":[{"crs":0.3,"dst":4.3},{"crs":-1.0,"dst":6.0},{"crs":1.0,"dst":2.0}],"sts":1,"tim":"13:42:25.058","ver":1}
```
| Поле | Что это |
| --- | --- |
| `obs` | объекты: `crs` — координата X (вдоль ряда антенн, поперёк направления обзора), `dst` — дальность (Z), метры, округлены до 0,1 м |
| `sts` | всегда 1 |
| `tim` | местное время сборки пакета `ЧЧ:ММ:СС.ммм` (не время съёма) |
| `ver` | `run.locator_server.protocol_version` |
В заголовке — `run.locator_server.device_id`. Пакет уходит после обработки каждой
новой коллекции в любом режиме; вне режима GPR и без объектов список `obs` пуст.
Объекты — те же, что видны на графике GPR: внутри окна видимости, после отбора по
числу и фильтра приближения. При пересчёте после смены настроек уходит пакет только
для новейшей коллекции. Новый клиент сразу получает последний отправленный пакет.
Пакет к радару — скорость в м/с:
```text
00 00 00 00 0c 00 00 00
{"vlc":1.25}
```
Остальные поля и номер устройства в заголовке не читаются. Скорость считается свежей
5 с после прихода. Пока она свежая и на странице GPR или 3D SAR выключена
**Ignore socket speed**, она заменяет скорость из поля, а её изменение пересчитывает
новейшую коллекцию в GPR и 3D SAR. Использованная скорость сохраняется в каждом
результате.
Клиентов может быть несколько; у каждого очередь на `client_queue_size` пакетов (по
умолчанию 32), при переполнении выбрасываются старые. Клиент, который 5 с не принимает
данные или прислал пакет больше `max_payload_bytes` (по умолчанию 65 536 байт),
отключается. Сообщения локатора пишутся в
`python_app/runtime/logs/data_processor.err.log`; если порт занят, процессор работает
без локатора и пишет туда же `locator server disabled`.
## Работа без экрана
На машине без оператора (Raspberry Pi) программу запускает служба systemd
`radar.service` (установка — в главе [Установка и запуск](installation.md)). Она
запускает `./start.sh --headless --skip-build`: окно не показывается, через 0,5 с
выполняется **Apply Radar**, ещё через 1 с — **Start**, даже если **Apply Radar** не
удался. Если **Start** не удался, программа завершается, и systemd запускает её
снова через 3 с.
Профиль служба берёт последний использованный (из
`python_app/runtime/gui_session_state.json`), а если его нет — корневой
`run_config.json`, то есть симулятор. Поэтому перед установкой службы загрузите или
сохраните на машине нужный профиль. Если запомненный профиль не читается, программа
пишет `Failed to load the last selected config profile; falling back to root run_config.json.`,
берёт корневой профиль и запоминает уже его: исправленный профиль нужно загрузить снова.
| Канал | Для чего |
| --- | --- |
| браузер, `http://<машина>:8080/` | запуск и остановка, временный референс, сохранение данных, профили из `run_configs/`, настройки обработки |
| GPIO-кнопка | временный референс |
| TCP-локатор, порт 8888 | объекты и скорость |
| `sudo systemctl start` / `stop` / `restart radar.service` | управление службой |
| `journalctl -u radar.service -f` | журнал службы |
Без экрана нет окон с ошибками: всё пишется в журнал. Окно калибровки и
**Save Config…** без экрана недоступны: калибровку снимайте, запустив окно программы
на той же машине. При запуске окна `./start.sh` сначала останавливает службу; после
закрытия окна она сама не запускается.
Если процесс конвейера упал, программа перезапускает конвейер: первый раз сразу,
следующие подряд — не раньше чем через 6, 12, 24, 48, затем 60 с после предыдущей
попытки; когда данные снова пошли, счёт начинается заново. После 400 подряд ошибок чтения
колец конвейер останавливается и сам уже
не запускается: нажмите **Start** в браузере.