1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
//! Wire protocol shared by the Enpose API and the on-device daemon.
//!
//! All packets are exactly [`PACKET_SIZE`] bytes laid out big-endian on
//! the wire so a packet capture shows the literal magic bytes `EnpR`
//! regardless of host byte order.
//!
//! Packet layout:
//!
//! ```text
//! offset size field
//! 0 4 MAGIC ("EnpR")
//! 4 2 PROTOCOL_VERSION
//! 6 4 serial number
//! 10 1 has_extrinsics flag (0 or 1)
//! 11 1 packet type (PKT_TYPE_*)
//! ```
/// UDP port the Enpose role-negotiation and discovery protocol uses.
///
/// Devices broadcast peer-announcement packets on this port at 1 Hz,
/// and clients send discovery requests to this port. The primary
/// device of every cluster replies to discovery requests by unicast
/// from this port back to the requester's ephemeral port.
pub const BROADCAST_PORT: u16 = 50884;
/// Wire-protocol version this API was built against.
///
/// Bumped only on incompatible packet-format changes. Packets carrying
/// a different version are still surfaced by [`crate::DeviceDiscovery`]
/// (with `compatible = false`) so the caller can present a helpful
/// "upgrade your firmware / client" entry instead of silently dropping
/// the device.
pub const PROTOCOL_VERSION: u16 = 1;
/// Magic prefix of every packet — the ASCII bytes `EnpR` interpreted
/// as a big-endian `u32`. Distinguishes Enpose traffic from any other
/// UDP datagram that happens to land on [`BROADCAST_PORT`].
pub const MAGIC: u32 = 0x456e7052;
/// Fixed packet size across all packet types, so receivers can use a
/// single `recv_from` buffer.
pub const PACKET_SIZE: usize = 12;
/// Packet type: a device announces its own identity (serial,
/// extrinsics-calibration state). Sent both as the 1 Hz cluster
/// broadcast and as the unicast reply to a discovery request.
pub const PKT_TYPE_PEER_INFO: u8 = 0;
/// Packet type: a client asks any reachable primary to identify
/// itself. Only the cluster's elected primary replies, with a
/// [`PKT_TYPE_PEER_INFO`] packet sent unicast to the requester.
pub const PKT_TYPE_DISCOVERY_REQUEST: u8 = 1;
/// UDP port the pose-streaming protocol uses. Clients send subscribe /
/// keep-alive packets to this port on the device's primary, and the
/// device unicasts [`PKT_TYPE_POSE_DATA`] packets back to each subscribed
/// client. Separate from [`BROADCAST_PORT`] so discovery and streaming
/// traffic never share a socket.
pub const POSE_PORT: u16 = 50885;
/// Packet type: a client subscribes to the pose stream. The same packet
/// doubles as the keep-alive — a client resends it at 1 Hz, and the
/// device drops a client it has not heard from within
/// [`POSE_KEEPALIVE_TIMEOUT_SECS`]. Sent client → device on
/// [`POSE_PORT`].
pub const PKT_TYPE_POSE_SUBSCRIBE: u8 = 2;
/// Packet type: a client unsubscribes from the pose stream. Lets the
/// device drop the client immediately instead of waiting for the
/// keep-alive timeout. Sent client → device on [`POSE_PORT`].
pub const PKT_TYPE_POSE_UNSUBSCRIBE: u8 = 3;
/// Packet type: a pose-data datagram. The fixed [`PACKET_SIZE`] header is
/// followed by a MessagePack-encoded `Vec<MarkerPose>` starting at offset
/// [`PACKET_SIZE`]. One datagram carries all markers localized from a
/// single camera frame. Sent device → client on [`POSE_PORT`].
pub const PKT_TYPE_POSE_DATA: u8 = 4;
/// How long the device keeps a pose-stream client without hearing a
/// subscribe/keep-alive packet from it before dropping the connection.
pub const POSE_KEEPALIVE_TIMEOUT_SECS: u64 = 5;
/// Interval at which a pose-stream client should resend its
/// subscribe/keep-alive packet to stay connected.
pub const POSE_KEEPALIVE_INTERVAL_SECS: u64 = 1;
/// Decoded contents of a packet that passed the magic-bytes check.
///
/// The `version` field is intentionally not validated by
/// [`parse_packet`]; callers decide whether to drop a version-mismatch
/// packet (the daemon does this for peer announcements) or surface it
/// to the user (discovery clients do this so an incompatible device
/// can still be listed).
/// Build a peer-info packet using this API's current
/// [`PROTOCOL_VERSION`].
/// Build a discovery-request packet. Carries `serial = 0` because the
/// requester is anonymous — the replying device fills its own serial
/// into the response.
/// Build a pose-stream subscribe / keep-alive packet. Carries
/// `serial = 0` because the client is anonymous; the device identifies
/// the client by its source address.
/// Build a pose-stream unsubscribe packet.
/// Build the fixed header of a [`PKT_TYPE_POSE_DATA`] packet. The caller
/// appends the MessagePack-encoded pose payload after this header; the
/// receiver decodes the payload from offset [`PACKET_SIZE`]. `serial` is
/// the sending device's factory serial.
/// Decode a packet.
///
/// Returns `None` only when the buffer is shorter than [`PACKET_SIZE`]
/// or when the magic prefix does not match — those are the conditions
/// that mean "this is not an Enpose packet at all".
///
/// A packet with an unrecognised [`ParsedPacket::pkt_type`] or a
/// [`ParsedPacket::version`] different from [`PROTOCOL_VERSION`] is
/// returned to the caller unmodified; rejection policy is the
/// caller's choice.