cmvr-es/README.md

1252 lines
50 KiB
Markdown
Raw Normal View History

# CMVR-ES
CMVR-ESCMVR Edge System是部署在机器人边缘主机上的 C++17 运行时。它负责统一加载设备与任务、通过 gRPC 提供稳定的设备控制接口,并通过 QUIC 主动连接平台上报节点在线状态、IP 地址、DeviceManager 设备快照以及允许丢帧的实时音视频。
本项目的协议边界是:
- 机械臂、AGV、灵巧手等控制与状态查询继续使用 gRPC
- 节点注册、心跳、IP 和设备状态主动上报使用 QUIC 可靠流;
- 摄像头和麦克风实时数据使用 QUIC DATAGRAM
- 浏览器不直接连接 CMVR 自定义 QUIC 协议,而是连接平台侧网关提供的 WebTransport、WebRTC、MSE/WebSocket 或其他 Web 接口。
## 当前状态
| 项目 | 当前状态 |
| --- | --- |
| 已验证构建平台 | Ubuntu 22.04 x86_64、GCC 11.4、CMake 3.30.3 |
| CMake / C++ 最低要求 | CMake 3.22、C++17 |
| ARM | 目录和部分依赖已准备,但当前依赖树不完整,尚未形成可复现的完整构建 |
| gRPC | 默认启用,监听 `0.0.0.0:50052`reflection 默认开启 |
| QUIC | 仓库提供 MsQuic v2.5.9 源码构建脚本和真实后端;运行配置默认关闭,缺少依赖时仍可构建不可用占位后端 |
| 物理设备 | 默认全部关闭,适合没有连接设备的开发主机 |
| 平台网关 / Web 前端 | 不在本仓库中,需要平台项目按本文协议接入 |
> [!IMPORTANT]
> 当前可复现构建目标是 `x86`。请不要把 `arm` 目录存在理解为 ARM 全量构建已经通过。
## 目录
- [1. 工程概览](#1-工程概览)
- [2. 系统架构](#2-系统架构)
- [3. 目录结构](#3-目录结构)
- [4. 依赖与环境准备](#4-依赖与环境准备)
- [5. 构建与安装](#5-构建与安装)
- [6. 配置说明](#6-配置说明)
- [7. 运行与测试](#7-运行与测试)
- [8. 生产部署](#8-生产部署)
- [9. 平台端接入](#9-平台端接入)
- [10. 开发扩展指南](#10-开发扩展指南)
- [11. 安全说明](#11-安全说明)
- [12. 已知限制与排障](#12-已知限制与排障)
## 1. 工程概览
### 1.1 核心能力
| 能力 | 说明 |
| --- | --- |
| 设备统一管理 | 通过设备抽象、设备工厂和 `DeviceManager` 加载不同厂商实现 |
| 任务统一管理 | 通过 `TaskManager` 运行周期任务和阻塞式服务任务 |
| gRPC 控制面 | 提供系统、相机、音频、机械臂、AGV、灵巧手和生物头等服务 |
| QUIC 节点在线面 | 边缘端主动连接平台完成注册、心跳、IP、gRPC 地址和 DeviceManager 快照上报 |
| QUIC 实时媒体面 | 通过 QUIC DATAGRAM 发送可丢弃的音视频帧,控制信息仍走可靠流 |
| 协议无关媒体分发 | `MediaSourceHub` 已供 gRPC RGB/麦克风流和 QUIC 彩色/麦克风轨道共享采集源 |
| 算法与仿真 | 包含运动学、轨迹规划、控制算法以及部分 MuJoCo 接入 |
### 1.2 代码中已包含的设备实现
下表表示仓库中存在对应代码,并不表示所有型号都已在当前主机和当前版本完成真机验证。
| 类别 | 已包含实现 |
| --- | --- |
| 摄像头 | UVC、RealSense、Hikvision、Mech-Eye、MuJoCo Camera |
| 麦克风 / 音箱 | FFmpeg Microphone、FFmpeg Speaker |
| 机械臂 | AUBO、Huayan、Motor Robot Arm |
| AGV | SRC1100、My AGV |
| 灵巧手 | PX-6AX Gen3、RH56DFTP |
| 生物头 | ESP32 BioHead |
| 电机与总线 | TI5 Motor、MuJoCo Motor、CAN / CANopen、PCAN |
### 1.3 协议职责
| 数据 | 推荐通道 | 原因 |
| --- | --- | --- |
| 机械臂、AGV 控制命令 | gRPC | 需要明确的请求结果、错误处理和稳定语义 |
| 设备状态和管理接口 | gRPC | 便于平台生成强类型客户端并统一治理 |
| 节点注册、心跳、IP 上报 | QUIC reliable stream | 边缘端主动连接,复用 QUIC TLS 会话和重连机制 |
| 视频、音频实时帧 | QUIC DATAGRAM | 网络拥塞时允许丢弃过期帧,避免旧数据阻塞实时数据 |
| 浏览器控制和播放 | 平台 Web API | 浏览器不能直接使用本项目的自定义 QUIC ALPN |
## 2. 系统架构
```mermaid
flowchart LR
MAIN["cmvr_es / main.cpp"] --> CONFIG["Proto Text 配置"]
MAIN --> DEVICE_MANAGER["DeviceManager"]
MAIN --> TASK_MANAGER["TaskManager"]
DEVICE_MANAGER --> DEVICE_FACTORY["DeviceFactory"]
DEVICE_FACTORY --> DEVICES["设备抽象与厂商驱动"]
DEVICES --> HARDWARE["串口 / CAN / 厂商 SDK / 仿真"]
TASK_MANAGER --> GRPC_TASK["GrpcServerTask"]
TASK_MANAGER --> QUIC_TASK["QuicEdgeTask"]
TASK_MANAGER --> PERIODIC_TASK["周期控制任务"]
GRPC_TASK --> GRPC_SERVICE["gRPC Services"]
GRPC_SERVICE --> DEVICE_MANAGER
GRPC_SERVICE -->|"RGB / Microphone stream"| MEDIA_HUB["MediaSourceHub"]
QUIC_TASK --> QUIC_SERVICE["QuicEdgeService"]
QUIC_SERVICE --> MEDIA_HUB
MEDIA_HUB --> MEDIA_ADAPTER["DeviceMediaSourceAdapter"]
MEDIA_ADAPTER --> DEVICES
QUIC_SERVICE -->|"可靠流:注册 / 心跳 / 媒体描述"| GATEWAY["Java / 平台 QUIC Gateway"]
QUIC_SERVICE -->|"DATAGRAM音视频"| GATEWAY
GATEWAY -->|"WebTransport / WebRTC / HTTP"| BROWSER["Browser"]
GATEWAY -->|"根据节点上报地址调用"| GRPC_SERVICE
```
### 2.1 启动流程
1. 程序从 `/proc/self/exe` 获取可执行文件目录。
2. 无参数启动时加载可执行文件旁的 `config/cmvr_es.pb.txt`;传入一个参数时加载指定根配置。
3. 以根配置文件所在目录作为其他业务配置和证书相对路径的配置根目录。
4. 加载日志配置并初始化日志。
5. `DeviceManager` 根据配置创建并初始化启用的设备。
6. 注册 gRPC 和 QUIC 任务工厂。
7. `TaskManager` 创建并启动启用的周期任务或阻塞式服务任务。
8. 主线程等待 `SIGINT``SIGTERM`
9. 收到退出信号后停止任务并关闭日志。
当前主退出路径会停止 `TaskManager` 中的任务,但没有显式调用 `DeviceManager::stop()`。需要统一关闭全部设备时,可调用 `SystemService/StopAll`;后续也应考虑补齐进程退出时的设备级统一停止。
### 2.2 MediaSourceHub
`MediaSourceHub` 是设备层与传输协议之间的协议无关媒体层。当前 gRPC 相机 RGB 流、gRPC 麦克风流,以及 QUIC 相机彩色轨道和麦克风轨道使用该 HubgRPC Depth/RGBD 流仍直接读取设备编码帧,尚未迁移到 Hub。
- 已接入 Hub 的 gRPC 与 QUIC 流共享同一设备采集源,后续新增协议也应从 Hub 订阅;
- 第一个订阅者启动设备流,最后一个订阅者释放设备流;
- 每个消费线程必须使用独立的 `Subscription`,读取游标不能跨线程共享;
- 描述信息、帧及其 payload 均为不可变对象,可以安全地跨协议共享;
- 环形队列有界,慢消费者不会阻塞生产者;
- 被覆盖的帧会形成精确的 dropped count协议层据此标记 discontinuity
2026-07-23 14:22:09 +08:00
- gRPC RGB 实时流会在积压或帧龄超过配置阈值时主动清空旧帧,避免慢客户端形成
数秒 FIFO 延迟;
- H.264/H.265 出现丢帧或 generation 变化后,协议层可通过 Hub 请求新的关键帧;
- source `start` 必须响应 cancellationsource `stop` 必须同步解除回调并回收生产线程。
关键文件:
- [`media_source_hub.h`](cmvr-es/manager/media_source_hub/include/media_source_hub.h)
- [`device_media_source_adapter.cpp`](cmvr-es/manager/media_source_hub/src/device_media_source_adapter.cpp)
- [`media_frame.h`](cmvr-es/common/media/media_frame.h)
- [`ring_buffer.h`](cmvr-es/common/base/ring_buffer.h)
## 3. 目录结构
```text
cmvr-es/
├── CMakeLists.txt
├── README.md
├── LICENSE
├── request.txt # 三方依赖清单
├── cmake/
│ ├── FindExternalLib.cmake # 仓库内依赖发现与安装
│ └── FindMsQuic.cmake # 固定版本、仓库优先的 MsQuic 发现
├── protos/
│ └── cmvr/
│ ├── api/ # 平台 gRPC API
│ ├── common/ # 通用协议类型
│ ├── config/ # Proto Text 配置 Schema
│ ├── msgs/ # 设备和状态消息
│ └── quic_edge/v1/ # QUIC 控制面、媒体协议与线协议文档
├── cmvr-es/
│ ├── main.cpp # 进程入口与生命周期
│ ├── config/ # 默认运行配置
│ ├── common/ # 日志、配置、基础容器和媒体模型
│ ├── hardware/ # 串口等底层能力
│ ├── devices/ # 设备抽象、工厂和厂商实现
│ ├── manager/
│ │ ├── device_manager/
│ │ ├── task_manager/
│ │ └── media_source_hub/
│ ├── service/
│ │ ├── grpc/
│ │ └── quic_edge/
│ ├── task/
│ │ ├── grpc_server_task/
│ │ ├── quic_edge_task/
│ │ └── touch_screen_task/
│ ├── algorithms/
│ └── simulate/
├── dependency/
│ ├── x86/third_party/ # x86_64 预置依赖和源码构建的 MsQuic
│ └── arm/third_party/ # ARM 部分依赖,当前尚未完整验证
├── model/ # 运行模型和机器人资源
├── assets/ # SDK 安装包、grpcurl 等辅助资产
├── python/ # 标定和辅助脚本
├── script/
│ └── build_msquic.sh # 构建固定版本 MsQuic 到 dependency/
├── test/
│ ├── quic_gateway/ # 本机真实 MsQuic 测试 Gateway
│ └── e2e/ # QUIC 合成媒体和完整进程联调
├── build/ # 本机构建目录,不纳入 Git
└── output/ # cmake --install 产物,不纳入 Git
```
### 3.1 模块开发文档
核心目录提供独立的扩展指南。新增模块时应先阅读目标目录文档,再沿其中列出的 Proto、配置、工厂、CMake 和测试链路完成接入。
| 模块 | 开发文档 | 主要内容 |
| --- | --- | --- |
| 设备层 | [`cmvr-es/devices/README.md`](cmvr-es/devices/README.md) | 新设备后端、新设备类别、生命周期和实时流安全 |
| 管理层 | [`cmvr-es/manager/README.md`](cmvr-es/manager/README.md) | DeviceManager、TaskManager、MediaSourceHub |
| 服务层 | [`cmvr-es/service/README.md`](cmvr-es/service/README.md) | gRPC Service 注册和 QUIC 协议引擎扩展 |
| 任务层 | [`cmvr-es/task/README.md`](cmvr-es/task/README.md) | Task 接口、运行模式、Factory 和配置接入 |
| 公共层 | [`cmvr-es/common/README.md`](cmvr-es/common/README.md) | 公共类型、媒体模型、环形队列和配置工具 |
| 算法层 | [`cmvr-es/algorithms/README.md`](cmvr-es/algorithms/README.md) | IK、规划、控制、感知和算法工厂 |
| 硬件层 | [`cmvr-es/hardware/README.md`](cmvr-es/hardware/README.md) | 串口/总线抽象、并发事务和权限 |
| 配置层 | [`cmvr-es/config/README.md`](cmvr-es/config/README.md) | Proto Text 配置树、ID 和生产配置 |
| 仿真层 | [`cmvr-es/simulate/README.md`](cmvr-es/simulate/README.md) | MuJoCo、仿真引擎和模拟设备边界 |
| 协议定义 | [`protos/README.md`](protos/README.md) | Proto 生成、兼容规则、Java 和 QUIC 版本 |
## 4. 依赖与环境准备
### 4.1 基础环境
2026-03-05 16:15:13 +08:00
已验证环境:
- Ubuntu 22.04.5 LTS x86_64
- GCC 11.4
- CMake 3.30.3
- C++17
- 建议至少预留 10 GB 可用磁盘空间用于依赖、构建和安装产物。
工程要求 CMake 3.22 或更高版本。若系统 CMake 版本过低,可使用 [`assets/`](assets/) 中随仓库提供的对应架构 CMake 安装包,或由系统管理员安装新版 CMake。
### 4.2 系统依赖
下面的命令面向 Ubuntu 22.04。基础构建依赖:
```bash
sudo apt-get update
sudo apt-get install -y \
build-essential \
cmake \
git \
pkg-config \
patchelf \
file \
openssl \
perl \
libboost-all-dev \
libssl-dev
```
图像、仿真和设备接入常用依赖:
2026-02-05 17:31:18 +08:00
```bash
2026-03-05 16:15:13 +08:00
sudo apt-get install -y \
libjpeg-dev \
libpng-dev \
libtiff-dev \
libv4l-dev \
libgtk-3-dev \
libgl1-mesa-dev \
libglu1-mesa-dev \
libglfw3-dev \
libassimp-dev \
libx11-dev \
liblapack-dev \
libblas-dev \
libzbar-dev \
libpthread-stubs0-dev \
libdc1394-dev \
nlohmann-json3-dev \
libxml2-dev \
libpulse-dev \
libasound2-dev \
libusb-1.0-0-dev \
libudev-dev
```
按实际设备选择安装:
```bash
# AUBO SDK 的 Qt 相关能力
sudo apt-get install -y qtbase5-dev
# CAN 调试工具
sudo apt-get install -y can-utils
# Matplot++ 交互绘图
sudo apt-get install -y gnuplot-qt
```
FFmpeg、OpenCV、gRPC、Protobuf、RealSense、Hikvision SDK、AUBO SDK、Pinocchio、MuJoCo、ViSP、OSQP 等主要 C/C++ 依赖优先从 `dependency/<arch>/third_party/` 查找。MsQuic 也安装到该依赖树,但由 [`script/build_msquic.sh`](script/build_msquic.sh) 从固定版本源码构建。完整清单和版本以 [`request.txt`](request.txt) 与 [`FindExternalLib.cmake`](cmake/FindExternalLib.cmake) 为准。
### 4.3 仓库依赖注意事项
当前依赖管理方式有两个需要特别注意的历史问题:
1. 三方依赖的主要来源是仓库中的 `dependency/<arch>/third_party/`,不是本项目的 Git submodule。
2. 当前 `.gitmodules` 与仓库实际 gitlink 不一致,执行 `git submodule update --init --recursive` 会因 `assets/toppra` 缺少映射而失败。
因此,不要把旧版 README 中的全仓库 submodule 命令作为安装步骤。MsQuic 构建脚本会在自己的构建缓存中,仅初始化官方源码要求的 QuicTLS submodule。`script/install.sh` 仍包含旧目录和硬编码架构,当前不作为标准安装入口。
## 5. 构建与安装
以下命令都从仓库根目录执行。
### 5.1 首次构建工具引导
根 CMake 当前固定从 `output/bin/protoc``output/bin/grpc_cpp_plugin` 生成 C++ protobuf/gRPC 代码,而 `output/` 不纳入 Git。因此干净检出后的第一次配置前需要先从仓库依赖树引导这两个工具
```bash
cd /path/to/cmvr-es
CMVR_GRPC_PREFIX="$PWD/dependency/x86/third_party/grpc/v1.76.0"
CMVR_PROTOC="$CMVR_GRPC_PREFIX/bin/protoc"
if [ ! -x "$CMVR_PROTOC" ]; then
CMVR_PROTOC="$CMVR_GRPC_PREFIX/bin/protoc-31.1.0"
fi
test -x "$CMVR_PROTOC"
test -x "$CMVR_GRPC_PREFIX/bin/grpc_cpp_plugin"
install -Dm755 "$CMVR_PROTOC" output/bin/protoc
install -Dm755 \
"$CMVR_GRPC_PREFIX/bin/grpc_cpp_plugin" \
output/bin/grpc_cpp_plugin
export LD_LIBRARY_PATH="$CMVR_GRPC_PREFIX/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"
```
`LD_LIBRARY_PATH` 仅用于首次配置和编译阶段找到仓库内的 Protobuf/gRPC 工具库。完成安装后,应用及安装库使用相对 RPATH标准部署不需要全局设置它。
### 5.2 开发构建
```bash
cmake -S . -B build \
-DCMAKE_BUILD_TYPE=Release \
-DCMVR_ARCH=x86 \
-DBUILD_TESTING=ON \
-DCMVR_MEDIA_SOURCE_HUB_BUILD_TESTS=ON
cmake --build build -j"$(nproc)"
ctest --test-dir build --output-on-failure
cmake --install build
```
安装前缀由根 CMake 固定为仓库下的 `output/`。传入普通的 `-DCMAKE_INSTALL_PREFIX=...` 不会改变该目录。
安装结果的核心结构为:
```text
output/
├── bin/
│ ├── cmvr_es
│ ├── config/
│ └── model/
├── lib/
└── log/ # 首次产生日志时创建
```
### 5.3 生产构建
生产构建可关闭无硬件测试目标:
```bash
cmake -S . -B build-release \
-DCMAKE_BUILD_TYPE=Release \
-DCMVR_ARCH=x86 \
-DBUILD_TESTING=OFF \
-DCMVR_MEDIA_SOURCE_HUB_BUILD_TESTS=OFF
cmake --build build-release -j"$(nproc)"
cmake --install build-release
ldd -r output/bin/cmvr_es
```
> [!WARNING]
> `CMVR_INSTALL_DEFAULT_RUNTIME_ASSETS` 默认为 `ON`,因此普通的
> `cmake --install` 会删除并重新复制 `output/bin/config/` 和
> `output/bin/model/`。不要把唯一一份生产配置、证书或现场模型只维护在这两个
> 目录中。需要更新程序和动态库但保留现有配置、模型时,在首次配置构建目录时
> 传入 `-DCMVR_INSTALL_DEFAULT_RUNTIME_ASSETS=OFF`。
### 5.4 构建真实 QUIC 后端
工程固定使用 MsQuic v2.5.9。它不需要安装到系统目录,也不需要 `sudo`。先从
仓库根目录执行:
```bash
script/build_msquic.sh \
--arch x86 \
--version 2.5.9 \
--jobs "$(nproc)" \
--clean
```
首次执行需要访问 GitHub以获取官方 `v2.5.9` tag 和其 QuicTLS submodule。
脚本还会校验该 tag 的已审核 commit版本或 commit 不在脚本白名单时直接失败。
源码、构建和 staging 缓存位于 `build/third_party/`,最终只把公开头文件、共享
运行库、许可证和构建信息安装到:
```text
dependency/x86/third_party/msquic/v2.5.9/
├── BUILD-INFO.txt
├── include/
│ ├── msquic.h
│ ├── msquic_posix.h
│ └── quic_sal_stub.h
├── lib/
│ ├── libmsquic.so
│ ├── libmsquic.so.2
│ └── libmsquic.so.2.5.9
└── share/licenses/msquic/
```
脚本使用静态 QuicTLS 构建 MsQuic因此生成的 `libmsquic.so` 不依赖系统
`libssl`、`libcrypto` 或 `libnuma`。随后以严格模式构建项目:
```bash
cmake -S . -B build-quic \
-DCMAKE_BUILD_TYPE=Release \
-DCMVR_ARCH=x86 \
-DCMVR_ENABLE_MSQUIC_BACKEND=ON \
-DCMVR_REQUIRE_MSQUIC=ON \
-DCMVR_ALLOW_SYSTEM_MSQUIC=OFF \
-DCMVR_MSQUIC_VERSION=2.5.9 \
-DBUILD_TESTING=ON \
-DCMVR_INSTALL_DEFAULT_RUNTIME_ASSETS=OFF
cmake --build build-quic -j"$(nproc)"
ctest --test-dir build-quic --output-on-failure
cmake --install build-quic
ldd -r output/bin/cmvr_es
```
`FindMsQuic.cmake` 默认只接受
`dependency/<arch>/third_party/msquic/v<CMVR_MSQUIC_VERSION>`,不会静默链接开发
主机上的系统 MsQuic。各选项含义如下
| 选项 | 默认值 | 作用 |
| --- | --- | --- |
| `CMVR_MSQUIC_VERSION` | `2.5.9` | 选择仓库依赖目录中的精确版本 |
| `CMVR_MSQUIC_ROOT` | 空 | 显式指定另一个可信安装前缀;须包含匹配版本的 `BUILD-INFO.txt` |
| `CMVR_ALLOW_SYSTEM_MSQUIC` | `OFF` | 是否允许在仓库和显式前缀以外搜索 |
| `CMVR_REQUIRE_MSQUIC` | `OFF` | 找不到真实后端时是否让 CMake 直接失败 |
正式 QUIC 构建建议始终设置
`CMVR_REQUIRE_MSQUIC=ON`、`CMVR_ALLOW_SYSTEM_MSQUIC=OFF`。配置阶段应看到
`MsQuic found`;若看到 `building QUIC edge in unavailable/stub mode`,当前产物
只能运行 gRPC不能启动 QUIC 任务。安装时MsQuic 运行库会复制到
`output/lib/`,部署仍分发完整 `output/`
在 ARM 主机原生构建时可把 `--arch` 改为 `arm`。跨架构构建还必须设置
`CMVR_MSQUIC_TOOLCHAIN_FILE=/absolute/path/to/toolchain.cmake`;这只解决
MsQuic 自身的交叉编译,整个工程的 ARM 依赖树仍需另行补齐和验证。
## 6. 配置说明
### 6.1 配置格式与加载规则
配置使用 Protobuf TextFormat不是 JSON 或 YAML。配置 Schema 位于 [`protos/cmvr/config/`](protos/cmvr/config/)。
默认配置树:
```text
cmvr_es.pb.txt
├── logger/logger.pb.txt
├── manager/device_manager.pb.txt
│ └── devices/<category>/*.pb.txt
└── manager/task_manager.pb.txt
└── tasks/<task>/*.pb.txt
```
关键入口:
| 配置 | 作用 |
| --- | --- |
| [`cmvr_es.pb.txt`](cmvr-es/config/cmvr_es.pb.txt) | 根配置,引用日志、设备管理和任务管理配置 |
| [`device_manager.pb.txt`](cmvr-es/config/manager/device_manager.pb.txt) | 设备实例、类别、配置文件和启用状态 |
| [`task_manager.pb.txt`](cmvr-es/config/manager/task_manager.pb.txt) | gRPC、QUIC、触屏等任务及运行模式 |
2026-07-23 14:22:09 +08:00
| [`grpc_server_task.pb.txt`](cmvr-es/config/tasks/grpc_server_task/grpc_server_task.pb.txt) | gRPC 地址、端口、reflection以及相机流积压/帧龄上限 |
| [`quic_edge_task.pb.txt`](cmvr-es/config/tasks/quic_edge_task/quic_edge_task.pb.txt) | QUIC 网关、TLS、重连、心跳和媒体轨道 |
| [`logger.pb.txt`](cmvr-es/config/logger/logger.pb.txt) | 日志级别、路由、目录和轮转参数 |
传入显式根配置时,业务配置、设备配置和 QUIC 证书的相对路径均以根配置文件所在目录解析:
```bash
./output/bin/cmvr_es /etc/cmvr-es/cmvr_es.pb.txt
```
日志配置中的相对 `directory` 是相对于可执行文件目录解析,而不是配置根目录。默认值 `../log` 对应 `output/log/` 或部署后的 `/opt/cmvr-es/log/`
2026-07-23 14:22:09 +08:00
配置 TextFormat 不具备“新配置给旧二进制读取”的前向兼容性。本次新增的 gRPC
相机流参数需要与新二进制一起部署;不要只更新
`grpc_server_task.pb.txt` 而继续运行旧的 `output/bin/cmvr_es`
### 6.2 默认无硬件开发配置
仓库默认配置适用于当前主机没有连接实际设备的场景:
- 所有物理设备条目默认 `enable: false`
- `touch_screen` 默认关闭;
- `quic_edge` 默认关闭;
- `grpc_server` 默认开启;
- gRPC 默认监听 `0.0.0.0:50052`reflection 默认开启。
因此,首次启动应先验证进程和 gRPC 服务,再逐个开启设备。不要一次性启用全部设备。
### 6.3 启用设备
启用设备时需要同时满足:
1. 对应设备集合配置中存在该实例;
2. `DeviceManager` 条目的 `id` 与设备配置中的 `id` 完全一致;
3. `DeviceManager` 条目的 `type` 与设备类别一致;
4. `enable: true`
5. 厂商 SDK、权限、串口/CAN/USB 和网络参数已经准备完成。
推荐按以下顺序验证:
1. 保持所有设备关闭,验证基础进程和 gRPC reflection
2. 一次只开启一个设备;
3. 检查启动日志和对应设备的单次 RPC
4. 再验证流式接口、并发访问和停止流程;
5. 最后启用 QUIC 媒体轨道。
### 6.4 启用 QUIC 注册与心跳
QUIC 必须同时在两处开启:
1. 在 [`task_manager.pb.txt`](cmvr-es/config/manager/task_manager.pb.txt) 中设置 `quic_edge` 任务的 `enable: true`
2. 在 [`quic_edge_task.pb.txt`](cmvr-es/config/tasks/quic_edge_task/quic_edge_task.pb.txt) 中设置 `quic_edge.enable: true`
随后配置:
- `server_host``server_port`
- ALPNv1 默认且要求精确匹配 `cmvr-quic-edge/1`
- 唯一的 `node_id`
- 心跳、ACK 超时和重连参数;
- 对外声明的 gRPC endpoint
- CA、服务端名称以及可选的客户端证书和私钥。
`heartbeat_interval_ms` 是边缘端配置的本地心跳周期,允许范围为
2503600000 ms。Gateway 在 `NodeRegisterResponse` 中返回 `0` 时保留该值;
返回非零值时使用 Gateway 协商值。需要完全按配置文件频率测试时,让测试
Gateway 返回 `0`
每次心跳还会携带一份 `DeviceManagerSnapshot`
- 管理器名称、版本和描述;
- 所有已启用设备,包括创建或初始化失败的已启用设备;
- 设备 ID、稳定的设备类别、具体实现名和是否启用
- Manager 生命周期、健康状态、是否确认异常、错误信息和更新时间。
禁用设备只保留在边缘端 DeviceManager 的本地快照中,不通过 heartbeat 上报。
已启用设备的创建、初始化或启动失败仍会保留在 heartbeat 设备表中。健康状态
`UNSPECIFIED` 表示当前设备后端没有提供可信的内存健康探针,不能解释为健康;
`has_error=false` 也只表示当前没有确认到错误。
当前 Camera、Microphone 和 DexHand 抽象已适配各自的内存状态AGV、Arm、
MotorSystem 等类别在实现无阻塞缓存探针前会诚实上报 `UNSPECIFIED`,但
DeviceManager 已知的 create/init/start/stop 失败仍会通过 `manager_state=ERROR`
`has_error=true` 上报。
媒体轨道可以为空。零轨道模式仍会完成节点注册、IP、设备表和心跳上报适合先联调在线状态。
正式环境应使用:
```text
tls.allow_insecure: false
tls.ca_file: "certs/quic_gateway_ca.pem"
tls.server_name: "< server_host 匹配的服务端名称>"
```
客户端证书和私钥必须同时配置或同时省略。`allow_insecure: true` 只允许用于开发联调。
### 6.5 启用 QUIC 媒体轨道
启用媒体前必须先启用对应设备,然后在 QUIC 配置中增加唯一的 `track_id`
```protobuf
tracks {
track_id: 1
source_kind: SOURCE_KIND_CAMERA
device_id: "right_hand_cam"
source_track_id: "right_hand_cam/video/color"
enable: true
}
tracks {
track_id: 2
source_kind: SOURCE_KIND_MICROPHONE
device_id: "mic1"
source_track_id: "mic1/audio/main"
enable: true
}
```
默认轨道命名规则:
- 摄像头彩色流:`<device_id>/video/color`
- 麦克风主音频流:`<device_id>/audio/main`。
`maximum_frame_bytes`、`maximum_datagram_bytes` 和 `datagram_send_queue_depth` 需要联合配置。一个完整帧的全部 DATAGRAM 分片必须能够作为批次进入发送队列,否则该帧会整体丢弃。
> [!NOTE]
> `grpc_endpoint_tls` 当前只描述“上报给平台的 gRPC 地址是否声称使用 TLS”它不会自动让本项目 gRPC Server 启用 TLS。
## 7. 运行与测试
### 7.1 启动
使用安装目录内的默认配置:
```bash
./output/bin/cmvr_es
```
使用外部配置根:
```bash
./output/bin/cmvr_es /etc/cmvr-es/cmvr_es.pb.txt
```
程序不接受更多位置参数。正常停止使用 `Ctrl+C`、`SIGINT` 或 `SIGTERM`
### 7.2 gRPC 快速验证
仓库提供 x86_64 版 grpcurl 资产:
```bash
tar -xzf assets/grpcurl_1.8.7_linux_x86_64.tar.gz -C /tmp
/tmp/grpcurl \
-plaintext \
127.0.0.1:50052 \
list
/tmp/grpcurl \
-plaintext \
127.0.0.1:50052 \
list cmvr.api.ArmService
```
系统信息请求:
```bash
/tmp/grpcurl \
-plaintext \
-d '{}' \
127.0.0.1:50052 \
cmvr.api.SystemService/GetSystemInfo
```
reflection 正常只说明 gRPC Server 已启动。具体设备 RPC 仍可能因为设备未启用、初始化失败或硬件离线而失败。
### 7.3 无硬件自动测试
```bash
ctest --test-dir build --output-on-failure
```
当前登记到 CTest 的核心测试:
| 测试 | 覆盖内容 |
| --- | --- |
| `hikvision_camera_callback_test` | Hikvision 回调生命周期和并发安全 |
| `media_source_hub_test` | 多订阅者、环形队列、丢帧和停止语义 |
2026-07-23 14:22:09 +08:00
| `grpc_camera_stream_policy_test` | gRPC 相机流积压阈值、帧龄判断和默认值 |
| `quic_edge_protocol_test` | QUIC 控制帧、DATAGRAM 和 fake transport |
| `quic_edge_task_test` | QUIC 任务配置和生命周期 |
| `cmvr_quic_msquic_e2e_test` | 真实 MsQuic loopback、注册/心跳、合成 H.264/AAC 描述和 DATAGRAM 重组 |
| `cmvr_es_quic_process_smoke_test` | 启动完整 `cmvr_es` 进程,以临时无设备配置连接本机测试 Gateway |
后两项仅在真实 MsQuic、`BUILD_TESTING=ON` 和 `openssl` 命令可用时登记;
完整进程冒烟还要求 Python 3.10 或更高版本。它们使用 CMake 在构建目录生成的短期 loopback
证书,不修改 `output/bin/config/`,也不要求连接摄像头或麦克风。可只运行真实
QUIC 验证:
```bash
ctest \
--test-dir build-quic \
--output-on-failure \
-R 'cmvr_quic_msquic_e2e_test|cmvr_es_quic_process_smoke_test'
```
`cmvr_quic_msquic_e2e_test` 向生产 `MediaSourceHub` 注入合成 H.264 视频和 AAC
音频,验证可靠控制流、两类轨道描述、真实 QUIC DATAGRAM 和完整帧重组。
`cmvr_es_quic_process_smoke_test` 则运行真正的 `cmvr_es` 可执行文件,动态创建
全部设备关闭、只启用 QUIC 的临时配置树,验证节点注册和至少两次心跳 ACK。
仓库中其他以 `_test` 命名的程序可能需要真机、SDK、仿真环境或人工观察不属于默认无设备验证。
### 7.4 进程烟雾测试
默认 gRPC 端口没有被占用时:
```bash
timeout \
--signal=TERM \
--kill-after=2s \
5s \
./output/bin/cmvr_es
```
如果进程保持运行并在 5 秒后由 `timeout` 终止,退出码通常为 `124`,这是烟雾测试的预期结果。测试期间应同时检查日志中是否存在配置、设备初始化或端口绑定错误。
### 7.5 本机 QUIC Gateway 联调
开发用真实 MsQuic Server 位于
[`test/quic_gateway/`](test/quic_gateway/),会验证 QUIC edge v1 控制帧、注册、
心跳、媒体元数据、DATAGRAM 和重组边界。它不包含生产鉴权、持久化、浏览器
转发等平台能力,不能作为生产 Gateway 部署。
CMake 会生成启动包装器,用来补齐构建树中 gRPC 等传递动态库的搜索路径:
```bash
cmake --build build-quic \
--target cmvr_quic_test_gateway cmvr_quic_test_certificate \
-j"$(nproc)"
build-quic/test/quic_gateway/run_cmvr_quic_test_gateway \
--bind 127.0.0.1 \
--port 4433 \
--cert build-quic/test/certs/server.crt \
--key build-quic/test/certs/server.key \
--scenario normal
```
自动验收优先使用上一节的两项 CTest它们会选择空闲 UDP 端口、创建临时配置
并安全关闭进程避免改动部署配置。Gateway 的参数、故障注入场景和 summary
判定字段见 [`test/quic_gateway/README.md`](test/quic_gateway/README.md),两条
E2E 的边界见 [`test/e2e/README.md`](test/e2e/README.md)。
## 8. 生产部署
### 8.1 打包
部署时应分发整个 `output/`,不能只复制 `cmvr_es`
```bash
tar \
--exclude='./log/*' \
-C output \
-czf cmvr-es-linux-x86_64.tar.gz \
.
```
目标主机需要使用相同 CPU 架构,并具有兼容的 Linux/glibc 环境。归档会排除开发主机上的历史日志。安装产物使用相对 RPATH
- `bin/cmvr_es` 从自身目录和 `../lib` 查找共享库;
- `lib/` 中的共享库从自身目录查找依赖;
- 正常部署不依赖系统全局 `LD_LIBRARY_PATH`
`output/` 仍会动态依赖目标系统的 libstdc++、PulseAudio/ALSA、X11、libudev、libusb以及按设备启用的驱动或厂商服务。目标机应安装对应运行时依赖并在启动服务前执行
```bash
ldd -r /opt/cmvr-es/bin/cmvr_es
```
命令输出中不应出现 `not found` 或未解析符号。
### 8.2 推荐目录
```text
/opt/cmvr-es/
├── bin/
├── lib/
└── log/
/etc/cmvr-es/
├── cmvr_es.pb.txt
├── logger/
├── manager/
├── devices/
├── tasks/
└── certs/
```
示例:
```bash
sudo install -d -m 0755 /opt/cmvr-es
sudo tar -C /opt/cmvr-es -xzf cmvr-es-linux-x86_64.tar.gz
sudo install -d -m 0750 /etc/cmvr-es
sudo cp -a /opt/cmvr-es/bin/config/. /etc/cmvr-es/
sudo install -d -m 0750 /etc/cmvr-es/certs
sudo install -d -m 0750 /opt/cmvr-es/log
```
外部配置引用模型时,建议使用 `/opt/cmvr-es/bin/model/...` 的绝对路径,避免配置根切换后产生歧义。
### 8.3 systemd
创建专用用户,并按实际硬件授予最小权限:
```bash
sudo groupadd --system cmvr-es
sudo useradd \
--system \
--gid cmvr-es \
--home /opt/cmvr-es \
--shell /usr/sbin/nologin \
cmvr-es
sudo usermod -aG video,audio,dialout cmvr-es
sudo chown -R cmvr-es:cmvr-es /opt/cmvr-es/log
sudo chown -R root:cmvr-es /etc/cmvr-es
sudo chmod -R g+rX /etc/cmvr-es
# 按需安装 QUIC 信任链、客户端证书和私钥
sudo install -o root -g cmvr-es -m 0640 \
/secure/path/quic_gateway_ca.pem \
/etc/cmvr-es/certs/quic_gateway_ca.pem
sudo install -o root -g cmvr-es -m 0640 \
/secure/path/cmvr_edge_cert.pem \
/etc/cmvr-es/certs/cmvr_edge_cert.pem
# 私钥只允许实际运行进程的用户读取
sudo install -o cmvr-es -g cmvr-es -m 0600 \
/secure/path/cmvr_edge_key.pem \
/etc/cmvr-es/certs/cmvr_edge_key.pem
```
根据设备情况还可能需要配置 udev、CAN 网卡、USB 权限和厂商 SDK 服务。不要为了方便直接让进程长期以 root 身份运行。
`/etc/systemd/system/cmvr-es.service` 示例:
```ini
[Unit]
Description=CMVR Edge System
Wants=network-online.target
After=network-online.target
[Service]
Type=simple
User=cmvr-es
Group=cmvr-es
WorkingDirectory=/opt/cmvr-es/bin
ExecStart=/opt/cmvr-es/bin/cmvr_es /etc/cmvr-es/cmvr_es.pb.txt
Restart=on-failure
RestartSec=3
KillSignal=SIGTERM
LimitNOFILE=65536
[Install]
WantedBy=multi-user.target
```
启用服务:
```bash
sudo systemctl daemon-reload
sudo systemctl enable --now cmvr-es
sudo systemctl status cmvr-es
journalctl -u cmvr-es -f
```
应用文件日志默认位于 `/opt/cmvr-es/log/cmvr_es.log`,终端路由的日志同时进入 journald。
### 8.4 网络与防火墙
| 方向 | 默认端口 | 协议 | 用途 |
| --- | ---: | --- | --- |
| Edge -> QUIC Gateway | `4433` | UDP / QUIC | 节点注册、心跳、IP 和实时媒体 |
| Platform -> Edge | `50052` | TCP / gRPC | 设备控制与状态查询 |
| Browser -> Gateway | 平台定义,通常 `443` | HTTPS / WebTransport / WebRTC | Web 控制和媒体播放 |
如果平台根据心跳上报的 gRPC endpoint 回连边缘节点,平台网络必须能够访问该 TCP 地址。IP 上报成功不代表该地址一定具备路由、防火墙和 NAT 可达性。
## 9. 平台端接入
### 9.1 gRPC 接入
gRPC 契约位于 [`protos/cmvr/api/`](protos/cmvr/api/),通用消息位于 [`protos/cmvr/msgs/`](protos/cmvr/msgs/) 和 [`protos/cmvr/common/`](protos/cmvr/common/)。
当前实际注册的服务:
| 服务 | 主要用途 |
| --- | --- |
| `cmvr.api.SystemService` | 系统信息、状态和 StopAll |
| `cmvr.api.CameraService` | 相机状态、图片、录像和 RGB/Depth/RGBD 流 |
| `cmvr.api.SpeakerService` | 播放、流式输入、暂停、音量 |
| `cmvr.api.MicPhoneService` | 录音、音量和服务端音频流 |
| `cmvr.api.DexHandService` | 灵巧手控制、传感器数据和双向流 |
| `cmvr.api.BioHeadService` | 表情、状态、急停和双向流 |
| `cmvr.api.ArmService` | 上使能、运动、伺服、停止和运动学 |
| `cmvr.api.AgvService` | 导航、速度、地图、建图和地图流 |
| `cmvr.api.HlcService` | 触屏 / HLC 交互 |
`test_service.proto` 存在于协议目录,但当前没有注册到 gRPC Server平台不能把它当作在线服务。
以下 RPC 虽然存在于 proto但当前实现不可用
- `cmvr.api.SystemService/UpdateParams` 固定返回业务失败,提示该能力不再支持;
- `cmvr.api.ArmService/getPoseMatrix` 返回 gRPC `UNIMPLEMENTED`
- `cmvr.api.ArmService/computeForwardKinematics` 返回 gRPC `UNIMPLEMENTED`
平台生成客户端后仍应以服务端实现状态为准不能把“proto 中存在”直接等同于“运行时已支持”。
流式方向:
- Camera RGB、Depth、RGBD双向流
- DexHand Sensor、BioHead Expression双向流
- Microphone Audio、AGV Map服务端流
- Speaker Audio客户端流。
Java 平台建议通过 Gradle/Maven 的 Protobuf 插件生成消息类和 grpc-java stub并在平台仓库中锁定 `protoc`、`protoc-gen-grpc-java`、`protobuf-java` 和 grpc-java 版本。生成时应保持 `protos/` 为 import 根目录。
当前 proto 没有统一声明 `java_package``java_multiple_files`。平台接入时不要自行修改包名后仍声称与线协议一致;如要补充 Java options应由边缘端与平台端在同一协议变更中完成并重新生成两端代码。
平台调用流程建议:
1. 通过 QUIC 注册/心跳维护节点在线表;
2. 保存最新 `node_id`、`session_id`、`observed_source_ip`、本地接口、advertised gRPC endpoint 和 `DeviceManagerSnapshot`
3. 按平台网络策略选择实际可达的 gRPC 地址;
4. 建立并复用 gRPC channel
5. 为控制请求设置 deadline、幂等策略、错误映射和审计
6.`kind` 做机器分支,用 `type_name` 做展示和诊断;
7. 区分 `UNSPECIFIED`、`DISABLED`、`FAULT`,不要因为 QUIC 在线或 `has_error=false` 就假设设备健康。
> [!WARNING]
> 当前 gRPC Server 使用 `grpc::InsecureServerCredentials()`,没有 TLS、认证和授权。只能部署在可信内网、VPN、服务网格或受保护的网关后方。
### 9.2 Java QUIC Gateway 接入
边缘端是 QUIC client平台网关是 QUIC server。本仓库没有提供 Java 网关实现。
Java 网关只需要为 QUIC 控制面生成普通 Protobuf Java 类,不需要生成 gRPC stub
```bash
mkdir -p platform-gateway/src/main/java
output/bin/protoc \
-I protos \
--java_out=platform-gateway/src/main/java \
protos/cmvr/quic_edge/v1/quic_edge.proto
```
完整协议见 [`QUIC edge v1 wire format`](protos/cmvr/quic_edge/v1/README.md)。网关至少需要实现以下行为。
#### 可靠控制流
边缘端建立 QUIC 连接后打开一个双向可靠流。消息格式为:
```text
uint32_be protobuf_length
protobuf EdgeControlEnvelope
```
接收端不能把一次 QUIC stream callback 当作一条完整消息,必须缓存并按长度前缀拆包。长度不包含 4 字节前缀。
会话顺序:
1. Edge 首先发送 `NodeRegisterRequest`
2. Gateway 返回 `accepted=true``session_id` 非空的 `NodeRegisterResponse`
3. Edge 开始发送 `NodeHeartbeat`
4. Gateway 返回相同 `session_id` 和精确 `acknowledged_sequence``NodeHeartbeatAck`
5. 有媒体轨道时Edge 先调用可靠流发送 `MediaSessionOpen``MediaTrackDescriptor`,再调用 DATAGRAM 发送媒体;
6. 编码参数或 generation 变化时Edge 同样先发送新的 descriptor再发送对应 generation 的 DATAGRAM。
QUIC reliable stream 与 DATAGRAM 之间没有跨通道到达顺序保证。即使 Edge 按上述顺序调用发送,媒体 DATAGRAM 仍可能先于 session/descriptor 元数据到达 Gateway。Gateway 对未知 `session_epoch``codec_generation_token` 的 DATAGRAM 必须限量暂存或直接丢弃,不能无界等待,也不能假设可靠元数据一定先到。
实现细节:
- `protocol_version` 当前为 `1`
- `message_sequence` 在每个方向独立严格递增,允许有间隔;
- Edge 当前第一条控制消息的 sequence 是 `0`,网关不能假设从 `1` 开始;
- 默认控制响应超时为 1 秒,注册或心跳 ACK 缺失/不匹配会触发重连;
- 每次心跳包含最新接口地址、advertised gRPC endpoint 和按设备 ID 排序的
`DeviceManagerSnapshot`
- `DeviceManagerSnapshot.devices` 只包含已启用设备;已启用但创建、初始化或启动
失败的设备仍会上报,禁用设备不会上报;
- `DEVICE_HEALTH_STATUS_UNSPECIFIED` 不等于健康;
- Java 对 `DeviceKind`、`ManagedDeviceState` 和 `DeviceHealthStatus`
`switch` 必须保留 `UNRECOGNIZED/default` 分支;如需转存未来版本值,保存
`getKindValue()` 等原始整数,不能把未知枚举降级成 `HEALTHY`
- `heartbeat_interval_ms` 由边缘配置提供本地值,注册响应返回 `0` 时保留本地值,
非零时采用 Gateway 协商值;
- `observed_source_ip` 必须从已认证的 QUIC peer 地址推导,不能复制客户端上报字段;
- 当前 Edge 接收方向实现支持 `NodeRegisterResponse`、`NodeHeartbeatAck` 和 `ProtocolError`
- 不要依赖 Gateway 下发 `MediaSessionClose` 来控制 Edge本版本尚未实现该入站行为。
#### 媒体 DATAGRAM
每个媒体 DATAGRAM 以固定 64 字节、网络字节序的 `CMQD` header 开始。字段和偏移详见 [`QUIC edge v1 wire format`](protos/cmvr/quic_edge/v1/README.md#media-datagram-wire-format)。
网关需要:
-`(session_epoch, track_id, frame_sequence)` 作为重组键;
- 校验 magic、版本、header size、fragment count、offset 和 frame size
- 在播放 deadline 到达时丢弃不完整帧,不等待旧帧阻塞新帧;
- 新的 `session_epoch` 到来时清除旧连接的残留分片;
- 根据 `MediaTrackDescriptor``codec_generation_token` 选择解码参数;
- 容忍 DATAGRAM 先于可靠元数据到达,对未知 session/generation 有界暂存或丢弃;
- 根据 keyframe 和 discontinuity 标志重置解码/转封装状态;
- 为每个节点和轨道设置内存、帧大小、并发和超时上限。
当前媒体模型支持 H.264、H.265、Opus、AAC 和 PCM S16LE。实际可用编码取决于具体设备和配置。
### 9.3 浏览器接入
浏览器不能直接连接 `cmvr-quic-edge/1` 自定义 ALPN也不承担节点注册服务。推荐拓扑
```text
cmvr_es
├── QUIC reliable stream + DATAGRAM ──> Platform QUIC Gateway
│ │
│ ├── WebTransport
│ ├── WebRTC
│ ├── WebSocket + MSE
│ └── HTTP/HLS 等平台接口
└── gRPC control <──────────────────── Platform Service
└── REST / WebSocket / gRPC-Web
Browser
```
平台网关负责:
- 终止 CMVR QUIC 和维护节点在线状态;
- 重组音视频帧;
- 根据浏览器兼容性转封装或转码;
- 对 Web 用户执行认证、授权、租户隔离和设备 ACL
- 处理慢浏览器、断线、关键帧请求和背压;
- 将浏览器控制请求转换为平台到边缘端的 gRPC
- 向浏览器隐藏设备密码、证书和内部 gRPC 地址。
H.265、AAC 等格式的浏览器支持度并不统一。平台不能假设收到的原始编码能够在所有浏览器中直接播放,应根据目标浏览器选择 WebRTC codec、fMP4/MSE、转码或降级策略。
### 9.4 平台联调清单
建议按以下顺序联调:
1. Edge 保持零媒体轨道,验证 UDP、TLS、ALPN 和注册;
2. 验证 `observed_source_ip`、本地接口列表和 gRPC endpoint 更新;
3. 验证精确 heartbeat ACK、超时和重连
4. 使用 grpcurl 或 Java client 验证平台到 Edge 的 gRPC 可达性;
5. 启用一个 fake/真实摄像头轨道,验证 descriptor 和 DATAGRAM 重组;
6. 模拟丢包、乱序、重复、超时和 Edge 重连;
7. 再启用音频、多轨道和多节点;
8. 最后接入浏览器转封装、权限和运维监控。
平台应至少监控:
- 节点在线数、session 变化和最后心跳时间;
- QUIC 握手/注册失败原因;
- 心跳 RTT、ACK 超时和重连次数;
- 每节点已启用设备总数、异常数、未知健康数以及状态更新时间;
- 每轨道 DATAGRAM、完整帧、丢帧和重组超时
- descriptor/generation 变化;
- gRPC 调用时延、错误码和设备离线状态;
- 网关内存、队列深度和浏览器订阅数。
## 10. 开发扩展指南
### 10.1 新增同类设备后端
以新增摄像头后端为例:
1. 在对应 config proto 的 `oneof backend` 中增加后端配置;
2. 实现已有摄像头抽象接口,不在设备类中加入 gRPC/QUIC 连接状态;
3. 在类别工厂中增加具体后端创建分支;
4. 增加 CMake target 和 SDK 查找;
5. 在设备集合配置中增加实例;
6.`DeviceManager` 配置中增加相同 ID 的条目;
7. 实时媒体设备补齐编码描述、时间戳、序列号、generation 和关键帧能力;
8. 增加不依赖真机的生命周期与并发测试,再进行真机测试。
设备抽象应保持薄且与具体控制器协议无关。控制器专有结构、错误码和报文字段应留在具体后端。
### 10.2 新增设备类别
除上述工作外,还需要:
- 增加新的抽象接口与类别工厂;
- 扩展 `DeviceConfigEntry::DeviceType`
- 在全局 `DeviceFactory` 注册 creator
-`DeviceManager` 中增加类型实例化和日志名称;
- 如需平台调用,增加 API proto、gRPC service 实现,并在 `GrpcServerTask` 中注册;
- 明确设备关闭、急停和进程退出时的生命周期语义。
### 10.3 新增任务
1. 增加任务配置 proto 和默认配置;
2. 实现 `Task` 接口;
3. 明确任务是 `PERIODIC_STEP` 还是 `BLOCKING_SERVICE`
4. 扩展 `TaskType` 并注册 TaskFactory creator
5.`TaskManager` 默认配置中增加任务;
6. 增加 CMake target 和 init/start/stop 测试。
### 10.4 新增媒体传输协议
后续接入 WebRTC、SRT 或其他协议时:
1. 直接订阅 `MediaSourceHub`
2. 每个消费线程创建独立 `Subscription`
3.`TrackDescriptor` 映射为目标协议的 codec metadata
4. 根据 dropped count、generation 和 frame 标志传播 discontinuity
5. H.264/H.265 丢帧后请求关键帧;
6. 协议断开或停止时及时 reset subscription
7. 不要修改摄像头或麦克风接口来保存协议连接状态;
8. 增加慢消费者、环形队列覆盖、设备重启和并发停止测试。
这一约束保证 MediaSourceHub 能快速适配其他协议,同时设备层保持稳定。
### 10.5 修改 Proto 的兼容性要求
- 不复用已经发布字段的 tag
- 删除字段时使用 `reserved`
- 优先增加可选字段,不随意改变现有语义;
2026-07-23 14:22:09 +08:00
- `FrameData` 新增的采集时间、源序列、PTS/DTS、SDK 帧率/时间戳/帧号字段保持
wire compatibilityJava 平台需重新生成 protobuf/gRPC 代码后才能读取新字段;
- gRPC API 和 QUIC v1 线协议分别维护版本兼容;
- 修改 QUIC DATAGRAM 固定头时必须提升协议版本并同步平台网关;
- 提交前重新生成并验证 C++、Java 代码;
- 协议文档、示例配置和自动测试必须与代码同一提交更新。
## 11. 安全说明
生产环境必须关注:
- gRPC 当前是明文且无认证授权,不能直接暴露到公网;
- QUIC 正式环境必须验证服务端证书,必要时使用 mTLS
- `allow_insecure` 仅用于开发;
- QUIC schema 当前没有业务 token 或设备 ACL应用级身份和授权需要平台设计
- 配置可以包含设备账号、密码、私钥和生产地址,不要提交真实凭据;
- 私钥和生产配置建议权限为 `0600`,由专用运行用户读取;
- 平台应限制允许控制的用户、节点、设备和 RPC
- 机械臂、AGV 等控制必须设置 deadline、速度/位置限制、急停和审计;
- 设备控制安全不能只依赖网络协议成功,仍需设备侧限位和安全控制器。
## 12. 已知限制与排障
### 12.1 首次配置提示找不到 protoc 或 grpc_cpp_plugin
确认已执行[首次构建工具引导](#51-首次构建工具引导),并在同一 shell 中保留 gRPC 依赖库的 `LD_LIBRARY_PATH` 后重新配置和编译。
### 12.2 CMake 显示 MsQuic not found
这不会影响 gRPC 和 fake transport 测试,但当前产物不能启动 QUIC 任务。检查:
- `CMVR_ENABLE_MSQUIC_BACKEND=ON`
- 已成功执行
`script/build_msquic.sh --arch <arch> --version 2.5.9`
- `CMVR_ARCH` 与依赖目录架构一致;
- `CMVR_MSQUIC_VERSION=2.5.9`
- `dependency/<arch>/third_party/msquic/v2.5.9/include/msquic.h` 存在;
- `dependency/<arch>/third_party/msquic/v2.5.9/lib/libmsquic.so` 存在;
- `BUILD-INFO.txt` 中的 `msquic_version``dependency_arch` 与本次构建一致。
需要保证构建不会悄悄退回占位后端时,增加
`-DCMVR_REQUIRE_MSQUIC=ON`。系统路径默认不会参与搜索;只有明确接受不可复现
的主机依赖时才设置 `-DCMVR_ALLOW_SYSTEM_MSQUIC=ON`。使用自定义可信前缀时,应
把绝对路径传给 `CMVR_MSQUIC_ROOT`,不必打开系统 fallback。
### 12.3 启动后 gRPC connection refused
检查:
```bash
ss -lntp | grep 50052
journalctl -u cmvr-es -n 200 --no-pager
2026-03-05 16:15:13 +08:00
```
2026-03-11 16:23:23 +08:00
常见原因:
- `grpc_server` 任务被关闭;
- 端口被其他进程占用;
- 主进程因配置或设备初始化错误提前退出;
- 平台访问的是不可路由的心跳上报地址;
- 主机或容器防火墙未开放 TCP 50052。
### 12.4 reflection 正常但设备 RPC 失败
reflection 只验证服务注册。继续检查:
- 设备配置和 `DeviceManager` 条目是否都启用;
- 两处设备 ID 是否一致;
- 设备 SDK、串口、CAN、USB 或网络是否可用;
- 运行用户是否具备权限;
- 日志中设备 `init()` 是否成功。
### 12.5 QUIC 持续重连
按顺序检查:
1. Edge 到 Gateway 的 UDP 端口是否可达;
2. ALPN 是否精确为 `cmvr-quic-edge/1`
3. CA、服务端证书和 `server_name` 是否匹配;
4. Gateway 是否返回 accepted 且非空 session ID
5. heartbeat ACK 的 session 和 sequence 是否精确匹配;
6. Gateway 是否在默认 1 秒控制响应超时内回复;
7. 是否错误地把 stream callback 边界当作消息边界。
### 12.6 找不到共享库或出现未解析符号
检查:
```bash
ldd output/bin/cmvr_es
ldd -r output/bin/cmvr_es
find output/lib -maxdepth 1 -type f -name '*.so*' -print
```
部署时确认复制了完整 `output/`,并保持 `bin/``lib/` 的相对位置。不要混用不同构建、不同架构或不兼容 glibc 的产物。
### 12.7 安装后现场配置被覆盖
普通构建中 `CMVR_INSTALL_DEFAULT_RUNTIME_ASSETS=ON`,因此
`cmake --install` 会重建 `output/bin/config/``output/bin/model/`。生产配置
应保存在 `/etc/cmvr-es/` 等外部目录,并通过命令行显式传入根配置。
本机已有调试配置,只想更新 `cmvr_es`、MsQuic 和其他动态库时,请在该构建目录
首次配置或重新配置时设置:
```bash
cmake -S . -B build-quic \
-DCMVR_INSTALL_DEFAULT_RUNTIME_ASSETS=OFF
cmake --build build-quic -j"$(nproc)"
cmake --install build-quic
```
该选项只跳过默认 `config/``model/` 的安装,不会阻止二进制和
`output/lib/` 更新。
### 12.8 Git submodule 初始化失败
当前仓库不应执行旧文档中的 `git submodule update --init --recursive`。构建依赖来自已跟踪的 `dependency/<arch>/third_party/``.gitmodules` 与 `assets/toppra` 的历史不一致需要单独整理。
### 12.9 当前未提供的能力
本仓库当前不包含:
- Java QUIC Gateway
- 浏览器播放器或 Web 控制前端;
- Dockerfile / 容器镜像;
- 可直接安装的 systemd unit
- 标准 gRPC health service
- gRPC TLS、认证和授权
- 完整可复现的 ARM 构建。
这些能力不能仅通过修改配置开启,需要在对应工程中实现和验证。
## License
2026-03-11 16:23:23 +08:00
许可证内容见 [`LICENSE`](LICENSE)。同时请遵守 `dependency/` 中各三方组件和厂商 SDK 的独立许可要求。