UDP Packet Schema
Each packet is a fixed-length 45-byte binary message. All multi-byte fields use little-endian byte order. Unknown bytes appended at the end are ignored by the receiver for forward compatibility.
| Field |
Type |
Bytes |
Description |
| header |
uint8[4] |
4 |
Magic bytes 0x52 0x43 0x54 0x50 — ASCII "RCTP". Packets with a wrong magic value are silently dropped. |
| timestamp |
float64 |
8 |
Unity Time.realtimeSinceStartup in seconds. Used for jitter measurement; not interpreted by the robot controller. |
| pos_x |
float32 |
4 |
End-effector X position in metres, VR world frame (right-handed, Y-up). Converted to mm robot frame by transform_position(). |
| pos_y |
float32 |
4 |
End-effector Y position in metres, VR world frame. |
| pos_z |
float32 |
4 |
End-effector Z position in metres, VR world frame. |
| rot_x, rot_y, rot_z, rot_w |
float32 × 4 |
16 |
End-effector rotation as a unit quaternion in VR world frame. Converted to Euler (roll/pitch/yaw) by quat_to_euler() before passing to the robot SDK. |
| gripper |
float32 |
4 |
Gripper openness: 0.0 = fully closed, 1.0 = fully open. Derived from pinch strength via VRGripperController.cs. |
| flags |
uint8 |
1 |
Bit 0: tracking valid (1 = hand detected). Bit 1: emergency stop requested (1 = operator pressed Menu). Remaining bits reserved. |
Byte offset summary: header 0–3, timestamp 4–11, pos_x 12–15, pos_y 16–19, pos_z 20–23, rot_x 24–27, rot_y 28–31, rot_z 32–35, rot_w 36–39, gripper 40–43, flags 44.
Python struct format string for unpacking:
import struct
PACKET_MAGIC = b'\x52\x43\x54\x50' # "RCTP"
PACKET_FMT = '<4sdfffffffff f B'
# ^ ^ ^^^^^^^^^^ ^ ^
# | | pos xyz | flags (uint8)
# | timestamp gripper (float32)
# header (4 bytes) rot xyzw (float32×4)
def parse_packet(data: bytes) -> dict | None:
if len(data) < struct.calcsize(PACKET_FMT):
return None
fields = struct.unpack_from(PACKET_FMT, data)
header, ts, px, py, pz, rx, ry, rz, rw, gripper, flags = fields
if header != PACKET_MAGIC:
return None
return {
"timestamp": ts,
"position": (px, py, pz),
"rotation": (rx, ry, rz, rw),
"gripper": gripper,
"valid": bool(flags & 0x01),
"estop": bool(flags & 0x02),
}
End-to-End Latency
Measured on a Wi-Fi 6 LAN with the AgileX Piper arm at 30 Hz control rate and 25% speed limit. Values are typical; actual latency depends on Wi-Fi conditions and robot servo rate.
~20 ms
Quest 3 Tracking Pipeline
Hand pose sensor fusion to Unity callback
<5 ms
UDP Transit
Wi-Fi 6 LAN; up to ~15 ms on older Wi-Fi
<2 ms
Python Parse & Queue
Struct unpack + queue insert
30–80 ms
Robot Trajectory Execution
Depends on speed limit and move distance
| Wi-Fi 6 LAN, 25% speed |
50–120 ms |
| Wi-Fi 5 LAN, 25% speed |
80–150 ms |
| Software e-stop response (Bit 1 path) |
1 control cycle (~33 ms at 30 Hz) |
| Tracking-loss detection |
Immediate (flags byte in current packet) |
Keep speed limits conservative to reduce latency feel.
The dominant perceptual latency component is robot trajectory execution. A lower SPEED_PERCENT makes the system feel more predictable even though actual round-trip time is similar. Start at 25% and raise only after motion is fully validated.