Running an MIT Joint Sweep
SDK Practice
Overview
Enable the hand, send joint 0 a fixed cosine position sequence, then disable and disconnect. The whole run takes 2 seconds.
This is the smaller of the two motion examples, meant to verify the command path works rather than to watch fingers move. The cosine amplitude on joint 0 is only 0.02 rad, about 1.15 degrees, and static friction leaves it with almost no visible displacement.
How much actually moves, though, depends on the hand’s starting posture. Each frame carries a complete 20-element command where all 19 joints other than joint 0 target position 0. If the hand starts curled, the very first frame pulls those 19 joints toward zero, and without any interpolation. In one run, nid 19 went from 0.6498 rad to 0.0143 rad, about 36 degrees of real motion, far beyond joint 0’s 1.15 degrees.
Matching examples: examples/python/wuji_hand_2/7.mit_sweep.py and examples/c/wuji_hand_2/7_mit_sweep.c.
For API and field definitions, see JointCommand and MitParam.
This example enables the motors. Clear people, cables, and desk objects from around the hand before starting, keep someone watching throughout, and stay ready to cut power.
When the hand doesn’t start at zero, the first frame pulls all 20 joints toward zero with no interpolation. For a gentle return to zero, run the smooth homing routine in Publishing Control Commands first.
Prerequisites
Hardware
| Item | Requirement |
|---|---|
| Wuji Hand 2 | 1, with all 20 joints online, or the example errors out |
| Connection | Ethernet |
| Network | Only this device may be on the network, for the reason under Runtime Branches |
Software
| Item | Requirement |
|---|---|
| Operating system | Linux or a Mac with Apple silicon, meeting the system requirements |
| Wuji SDK | v2026.8.31 or later |
| Python | 3.10 or later |
| C toolchain | CMake 3.16 or later, plus the extracted SDK package |
This example takes no command-line arguments.
Before You Start
Confirm the device’s MIT parameters and effort limit sit at factory values:
mit_params: kp=3, kd=0.05 (identical across all 20 joints)
effort_limit: 1.5 (identical across all 20 joints) Lowering kp erases what little displacement there is, and raising it goes beyond this example’s safety envelope.
Data Flow
The sequence is generated locally in full, then sent frame by frame with no interpolation and no replanning. Each JointCommand is a position, velocity, and torque triple, and this example fills only position, leaving velocity and torque at 0.
Note that every frame is a complete 20-element command. The 19 positions other than joint 0 are hard-coded to 0, which continuously commands them home, and that motion isn’t bounded by the 0.02 rad amplitude.
Integration Steps
1. Connect and Check the Joints
Discover and connect to a Wuji Hand 2, then read the online joint count. The example requires all 20 joints online and doesn’t enable the device if this check fails.
hand = manager.auto_connect(device_name="hand_2_mit_sweep")
if int(hand.online_joints_count().get()) != 20:
raise RuntimeError("expected 20/20 online joints") C performs the same check with wuji_scan(), wuji_connect(), and wuji_hand_2_online_joints_count().
2. Enable and Capture the Starting Posture
Call enable() and create a joint_command publisher. The example also subscribes to joint_states, uses nid_to_joint_index to arrange a complete frame in the fixed 20-joint order, and transitions smoothly from the current posture to the sweep start.
hand.enable()
publisher = hand.joint_command().publish()
start = capture_start_positions(hand)
sweep = generate_sweep()
send_sweep(ramp_frames(start, sweep[0], 1000), publisher,
wuji_sdk, interval=0.001) C uses wuji_hand_2_enable(hand, NULL) and wuji_hand_2_joint_command_publish(), and copies a complete state frame through a callback.
3. Send Sweep Commands at a Fixed Interval
The sweep changes only joint 0, with an amplitude of 0.02 radians. It sends 101 frames at 20 ms intervals. Each frame contains 20 JointCommand entries, with other joints held at zero. The send loop must respond to Ctrl+C.
send_sweep(sweep, publisher, wuji_sdk) capture_start_positions(), ramp_frames(), generate_sweep(), and send_sweep() are helpers in Python example. Retain complete-frame validation, the one-second starting transition, and stop checks. Don’t send the first sweep frame from an unknown posture.
C uses the corresponding capture_start_positions(), send_ramp(), and send_sweep() helpers. They send commands through wuji_joint_command_publisher_send(publisher, commands).
4. Stop Motion and Clean Up
On completion or interruption, disable first, close the publisher, then disconnect and release the device. The C example uses wuji_hand_2_disable(hand, NULL) and wuji_joint_command_publisher_close(publisher), with the same cleanup order on every error path.
hand.disable()
publisher.close()
manager.disconnect_all() Python example and C example catch cleanup errors individually so a failed disable doesn’t skip publisher closure or disconnection.
Running It
Python
C
python examples/python/wuji_hand_2/7.mit_sweep.py./build/hand2/7_mit_sweep A normal run prints nothing to standard output and exits 0. Only errors go to standard error. Two quiet seconds mean success.
Measurements
On a right-hand Wuji Hand 2 with all 20 joints online, subscribing to joint_states alongside to record joint 0’s response:
| Commanded position | Actual position | Actual torque |
|---|---|---|
| +0.0000 | +0.0050 | −0.0030 |
| +0.0100 | +0.0046 | +0.0245 |
| +0.0200 | +0.0045 | +0.0803 |
| +0.0100 | +0.0046 | +0.0392 |
| +0.0000 | +0.0045 | −0.0219 |
| Metric | Measured |
|---|---|
| Commands and duration | 101 commands over 2.016 seconds |
| Actual send rate | 49.9 Hz |
| Command peak | 0.0200 rad |
| Actual joint displacement range | 0.0004 rad |
| Torque range | 0.1384 |
enable() duration | 0.001 seconds |
Joint 0 barely moves, and that’s expected. Torque tracks the command’s rise and fall precisely, showing the command went out and the motor responded under MIT impedance. A 0.02 rad target error at kp=3 produces only about 0.06 of torque, which doesn’t overcome the joint’s static friction, leaving an actual displacement of 0.0004 rad, roughly 0.023 degrees and invisible to the eye.
The table above comes from a hand already near zero. From a contrasting run: starting with the hand curled, the same example pulled the other 19 joints toward zero with the displacements below.
| nid | Before | After | Displacement |
|---|---|---|---|
| 7 | +0.5018 | −0.0018 | −0.5036 |
| 9 | +0.6070 | +0.0382 | −0.5688 |
| 19 | +0.6498 | +0.0143 | −0.6355 |
| 22 | +0.5375 | +0.0652 | −0.4723 |
nid 19 moved 0.6355 rad, about 36 degrees. So “smallest amplitude” holds only when the hand already sits at zero.
Success here means the process exits quietly with code 0, plus a torque response visible on a joint_states subscription, not fingers you can see move. To watch real motion, see Replaying a Thumb Opposition.
Notes
| Branch point | Behavior | What to do |
|---|---|---|
| Several devices on the network | The example uses auto_connect(), which raises Found 6 devices, please specify SN to connect and exits without connecting to the wrong one | On a shared network, name a serial number. See Adapting It |
| Fewer than 20 joints online | Raises expected 20/20 online joints, got N/20 without enabling | Investigate the offline joints first |
| Enable fails | Raises enable failed: ... and sends no commands | Check fault codes and call clear_fault() if needed |
Ctrl+C | Exit code 130, stopping further commands and performing no return motion | The hand holds its position, then is disabled |
| Runtime exception | Exit code 1 | Cleanup still runs |
| Normal completion | Exit code 0 | Disables right after the last command |
Whichever path it takes, cleanup disables the motors, closes the publisher, and disconnects. Neither errors nor interrupts add any recovery motion, so the hand never returns to its starting posture on its own.
Adapting It
Name an SN on a Shared Network
auto_connect() means “connect to the one device found on the bus.” With more than one present it refuses and lists every serial number rather than guessing:
Internal error: Found 6 devices, please specify SN to connect.
Available: WG1KA00XXXXXXXXX, WH2KA00XXXXXXXXX, DF1XE00XXXXXXXXX, ... That design avoids disturbing someone else’s device, at the cost of the example not running as-is on a shared network. Replace step 1:
hand = manager.connect(sn="WH2KA00XXXXXXXXX", device_name="hand_2_mit_sweep") To Actually See a Joint Move
This example isn’t built for observing motion. For visible range, use the complete motion sequence in Replaying a Thumb Opposition. Raising this example’s amplitude yourself exceeds its safety envelope and isn’t advised.
How to Read Joint State
joint_states is a subscription resource and has no get() method. To read actual positions while commanding, call subscribe_with_callback() to receive continuously and take values from the latest frame. Calling hand.joint_states().get() directly raises AttributeError.
Changing the Command Rate
The interval is hard-coded at 20 ms. A shorter interval raises the command rate, but the fixed 101-frame sequence simply finishes sooner without changing the trajectory’s shape.
Related
- Replaying a Thumb Opposition: the larger complete four-finger motion
- Publishing Control Commands: general
joint_commandusage - Subscribing With Callbacks: how the measurements on this page were collected
- Common Data Types:
JointCommandand joint-state structures
ドキュメント