# Веб-интерфейс, кнопка и локатор Кроме окна, с программой работают из браузера, 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** в браузере.