From 08d58b2ccf5cc96f3eadcfc7f6d6fbb9d295ac20 Mon Sep 17 00:00:00 2001 From: linbo <1034003879@qq.com> Date: Tue, 4 Aug 2026 15:20:29 +0800 Subject: [PATCH] feat(quic): add robot ID to registration and heartbeat --- .../quic_edge_task/quic_edge_task.pb.txt | 1 + .../quic_edge/src/quic_edge_service.cpp | 28 +++++++++++------- .../tests/quic_edge_protocol_test.cpp | 29 ++++++++++++++++++- .../quic_edge_config/quic_edge_config.proto | 4 +++ protos/cmvr/quic_edge/v1/README.md | 18 +++++++----- protos/cmvr/quic_edge/v1/quic_edge.proto | 2 ++ 6 files changed, 63 insertions(+), 19 deletions(-) diff --git a/cmvr-es/config/tasks/quic_edge_task/quic_edge_task.pb.txt b/cmvr-es/config/tasks/quic_edge_task/quic_edge_task.pb.txt index fd4b000d..f850dc00 100644 --- a/cmvr-es/config/tasks/quic_edge_task/quic_edge_task.pb.txt +++ b/cmvr-es/config/tasks/quic_edge_task/quic_edge_task.pb.txt @@ -7,6 +7,7 @@ quic_edge { server_port: 4433 alpn: "cmvr-quic-edge/1" node_id: "cmvr-edge" + robot_id: "CN-CMVR-MBLRV1-CHAGAN-20260731-001" software_version: "0.1" # The existing cmvr-es gRPC server remains the robot-control endpoint. "auto" diff --git a/cmvr-es/service/quic_edge/src/quic_edge_service.cpp b/cmvr-es/service/quic_edge/src/quic_edge_service.cpp index bd513457..7145f3a0 100644 --- a/cmvr-es/service/quic_edge/src/quic_edge_service.cpp +++ b/cmvr-es/service/quic_edge/src/quic_edge_service.cpp @@ -217,6 +217,7 @@ void populateDescriptor(const config::QuicEdgeConfig& config, { if (!descriptor) return; descriptor->set_node_id(node_id); + descriptor->set_robot_id(config.robot_id()); descriptor->set_boot_id(boot_id); descriptor->set_software_version(software_version); const auto interfaces = @@ -442,6 +443,10 @@ bool QuicEdgeService::validateConfig(const config::QuicEdgeConfig& config, setError(error, "QUIC edge task id is empty"); return false; } + if (config.robot_id().empty()) { + setError(error, "QUIC edge robot_id is empty"); + return false; + } if (config.server_host().empty()) { setError(error, "QUIC edge server_host is empty"); return false; @@ -1097,6 +1102,7 @@ bool QuicEdgeService::sendNodeRegistration(std::string* error) << ", message_sequence=" << envelope.message_sequence() << ", payload_bytes=" << serialized.size() << ", node_id=" << node.node_id() + << ", robot_id=" << node.robot_id() << ", boot_id=" << node.boot_id() << ", grpc_endpoint=" << node.grpc_endpoint().host() << ':' << node.grpc_endpoint().port() @@ -1145,6 +1151,7 @@ bool QuicEdgeService::sendHeartbeat(const std::uint64_t sequence, envelope.set_message_sequence(control_message_sequence_++); auto* heartbeat = envelope.mutable_node_heartbeat(); heartbeat->set_node_id(node_id_); + heartbeat->set_robot_id(config_.robot_id()); heartbeat->set_boot_id(boot_id_); heartbeat->set_session_id(session_id); heartbeat->set_sequence(sequence); @@ -1163,16 +1170,17 @@ bool QuicEdgeService::sendHeartbeat(const std::uint64_t sequence, return false; } if (!sendControlEnvelope(serialized, error)) return false; - // CMVR_LOG(INFO) << "[QuicEdgeService] sent NodeHeartbeat" - // << ", message_sequence=" << envelope.message_sequence() - // << ", payload_bytes=" << serialized.size() - // << ", session=" << session_id - // << ", heartbeat_sequence=" << sequence - // << ", grpc_endpoint=" << heartbeat->grpc_endpoint().host() - // << ':' << heartbeat->grpc_endpoint().port() - // << ", interfaces=" << heartbeat->local_interfaces_size() - // << ", devices=" << heartbeat->device_manager().devices_size() - // << ", sent_at_unix_ms=" << sent_at_unix_ms; + CMVR_LOG(INFO) << "[QuicEdgeService] sent NodeHeartbeat" + << ", message_sequence=" << envelope.message_sequence() + << ", payload_bytes=" << serialized.size() + << ", robot_id=" << heartbeat->robot_id() + << ", session=" << session_id + << ", heartbeat_sequence=" << sequence + << ", grpc_endpoint=" << heartbeat->grpc_endpoint().host() + << ':' << heartbeat->grpc_endpoint().port() + << ", interfaces=" << heartbeat->local_interfaces_size() + << ", devices=" << heartbeat->device_manager().devices_size() + << ", sent_at_unix_ms=" << sent_at_unix_ms; return true; } diff --git a/cmvr-es/service/quic_edge/tests/quic_edge_protocol_test.cpp b/cmvr-es/service/quic_edge/tests/quic_edge_protocol_test.cpp index 845191d8..2aa5d876 100644 --- a/cmvr-es/service/quic_edge/tests/quic_edge_protocol_test.cpp +++ b/cmvr-es/service/quic_edge/tests/quic_edge_protocol_test.cpp @@ -105,6 +105,7 @@ public: if (envelope.has_node_register_request()) { const auto& request = envelope.node_register_request(); last_registered_node_id_ = request.node().node_id(); + last_registered_robot_id_ = request.node().robot_id(); last_grpc_endpoint_port_ = request.node().grpc_endpoint().port(); last_interface_count_ = request.node().local_interfaces_size(); cmvr::quic_edge::v1::EdgeControlEnvelope response; @@ -253,6 +254,12 @@ public: return last_registered_node_id_; } + std::string lastRegisteredRobotId() const + { + std::lock_guard lock(mutex_); + return last_registered_robot_id_; + } + std::uint32_t lastGrpcEndpointPort() const { std::lock_guard lock(mutex_); @@ -335,6 +342,7 @@ private: std::uint32_t heartbeat_ack_delay_ms_{0U}; std::uint64_t server_message_sequence_{0}; std::string last_registered_node_id_; + std::string last_registered_robot_id_; std::uint32_t last_grpc_endpoint_port_{0}; int last_interface_count_{0}; std::vector> controls_; @@ -359,6 +367,7 @@ config::QuicEdgeConfig validConfig(const std::string& source_track_id) config.set_server_port(4433); config.set_alpn("cmvr-quic-edge/1"); config.set_node_id("test-node"); + config.set_robot_id("CN-CMVR-MBLRV1-CHAGAN-20260731-001"); config.set_software_version("test-version"); config.set_grpc_endpoint_host("auto"); config.set_grpc_endpoint_port(50052U); @@ -507,6 +516,8 @@ bool testServiceWithSharedHub() service.stats().heartbeats_acknowledged >= 1U; })); CHECK_TRUE(transport_view->lastRegisteredNodeId() == "test-node"); + CHECK_TRUE(transport_view->lastRegisteredRobotId() == + "CN-CMVR-MBLRV1-CHAGAN-20260731-001"); CHECK_TRUE(transport_view->lastGrpcEndpointPort() == 50052U); CHECK_TRUE(transport_view->connectCount() == 1U); @@ -664,6 +675,8 @@ bool testDeviceManagerSnapshotInHeartbeat() const auto heartbeat = transport_view->lastHeartbeat(); service.stop(); + CHECK_TRUE(heartbeat.robot_id() == + "CN-CMVR-MBLRV1-CHAGAN-20260731-001"); CHECK_TRUE(heartbeat.has_device_manager()); CHECK_TRUE(heartbeat.device_manager().manager_name() == "edge-device-manager"); @@ -1016,6 +1029,20 @@ bool testLifecycleStateGuards() return true; } +bool testRobotIdIsRequired() +{ + media::MediaSourceHub hub; + auto config = validPresenceOnlyConfig(); + config.clear_robot_id(); + auto transport = std::make_unique(); + quic_edge::QuicEdgeService service( + std::move(config), std::move(transport), hub); + std::string error; + CHECK_TRUE(!service.initialize(&error)); + CHECK_TRUE(error.find("robot_id") != std::string::npos); + return true; +} + } // namespace int main() @@ -1031,7 +1058,7 @@ int main() !testRegistrationRejectionBacksOff() || !testHeartbeatAckRequiresSessionId() || !testSlowMediaStartDoesNotBlockHeartbeat() || - !testLifecycleStateGuards()) { + !testLifecycleStateGuards() || !testRobotIdIsRequired()) { return 1; } std::cout << "quic_edge_protocol_test: PASS\n"; diff --git a/protos/cmvr/config/quic_edge_config/quic_edge_config.proto b/protos/cmvr/config/quic_edge_config/quic_edge_config.proto index 904e5604..48486a9c 100644 --- a/protos/cmvr/config/quic_edge_config/quic_edge_config.proto +++ b/protos/cmvr/config/quic_edge_config/quic_edge_config.proto @@ -83,6 +83,10 @@ message QuicEdgeConfig { // IP reporting and heartbeat continue without a camera or microphone. repeated QuicEdgeTrackConfig tracks = 20; bool include_loopback_interfaces = 21; + + // Immutable robot product identity, for example: + // CN-CMVR-MBLRV1-CHAGAN-20260731-001. + string robot_id = 22; } message QuicEdgeRootConfig { diff --git a/protos/cmvr/quic_edge/v1/README.md b/protos/cmvr/quic_edge/v1/README.md index 4361811e..be3c2178 100644 --- a/protos/cmvr/quic_edge/v1/README.md +++ b/protos/cmvr/quic_edge/v1/README.md @@ -121,8 +121,9 @@ explicit. The legal session order is: 1. The edge sends `NodeRegisterRequest` as its first application message after - every QUIC connect or reconnect. It includes `node_id`, `boot_id`, software - version, the current IPv4/IPv6 interface snapshot and advertised gRPC endpoint. + every QUIC connect or reconnect. It includes `node_id`, the immutable + `robot_id`, `boot_id`, software version, the current IPv4/IPv6 interface + snapshot and advertised gRPC endpoint. 2. The gateway replies with `NodeRegisterResponse`. Media and heartbeat must not start until `accepted=true` and a non-empty `session_id` are received. 3. The edge sends `NodeHeartbeat` at the negotiated interval. The gateway returns @@ -189,12 +190,13 @@ registered QUIC connection. The edge clamps the negotiated value to its supported safety range and returns to the local value on reconnect until a new registration response is accepted. -`device_manager = 9` is an additive protobuf field in `NodeHeartbeat`, so this -extension remains QUIC edge protocol v1. Existing gateways ignore the unknown -field. Updated gateways must continue accepting older v1 heartbeats where -`device_manager` is absent and must not treat an absent snapshot as an empty, -healthy DeviceManager. Enum values may only be appended; existing numeric -meanings must never be renumbered or reused. +`NodeDescriptor.robot_id = 6`, `NodeHeartbeat.device_manager = 9` and +`NodeHeartbeat.robot_id = 10` are additive protobuf fields, so these extensions +remain QUIC edge protocol v1. Existing gateways ignore unknown fields. Updated +gateways must continue accepting older v1 messages where these fields are absent +and must not treat an absent device snapshot as an empty, healthy DeviceManager. +Enum values may only be appended; existing numeric meanings must never be +renumbered or reused. DATAGRAM negotiation is required only when at least one media track is enabled. The reliable registration and heartbeat path remains valid for a zero-track diff --git a/protos/cmvr/quic_edge/v1/quic_edge.proto b/protos/cmvr/quic_edge/v1/quic_edge.proto index 3dca447c..48f3a321 100644 --- a/protos/cmvr/quic_edge/v1/quic_edge.proto +++ b/protos/cmvr/quic_edge/v1/quic_edge.proto @@ -128,6 +128,7 @@ message NodeDescriptor { string software_version = 3; repeated NetworkInterfaceAddress local_interfaces = 4; GrpcEndpoint grpc_endpoint = 5; + string robot_id = 6; } // This must be the first application message sent after each QUIC connection @@ -162,6 +163,7 @@ message NodeHeartbeat { repeated NetworkInterfaceAddress local_interfaces = 7; GrpcEndpoint grpc_endpoint = 8; DeviceManagerSnapshot device_manager = 9; + string robot_id = 10; } message NodeHeartbeatAck {