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

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

AttributeTypeDescription
serial_numberstrDevice serial number
device_namestrName given at connect (default "wuji_hand_2")
infoOptional[DeviceInfo]Device info: serial_number, firmware_version
is_connectedboolConnection 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–20

2.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=ERROR

For 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 NaN

2.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 joints

2.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 back

Constraints: 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 joints

2.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")
bankMeaningWhen to use
"current"Logs written by the firmware currently runningDefault — troubleshoot live issues
"other"Logs left by the firmware version before an upgrade or rollbackOnly 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 files

3. Joint Traversal

JointHandle and FingerHandle provide only label / index info, used to index into the arrays returned by diagnostics() / joint_state.

MethodReturn typeDescription
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.

AttributeTypeDescription
labelstrJoint label, format {finger}_S{1..4}, e.g. "thumb_S1", "pinky_S4"
indexintGlobal index (0–19)

3.2 FingerHandle

Finger handle: traverse the 4 joints of this finger.

MethodReturn typeDescription
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):

KeyTypeDescription
vbusfloatBus voltage (V)
temperaturefloatTemperature (°C)
inverter_stateintInverter state (see the table below)
fault_codeintFault code (0 = normal)
raw_angleintRaw encoder angle
encoder_validintEncoder valid flag (0 / 1)

inverter_state enum:

ValueStateDescription
0InitializationHardware init (EN=0, nSLEEP=1, PWM=off)
1SelfCheckSelf-check
2ReadyReady, waiting for Enable
3PreChargingBus pre-charge (EN=1, PWM=on, CCR=0)
4EnabledMotor enabled, control active
5SleepLow-power sleep (EN=0, nSLEEP=0)
6FaultFault, output disabled — clearable via clear_fault()
7FatalFatal 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.

AttributeTypeDescription
cycle_idintFrame cycle ID
num_jointsintJoint count
jointslist[JointEntry]Joint state array

4.4 JointEntry

Single-joint state.

AttributeTypeDescription
nidintNode ID
statusintStatus code
target_posfloatTarget position (rad, joint-side)
actual_posfloatActual position (rad, joint-side)
target_velfloatTarget velocity (rad/s)
actual_velfloatActual velocity (rad/s)
torquefloatTorque (A)
bcast_seqintFrame 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_pct

5. Joint Numbering

FingerS1S2S3S4
thumb0123
index4567
middle891011
ring12131415
pinky16171819

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}")