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

# Publishing Control Commands

## Overview

Configure MIT impedance control parameters, enable the motors, then **interpolate smoothly** from each joint's measured position to zero and hold there.

This is the basic pattern for controlling a Wuji Hand 2. The example homes the hand, but the same `publisher.send()` path carries teleoperation, trajectory playback, and output from a higher-level controller.

Matching examples: `examples/python/wuji_hand_2/2.publish.py` and `examples/c/wuji_hand_2/1_control_motion.c`.

For joint command fields and value ranges, see [Joint State and Control](/en/docs/wuji-hand/latest/sdk-reference/joint-control), and for control modes and safety bounds, 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 enables the motors and drives every online joint. Clear people, cables, and desk objects from around the hand before starting, keep someone watching throughout, and stay ready to cut power.
</Info>

## Interpolation

Sending `pos=0` directly and interpolating to `pos=0` are entirely different motions under MIT impedance.

MIT control produces torque proportional to position error. With the hand starting curled, the first frame at `pos=0` carries an error of "current angle − 0", and with `kp=3` that becomes a large torque that snaps the hand back to zero.

Interpolating means reading the current position first, then pulling the target linearly from there to 0. Each frame's error stays small, torque stays small with it, and the whole motion is gentle. **Gentleness comes from the target trajectory, not from lowering the gains**, so `kp` and `kd` keep their normal values.

## Prerequisites

### Hardware

| Item | Requirement |
| - | - |
| Wuji Hand 2 | 1, with at least one joint online |
| Connection | Ethernet |

This example connects to the **first** Wuji Hand 2 it finds (`hand_devices[0]`). On a shared network, change it to name a serial number.

### 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, plus the extracted SDK package |

### Control Parameters

| Parameter | Example value | Meaning |
| - | - | - |
| `KP` | 3.0 | Position stiffness. Higher tracks more tightly and hits harder |
| `KD` | 0.05 | Damping |
| `EFFORT_LIMIT` | 1.5 | Torque limit in amperes |
| `PUB_HZ` | 200 | Command publish rate |
| `HOME_SECONDS` | 1.5 | Homing duration. Longer is gentler |

These three control values match the factory defaults, and reading them back returns `kp=3, kd=0.05, effort_limit=1.5`.

## Data Flow

```mermaid theme={null}
flowchart TD
    CFG["effort_limit().set() and mit_params().set()<br/>a scalar broadcasts to every joint"]
    EN["enable()<br/>returns right away"]
    WAIT["subscribe joint_diagnostics<br/>wait for every ext_state to reach 2 (Enabled)"]
    READ["joint_states subscription<br/>read the current 20 joint positions"]
    LERP["linear interpolation<br/>pull the target from current toward 0"]
    PUB["joint_command().publish()<br/>20 JointCommand entries per frame"]
    FW["firmware MIT impedance control"]

    CFG --> EN
    EN -->|"the action is issued, motors aren't ready yet"| WAIT
    WAIT -->|"all Enabled"| READ
    READ -->|"starting posture"| LERP
    LERP -->|"200 Hz"| PUB
    PUB --> FW
```

### Parameter Setters Broadcast

`set()` on `effort_limit` and `mit_params` is polymorphic:

```python theme={null}
hand.effort_limit().set(1.5)          # scalar, broadcast to every joint
hand.effort_limit().set([1.5] * 20)   # 20-element list, applied per joint
hand.mit_params().set((3.0, 0.05))    # (kp, kd) tuple, broadcast
```

## Runtime Handling

| Branch point | Behavior | What to do |
| - | - | - |
| **Commanding right after enable** | `enable()` is an action, and its return doesn't mean the motors are ready. The call itself returns in **2 ms** while every motor reaches Enabled in **52 ms** | Subscribe to `joint_diagnostics` and wait for every `status_word.ext_state` to reach 2 before commanding |
| No joints online | `online_joints_count()` is 0 and the example exits | Investigate why the joints are offline |
| Enable times out | Not all Enabled within 5 seconds, and it prints a timeout | Check fault codes and call `clear_fault()` if needed |
| Position of an offline joint | `position` is `None` | The example treats it as 0.0 so interpolation always has a defined start |
| Several devices on the network | Connects to the first, and scan order changes which one that is | Change it to name a serial number |

`ext_state` values: 0 is Init, 1 is Ready, 2 is Enabled, 3 is Stopped. Before enable every joint reads 1, and within 52 ms after enable every joint reads 2.

