cmvr-es/cmvr-es/service/README.md

8.7 KiB
Raw Blame History

Service 模块开发指南

service/ 实现边缘端对外协议和设备抽象之间的适配。Service 负责解析请求、查找设备、转换 DTO 和返回结果,不负责创建具体设备后端。

返回项目总览。

当前结构

目录 职责
grpc/ 入站设备控制、状态查询和兼容流式接口
quic_edge/ 边缘端主动连接平台的 QUIC client、控制状态机和媒体 packetizer
quic_edge/tests/ 已登记到 CTest 的 QUIC 协议测试

两个遗留 gRPC client test 位于 grpc/src/*_client_test.cpp,当前没有通过 add_test() 登记。

gRPC 和 QUIC 的职责边界:

  • 机械臂、AGV 等可靠控制继续使用 gRPC;
  • 节点注册、心跳和 IP 上报使用 QUIC reliable stream;
  • 实时音视频使用 QUIC DATAGRAM;
  • quic_edge/ 不是平台 Gateway,也不是浏览器服务器。

新增 gRPC Service

当前没有动态 service registry,必须完成以下全部步骤。

1. 定义 Proto

在 ../../protos/cmvr/api/ 增加或扩展:

  • <domain>_command.proto
  • <domain>_service.proto

import 路径必须相对于 protos/。兼容规则见 ../../protos/README.md。

2. 实现 Service

目录约定:

service/grpc/
├── include/grpc_example_service.h
└── src/grpc_example_service.cpp

实现类继承生成的:

cmvr::api::ExampleService::Service

通过已初始化的 DeviceManager 获取抽象设备。不要在 service 中创建厂商 SDK 对象,不要绕过设备 factory。

3. 加入 service target

将实现 .cpp 加入 CMakeLists.txt 的 service library,并声明最小依赖。

4. 注册到 GrpcServerTask

还必须修改:

完成:

  1. 增加 service owner;
  2. 在 start 中构造;
  3. 调用 builder.RegisterService(...);
  4. 在 clearServices() 中 reset。

漏掉该步骤时项目可能编译成功,但服务不会出现在 reflection 或运行时。

5. 测试

  • 直接测试 service handler 或启动临时 gRPC server;
  • 覆盖设备不存在、类型不匹配、设备错误和取消;
  • 使用 grpcurl/reflection 验证服务全名;
  • 流式 RPC 覆盖客户端断开和慢消费者;
  • 在 CMake 中使用 if(BUILD_TESTING) 包裹测试目标,并通过 add_test() 登记。

grpc_arm_client_test 和 grpc_hlc_client_test 是未登记到 CTest 的历史可执行文件,不能代表默认自动覆盖。

gRPC 实现约束

错误语义

当前历史服务存在两种风格:

  • gRPC status 返回 OK,业务失败写入 Feedback header;
  • 使用非 OK gRPC status 表达 transport/API 失败。

扩展已有服务时保持其兼容语义。新增服务必须在设计时明确:

  • 哪些错误使用 gRPC status;
  • 哪些错误使用业务 Feedback;
  • 是否允许部分成功;
  • deadline/cancellation 如何映射;
  • 不得同时返回互相矛盾的 transport 和业务状态。

流式 RPC

  • 检查 context->IsCancelled();
  • 检查 Read() / Write() 返回;
  • 使用 RAII 或 MediaSourceHub Subscription 释放 producer lease;
  • 不持有设备状态锁进行网络写;
  • 为 wait/read 使用有限 timeout;
  • 慢客户端不能阻塞设备生产线程;
  • H.264/H.265 丢帧后等待关键帧恢复;
  • gRPC RGB 流在积压超过 camera_stream_max_pending_frames 或帧龄超过 camera_stream_max_frame_age_ms 时主动丢弃旧帧,请求 IDR,并从下一个关键帧恢复。

当前仅 gRPC RGB 和麦克风流使用 MediaSourceHub;Depth/RGBD 仍直接读取设备帧。

gRPC 相机实时流默认最多保留 2 帧积压、最大允许 250 ms 帧龄。两个配置项填 0 时使用上述默认值。该策略以低延迟为目标,不保证每个视频帧都到达客户端;控制命令 仍由普通 gRPC RPC 承担。

FrameData 附带 capture_utc_ns、source_sequence、pts、dts、 source_fps、source_timestamp 和 source_frame_number。平台端可用采集时间 与接收时间的差值区分设备、网络、服务端写阻塞和客户端解码/渲染队列延迟。新增字段 保持 protobuf wire compatibility,旧客户端可以继续连接,但需要重新生成代码后才能 读取这些诊断字段。

当前安全状态

GrpcServerTask 使用同步 grpc::ServerBuilder 和 grpc::InsecureServerCredentials()。reflection 由配置控制。当前没有 gRPC TLS、认证、授权或标准 health service。

QUIC 配置中的 grpc_endpoint_tls 只是上报字段,不会启用 gRPC TLS。

扩展 QUIC Edge

关键层次:

层 主要文件
控制流 framing quic_edge/src/control_framing.cpp
DATAGRAM 固定头和分片 quic_edge/src/datagram_packetizer.cpp
会话和状态机 quic_edge/src/quic_edge_service.cpp
传输抽象 quic_edge/include/quic_transport.h
MsQuic 后端 quic_edge/src/msquic_transport.cpp
设备媒体适配 quic_edge/src/quic_edge_device_adapter.cpp
DeviceManager 心跳适配 quic_edge/src/quic_edge_device_adapter.cpp

线协议见 ../../protos/cmvr/quic_edge/v1/README.md。

新增 Transport 后端

  1. 实现 QuicTransport 完整接口;
  2. 明确 callback 所在线程;
  3. stop/close 后不得再访问已销毁 service;
  4. QUEUED 表示 transport 接管待发送数据;
  5. WOULD_BLOCK 或 ERROR 不得接管任何字节;
  6. DATAGRAM batch 本地准入必须原子;
  7. native send 部分失败时关闭连接并清理 session;
  8. 添加 fake transport 故障注入测试;
  9. 在默认 transport factory 中显式选择后端。

新增控制消息

不能只修改 Proto,还要同步:

  • envelope 构造与发送;
  • 入站 dispatch;
  • 合法状态和消息时序;
  • message sequence 校验;
  • session ID 和 heartbeat sequence 校验;
  • reconnect 后状态清理;
  • Java Gateway 对端;
  • framing、状态机和 fake transport 测试。

当前 Edge 入站只接受:

  • NodeRegisterResponse
  • NodeHeartbeatAck
  • ProtocolError

虽然 Proto 定义了 MediaSessionClose,本版本 Edge 收到它仍会判为 unexpected,不应将其描述为已实现的双向控制能力。

DeviceManager 心跳快照

QuicEdgeService 通过可注入的 DeviceSnapshotProvider 获取协议无关的纯值 快照。生产构造绑定已经初始化的 DeviceManager,fake transport 测试则注入 合成快照,因此协议状态机不需要创建硬件对象或依赖 DeviceManager 单例。 DeviceManager 的本地快照继续保留禁用设备;QUIC wire 映射层仅序列化 enabled=true 的设备。已启用但创建、初始化或启动失败的设备不会被过滤。

心跳线程只读取 Manager 维护的内存状态,不能在这里同步访问厂商 SDK、网络或 设备总线。新增设备健康探针必须实现 AbstractDevice::healthSnapshot() 的 线程安全、无阻塞 I/O 契约;未实现时上报 UNSPECIFIED,不得伪造为健康。 设备异常字符串会限长,整条消息仍受 maximum_control_frame_bytes 约束。

跨 QUIC 通道顺序

Edge 会先调用可靠流发送 session/descriptor,再调用 DATAGRAM 发送媒体,但 QUIC stream 与 DATAGRAM 没有跨通道到达顺序保证。

Gateway 必须容忍 DATAGRAM 先到,对未知 session epoch 或 codec generation 的数据有界暂存或丢弃。

QUIC 测试要求

参考 quic_edge/CMakeLists.txt 和 quic_edge_protocol_test,至少覆盖:

  • 控制消息拆包、粘包和超限;
  • 重复或倒退 sequence;
  • 注册、ACK 超时和重连;
  • DeviceManager 已启用设备过滤、类型/状态映射和 provider 失败隔离;
  • Gateway 返回零心跳周期时采用本地 heartbeat_interval_ms;
  • session epoch 清理;
  • DATAGRAM header 字节序;
  • 分片边界和超大帧;
  • 原子队列准入和背压;
  • 丢帧、generation 和关键帧恢复;
  • stop 与 callback 并发。
cmake --build build --target quic_edge_protocol_test
ctest \
  --test-dir build \
  -R '^quic_edge_protocol_test$' \
  --output-on-failure

提交检查

  • service 不创建具体硬件后端
  • Proto、实现、CMake 和 GrpcServerTask 注册均已更新
  • 错误语义与已有服务兼容
  • deadline、cancel、Read/Write 失败均处理
  • 流式资源通过 RAII 释放
  • gRPC 安全能力没有被配置字段误描述
  • QUIC 状态机与 Gateway 同步更新
  • 协议测试已登记到 CTest