11 KiB
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,也不是浏览器服务器。
SystemService 设备清单
SystemService/GetDeviceList 返回 DeviceManager 的当前只读快照,只包含
enabled=true 的设备。启用但创建、初始化、启动或健康检查失败的设备仍会返回,
并通过 manager_state、health、has_error 和 error_message 描述异常。
接口同时返回稳定的 device_type 和仅用于展示/诊断的具体 type_name;调用方
不得使用 type_name 做设备类别判断。
该 RPC 不修改配置、不动态注册设备,也不触发设备生命周期操作。启用 reflection 后可直接查询:
grpcurl -plaintext \
-d '{}' \
127.0.0.1:50052 \
cmvr.api.SystemService/GetDeviceList
MotorService
MotorService 将 gRPC 电机命令适配到已经由 DeviceManager 创建的
MotorManager 和 AbstractMotor,不直接持有现场总线或厂商驱动。
关键文件:
- Proto:
../../protos/cmvr/api/motor_service.proto和../../protos/cmvr/api/motor_command.proto - 实现:
grpc/include/grpc_motor_service.h和grpc/src/grpc_motor_service.cpp - 注册:
../task/grpc_server_task/src/grpc_server_task.cpp - 单元测试:
grpc/tests/grpc_motor_service_test.cpp
服务按单电机仲裁。同步 Profile 命令、Cyclic Position/Velocity 双向流、
setEnabled、状态读取和软件 emergencyStop 共用同一控制权状态:
- 同一电机已有 owner 时拒绝新的控制调用;
- cyclic 流首帧必须是
open,后续 setpoint sequence 必须严格递增; - reader 使用 latest-wins 邮箱,客户端必须持续并发读取反馈;
- 取消、deadline、watchdog、非法帧、后端拒绝或写失败都会触发 Quick Stop;
- 任何清理 Quick Stop 未确认时,服务进入 fail-closed 锁存;
- 只有成功执行
setEnabled(true)才解除服务内软件急停锁存; - 服务层 Quick Stop 和
emergencyStop都不具备功能安全等级。
AUBO 控制柜 IO 不经过 MotorService,由
ArmService/ExecuteJsonCommand 转发到目标 RobotArm。厂商命令和安全约束见
AUBO 控制柜 IO。
旧的 SystemService/ExecuteJsonCommand 已移除;相机 PTZ 应使用类型化的
CameraService/ControlPtz。
新增 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
还必须修改:
../task/grpc_server_task/include/grpc_server_task.h../task/grpc_server_task/src/grpc_server_task.cpp
完成:
- 增加 service owner;
- 在 start 中构造;
- 调用
builder.RegisterService(...); - 在
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 后端
- 实现
QuicTransport完整接口; - 明确 callback 所在线程;
- stop/close 后不得再访问已销毁 service;
QUEUED表示 transport 接管待发送数据;WOULD_BLOCK或ERROR不得接管任何字节;- DATAGRAM batch 本地准入必须原子;
- native send 部分失败时关闭连接并清理 session;
- 添加 fake transport 故障注入测试;
- 在默认 transport factory 中显式选择后端。
新增控制消息
不能只修改 Proto,还要同步:
- envelope 构造与发送;
- 入站 dispatch;
- 合法状态和消息时序;
- message sequence 校验;
- session ID 和 heartbeat sequence 校验;
- reconnect 后状态清理;
- Java Gateway 对端;
- framing、状态机和 fake transport 测试。
当前 Edge 入站只接受:
NodeRegisterResponseNodeHeartbeatAckProtocolError
虽然 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