Add adaptive per-vehicle layouts, full-panel dragging, source controls and automatic preview recovery for RealSense, Insta360 X4 and XGRIDS K1 observation views. Integrate cached Cesium map layers through the private NAS gateway link, retain bounded sensor command receipts, and document validation and operational handoff.
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. |
| Plugin-owned device UI | Реализован | XGRIDS provisioning, project/acquisition/replay, diagnostics, metrics, optional spatial controls и scoped styles физически находятся в plugins/xgrids-k1/frontend; generic Control Station предоставляет frontend SDK, model catalog и host slots. |
| Локальный control plane | Реализован | React получает состояние и выполняет операции через FastAPI REST и WebSocket на loopback. |
| K1 BLE → Wi-Fi | Реализован | Реальный BLE-поиск всех видимых устройств и одна подтверждённая provisioning-запись выбранному устройству. |
| K1 live/replay MQTT | Реализован | Read-only приём, обязательное локальное имя проекта, raw-first сохранение, декодирование облака/позы и device-reported scan time/distance/speed. |
| Автоматический 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/device metrics | Три последние сессии, background preparation, capture-clock-bound cache v9, 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 подключён, но point-cloud + one-camera archived playback ещё не прошёл physical acceptance. |
| Legacy Foxglove module | Только regression | Модуль и тесты сохранены для сравнения декодирования. Текущий live/replay runtime не запускает Foxglove WebSocket и не использует TCP 8765. |
| Карты и миссии | Интерфейсный каркас | Реальные map/mission backends и vehicle control ещё не подключены. |
Приложение не генерирует демонстрационное облако, траекторию, кадры или
метрики. Если реальных данных нет, область сцены остаётся пустой, а числовые поля
показывают —.
Архитектура данных
K1 MQTT :1883, read-only
└── raw .k1mqtt + metadata + durable clock origin сохраняются первыми
├── injected ModelingReport observer -> device scan time/distance/speed
└── bounded latest-wins visual preview queue (4 сообщения)
└── явно внедрённый K1 protobuf/LZ4 point/pose 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
└── capture-clock-bound atomic RRD cache v9 + 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 и сохраняет одну структуру для всех функциональных модулей:
AppHeader— марка NODE DC, выбор архитектурного раздела и состояние локального backend.AdminNavigationPanel— контекст аппарата и список рабочих поверхностей выбранного раздела.LandingStage— стартовая ситуационная поверхность и быстрые переходы.ApplicationPanel— единый контейнер активной рабочей поверхности.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, а не зашита в разметку каждой
страницы.
| Раздел | Назначение |
|---|---|
| Центр | Живое состояние вычислительных узлов, процессов, сети и подключённых устройств. |
| Парк | Аппараты, текущее устройство, сенсоры и конфигурации борта. |
| Наблюдение | Только live-пространственная сцена, камеры, карта, объекты, телеметрия и время. |
| Миссии | Планировщик, маршруты, сценарии и исполнение. Командный backend отключён. |
| Данные | Сохранённые сессии, replay, публичные датасеты, потоки, сущности и экспорт. |
| Система | Модули, интеграции, сеть, аудит и настройки платформы. |
| Тестировочный контур | Архив лабораторных работ, конфигурации экспериментов и визуальные результаты. |
Карточки возможностей имеют четыре честных уровня: работает сейчас, готово к источнику, интерфейсный контракт и последующий этап. Каталог не следует читать как утверждение, что для каждой карточки уже существует backend.
Установка, проверка, сборка и запуск
Требуются Node.js 20.19+ либо 22.12+, uv и соседний checkout
NODEDC_DESIGN_GUIDELINE: зависимости @nodedc/* подключены к нему через
локальные file: пути. Python устанавливается только в .venv репозитория.
Канонический путь от корня репозитория:
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:
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
- Запустить
uv run k1link serveи открыть Mission Core Control Station. - Выбрать Парк → Локальное устройство.
- В каталоге моделей выбрать XGRIDS LixelKity K1. Только после этого активируется runtime и монтируется custom UI XGRIDS-плагина.
- Включить K1, дождаться стабильного индикатора, вручную сверить firmware
3.0.2и direct-LAN топологию, затем подтвердить это в форме. Mission Core не читает firmware с устройства; такое подтверждение остаётсяoperator-attested, а не device-derived evidence. - Нажать Показать все BLE-устройства. Интерфейс показывает полный результат шестисекундного поиска; метка совместимости является подсказкой, выбор делает оператор.
- Ввести SSID и пароль существующей сети и явно запустить подключение. Это одна reviewed provisioning-запись без автоматических повторов.
- Ввести обязательное название проекта. Оно NFKC-нормализуется, trim-ится, проверяется на control/surrogate characters и лимит 96 Unicode characters, сохраняется как display metadata и не используется в filesystem path.
- Запустить локальный live-приём по определённому адресу K1 либо replay
локального
.k1mqtt/проверенного TSV. Физическое сканирование K1 запускается и останавливается подтверждённым двойным нажатием кнопки устройства. - Backend автоматически поднимет Rerun gRPC на TCP 9876 и опубликует адрес в state. Ручной source вводить не требуется.
- Открыть Наблюдение → Пространственная сцена. Plugin-owned spatial block показывает local acquisition phase, stop local reception и полученные от K1 scan time/distance/speed; облако, траектория и preview metrics появляются только после реальных сообщений.
- Физически остановить K1, дождаться steady green, затем остановить локальный приём из device workflow или spatial block, чтобы запечатать evidence.
- После нормального 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 версии:
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;
- nonterminal acquisition fail-closed блокирует manual source apply/reset, saved session switch и persisted replay reattach до завершения текущего приёма; automatic-source action очищает старый source и открывает сцену только после successful start, failed start сохраняет текущую сцену, а guard не останавливает устройство;
- ошибка запуска показывается в 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 и public frontend SDK surface. |
src/core/runtime/ |
Нормализованное состояние активного устройства, spatial source и fail-closed source-switch guard. |
src/composition/devicePlugins.ts |
Единственный allowlist импортов конкретных device plugins. |
src/workspaces/DeviceWorkspace.tsx |
Generic выбор модели и device.connection slot. |
../../plugins/xgrids-k1/frontend/ |
Plugin-owned K1 BLE/Wi-Fi, project/acquisition/replay UI, optional spatial controls, API client, runtime mapper и scoped styles. |
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.
- Bridge и Direct Connect выполняют только отдельно рассмотренную 99-байтную provisioning-запись. Quick Connect выполняет отдельную рассмотренную 100-байтную AP-enable запись и после AP-ready допускает ровно одну системную ассоциацию с заранее материализованным exact-firmware профилем. Пароль не попадает в браузер/API/argv; системный Wi-Fi Keychain, post-write prompt и автоматические повторы запрещены.
- MQTT data live/replay остаётся subscribe-only. Команды изолированы в plugin-owned canonical control session и доступны только через отдельные operator-present UI checkpoints.
- Exact start/stop codec, response correlator и device-status state machine не владеют transport. Plugin v0.5.0 связывает их отдельным single-owner MQTT контуром без wall-clock переходов, reconnect или automatic retry; после STOP обязательны live READY и подтверждённый постоянный зелёный индикатор.
- Live-сессии сначала сохраняют сырые сообщения и camera segments, затем формируют disposable preview. При перегрузке preview может быть отброшен, native evidence сохраняется. MQTT durability имеет bounded group-commit RPO, camera durability — текущий незавершённый fragment RPO; это не zero-loss claim.
- Новая session summary hash-bind-ит durable clock origin и атомарно переключает
pointer с provisional
mqtt.timeline.jsonна content-addressedmqtt.timeline.session-<sha256>.json: только owner-sealedsessionscope может рекламировать combined media replay. Cache v9 создаёт реальные origin/end RRD rows; provisionaltransportscope fails closed. - Timeline
capture_timeсохраняет Unix-время приёма сообщения Mac, а окно накопления viewer использует session-localstream_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. Физический archived playback point cloud + одна выбранная камера ещё не принят.
.runtime/, canonical evidence roots и legacysessions/игнорируются Git и могут содержать адреса, изображения, идентификаторы, траекторию и карту помещения. В репозиторий попадают только код, тесты, redacted manifests и безопасная документация.