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

# ROS 2 Integration

After connecting the device through `wuji_hand2_driver`, you can read joint and sensor state, view the model in RViz, and control the device through joint commands and services.

## Installation

Use Ubuntu 26.04 (resolute) with ROS 2 Lyrical installed, on x86\_64 or aarch64. The commands below fetch the source, load ROS, install dependencies and Hand 2 descriptions, and build the workspace. The build downloads and verifies the Wuji SDK C package for the host architecture.

```bash theme={null}
# Fetch the source and load ROS 2
git clone https://github.com/wuji-technology/wuji-ros2.git "$HOME/wuji-ros2"
cd "$HOME/wuji-ros2"
source /opt/ros/lyrical/setup.bash
# Install build tools and dependencies
sudo apt-get update
sudo apt-get install -y ros-dev-tools
rosdep update
# Install the Hand 2 descriptions and workspace dependencies
scripts/install_hand2_descriptions.sh
rosdep install --from-paths . --ignore-src -r -y
# Build the workspace. The SDK C package is downloaded and verified automatically
colcon build --cmake-args -DBUILD_TESTING=OFF
# Load the built workspace
source install/setup.bash
```

> [!NOTE]
>
> * The description installer requires `/etc/apt/sources.list.d/ros2.sources`. If it is missing, configure the Lyrical package source using the official ROS 2 installation steps, then rerun the script.
> * If rosdep has never been initialized on the computer, run `sudo rosdep init` once before `rosdep update`. The `sudo` commands require administrator access.
> * If the SDK download fails, download and extract `wuji-sdk-c-2026.10.12-x86_64-linux-gnu.tar.gz` or `wuji-sdk-c-2026.10.12-aarch64-linux-gnu.tar.gz` from [Wuji SDK v2026.10.12](https://github.com/wuji-technology/wuji-sdk/releases/tag/v2026.10.12), matching your host architecture. Confirm that the extracted directory contains `include/wuji_sdk.h`, `lib/libwuji_sdk_c.so`, and `LICENSE`. Replace `/path/to/sdk` with the absolute path to that directory, then run this command from `wuji-ros2` instead of the default build command:
>
>   ```bash theme={null}
>   colcon build --cmake-args -DBUILD_TESTING=OFF -DWUJI_SDK_ROOT=/path/to/sdk
>   ```
> * CMake does not validate the SDK version when `WUJI_SDK_ROOT` is set. Use the release package specified above.

In each new terminal, enter the workspace and load ROS and the built packages:

```bash theme={null}
cd "$HOME/wuji-ros2"
source /opt/ros/lyrical/setup.bash
source install/setup.bash
```

## Select a Device

Follow the [User Notice](https://docs.wuji.tech/docs/en/wuji-hand/beta1/user-notice.md) to secure the hand and prepare power and networking. Then scan for devices:

```bash theme={null}
ros2 run wuji_tools wuji_list
```

Record the target device's required `serial_number` and `hand_side`. If connecting by address, also record its optional `endpoint`.

| Parameter | Purpose |
| - | - |
| `serial_number` | Selects the device. Validates its identity when `endpoint` is set |
| `hand_side` | Selects the left- or right-hand model and validates device handedness |
| `endpoint` | Connects directly to the device's `IP:port` network address |

## View the Model and State

### Run the Validation

**1. Start the Driver and View the Model**

Replace `HAND_SERIAL_NUMBER` and `HAND_SIDE` with the serial number and hand side recorded in [Select a Device](#select-a-device).

Run `hand2_view.launch.py` from the built `wuji_bringup` package to start the driver, `robot_state_publisher`, and RViz:

```bash theme={null}
ros2 launch wuji_bringup hand2_view.launch.py \
  serial_number:=HAND_SERIAL_NUMBER hand_side:=HAND_SIDE \
  model_revision:=1 namespace:=hand
```

To connect by the `endpoint` shown in the scan, replace `HAND_ENDPOINT` with that `IP:port` address and use this command:

```bash theme={null}
ros2 launch wuji_bringup hand2_view.launch.py \
  serial_number:=HAND_SERIAL_NUMBER hand_side:=HAND_SIDE \
  endpoint:=HAND_ENDPOINT model_revision:=1 namespace:=hand
```

> [!NOTE]
>
> The driver waits up to 10 seconds for the device to connect by default. Append `connect_timeout:=20.0` to the launch command to extend the connection wait to 20 seconds. Launch arguments are read only at startup. Boolean arguments accept only lowercase `true` / `false`.

This launch file requires `serial_number` and fixes `auto_enable` to `false`. It doesn't enable joints at startup or disable a hand that was already enabled. When the launch stops, the driver calls the disable API for all joints. If the call fails, it logs a warning and continues to exit. Confirm that the device has actually stopped.

`model_revision:=1` selects `wuji_hand2_1_description`. Selecting a model doesn't change the firmware. Data and control capabilities still depend on the SDK resources served by the device.

The model root is `l_mount` for the left hand or `r_mount` for the right hand, and RViz uses that root frame. The parent application must provide the transform from the mount to its robot or world frame.

**2. Check State Data**

Keep the launch running. Open a new terminal, enter the workspace, and load ROS and the built packages before checking state data:

```bash theme={null}
cd "$HOME/wuji-ros2"
source /opt/ros/lyrical/setup.bash
source install/setup.bash
ros2 run wuji_bringup hand2_status_demo --ros-args -r __ns:=/hand
```

The example observes `joint_states`, `joint_diagnostics`, and `imu` for 10 seconds by default and reports frame counts and average receive rates. It succeeds after receiving at least one joint state with 20 positions, one joint diagnostic message with 20 entries, and one IMU message. This check doesn't validate motion control or every sensor.

The demo reports PASS/FAIL after the full observation period. To observe for 30 seconds, run:

```bash theme={null}
ros2 run wuji_bringup hand2_status_demo --ros-args -r __ns:=/hand -p duration:=30.0
```

> [!NOTE]
>
> If the driver connects but RViz shows `No tf data` or the status example receives no data, first check namespaces, `ROS_DOMAIN_ID`, and QoS across terminals. If the problem persists, try another ROS 2 middleware. Install Cyclone DDS:
>
> ```bash theme={null}
> sudo apt-get install ros-lyrical-rmw-cyclonedds-cpp
> ```
>
> Stop the current launch. Run this command in each terminal used for steps 1 and 2:
>
> ```bash theme={null}
> export RMW_IMPLEMENTATION=rmw_cyclonedds_cpp
> ```
>
> Then rerun the commands in steps 1 and 2. Set the same ROS 2 middleware in any new terminals you open later.

### State and Sensor Topics

The driver publishes joint states, joint diagnostics, and IMU data on the topics below. Subscribe using the listed message types. Topic names are relative: with `namespace:=hand`, `joint_states` resolves to `/hand/joint_states`.

| Topic | Message Type | Content |
| - | - | - |
| `joint_states` | `sensor_msgs/msg/JointState` | Position (rad), velocity (rad/s), and current (A) for valid joints. `effort` is current, not measured torque. Identify joints by `name` |
| `joint_diagnostics` | `wuji_msgs/msg/JointDiagnosticsArray` | Raw SDK status words, fault codes, current, voltage, temperature, and communication statistics for online joints, not the standard ROS `/diagnostics` interface |
| `imu` | `sensor_msgs/msg/Imu` | Angular velocity and linear acceleration. No orientation estimate, with `orientation_covariance[0] = -1` |

> [!NOTE]
>
> For field definitions, see `wuji_msgs/msg/JointDiagnosticsArray.msg` and `wuji_msgs/msg/JointDiagnostics.msg` in the `wuji-ros2` repository, and the ROS 2 [JointState.msg](https://github.com/ros2/common_interfaces/blob/lyrical/sensor_msgs/msg/JointState.msg) and [Imu.msg](https://github.com/ros2/common_interfaces/blob/lyrical/sensor_msgs/msg/Imu.msg) definitions. After sourcing the workspace, run `ros2 interface show wuji_msgs/msg/JointDiagnosticsArray` to inspect the custom message and its nested fields.

## Control the Device

The driver sends position, velocity, and current-proxy targets without switching control modes. Motion depends on the device's current control mode and parameters.

### Send Joint Commands

**1. Start the driver**

Stop the view launch started in [1. Start the Driver and View the Model](#start-driver-view-model). Secure the unloaded hand and keep people clear of moving parts. Start control with the same serial number and hand side:

```bash theme={null}
ros2 launch wuji_bringup hand2.launch.py \
  serial_number:=HAND_SERIAL_NUMBER hand_side:=HAND_SIDE \
  model_revision:=1 namespace:=hand auto_enable:=true
```

`auto_enable:=true` enables all joints after startup. It doesn't send motion targets yet.

**2. Check the current positions**

Keep the driver running. Open a new terminal.

> [!NOTE]
>
> If the driver uses Cyclone DDS, configure the same ROS 2 middleware in this terminal:
>
> ```bash theme={null}
> export RMW_IMPLEMENTATION=rmw_cyclonedds_cpp
> ```

Run these commands to load ROS and read joint positions:

```bash theme={null}
cd "$HOME/wuji-ros2"
source /opt/ros/lyrical/setup.bash
source install/setup.bash
ros2 topic echo /hand/joint_states sensor_msgs/msg/JointState --once --timeout 20 --field position
```

The wave demo sends zero targets immediately. Before running it, confirm that the path from the current position to zero is safe. The joints may move quickly toward zero.

**3. Run the wave demo**

In the terminal with the ROS environment loaded, run:

```bash theme={null}
ros2 run wuji_bringup hand2_wave_demo --ros-args -r __ns:=/hand
```

The wave demo publishes zero targets to `/hand/joint_commands` for one second, then publishes wave commands at 100 Hz. Thumb and abduction targets stay at 0 rad. The other four fingers move between 0 and 1.0 rad with a 0.5 Hz cosine wave. Observe the hand and joint position changes.

**4. Stop and disable**

Press `Ctrl+C` to stop the wave demo, then disable all joints:

```bash theme={null}
ros2 service call /hand/set_enabled wuji_msgs/srv/SetEnabled \
  '{enabled: false, mask: [true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true]}'
```

Confirm the response contains `success: true` before stopping the driver launch, and verify that the hand has stopped.

> [!WARNING]
>
> The driver has no command watchdog. Stopping the publisher doesn't send zeros, trigger an emergency stop, or disable the hand. When the launch stops, the driver calls the disable API for all joints. If the call fails, it logs a warning and continues to exit. Confirm that the device has actually stopped. If the disable request fails, don't assume that exiting the launch or losing the connection has disabled the hand.

### Joint Command Format

Publish `sensor_msgs/msg/JointState` messages to `joint_commands`. The publisher fills the `name`, `position`, `velocity`, and `effort` fields. They aren't launch arguments.

**Named commands:** When writing a publisher, put joint names published in `joint_states` in `name`, and provide `position` values in the same order. You can specify a subset of joints in any order. The driver fills targets for unlisted joints with zero, so explicitly include any joints whose targets you want to preserve.

**Fixed-order commands:** Leave `name` empty and provide all 20 `position` values in the order below. The wave demo above uses this format. Prefix the listed joint names with `l_` or `r_` for the selected hand. Fixed-length services use the same order.

| Index | Joint Name Bodies, in Order |
| - | - |
| 0–3 | `thumb_cmc_flex`, `thumb_cmc_abd`, `thumb_mcp`, `thumb_ip` |
| 4–7 | `index_finger_mcp_flex`, `index_finger_mcp_abd`, `index_finger_pip`, `index_finger_dip` |
| 8–11 | `middle_finger_mcp_flex`, `middle_finger_mcp_abd`, `middle_finger_pip`, `middle_finger_dip` |
| 12–15 | `ring_finger_mcp_flex`, `ring_finger_mcp_abd`, `ring_finger_pip`, `ring_finger_dip` |
| 16–19 | `pinky_mcp_flex`, `pinky_mcp_abd`, `pinky_pip`, `pinky_dip` |

In either format, empty `velocity` and `effort` arrays are filled with zeros. Nonempty arrays must match the number of commanded joints. Unknown, duplicate, or opposite-side names, invalid lengths, nonfinite values, and values outside the float32 range reject the complete command.

### Call Control Services

Services use reliable QoS and the same namespace. Check both the success flag and error information in the response.

| Service | Type | Behavior |
| - | - | - |
| `set_enabled` | `wuji_msgs/srv/SetEnabled` | Enables or disables joints selected by a 20-entry mask |
| `clear_faults` | `wuji_msgs/srv/ClearFaults` | Clears faults for joints selected by a 20-entry mask |
| `set_effort_limit` | `wuji_msgs/srv/SetEffortLimit` | Sets one current limit in A for all joints |
| `set_effort_limits` | `wuji_msgs/srv/SetEffortLimits` | Sets 20 current limits in A in the fixed order |
| `set_mit_params` | `wuji_msgs/srv/SetMitParams` | Sets 20 `kp` and `kd` values in the fixed order |
| `emergency_stop` | `std_srvs/srv/Trigger` | Calls the SDK whole-hand emergency stop |

An all-false mask changes no joints. Fixed-length services require all 20 entries and don't accept joint names or partial arrays. Current limits, `kp`, and `kd` must be finite and nonnegative. `kp` and `kd` retain their SDK definitions without additional physical units.

Rejected joint commands produce rate-limited ERROR logs in the driver terminal, with no result topic. Failed service calls return `success: false` and error information. The driver keeps running.

To request an emergency stop, call:

```bash theme={null}
ros2 service call /hand/emergency_stop std_srvs/srv/Trigger '{}'
```

The driver doesn't latch the emergency-stop state. After the call, the controlling application must stop publishing joint commands, or later commands may still take effect.


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