> ## Documentation Index
> Fetch the complete documentation index at: https://docs.wuji.tech/llms.txt
> Use this file to discover all available pages before exploring further.

# Joint States and Control

The whole-hand API covers 20 joints. Single-joint reads and actions go through the `JointHandle` returned by `hand.joint(k)` / `hand.joints()` — see [Joint Traversal](#joint-traversal).

## joint\_states (SUB)

**Joint state stream**: subscribe to the real-time state frames of the 20 joints, at a native output rate of 1000 Hz. Lower the push rate through the subscription handle: `sub.set_rate(hz)` returns the rate in effect, and `0` restores the native rate — see [Adjusting the Stream Output Rate](/en/docs/wuji-sdk/data-subscription/) for full usage. This stream and `joint_diagnostics` ride the same device stream, so lowering the rate of one lowers both.

<Info>
  Push-rate tuning applies to the `joint_states`, `joint_diagnostics`, and `imu` streams. Upgrade the firmware and the Wuji SDK together to versions that support it — see the release notes for the version requirements.
</Info>

```python theme={null}
sub = hand.joint_states().subscribe() # → Subscription[JointStateFrame]
frame = sub.recv() # synchronous, non-blocking; None = no data yet
frame = await sub.recv_async() # asynchronous wait
print(frame.header.seq, frame.header.timestamp_us)
for j in frame.joints:
    print(j.nid, j.position, j.velocity, j.effort)
sub.close()
```

Callback mode:

```python theme={null}
def on_state(frame):
    print(frame.header.seq, [j.position for j in frame.joints[:4]])

cb = hand.joint_states().subscribe_with_callback(on_state)
# ...
cb.close()
```

<Info>
  `position` is the joint-side angle (rad) and `velocity` the joint-side angular velocity (rad/s)—this is the only recommended path for joint angle and speed. Frames are variable-length and contain only online joints — identify each entry by its `nid`. The `joint_diagnostics` stream doesn't carry joint angles.
</Info>

## joint\_command (PUB)

**Joint command**: publish positions / velocities / torque feedforward for the 20 joints. Each send takes exactly 20 `JointCommand` entries, one `position` / `velocity` / `effort` per joint.

```python theme={null}
from wuji_sdk import JointCommand

pub = hand.joint_command().publish() # → JointCommandPublisher
pub.send([JointCommand(position=p, velocity=0.0, effort=0.0) for p in positions])
pub.close()
```

To turn human hand keypoints into these 20-joint commands, use Wuji SDK [Hand Retargeting](/en/docs/wuji-sdk/retargeting/).

## online\_joints\_count (GET)

Get the **online joint count** (0–20).

```python theme={null}
n = hand.online_joints_count().get() # → int, 0–20
```

## Joint Traversal

`JointHandle` provides `label` / `index` (for indexing into 20-element return arrays such as `effort_limit()` / `mit_params()`), plus single-joint resources and actions. `FingerHandle` traverses joints by finger.

| Method | Return type | Description |
| - | - | - |
| `hand.joints()` | `list[JointHandle]` | All 20 joints |
| `hand.fingers()` | `list[FingerHandle]` | All 5 fingers |

```python theme={null}
for joint in hand.joints():
    print(joint.label, joint.index) # e.g. "thumb_S1" 0

for finger in hand.fingers():
    for joint in finger.joints():          # 4 joints per finger, in S1..S4 order
        print(joint.label)                 # "thumb_S1", "thumb_S2", ...
```

### JointHandle

**Joint handle**: provides `label` / `index`, single-joint resources, and single-joint actions.

| Member | Type | Description |
| - | - | - |
| `label` | `str` | Property. Joint label, format `{finger}_S{1..4}`, e.g. `"thumb_S1"`, `"pinky_S4"` |
| `index` | `int` | Property. Global index (0–19) |
| `effort_limit()` | Resource (SET/GET) | Single-joint torque cap (A) |
| `error_code()` | Resource (GET) | The joint's current error code — decode with `describe_error()`. Returns an integer, so format it as `f"0x{code:04X}"` if you print it directly |
| `status_word()` | Resource (GET) | The joint's status word |
| `enable()` / `disable()` | Action | Enable / disable this joint |
| `clear_fault()` | Action | Clear this joint's faults |
| `set_origin()` / `clear_origin()` | Action | Set / clear this joint's user origin |

### FingerHandle

**Finger handle**: traverse the 4 joints of this finger.

| Method | Return type | Description |
| - | - | - |
| `joints()` | `list[JointHandle]` | The 4 joints of this finger, in S1..S4 order |

## Joint Numbering

| Finger | S1 | S2 | S3 | S4 |
| - | - | - | - | - |
| thumb | 0 | 1 | 2 | 3 |
| index | 4 | 5 | 6 | 7 |
| middle | 8 | 9 | 10 | 11 |
| ring | 12 | 13 | 14 | 15 |
| pinky | 16 | 17 | 18 | 19 |

Label format: `{finger}_S{1..4}` (e.g. `thumb_S1`, `pinky_S4`). For the full table with motion ranges and model naming, see the [Control Guide](/en/docs/wuji-hand/latest/control-guide#joint-table).

## Data Types

### JointStateFrame

**Joint state frame**: return value of `hand.joint_states().subscribe().recv()`. Variable-length, online joints only.

| Attribute | Type | Description |
| - | - | - |
| `header` | `FrameHeader` | `seq` / `timestamp_us` / `frame_id` |
| `num_joints` | `int` | Joint count in this frame |
| `joints` | `list[JointStateEntry]` | Joint state array |

### JointStateEntry

**Single-joint state**.

| Attribute | Type | Description |
| - | - | - |
| `nid` | `int` | Node ID — identifies the joint across frames |
| `position` | `float` | Position (rad, joint-side) |
| `velocity` | `float` | Velocity (rad/s) |
| `effort` | `float` | Torque (A) |

### JointCommand

**Joint command**: element of the `joint_command().publish().send()` argument.

```python theme={null}
class JointCommand:
    position: float # position (rad)
    velocity: float # velocity (rad/s)
    effort: float # torque feedforward (A)
```

<Tabs>
  <Tab title="Python">
    Python: See the Python API examples earlier on this page.
  </Tab>

  <Tab title="C">
    The C API matches the Python API on this page and uses the `wuji_hand_2_` prefix. For initialization conventions, memory management, error codes, and build setup, see the [C API Reference](/en/docs/wuji-sdk/data-reference/).

    ### Subscribe to Joint States

    Variable-length frames carry a `FrameHeader` (`seq` + `timestamp_us` + `frame_id` = `"l_wrist"` or `"r_wrist"`). The `joints` array identifies online joints by `nid`:

    ```c theme={null}
    void on_state(WujiFrameKind kind, const WujiJointStateFrame* frame, void* user) {
        if (kind != WUJI_FRAME_KIND_OK) return;
        printf("seq=%u frame=%s n=%u\n", frame->header.seq, frame->header.frame_id, frame->num_joints);
        for (size_t i = 0; i < frame->joints_len; i++) {
            const WujiJointStateEntry* j = &frame->joints[i];
            printf("  nid=%u pos=%+.3f vel=%+.3f eff=%+.3f\n", j->nid, j->position, j->velocity, j->effort);
        }
    }

    WujiSub* sub = NULL;
    wuji_hand_2_subscribe_joint_states(dev, on_state, NULL, &sub);
    // ... close with: wuji_sub_close(sub);
    ```

    `nid` is the firmware stream numbering, not the flat 0–19 joint index. Convert it with `wuji_hand_2_nid_to_joint_index` (see Stream NID and Joint Index below).

    ### Stream NID and Joint Index

    Both `WujiJointStateEntry.nid` and `WujiJointDiagnosticsEntry.nid` carry the firmware stream numbering, not the flat 0–19 joint index used by per-joint arrays and `wuji_hand_2_joint_label`. Two static functions convert between the two. Neither requires a device connection.

    ```c theme={null}
    WujiStatus wuji_hand_2_nid_to_joint_index(uint8_t nid, uint8_t *out);
    WujiStatus wuji_hand_2_joint_index_to_nid(uint8_t joint_index, uint8_t *out);
    ```

    Both return `WujiStatus`. `*out` is valid only when the call returns `WUJI_STATUS_OK` and is left untouched otherwise, so check the status before using the result. `wuji_hand_2_nid_to_joint_index` returns `WUJI_STATUS_ERR_INVALID_ARG` for a `NULL` `out` or a non-joint `nid` (ROOT `nid` 0, a tactile slot, or an out-of-range value). `wuji_hand_2_joint_index_to_nid` returns `WUJI_STATUS_ERR_INVALID_ARG` for a `NULL` `out` or an index outside 0–19. On failure, `wuji_last_error()` names the rejected value and lists the valid joint nids.

    ### Realtime Command Publisher

    Open the publisher, then call `send` with exactly 20 `WujiJointCommand` entries (each `position` / `velocity` / `effort`). Pass 0 for fields you do not want to feed forward:

    ```c theme={null}
    WujiJointCommandPublisher* pub = NULL;
    wuji_hand_2_joint_command_publish(dev, &pub);

    WujiJointCommand cmds[20] = {0};
    // cmds[i].position = ...; cmds[i].velocity = 0; cmds[i].effort = 0;
    wuji_joint_command_publisher_send(pub, cmds);

    wuji_joint_command_publisher_close(pub);
    ```

    Call `send` at a fixed rate (typically 200 Hz–1 kHz) to maintain continuous control. `close` is NULL-safe, but you still need to call `wuji_joint_command_publisher_close` to release the publisher handle, otherwise the resource leaks.
  </Tab>
</Tabs>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.