328 lines
28 KiB
Markdown
328 lines
28 KiB
Markdown
# NODEDC MISSION CORE · Control Station
|
||
|
||
Универсальный браузерный пункт управления Mission Core на React 19, TypeScript и
|
||
Vite. Приложение задаёт общую операторскую оболочку для аппаратов, сенсоров,
|
||
наблюдения, миссий и записей. XGRIDS/LixelKity K1 является первым реальным
|
||
device adapter, но структура интерфейса от него не зависит.
|
||
|
||
Внутри пространственной рабочей поверхности встроен открытый Rerun Web Viewer.
|
||
Это self-hosted frontend-компонент из npm-пакета `@rerun-io/web-viewer`, а не
|
||
переход во внешний облачный интерфейс. Для K1 live и adapter file-replay backend
|
||
сам создаёт Rerun gRPC/proxy source. Saved observation sessions открывают
|
||
проверенную digest-bound RRD generation через same-origin HTTP; ручной адрес
|
||
нужен только для другого совместимого Rerun source.
|
||
|
||
## Текущее состояние
|
||
|
||
| Контур | Состояние | Что это означает |
|
||
| --- | --- | --- |
|
||
| Mission Core fixed shell | Реализован | Header, навигация по разделам, рабочая поверхность, окна и инспекторы работают в одном приложении. |
|
||
| Device plugin registry | Реализован, v1alpha1 + v1alpha2 | До выбора модели provider остаётся inert и не делает I/O. v1alpha1 сохраняет одну модель; v1alpha2 допускает одну или несколько моделей и требует profile coverage каждой. Custom `device.connection` UI key и backend factory подключаются одним reviewed import в composition root. |
|
||
| Локальный control plane | Реализован | React получает состояние и выполняет операции через FastAPI REST и WebSocket на loopback. |
|
||
| K1 BLE → Wi-Fi | Реализован | Реальный BLE-поиск всех видимых устройств и одна подтверждённая provisioning-запись выбранному устройству. |
|
||
| K1 live/replay MQTT | Реализован | Read-only приём, raw-first сохранение, декодирование облака точек и позы, реальные метрики. |
|
||
| Автоматический MQTT → Rerun | Реализован | Первый live/adapter file-replay поднимает process-wide `RecordingStream` и gRPC/proxy на TCP 9876; следующие такие сессии переиспользуют его. Saved observation replay использует отдельный immutable HTTP RRD path. |
|
||
| Встроенный Rerun Viewer | Реализован | Self-hosted npm-компонент автоматически открывает текущий gRPC source внутри Control Station; внешний viewer не используется. |
|
||
| Контролы сцены → Rerun | Реализованы для текущей геометрии | Работают размер и видимость точек, атрибут цвета, палитра, окно накопления, траектория, сетка, host timeline и сохранение/восстановление spatial layout. Проекция и семантические слои ещё не подключены. |
|
||
| Сохранённые observation sessions | Реализованы для point/pose | Три последние сессии, background preparation, cache v6, generation-bound RRD, atomic admission, autoplay, play/pause/seek и controlled switching больших записей. |
|
||
| K1 camera preview и archive | Live реализован; recorded contract реализован | Обе RTSP/H.264 камеры физически приняты в live UI. Новые acquisition-owned fMP4 archives не зависят от browser windows; recorded player подключён, но реальная архивная K1 camera-session ещё не прошла physical acceptance. |
|
||
| Legacy Foxglove module | Только regression | Модуль и тесты сохранены для сравнения декодирования. Текущий live/replay runtime не запускает Foxglove WebSocket и не использует TCP 8765. |
|
||
| Карты и миссии | Интерфейсный каркас | Реальные map/mission backends и vehicle control ещё не подключены. |
|
||
|
||
Приложение не генерирует демонстрационное облако, траекторию, кадры или
|
||
метрики. Если реальных данных нет, область сцены остаётся пустой, а числовые поля
|
||
показывают `—`.
|
||
|
||
## Архитектура данных
|
||
|
||
```text
|
||
K1 MQTT :1883, read-only
|
||
└── raw .k1mqtt + metadata + SHA-256 сохраняются первыми
|
||
└── bounded latest-wins preview queue (32 сообщения)
|
||
└── явно внедрённый K1 protobuf/LZ4 normalizer
|
||
└── transport-neutral DecodedPointCloudView / DecodedPoseView
|
||
└── Rerun Points3D + Transform3D + LineStrips3D
|
||
└── gRPC/proxy TCP 9876
|
||
└── rerun_grpc_url в REST/WebSocket state
|
||
└── встроенный @rerun-io/web-viewer
|
||
└── пространственная сцена Mission Core
|
||
|
||
Mission Core Control Station ←→ REST /api/v1/device-plugins/*
|
||
+ plugin-scoped WebSocket events
|
||
FastAPI на 127.0.0.1:8000
|
||
CoreBluetooth + live/replay runtime
|
||
|
||
sealed/recovered observation session
|
||
└── SQLite catalog + bounded background preparation
|
||
└── atomic RRD cache v6 + recorded-media manifest v2
|
||
└── generation-bound same-origin HTTP
|
||
└── aggregate admission
|
||
└── Rerun native receiver + recorded fMP4 player
|
||
```
|
||
|
||
В момент готовности live/file-replay `RerunBridge` backend публикует адрес вида
|
||
`rerun+http://127.0.0.1:9876/proxy`. Frontend автоматически назначает его сцене,
|
||
если оператор не указал ручной source. После остановки приёма URL и встроенный
|
||
viewer остаются активны, а следующая сессия сбрасывает session-local геометрию,
|
||
траекторию и метрики и использует тот же listener. Он закрывается вместе с
|
||
процессом `k1link serve`.
|
||
|
||
Поля `foxglove_ws_url` и `foxglove_viewer_url` пока остаются в API как
|
||
совместимость со старым контрактом, но текущий runtime держит их пустыми.
|
||
|
||
## Фиксированная оболочка
|
||
|
||
Shell собран из локальных NODE.DC UI packages и сохраняет одну структуру для
|
||
всех функциональных модулей:
|
||
|
||
1. `AppHeader` — марка NODE DC, выбор архитектурного раздела и состояние
|
||
локального backend.
|
||
2. `AdminNavigationPanel` — контекст аппарата и список рабочих поверхностей
|
||
выбранного раздела.
|
||
3. `LandingStage` — стартовая ситуационная поверхность и быстрые переходы.
|
||
4. `ApplicationPanel` — единый контейнер активной рабочей поверхности.
|
||
5. `Window` и `Inspector` — источник, отображение, слои и компоновка без
|
||
раскрытия внутренних панелей визуального движка.
|
||
|
||
Встроенный Rerun Viewer работает как canvas внутри этой оболочки. Его верхняя,
|
||
blueprint-, selection- и time-панели скрыты, чтобы продуктовые действия жили в
|
||
Control Station. Размер точек, способ окрашивания и палитра, 12-секундное по
|
||
умолчанию накопление, видимость облака и траектории и сетка связаны с backend и
|
||
Rerun Blueprint. Host timeline управляет сохранённым `session_time`, а spatial
|
||
layout сохраняет display settings, tool windows и source-window geometry через
|
||
revisioned API. Смена 2D/3D/карты и семантические слои пока остаются
|
||
интерфейсным контрактом.
|
||
|
||
## Архитектурные разделы
|
||
|
||
Навигация описана данными в `src/productModel.ts`, а не зашита в разметку каждой
|
||
страницы.
|
||
|
||
| Раздел | Назначение |
|
||
| --- | --- |
|
||
| Центр | Оперативный обзор, состояние контура и активность оператора. |
|
||
| Парк | Аппараты, текущее устройство, сенсоры и конфигурации борта. |
|
||
| Наблюдение | Пространственная сцена, камеры, карта, объекты, телеметрия и время. |
|
||
| Миссии | Планировщик, маршруты, сценарии и исполнение. Командный backend отключён. |
|
||
| Данные | Сессии, потоки, сущности, playback и экспорт доказательств. |
|
||
| Система | Модули, интеграции, сеть, аудит и настройки платформы. |
|
||
|
||
Карточки возможностей имеют четыре честных уровня: работает сейчас, готово к
|
||
источнику, интерфейсный контракт и последующий этап. Каталог не следует читать
|
||
как утверждение, что для каждой карточки уже существует backend.
|
||
|
||
## Установка, проверка, сборка и запуск
|
||
|
||
Требуются Node.js 20.19+ либо 22.12+, `uv` и соседний checkout
|
||
`NODEDC_DESIGN_GUIDELINE`: зависимости `@nodedc/*` подключены к нему через
|
||
локальные `file:` пути. Python устанавливается только в `.venv` репозитория.
|
||
|
||
Канонический путь от корня репозитория:
|
||
|
||
```bash
|
||
cd /Users/dcconstructions/Downloads/mnt/NODEDC/NODEDC_MISSION_CORE
|
||
uv sync --frozen --group dev
|
||
|
||
cd apps/control-station
|
||
npm ci
|
||
npm run test:unit
|
||
npm run typecheck
|
||
npm run build
|
||
|
||
cd ../..
|
||
uv run k1link serve
|
||
```
|
||
|
||
Открыть `http://127.0.0.1:8000`. Команда `serve` отдаёт собранный `dist/` и
|
||
локальный API. Она намеренно привязана только к `127.0.0.1`; LAN bind не
|
||
предусмотрен, потому что endpoint подключения кратковременно принимает пароль
|
||
Wi-Fi.
|
||
|
||
Этот locked bootstrap повторяем в текущем workspace, но он ещё не является
|
||
standalone release install. Локальные `file:` зависимости берутся из соседнего
|
||
`NODEDC_DESIGN_GUIDELINE`, а lockfile не фиксирует Git revision или content hash
|
||
этого checkout. До CI/portable packaging эти пакеты нужно опубликовать,
|
||
завендорить либо проверять по неизменяемой donor revision.
|
||
|
||
Для разработки интерфейса после запуска backend:
|
||
|
||
```bash
|
||
cd apps/control-station
|
||
npm run dev
|
||
```
|
||
|
||
Для полного live-viewer контура Vite должен открыться на
|
||
`http://127.0.0.1:5173`: только 5173, production preview 4173 и backend 8000
|
||
входят в текущий Rerun CORS allowlist. Если Vite сообщает fallback на 5174 или
|
||
выше, освободите 5173 и перезапустите `npm run dev`; REST/WebSocket proxy на
|
||
fallback-порту может работать, но прямой browser → Rerun 9876 будет отклонён.
|
||
Vite проксирует весь `/api` (включая WebSocket) на `http://127.0.0.1:8000`.
|
||
Другой локальный backend можно указать переменной `VITE_API_TARGET`. Preview
|
||
production-сборки запускается командой `npm run preview` на
|
||
`http://127.0.0.1:4173`.
|
||
|
||
## Операторский путь для текущего K1 adapter
|
||
|
||
1. Запустить `uv run k1link serve` и открыть Mission Core Control Station.
|
||
2. Выбрать **Парк → Локальное устройство**.
|
||
3. В каталоге моделей выбрать **XGRIDS LixelKity K1**. Только после этого
|
||
активируется runtime и монтируется custom UI XGRIDS-плагина.
|
||
4. Включить K1, дождаться стабильного индикатора, вручную сверить firmware
|
||
`3.0.2` и direct-LAN топологию, затем подтвердить это в форме. Mission Core
|
||
не читает firmware с устройства; такое подтверждение остаётся
|
||
`operator-attested`, а не device-derived evidence.
|
||
5. Нажать **Показать все BLE-устройства**. Интерфейс показывает полный результат
|
||
шестисекундного поиска; метка совместимости является подсказкой, выбор делает
|
||
оператор.
|
||
6. Ввести SSID и пароль существующей сети и явно запустить подключение. Это одна
|
||
reviewed provisioning-запись без автоматических повторов.
|
||
7. Запустить live-приём по определённому адресу K1 либо replay локального
|
||
`.k1mqtt`/проверенного TSV. Физическое сканирование K1 запускается и
|
||
останавливается подтверждённым двойным нажатием кнопки устройства.
|
||
8. Backend автоматически поднимет Rerun gRPC на TCP 9876 и опубликует адрес в
|
||
state. Ручной source вводить не требуется.
|
||
9. Открыть **Наблюдение → Пространственная сцена**. Реальные облако и траектория,
|
||
частота, число точек, задержка и пропуски preview появятся после прихода
|
||
сообщений K1.
|
||
10. После нормального stop или recovery открыть **Сохранённые сессии**. Дождаться
|
||
состояния **Готово**, выбрать запись и использовать host timeline. Evidence
|
||
сохраняется автоматически; disk action сохраняет только workspace layout.
|
||
|
||
В проверочном live-сеансе через этот путь прошло 80 реальных MQTT-сообщений:
|
||
38 кадров `lio_pcl`, 42 кадра `lio_pose`, 2 775 точек в последнем облаке и
|
||
0 ошибок декодирования.
|
||
|
||
## Источники Rerun
|
||
|
||
Live/replay runtime автоматически назначает первый URL из примера. В
|
||
**Наблюдение → Пространственная сцена → Источник** можно оставить ручное поле
|
||
пустым либо указать другой адрес, который понимает `@rerun-io/web-viewer`
|
||
зафиксированной в `package.json` версии:
|
||
|
||
```text
|
||
rerun+http://127.0.0.1:9876/proxy
|
||
http://127.0.0.1:8080/recording.rrd
|
||
https://example.internal/recording.rrd
|
||
```
|
||
|
||
- `rerun+http://…/proxy` — живой Rerun gRPC source через доступный браузеру
|
||
proxy;
|
||
- `http(s)://…/recording.rrd` — RRD-запись по HTTP(S);
|
||
- версия RRD должна быть совместима с версией Web Viewer;
|
||
- ручной URL хранится только в состоянии текущей страницы и имеет приоритет над
|
||
автоматическим source;
|
||
- ошибка запуска показывается в viewport, без подстановки фиктивных данных.
|
||
|
||
После готовности Viewer выбор Rerun entity возвращает `entityPath` и имя view в
|
||
оболочку. Backend принимает и применяет к Rerun размер точки `0.5–12.0`, режимы
|
||
цвета `intensity`, `height`, `distance`, `rgb`, `class`, палитры Turbo, Viridis,
|
||
Plasma, grayscale и custom, накопление `0–120` секунд, а также видимость облака,
|
||
траектории и сетки. Значение по умолчанию — 12 секунд истории реальных кадров.
|
||
Для текущего firmware-3 `lio_pcl` доказана только интенсивность из младшего
|
||
байта `rgbi`; RGB используется лишь когда он действительно присутствует в
|
||
декодированном формате. Custom/class сейчас означает выбранный сплошной цвет,
|
||
а не готовую семантическую классификацию.
|
||
|
||
Для saved session host timeline вызывает Rerun play/pause/seek только после
|
||
полного admission. Accumulation и display settings применяются через отдельный
|
||
маленький blueprint RRD и не заменяют основную запись. Versioned
|
||
`observation.spatial` layout сохраняется на host с optimistic revision; он не
|
||
содержит sensor evidence. Переключатель 2D/3D/карты и семантические слои пока не
|
||
вызывают готовый presentation backend.
|
||
|
||
## Контракт локального API
|
||
|
||
| Метод | Route | Тело / назначение |
|
||
| --- | --- | --- |
|
||
| `GET` | `/api/health` | Проверка локального сервиса. |
|
||
| `GET` | `/api/v1/device-plugins` | Валидированные manifests установленных device plugins. |
|
||
| `GET` | `/api/v1/device-models` | Backend-каталог моделей из валидированных manifests; UI-каталог текущей сборки формируется отдельным static composition root. |
|
||
| `POST` | `/api/v1/device-plugins/{pluginId}/actions/{actionId}` | Namespaced действие через host allowlist; тело `{ "input": { ... } }`. |
|
||
| `WS` | `/api/v1/device-plugins/{pluginId}/events` | Plugin-scoped snapshots с `pluginId` и монотонным `sequence`. |
|
||
| `GET` | `/api/v1/observation-sessions` | Последние cataloged sessions и authoritative preparation state. |
|
||
| `POST` | `/api/v1/observation-sessions/{id}/replay` | Verified replay launch либо HTTP 202 preparation handle. |
|
||
| `GET` | `/api/v1/observation-sessions/{id}/recording-preparation` | Exact-job polling с `If-Match`. |
|
||
| `GET` | `/api/v1/observation-sessions/{id}/recording.rrd` | Immutable generation-bound RRD; arbitrary chunked `send_rrd` не используется. |
|
||
| `GET` | `/api/v1/observation-sessions/{id}/media/{artifact}/...` | Manifest/init/segments записанных камер по opaque identifiers. |
|
||
| `GET`, `PUT` | `/api/v1/workspace-layouts/observation.spatial` | Versioned host layout с optimistic revision. |
|
||
|
||
Frontend XGRIDS-плагина использует только v1alpha namespaced routes. Старые
|
||
`/api/state`, `/api/events`, `/api/ble/scan`, `/api/connect`, `/api/session/*` и
|
||
`/api/viewer/settings` сохранены как deprecated compatibility shims внутри
|
||
XGRIDS backend contribution.
|
||
|
||
В v1alpha1/v1alpha2 backend composition загружается из manifests, а frontend plugins
|
||
статически включаются в сборку через `src/composition/devicePlugins.ts`.
|
||
Автоматической runtime-сверки двух installed sets пока нет: их соответствие —
|
||
проверяемое требование сборки до появления подписанных plugin bundles и startup
|
||
compatibility handshake. Поля manifest `permissions`, `mutating` и
|
||
`secretFields` пока являются декларативными метаданными; generic host валидирует
|
||
их форму и action allowlist, но ещё не реализует на их основе RBAC, подтверждения
|
||
оператора или secret-vault substitution.
|
||
Точный K1 compatibility profile остаётся неактивным, пока оператор явно не
|
||
подтвердит firmware `3.0.2` и direct-LAN топологию. Runtime не получает версию
|
||
firmware с устройства и явно помечает основание как `operator-attested`.
|
||
Ошибки валидации namespaced action endpoint и deprecated `/api/connect`
|
||
возвращаются как общий `422` без исходных значений запроса, поэтому отклонённое
|
||
значение credential-поля не отражается клиенту.
|
||
|
||
Legacy `/api/state` и изменяющие состояние ответы могут вернуть snapshot напрямую или
|
||
как `{ "state": { ... } }`. v1alpha2 snapshot отдельно содержит `device_ref`,
|
||
`device_session`, `acquisition`, `operations`, compatibility/calibration/sensor
|
||
facts и старые presentation-поля `phase`, `message`, `devices`,
|
||
`selected_device_id`, `k1_ip`, `source_mode`, `metrics`, `rerun_grpc_url` и
|
||
`viewer_settings`. Legacy-поля `foxglove_ws_url`/`foxglove_viewer_url` остаются
|
||
пустыми. OpenAPI доступен по `/api/docs`.
|
||
|
||
## Карта исходников frontend
|
||
|
||
| Файл | Ответственность |
|
||
| --- | --- |
|
||
| `src/App.tsx` | Fixed shell, выбор разделов и окна source/display/layers/layout. |
|
||
| `src/productModel.ts` | Архитектурные разделы, рабочие поверхности и уровни готовности. |
|
||
| `src/core/device-plugins/` | Vendor-neutral manifest parser, registry, lifecycle и plugin host. |
|
||
| `src/core/runtime/` | Нормализованное состояние активного устройства и spatial source. |
|
||
| `src/composition/devicePlugins.ts` | Единственный allowlist импортов конкретных device plugins. |
|
||
| `src/workspaces/DeviceWorkspace.tsx` | Generic выбор модели и `device.connection` slot. |
|
||
| `src/device-plugins/xgrids-k1/` | Реальный K1 BLE/Wi-Fi/live/replay UI, client и compatibility mapper. |
|
||
| `src/workspaces/Workspaces.tsx` | Оперативный обзор, spatial viewport и остальные продуктовые поверхности. |
|
||
| `src/components/RerunViewport.tsx` | Live/recorded lifecycle WebViewer, native RRD open, atomic admission, playback и selection events. |
|
||
| `src/components/ObservationSessionSelect.tsx` | Три последние сессии, состояния `Готово` / `Обработка` / `Ошибка`. |
|
||
| `src/components/RecordedFmp4Player.tsx` | Проверенный generation-bound MSE playback архивных камер. |
|
||
| `src/core/observation/sessionArchive.ts` | Строгий wire contract catalog/preparation/replay/media API. |
|
||
| `src/core/observation/recordedSessionAdmission.ts` | Общий RRD/camera gate до публикации recorded workspace. |
|
||
| `src/core/observation/workspaceLayout.ts` | Versioned spatial layout и optimistic revision. |
|
||
| `src/sceneSettings.ts` | Типизированный UI-профиль пространственной сцены. |
|
||
| `src/presentation.ts` | Vendor-neutral подписи lifecycle и форматирование нормализованных метрик. |
|
||
| `src/styles.css`, `src/styles/*` | Компоновка shell и рабочих поверхностей. |
|
||
|
||
## Safety и чувствительные данные
|
||
|
||
- Mission Core Control Station и credential endpoint доступны только на loopback.
|
||
- Исключение: Rerun gRPC/proxy на TCP 9876 сейчас слушает все сетевые интерфейсы,
|
||
хотя автоматически возвращаемый URL содержит `127.0.0.1`. На нём нет
|
||
connector-level authentication или TLS. Этот порт допустим только в доверенной
|
||
лабораторной LAN; его нельзя пробрасывать в публичный Интернет или cellular
|
||
WAN без аутентифицированного TLS reverse proxy. Listener намеренно остаётся
|
||
активным между сессиями; для его закрытия нужно остановить `k1link serve`.
|
||
- Пароль Wi-Fi находится только в React memory, передаётся в JSON POST body,
|
||
очищается после успешного ответа и не сохраняется в URL/local storage.
|
||
- BLE-подключение выполняет только отдельно рассмотренную provisioning-запись;
|
||
случайные GATT writes и автоматические повторы запрещены.
|
||
- MQTT live/replay не публикует команды устройству. Запуск и остановка
|
||
физического сканирования остаются за кнопкой K1.
|
||
- Live-сессии сначала сохраняют сырые сообщения и camera segments, затем
|
||
формируют disposable preview. При перегрузке preview может быть отброшен,
|
||
native evidence сохраняется. MQTT durability имеет bounded group-commit RPO,
|
||
camera durability — текущий незавершённый fragment RPO; это не zero-loss claim.
|
||
- Timeline `capture_time` сохраняет Unix-время приёма сообщения Mac, а окно
|
||
накопления viewer использует session-local `stream_time`. `capture_time` — не
|
||
доказанный timestamp сенсора K1 и не photon-to-screen latency.
|
||
- Панорамный камерный поток в MQTT report topics отсутствует; отдельные
|
||
left/right RTSP preview доступны через generic camera windows. LiDAR и camera
|
||
сейчас синхронизированы только по host arrival, не по доказанным sensor clocks.
|
||
- `.runtime/`, canonical evidence roots и legacy `sessions/` игнорируются Git и
|
||
могут содержать адреса, изображения, идентификаторы, траекторию и карту
|
||
помещения. В репозиторий попадают только код, тесты, redacted manifests и
|
||
безопасная документация.
|