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

# Replaying a Thumb Opposition

## Overview

Read the recording shipped with the example, pick the left- or right-hand file by device handedness, and replay 30000 joint-angle frames straight through at 1 kHz.

This is the larger of the two motion examples, meant for observing the hand's full range. The example does no interpolation, no resampling, no smoothing, and adds no recovery motion. What the recording holds is what goes out.

Matching examples: `examples/python/wuji_hand_2/6_opposition/opposition.py` and `examples/c/wuji_hand_2/6_opposition/opposition.c`.

For joint commands and control modes, see [Control Guide](/en/docs/wuji-hand/latest/control-guide). This page covers only how to run it and what the measurements show.

<Info>
  This example drives all 20 joints through large motions, with a measured command peak of 1.43 rad (about 82 degrees) sustained over 30 seconds. Before starting, confirm the hand is firmly mounted, clear people, cables, and desk objects from around it, keep someone watching throughout, and stay ready to cut power.
</Info>

## Prerequisites

### Hardware

| Item | Requirement |
| - | - |
| Wuji Hand 2 | 1, with **all 20 joints online** |
| Mounting | The device must be firmly mounted. 30 seconds of continuous large motion produces noticeable reaction forces |
| Connection | Ethernet |
| Network | **Only this device may be on the network**, since the example uses `auto_connect()` |

### Software

| Item | Requirement |
| - | - |
| Operating system | Ubuntu 22.04 LTS or later |
| Wuji SDK | v2026.8.31 or later |
| Python | 3.10 or later |
| C toolchain | CMake 3.16 or later. This example is a standalone CMake project |

### Replay Files

The example directory ships `data/right.replay` and `data/left.replay`. **Copy the entire `6_opposition/` directory**, since taking only the source file fails when it can't find the data.

## Data Flow

```mermaid theme={null}
flowchart TD
    HAND["handedness()<br/>read device handedness"]
    PICK["pick right.replay or left.replay"]
    VAL["validate the container<br/>magic, version, side, and length"]
    READ["unpack each frame<br/>index plus 20 float32"]
    PUB["joint_command().publish()<br/>1 ms apart"]
    FW["firmware MIT impedance control"]

    HAND --> PICK
    PICK --> VAL
    VAL -->|"enable only after validation passes"| READ
    READ -->|"30000 frames"| PUB
    PUB -->|"1 kHz"| FW
```

### File Format

A fixed-length container with a 16-byte header followed by 84 bytes per frame.

| Section | Layout | Note |
| - | - | - |
| Header | `<8sHBBI` | magic `WJH2RPL\0`, version, handedness ID, joint count, frame count |
| Per frame | `<I20f` | 4-byte frame index plus 20 `float32` joint angles in radians |

Handedness ID is 1 for right and 2 for left. The shipped files measure 30000 frames and 2520016 bytes, matching `16 + 30000 x 84` exactly.

**Validation completes before enabling.** If any of magic, version, handedness, joint count, or file length fails, the example reports an error and exits without enabling the motors.

## Runtime Handling

| Branch point | Behavior | What to do |
| - | - | - |
| Handedness mismatch | `side_id` validation fails when a right hand reads `left.replay` | Reports an error and exits without enabling. The example picks by device handedness, so this rarely triggers |
| File missing or corrupt | Reports `replay file not found` or a container validation failure | Confirm you copied the whole `6_opposition/` directory |
| Fewer than 20 joints online | Reports an error and exits | Investigate the offline joints first |
| Several devices on the network | `auto_connect()` reports `Found N devices, please specify SN` and exits | On a shared network, change it to name a serial number |
| `Ctrl+C` | Exit code 130, stopping further frames and **performing no return motion** | The hand holds the posture it stopped in, then is disabled |
| Normal completion | Exit code 0 | The recording's final frame already returns to an open hand |

On interrupt the hand holds its current posture rather than opening automatically. Stopping mid-motion can leave it clenched, which needs handling by hand.

