syntax = "proto3"; package cmvr.quic_edge.v1; // Every application message on the edge-opened reliable bidirectional stream // is carried by this envelope. message_sequence is strictly increasing per // sender (gaps are allowed) and is independent from the heartbeat sequence used // for liveness acknowledgement. message EdgeControlEnvelope { uint32 protocol_version = 1; uint64 message_sequence = 2; oneof payload { NodeRegisterRequest node_register_request = 10; NodeRegisterResponse node_register_response = 11; NodeHeartbeat node_heartbeat = 12; NodeHeartbeatAck node_heartbeat_ack = 13; MediaSessionOpen media_session_open = 20; MediaTrackDescriptor media_track_descriptor = 21; MediaSessionClose media_session_close = 22; ProtocolError protocol_error = 30; } } message NetworkInterfaceAddress { enum AddressFamily { ADDRESS_FAMILY_UNSPECIFIED = 0; ADDRESS_FAMILY_IPV4 = 1; ADDRESS_FAMILY_IPV6 = 2; } string interface_name = 1; string ip_address = 2; AddressFamily family = 3; bool loopback = 4; } // The existing cmvr-es gRPC server remains the robot-control endpoint. The // edge advertises its current reachable address through the QUIC control plane. message GrpcEndpoint { string host = 1; uint32 port = 2; bool tls = 3; } // Stable protocol-level categories for devices managed by cmvr-es. These // values intentionally do not reuse the configuration or gRPC API enums: // their zero values and supported categories have different semantics. enum DeviceKind { DEVICE_KIND_UNSPECIFIED = 0; DEVICE_KIND_AGV = 1; DEVICE_KIND_ARM = 2; DEVICE_KIND_BATTERY = 3; DEVICE_KIND_BIO_HEAD = 4; DEVICE_KIND_CAMERA = 5; DEVICE_KIND_CAN_BUS = 6; DEVICE_KIND_DEX_HAND = 7; DEVICE_KIND_GRIPPER = 8; DEVICE_KIND_MICROPHONE = 9; DEVICE_KIND_MOTOR = 10; DEVICE_KIND_MOTOR_SYSTEM = 11; DEVICE_KIND_ROBOT = 12; DEVICE_KIND_SPEAKER = 13; } // DeviceManager's view of a configured entry. REGISTERED means that the // manager owns a device record but has no more specific lifecycle signal. enum ManagedDeviceState { MANAGED_DEVICE_STATE_UNSPECIFIED = 0; MANAGED_DEVICE_STATE_DISABLED = 1; MANAGED_DEVICE_STATE_INITIALIZING = 2; MANAGED_DEVICE_STATE_REGISTERED = 3; MANAGED_DEVICE_STATE_READY = 4; MANAGED_DEVICE_STATE_RUNNING = 5; MANAGED_DEVICE_STATE_STOPPED = 6; MANAGED_DEVICE_STATE_ERROR = 7; } // Health is independent of lifecycle. UNSPECIFIED means that no trustworthy // health observation is available and must never be interpreted as healthy. enum DeviceHealthStatus { DEVICE_HEALTH_STATUS_UNSPECIFIED = 0; DEVICE_HEALTH_STATUS_HEALTHY = 1; DEVICE_HEALTH_STATUS_DEGRADED = 2; DEVICE_HEALTH_STATUS_FAULT = 3; } message ManagedDeviceStatus { string device_id = 1; DeviceKind kind = 2; // Concrete implementation name when a device object exists; otherwise a // category label. It is for display/diagnostics only. Consumers use kind, // rather than this free-form string, for machine decisions. string type_name = 3; bool enabled = 4; ManagedDeviceState manager_state = 5; DeviceHealthStatus health = 6; // false means that no error is currently confirmed. It does not turn // DEVICE_HEALTH_STATUS_UNSPECIFIED into a healthy observation. bool has_error = 7; string error_message = 8; // Time at which DeviceManager last changed the lifecycle/error record. // DeviceManagerSnapshot.sampled_at_unix_ms is the freshness timestamp for // the health observation carried by this heartbeat. uint64 status_updated_at_unix_ms = 9; } message DeviceManagerSnapshot { string manager_name = 1; string manager_version = 2; string manager_description = 3; // Current cmvr-es senders include only enabled devices. The enabled field in // each row and DISABLED enum value remain part of v1 for wire compatibility. repeated ManagedDeviceStatus devices = 4; uint64 sampled_at_unix_ms = 5; } message NodeDescriptor { string node_id = 1; string boot_id = 2; 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 // is established. A reconnect always creates a new registration session. message NodeRegisterRequest { NodeDescriptor node = 1; uint64 sent_at_unix_ms = 2; } message NodeRegisterResponse { bool accepted = 1; string session_id = 2; string message = 3; // Zero tells the edge to keep its locally configured interval. uint32 heartbeat_interval_ms = 4; // Derived by the receiver from the authenticated QUIC peer address. It is // not copied from a client-supplied local interface. string observed_source_ip = 5; } // A heartbeat carries a fresh network snapshot so address changes are reported // without opening a second protocol or connection. message NodeHeartbeat { string node_id = 1; string boot_id = 2; string session_id = 3; uint64 sequence = 4; uint64 sent_at_unix_ms = 5; string software_version = 6; repeated NetworkInterfaceAddress local_interfaces = 7; GrpcEndpoint grpc_endpoint = 8; DeviceManagerSnapshot device_manager = 9; string robot_id = 10; } message NodeHeartbeatAck { bool accepted = 1; uint64 acknowledged_sequence = 2; string message = 3; uint64 server_time_unix_ms = 4; string observed_source_ip = 5; string session_id = 6; } message MediaSessionOpen { string node_id = 1; uint64 session_epoch = 2; // Binds the media epoch to the accepted node registration on this QUIC // connection. Media must not start before this session is assigned. string session_id = 3; } enum MediaKind { MEDIA_KIND_UNSPECIFIED = 0; MEDIA_KIND_VIDEO = 1; MEDIA_KIND_AUDIO = 2; } message MediaTrackDescriptor { uint32 track_id = 1; MediaKind kind = 2; string device_id = 3; string codec = 4; uint64 codec_generation = 5; // The exact MediaSourceHub track and the 32-bit token repeated in every // DATAGRAM header. The full generation remains on the reliable stream. string source_track_id = 6; uint32 codec_generation_token = 7; string payload_format = 8; // Video fields. They are zero for audio tracks. uint32 width = 10; uint32 height = 11; uint32 frames_per_second = 12; // Audio fields. They are zero for video tracks. uint32 sample_rate = 20; uint32 channels = 21; // Decoder initialization bytes, for example AVCC/HVCC or AudioSpecificConfig. // Existing cmvr-es sources may leave this empty when configuration NAL units // are carried in-band. bytes codec_config = 30; } message MediaSessionClose { string reason = 1; string session_id = 2; uint64 session_epoch = 3; } message ProtocolError { uint32 code = 1; string message = 2; uint64 related_message_sequence = 3; bool fatal = 4; }