HumansXDocumentation Center 日本語Website ↗

Subscribing Asynchronously

SDK Practice

Overview

Subscribe to the Wuji Hand 2 joint_states stream with async/await. Compared with callbacks, frames come out sequentially inside a coroutine with no cross-thread handoff.

When your application already runs on asyncio, this fits more naturally. Concurrency across devices falls out of asyncio.gather() rather than thread management.

Matching example: examples/python/wuji_hand_2/1.subscribe_async.py.

For API and field definitions, see JointStateFrame and JointStateEntry.

The C API has no async form. C subscriptions all go through callbacks, described in Subscribing With Callbacks.

This example only reads. It doesn’t enable the motors, and the fingers don’t move.

Prerequisites

Hardware

ItemRequirement
Wuji Hand 21
ConnectionEthernet

Software

ItemRequirement
Operating systemLinux or a Mac with Apple silicon, meeting the system requirements
Wuji SDKv2026.8.31 or later
Python3.10 or later
pip install wuji-sdk

Data Flow

The handle from subscribe() supports both retrieval styles: await recv_async() asynchronously and recv() synchronously. Frame structure is identical to the callback form.

Integration Steps

See Python example for the complete implementation.

1. Discover and Connect to the Device

Get the manager with SdkManager.instance(), filter scan() for Wuji Hand 2 devices, and confirm that matches exist before connecting. Python example connects to all discovered Wuji Hand 2 devices. The excerpt shows the key calls for one device.

manager = SdkManager.instance()
hands = [d for d in manager.scan() if d.device_type == DeviceType.WujiHand2]
if not hands:
    raise RuntimeError("No Wuji Hand 2 found")
hand = manager.connect(sn=hands[0].sn, device_name="hand_0")

2. Read State Frames in a Coroutine

subscribe() creates the subscription, and recv_async() waits for the next frame. Read the current frame’s joint list and identify joints by nid. Close the subscription in finally so coroutine cancellation also releases it.

async def consume(hand):
    sub = hand.joint_states().subscribe()
    try:
        while True:
            frame = await sub.recv_async()
            for joint in frame.joints:
                print(joint.nid, joint.position, joint.velocity, joint.effort)
    finally:
        sub.close()

3. Run the Consumer and Disconnect

Await the consumer in main(). For several devices, create one task per device and pass them to asyncio.gather(). Disconnect after the tasks finish, and use asyncio.run(main()) as the entry point. This path only reads state and doesn’t require enabling the motors.

try:
    await asyncio.gather(consume(hand))
finally:
    manager.disconnect_all()

Running It

python examples/python/wuji_hand_2/1.subscribe_async.py
Connected: WH2KA00XXXXXXXXX (20 joints online)
[hand_0][JointState] seq=147508 t=210677379743us frame='r_wrist' thumb_pos=['+0.003', '-0.018', '+0.021', '+0.056']

Measurements

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

MetricMeasured
recv_async() throughput1088 Hz (3265 frames in 3 seconds)
Synchronous recv() on the same handle1091 frames in 1 second
frame_idr_wrist
joints length20

Both retrieval styles reach the same throughput and both keep up with the device’s roughly 1 kHz broadcast. Choose based on how your application is structured, not on performance.

Notes

Branch pointBehaviorWhat to do
Queue emptyrecv_async() suspends the coroutine and yields the event loopExpected behavior, so don’t busy-wait
Queue empty with synchronous recv()Returns None immediately without blockingPace the loop yourself when using recv()
Device disconnectsrecv_async() raises WujiExceptionSince v2026.8.17 it raises immediately, where earlier versions could return a buffered frame or None
Several devicesThe example connects to each device it finds and runs them concurrently with asyncio.gather()On a shared network, change it to name a serial number
Coroutine cancelledgather raises and finally disconnects the devicesThe example relies on disconnect_all() in finally

Adapting It

Async or Callback

Async recv_async()Callback subscribe_with_callback()
Runs onYour event loopAn SDK worker thread
BackpressureQueue growth is visible, and you can drop frames deliberatelyA blocking callback directly slows the worker thread
FitsApplications already on asyncioSimple forwarding, writing to a queue
C APINoneAvailable

Concurrency Across Devices

One coroutine per device with asyncio.gather() is enough. Devices don’t block each other.

Lower the Rate

About 1 kHz is higher than most applications need. Use set_rate() on the same handle, noting the firmware version requirement described in Adjusting Stream Rate.