> ## 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 [Wuji SDK data subscription](https://docs.wuji.tech/docs/en/wuji-sdk/latest/data-subscription.md#adjusting-the-stream-output-rate) for full usage. This stream and `joint_diagnostics` ride the same device stream, so lowering the rate of one lowers both.

> [!NOTE]
>
> 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.

```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()
```

> [!NOTE]
>
> `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.

## 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](https://docs.wuji.tech/docs/en/wuji-sdk/latest/retargeting.md).

## 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`).

## 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)
```

**Python**

Python: See the Python API examples earlier on this page.

**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](https://docs.wuji.tech/docs/en/wuji-sdk/latest/data-reference.md).

### 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);
```

### 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.


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