UDP Reception – Joint Poses
This is the joint UDP sent by the HumanHand host and the SDK demo. Joint order and axes match the SDK arrays. Applications may skip UDP and read the SDK directly.
1. Overview
| Output format | Origin | Joints per hand | Protocol | Packet size |
|---|---|---|---|---|
| Joint pose (wrist-relative) | Palm root | 21 | FSNH version 4 | 612 bytes |
| Joint pose (forearm-relative) | Forearm module | 22 | FSNH version 4 | 640 bytes |
| Item | Description |
|---|---|
| Source | Glove joint poses; the forearm format requires a forearm module |
| Transport | UDP unicast, connectionless |
| Rate | 120 Hz per hand by default |
| Hand | One hand per packet. hand=1 left, hand=2 right |
| Two hands | Same destination IP and port; one packet for left, one for right. The receiver splits on hand. The two packets need not arrive together |
Host path: User Settings → Data Sending → Output format. SDK demo menu: send wrist-relative or forearm-relative joints.
2. Sending Configuration
| Parameter | Default | Description |
|---|---|---|
| Destination IP | 127.0.0.1 | Host: Data Sending. SDK demo: prompted at start |
| Destination port | 15001 | Both hands use this port |
| Byte order | Little-endian | All multibyte fields |
3. Coordinate System
| Item | Description |
|---|---|
| Wrist-relative | Origin at the palm root (same as SDK hh_sdk_poll) |
| Forearm-relative | Origin at the forearm (same as SDK hh_sdk_forearm_root); axes match the wrist format |
| Position unit | meters (m) |
| Rotation | Quaternion w, x, y, z |
| Axes | Left: right, forward, up; right: left, back, up |
| Axis | Left hand | Right hand |
|---|---|---|
| X | Right | Left |
| Y | Forward (along the fingers) | Back |
| Z | Up (dorsal) | Up |
The payload contains only that hand's joints. Use hand to select the side.
4. Joint Indices
4.1 Wrist-relative (21)
| Index | Part |
|---|---|
| 0 | Palm root (position ≈ 0) |
| 1–4 | Thumb: MCP, proximal, distal, tip |
| 5–8 | Index |
| 9–12 | Middle |
| 13–16 | Ring |
| 17–20 | Little |
Tips: 4, 8, 12, 16, 20.
4.2 Forearm-relative (22)
| Index | Part |
|---|---|
| 0 | Forearm (position always 0, identity quaternion) |
| 1 | Wrist (the palm root above) |
| 2–21 | Indices 1–20 of the wrist format (thumb MCP … little tip) |
Tips: 5, 9, 13, 17, 21.
5. Packet Layout
24-byte header, then that hand's joints. Each joint is 28 bytes: xyz (12) + wxyz (16).
Wrist: 24 + 21×28 = 612
Forearm: 24 + 22×28 = 640Send exactly that many bytes.
5.1 Header (24 bytes)
| Offset | Size | Type | Field | Description |
|---|---|---|---|---|
| 0 | 4 | uint32 | magic | 0x464E4853 ("FSNH") |
| 4 | 1 | uint8 | version | 4 |
| 5 | 1 | uint8 | hand | 1 left / 2 right |
| 6 | 1 | uint8 | joint_count | 21 wrist-relative, 22 forearm-relative |
| 7 | 1 | uint8 | status | See 5.2 |
| 8 | 4 | uint32 | frame_index | Per-hand frame counter |
| 12 | 8 | uint64 | timestamp_ns | Host monotonic clock, nanoseconds |
| 20 | 4 | uint32 | quat_order | 0x77787A79 (wxyz) |
Packet length also distinguishes the two formats: 612 = wrist, 640 = forearm. It must match joint_count.
5.2 status
| Bit | Mask | Meaning |
|---|---|---|
| 0 | 0x01 | 1 = new solve; 0 = repeat of the previous tick |
| 1 | 0x02 | 22-point packets only: 1 = forearm origin live this tick; 0 = last good forearm. 0 on 21-point packets |
| 2–7 | Must be 0 |
5.3 One joint (28 bytes)
| Relative offset | Field | Type |
|---|---|---|
| 0 | position.x/y/z | float32×3 |
| 12 | quaternion.w/x/y/z | float32×4 |
joint_count joints start at offset 24.
6. Receiving two hands
- Bind one UDP port (default 15001).
- On each datagram, read
hand: 1 → left cache, 2 → right cache. - Left and right
frame_index/timestamp_nsare independent. Do not pair them as one instant. - If one side has no new packet, keep that side's last packet. Do not copy the other side.
7. Timestamp and Rate
| Field | Meaning |
|---|---|
| timestamp_ns | Host monotonic clock, nanoseconds |
| frame_index | That hand's solve counter; stops increasing if the solver stalls |
Default interval is about 8.33 ms per hand (120 Hz). Two hands yield about 240 datagrams per second on that port.
8. Receiver Checklist
magic == 0x464E4853andversion == 4- Length 612 with
joint_count==21, or length 640 withjoint_count==22 handis 1 or 2quat_order == 0x77787A79statusbits 2–7 are 0- Parse only this packet's
joint_countjoints
9. C Struct Reference
Pack or unpack with the structs below.
#pragma pack(push, 1)
#define FSNH_MAGIC 0x464E4853u
#define FSNH_VERSION 4u
#define FSNH_HAND_LEFT 1u
#define FSNH_HAND_RIGHT 2u
#define FSNH_JOINTS_WRIST 21u
#define FSNH_JOINTS_FOREARM 22u
#define FSNH_STATUS_NEW 0x01u
#define FSNH_STATUS_FOREARM_LIVE 0x02u
#define FSNH_PKT_WRIST 612u
#define FSNH_PKT_FOREARM 640u
#define FSNH_QUAT_ORDER_WXYZ 0x77787A79u
typedef struct {
float x, y, z;
float qw, qx, qy, qz;
} FsnhJointPose;
typedef struct {
uint32_t magic;
uint8_t version;
uint8_t hand;
uint8_t joint_count;
uint8_t status;
uint32_t frame_index;
uint64_t timestamp_ns;
uint32_t quat_order;
} FsnhHeader;
typedef struct {
FsnhHeader header;
FsnhJointPose joints[FSNH_JOINTS_WRIST];
} FsnhPacketWrist;
typedef struct {
FsnhHeader header;
FsnhJointPose joints[FSNH_JOINTS_FOREARM];
} FsnhPacketForearm;
#pragma pack(pop)For a wrist packet, send sizeof(FsnhPacketWrist) (612) bytes only.
10. Sample Receiver
The site provides udp_receiver.py on the host default port 15001:
python3 udp_receiver.py --port 15001Packet length selects the live table: 612/640 joints, 240 coil+IMU, 1860 Pico body. Two hands share the port; each hand is its own table, refreshed in place about once a second.
11. Comparison with EMF + Transmitter IMU
| Item | Joint poses | EMF + transmitter IMU |
|---|---|---|
| Protocol | FSNH version 4 | FNIM version 2 |
| Packet size | 612 B (wrist) / 640 B (forearm) | 240 B |
| Content | 21 or 22 joints/hand | 6 slot poses + transmitter IMU |
| Position unit | Metres | Millimetres |
| Frame | Left RFU / right LBU | Magnetic-transmitter tracking frame |
| Two hands | Same port, separate packets | Separate left and right packets |
| Timestamp | Monotonic-clock nanoseconds | Monotonic-clock nanoseconds |