## Integration Steps

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    import struct, time, wuji_sdk
    from wuji_sdk import JointCommand

    HEADER = struct.Struct("<8sHBBI")
    FRAME  = struct.Struct("<I20f")

    manager = wuji_sdk.SdkManager.instance()
    hand = manager.auto_connect("hand_2_opposition")   # with several devices, use connect(sn=...)

    # 1. Pick the replay file by device handedness
    side = str(hand.handedness().get()).lower()        # "right" or "left"

    # 2. Open and validate the container, before enabling anything
    data = open(f"data/{side}.replay", "rb").read()
    magic, version, side_id, joint_count, frame_count = HEADER.unpack(data[:HEADER.size])
    assert magic == b"WJH2RPL\0" and joint_count == 20
    assert len(data) == HEADER.size + frame_count * FRAME.size

    # 3. Gate, then enable
    assert int(hand.online_joints_count().get()) == 20
    hand.enable()

    # 4. Replay frame by frame at a fixed 1 ms interval
    publisher = hand.joint_command().publish()
    t0 = time.monotonic()
    for i in range(frame_count):
        off = HEADER.size + i * FRAME.size
        qpos = FRAME.unpack(data[off:off + FRAME.size])[1:]   # skip the frame index
        publisher.send([JointCommand(v, 0.0, 0.0) for v in qpos])
        wait = t0 + (i + 1) * 0.001 - time.monotonic()
        if wait > 0:
            time.sleep(wait)

    # 5. Clean up
    hand.disable()
    publisher.close()
    manager.disconnect_all()
    ```
  </Tab>

  <Tab title="C">
    ```c theme={null}
    #include "wuji_sdk.h"

    /* 1. Pick the file by device handedness */
    WujiHandedness side;
    wuji_hand_2_handedness(dev, &side);
    const char *path = (side == WUJI_HANDEDNESS_RIGHT) ? "data/right.replay"
                                                       : "data/left.replay";

    /* 2. Open and validate the container
          (magic / version / side_id / joint_count / file length) */
    FILE *fp = fopen(path, "rb");
    /* 16-byte header: char magic[8] + uint16 version + uint8 side_id
                       + uint8 joint_count + uint32 frame_count */

    /* 3. Gate, then enable */
    uint32_t online = 0;
    wuji_hand_2_online_joints_count(dev, &online);
    if (online != 20) { /* report an error and exit without enabling */ }
    wuji_hand_2_enable(dev);

    /* 4. Replay frame by frame, 1 ms apart */
    struct WujiPub *pub = NULL;
    wuji_hand_2_joint_command_publisher(dev, &pub);
    WujiJointCommand cmd[20] = {0};
    for (uint32_t i = 0; i < frame_count; i++) {
        uint32_t seq; float qpos[20];
        fread(&seq, 4, 1, fp);
        fread(qpos, 4, 20, fp);
        for (int j = 0; j < 20; j++) cmd[j].position = qpos[j];
        wuji_hand_2_publish_joint_command(pub, cmd, 20);
        usleep(1000);
    }

    /* 5. Clean up */
    wuji_hand_2_disable(dev);
    wuji_pub_close(pub);
    fclose(fp);
    wuji_dev_disconnect(dev);
    wuji_dev_release(dev);
    ```
  </Tab>
</Tabs>

The C side is a standalone CMake project and needs its own configure step:

```bash theme={null}
cmake -S examples/c/wuji_hand_2/6_opposition -B build/hand2-opposition \
  -DWUJI_SDK_INCLUDE_DIR="${SDK}/include" \
  -DWUJI_SDK_LIB="${SDK}/lib/libwuji_sdk_c.so"
cmake --build build/hand2-opposition
```

## Running It

<Tabs>
  <Tab title="Python">
    ```bash theme={null}
    python examples/python/wuji_hand_2/6_opposition/opposition.py
    ```
  </Tab>

  <Tab title="C">
    ```bash theme={null}
    ./build/hand2-opposition/6_opposition
    ```
  </Tab>
</Tabs>

### Measurements

A complete run on a right-hand Wuji Hand 2 with all 20 joints online:

| Metric | Measured |
| - | - |
| Frames replayed | 30000, none dropped |
| Total duration | 30.00 seconds |
| **Actual publish rate** | **1000.0 Hz** |
| Command peak | 1.4315 rad (82.0 degrees) |
| Largest absolute starting angle | 0.0668 rad |
| Largest absolute ending angle | 0.0563 rad |

Python held the 1 ms target interval reliably, and accumulated error across 30000 frames is negligible.

**The hand settles on its own at the end.** The recording's last frame already returns to an open position, so a normal run needs no extra homing. That comes from the recording's content rather than anything the example does, and an interrupted run gets no such guarantee.

### Range of Motion per Finger

Per-joint travel computed offline from the right-hand replay file:

| Finger | Travel across four joints (rad) | Peak |
| - | - | - |
| Thumb | 1.125 / 0.734 / 1.296 / 0.801 | +1.319 |
| Index | 0.799 / 0.477 / 1.428 / 1.257 | **+1.431** |
| Middle | 0.787 / 0.620 / 1.205 / 0.298 | +1.197 |
| Ring | 0.930 / 0.434 / 0.795 / 0.104 | +0.925 |
| Pinky | 0.630 / 0.524 / 1.188 / 0.322 | +1.184 |

The index finger's third joint travels furthest at 1.428 rad, about 81.8 degrees.

## Adapting It

### Name an SN on a Shared Network

As with the [MIT joint sweep](/en/docs/wuji-hand/latest/sdk-practice/mit-sweep), `auto_connect()` refuses to connect when it finds several devices and lists every serial number rather than guessing. Change it to `manager.connect(sn="WH2KA00XXXXXXXXX", device_name="hand_2_opposition")`.

### Replay Your Own Recording

Generate a file in the same container format and the whole replay path works unchanged: a 16-byte header plus 84 bytes per frame. The frame index is skipped during replay, and only the 20 joint angles drive control. Make sure `side_id` matches the target device's handedness, or container validation fails.

### Changing Playback Speed

The interval is hard-coded at 1 ms. Increasing it slows the whole motion, but since nothing is resampled the joint-angle sequence stays the same and each posture simply holds longer. Real speed changes need you to resample the trajectory yourself.

### Recovering After an Interrupt

`Ctrl+C` triggers no return motion. After stopping mid-run, the hand holds its posture, and the smooth homing routine in [Publishing Control Commands](/en/docs/wuji-hand/latest/sdk-practice/publish-command) brings it gently back to zero. Don't send `pos=0` directly, since a large error under MIT impedance snaps the hand back hard.

## Related

* [Publishing Control Commands](/en/docs/wuji-hand/latest/sdk-practice/publish-command): general `joint_command` usage and smooth homing
* [Running an MIT Joint Sweep](/en/docs/wuji-hand/latest/sdk-practice/mit-sweep): the smallest command-path check
* [Subscribing With Callbacks](/en/docs/wuji-hand/latest/sdk-practice/subscribe-callback): read actual joint state during replay
* [Common Data Types](/en/docs/wuji-sdk/data-reference/): the `JointCommand` structure


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