## Integration Steps

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

    manager = SdkManager.instance()
    hands = [d for d in manager.scan() if d.device_type == DeviceType.WujiHand2]
    hand = manager.connect(sn=hands[0].sn, device_name="wuji_hand_2")

    # 1. Configure control parameters. Firmware defaults to MIT mode,
    #    so control_mode needs no separate setting
    hand.effort_limit().set(1.5)
    hand.mit_params().set((3.0, 0.05))

    # 2. Enable
    hand.enable()

    # 3. Wait for the motors to actually be ready, or commands race ahead of enable
    diag = hand.joint_diagnostics().subscribe()
    deadline = time.monotonic() + 5.0
    while time.monotonic() < deadline:
        time.sleep(0.05)
        frame = diag.recv()
        if frame and frame.joints and all(e.status_word.ext_state == 2 for e in frame.joints):
            break
    diag.close()

    # 4. Read the current posture as the interpolation start
    sub = hand.joint_states().subscribe()
    frame = sub.recv()
    start = [float(j.position) if j.position is not None else 0.0 for j in frame.joints]
    sub.close()

    # 5. Interpolate to zero at 200 Hz
    publisher = hand.joint_command().publish()
    steps, dt = int(1.5 * 200), 1.0 / 200
    t0 = time.monotonic()
    for i in range(1, steps + 1):
        a = i / steps
        target = [p * (1.0 - a) for p in start]
        publisher.send([JointCommand(v, 0.0, 0.0) for v in target])
        wait = (t0 + dt * i) - time.monotonic()
        if wait > 0:
            time.sleep(wait)

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

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

    /* 1. Configure control parameters */
    wuji_hand_2_set_effort_limit_all(dev, 1.5f);
    wuji_hand_2_set_mit_params_all(dev, 3.0f, 0.05f);

    /* 2. Enable */
    wuji_hand_2_enable(dev);

    /* 3. Wait for readiness: subscribe to diagnostics and check every ext_state is 2 */
    struct WujiSub *diag = NULL;
    wuji_hand_2_subscribe_joint_diagnostics(dev, on_diag, &ctx, &diag);
    /* Poll the state your callback writes into ctx until all Enabled or timeout */

    /* 4. Read the current posture */
    /* Take one frame from a joint_states subscription, treating offline joints as 0 */

    /* 5. Publish the interpolation at a fixed rate */
    struct WujiPub *pub = NULL;
    wuji_hand_2_joint_command_publisher(dev, &pub);
    WujiJointCommand cmd[20] = {0};
    for (int i = 1; i <= steps; i++) {
        double a = (double)i / steps;
        for (int j = 0; j < 20; j++) cmd[j].position = start[j] * (1.0 - a);
        wuji_hand_2_publish_joint_command(pub, cmd, 20);
        usleep(5000);   /* 200 Hz */
    }

    /* 6. Clean up */
    wuji_hand_2_disable(dev);
    wuji_pub_close(pub);
    wuji_sub_close(diag);
    wuji_dev_disconnect(dev);
    wuji_dev_release(dev);
    ```
  </Tab>
</Tabs>

Step 3 isn't optional. In step 6, disable before closing the publisher.

## Running It

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

  <Tab title="C">
    ```bash theme={null}
    ./build/hand2/1_control_motion
    ```
  </Tab>
</Tabs>

### Measurements

On a right-hand Wuji Hand 2 with all 20 joints online:

| Metric | Measured |
| - | - |
| `enable()` call returns in | 0.0020 seconds |
| **Every motor reaches Enabled** | **0.0522 seconds** |
| `ext_state` before enable | All 1 (Ready) |
| `ext_state` after enable | All 2 (Enabled) |
| Homing frames and duration | 301 frames, 1.505 seconds |
| Actual publish rate | 200.0 Hz |
| Largest starting offset | 0.0674 rad (3.9 degrees, since the hand already sat near zero) |
| Residual after homing | First 4 joints at `+0.005 −0.019 −0.021 +0.008` |

There's a 26x gap between `enable()` returning and the motors being ready. Commands sent inside that window are discarded or behave unexpectedly, which is why the wait is required.

After homing, joints don't land exactly on 0 but within 0.02 rad. MIT impedance is compliant control, and static friction plus gravity leave a steady-state offset between actual position and target. That's normal.

## Adapting It

### Tune How Gentle Homing Is

Change `HOME_SECONDS`. A longer duration makes each frame's position step smaller and the motion gentler. Don't chase gentleness by lowering `kp`, which sacrifices tracking accuracy overall.

### Feed Your Own Trajectory

Replace the interpolation in step 5 with your own trajectory source, leaving the `publisher.send()` call unchanged. Each `send()` carries a complete 20-element frame, which the SDK serializes and transmits without waiting for a response, so it sustains a high-rate loop.

### Choosing a Publish Rate

The example uses 200 Hz. Higher rates make trajectories smoother, provided generating one frame stays reliably faster than the period. The measurement above shows an actual rate of exactly 200.0 Hz, so there's headroom at this scale.

### Controlling Only Some Joints

Every frame still carries the full 20-element array. For joints you don't want to move, fill in **their current measured positions** rather than 0. Filling 0 commands them home, which is the trap the [MIT joint sweep](/en/docs/wuji-hand/latest/sdk-practice/mit-sweep) ran into.

## Related

* [Running an MIT Joint Sweep](/en/docs/wuji-hand/latest/sdk-practice/mit-sweep): the minimal command-path check, and a warning that it pulls other joints toward zero
* [Replaying a Thumb Opposition](/en/docs/wuji-hand/latest/sdk-practice/opposition-replay): high-rate playback of a complete recorded trajectory
* [Subscribing With Callbacks](/en/docs/wuji-hand/latest/sdk-practice/subscribe-callback): how to read joint state
* [Common Data Types](/en/docs/wuji-sdk/data-reference/): `JointCommand` and diagnostic structures


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