SDK Reference
The Python semantic API for Wuji Hand 2. Unified pattern: hand.{resource}().{action}(). All operations target the whole hand (20 joints).
This page focuses on the Wuji Hand 2–specific semantic API. The SDK builds on the shared Wuji SDK (wuji_sdk, pip install wuji-sdk)—for general installation, device connection, and data subscription, see the Wuji SDK docs; for source, see the wuji-sdk repository.
1. Connection
1.1 Connect via SdkManager (Recommended)
from wuji_sdk import SdkManager, Handedness
manager = SdkManager.instance()
# Auto-discover and connect to the first Wuji Hand 2 on the LAN
hand = manager.auto_connect(device_name="wuji_hand_2")
# Connect explicitly by serial number or address
hand = manager.connect(sn=<device_sn>, device_name="wuji_hand_2")
hand = manager.connect(address="192.168.3.110:50001", device_name="wuji_hand_2")
# Or connect by handedness directly, without a serial number
hand = manager.connect(handedness=Handedness.Right, device_name="wuji_hand_2")The device_name="wuji_hand_2" overloads of auto_connect and connect return a WujiHand2 instance directly (with type hints). handedness is mutually exclusive with sn and address. If multiple Hand 2 units of the same handedness are on the network, AmbiguousHandedness is raised—specify sn instead.
1.2 ConnectOptions
from wuji_sdk import SdkManager, ConnectOptions
opts = ConnectOptions(
timeout_ms=1000,
retry_count=3,
enable_bridge=True, # Default True: lets multiple clients (Wuji Studio + recording scripts + your app) connect at once
)
hand = manager.connect(sn=<device_sn>, device_name="wuji_hand_2", options=opts)Set enable_bridge=False for exclusive single-client mode.
1.3 Instance Attributes
| Attribute | Type | Description |
|---|---|---|
serial_number | str | Device serial number |
device_name | str | Name given at connect (default "wuji_hand_2") |
info | Optional[DeviceInfo] | Device info: serial_number, firmware_version |
is_connected | bool | Connection state |
hand.hw_version().get() returns the factory hardware version HwVersion(major, minor, patch). 0.0.0 means it wasn't written at the factory.
2. Hand-Level Resources
All operations target the whole hand (20 joints). No single-joint API exists. Get single-joint info from the handles returned by hand.joints() / hand.fingers() (see Section 3, Joint Traversal).
2.1 handedness (GET)
Get the handedness result (left / right).
side = hand.handedness().get() # → "left" or "right"2.2 online_joints_count (GET)
Get the online joint count (0–20).
n = hand.online_joints_count().get() # → int, 0–202.3 diagnostics (GET)
Diagnostics snapshot: returns a diagnostic report with one DiagnosticStatus per joint (20), indexed to match hand.joints().
report = hand.diagnostics().get() # → DiagnosticArray
for s in report.status:
print(s.name, s.level, s.message) # name like "thumb_S1"; level: 0=OK / 1=WARN / 2=ERRORFor joint angles, use JointState.actual_pos.
2.4 comm_diag (GET, 1 Hz)
Communication diagnostics: returns the throughput / error rate of the 5 fingers, once per second.
diag = hand.comm_diag().get() # → HandCommunicationDiagnostics
for finger in diag.fingers: # throughput / error rate for each of the 5 fingers
print(finger.tx_kbps, finger.rx_kbps, finger.error_per_sec)Use it to troubleshoot communication between the joints and the control board.
2.5 control_mode (SET)
Set the control mode.
hand.control_mode().set("mit") # "off" / "mit" / "position" / "velocity" / "current"Case-sensitive — must be lowercase.
2.6 effort_limit (SET/GET)
Effort limit: read / write the per-joint torque cap (A).
hand.effort_limit().set(1.5) # set all joints to 1.5 A
limits = hand.effort_limit().get() # → list[Optional[float]]2.7 mit_params (SET/GET)
MIT impedance parameters: 5×4 matrix, per-joint kp / kd.
kp = [[1.0]*4 for _ in range(5)] # 5 fingers × 4 joints
kd = [[0.05]*4 for _ in range(5)]
hand.mit_params().set(kp=kp, kd=kd) # per-joint kp / kd
mp = hand.mit_params().get() # → MitParams(kp=[[...]], kd=[[...]]); offline joints are NaN2.8 clear_fault (SET)
Clear fault.
hand.clear_fault().set(1)2.9 User Origin
Calibrate the current physical position as the joint-side zero for one joint (or the whole hand). If the joint is moving, it takes effect at the next IDLE.
hand.set_origin(joint_index) # single joint: set the current position of joint_index (0..19) as the new zero
hand.clear_origin(joint_index) # single joint: clear the user origin (offset3 → 0)
hand.set_origin_all() # whole hand: refresh the user origin of all joints at once
hand.clear_origin_all() # whole hand: clear the user origin of all joints2.10 Soft Limit
Per-joint joint-side angle soft limit (rad). With enabled=False, it falls back to the factory hardware range.
hand.set_soft_limit(joint_index, min_rad, max_rad, enabled=True) # configure, min <= max
min_rad, max_rad, enabled = hand.get_soft_limit(joint_index) # read backConstraints: min_rad <= max_rad. min == max is a valid single-point clamp. Both ends must be finite (no NaN / Inf).
2.11 Enable / Disable
hand.enable() # enable all joints
hand.disable() # disable all joints2.12 joint_state (SUB)
Joint state stream: subscribe to the real-time state frames of the 20 joints.
sub = hand.joint_state().subscribe() # → Subscription[JointState]
data = sub.recv() # synchronous, non-blocking; None = no data yet
data = await sub.recv_async() # asynchronous wait
print(data.cycle_id)
for j in data.joints:
print(j.actual_pos, j.actual_vel, j.torque)
sub.close()Callback mode:
def on_state(state):
print(state.cycle_id, [j.actual_pos for j in state.joints[:4]])
cb = hand.joint_state().subscribe_with_callback(on_state)
# ...
cb.close()actual_pos is the joint-side angle (rad) and actual_vel the joint-side angular velocity (rad/s)—this is the only recommended path for joint angle and speed. diagnostics() returns a status-level report (DiagnosticArray) and doesn't include directly consumable joint angles.
2.13 joint_command (PUB)
Joint command: publish positions / velocities / torque feedforward for the 20 joints.
pub = hand.joint_command_publisher() # → JointCommandPublisher
pub.send(positions) # list[float], 20 positions (rad)
pub.send(positions, velocities) # + velocities
pub.send(positions, velocities, efforts) # + torque feedforward
pub.close()2.14 Flash Log Export (Diagnostics)
# Export the running logs of the current firmware (use this for most troubleshooting)
result = await hand.dump_hand_logs(bank="current", out_dir="./logs")bank | Meaning | When to use |
|---|---|---|
"current" | Logs written by the firmware currently running | Default — troubleshoot live issues |
"other" | Logs left by the firmware version before an upgrade or rollback | Only when you need to trace behavior before an upgrade / rollback |
Each call creates a separate session directory <sn>-<unix_ts>/ under out_dir, containing joint{0..19}.log for each joint plus one sboard.log (JSONL: one {"timestamp_ms", "level", "target", "message"} per line). out_dir is optional and defaults to ~/.wuji/hand_logs. Each call returns a dict:
result = await hand.dump_hand_logs(bank="current")
print(result["session_dir"]) # str: absolute path of this session directory
print(result["files"]) # list[str]: paths of the written .log files3. Joint Traversal
JointHandle and FingerHandle provide only label / index info, used to index into the arrays returned by diagnostics() / joint_state.
| Method | Return type | Description |
|---|---|---|
hand.joints() | list[JointHandle] | All 20 joints |
hand.fingers() | list[FingerHandle] | All 5 fingers |
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", ...3.1 JointHandle
Joint handle: provides label / index.
| Attribute | Type | Description |
|---|---|---|
label | str | Joint label, format {finger}_S{1..4}, e.g. "thumb_S1", "pinky_S4" |
index | int | Global index (0–19) |
3.2 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 |
4. Data Types
4.1 DiagnosticArray / DiagnosticStatus
Diagnostic report: return value of hand.diagnostics().get().
class DiagnosticArray:
seq: int
timestamp_us: int
frame_id: str
status: list[DiagnosticStatus] # 20 items, indexed to match hand.joints()
class DiagnosticStatus:
level: int # 0=OK / 1=WARN / 2=ERROR
name: str # joint label, e.g. "thumb_S1"
message: str # human-readable message
hardware_id: str # device SN
values: list[tuple[str, str]] # diagnostic key-value pairs (empty for offline joints)values has a fixed set of 6 keys (the type column shows the semantics carried in the string value):
| Key | Type | Description |
|---|---|---|
vbus | float | Bus voltage (V) |
temperature | float | Temperature (°C) |
inverter_state | int | Inverter state (see the table below) |
fault_code | int | Fault code (0 = normal) |
raw_angle | int | Raw encoder angle |
encoder_valid | int | Encoder valid flag (0 / 1) |
inverter_state enum:
| Value | State | Description |
|---|---|---|
| 0 | Initialization | Hardware init (EN=0, nSLEEP=1, PWM=off) |
| 1 | SelfCheck | Self-check |
| 2 | Ready | Ready, waiting for Enable |
| 3 | PreCharging | Bus pre-charge (EN=1, PWM=on, CCR=0) |
| 4 | Enabled | Motor enabled, control active |
| 5 | Sleep | Low-power sleep (EN=0, nSLEEP=0) |
| 6 | Fault | Fault, output disabled — clearable via clear_fault() |
| 7 | Fatal | Fatal error — recoverable only by hardware reset |
4.2 MitParam / MitParams
MIT parameters types.
class MitParam: # single joint
kp: float
kd: float
class MitParams: # whole-hand matrix (returned by hand.mit_params().get())
kp: list[list[float]] # 5×4, offline joints are NaN
kd: list[list[float]]4.3 JointState
Joint state frame: return value of hand.joint_state().subscribe().recv(), aggregates 20 joints.
| Attribute | Type | Description |
|---|---|---|
cycle_id | int | Frame cycle ID |
num_joints | int | Joint count |
joints | list[JointEntry] | Joint state array |
4.4 JointEntry
Single-joint state.
| Attribute | Type | Description |
|---|---|---|
nid | int | Node ID |
status | int | Status code |
target_pos | float | Target position (rad, joint-side) |
actual_pos | float | Actual position (rad, joint-side) |
target_vel | float | Target velocity (rad/s) |
actual_vel | float | Actual velocity (rad/s) |
torque | float | Torque (A) |
bcast_seq | int | Frame sequence number |
4.5 HandCommunicationDiagnostics
Communication diagnostics data.
class HandCommunicationDiagnostics:
fingers: list[FingerCommunicationDiagnostics] # length 5
class FingerCommunicationDiagnostics:
tx_frame_total: int
rx_frame_total: int
tx_kbps: int
rx_kbps: int
error_per_sec: int
crc_error_total: int
frame_format_error_total: int
uart_hw_error_total: int
transfer_stats: list[TransferStats]
nodes: list[NodeDiagnostics] # per-node: online / ms_since_last_response / response_rate_pct5. 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).
6. Exception Handling
All SDK operation errors raise a unified WujiException whose message carries an error-type prefix (Disconnected, Timeout, PathNotFound, SchemaMismatch, SerializeError, …). Use try / except as needed.
from wuji_sdk import WujiException
try:
hand.joint_state().subscribe()
except WujiException as e:
print(f"SDK error: {e}")