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
| Item | Requirement |
|---|---|
| Wuji Hand 2 | 1 |
| Connection | Ethernet |
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 |
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:
| Metric | Measured |
|---|---|
recv_async() throughput | 1088 Hz (3265 frames in 3 seconds) |
Synchronous recv() on the same handle | 1091 frames in 1 second |
frame_id | r_wrist |
joints length | 20 |
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 point | Behavior | What to do |
|---|---|---|
| Queue empty | recv_async() suspends the coroutine and yields the event loop | Expected behavior, so don’t busy-wait |
Queue empty with synchronous recv() | Returns None immediately without blocking | Pace the loop yourself when using recv() |
| Device disconnects | recv_async() raises WujiException | Since v2026.8.17 it raises immediately, where earlier versions could return a buffered frame or None |
| Several devices | The 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 cancelled | gather raises and finally disconnects the devices | The example relies on disconnect_all() in finally |
Adapting It
Async or Callback
Async recv_async() | Callback subscribe_with_callback() | |
|---|---|---|
| Runs on | Your event loop | An SDK worker thread |
| Backpressure | Queue growth is visible, and you can drop frames deliberately | A blocking callback directly slows the worker thread |
| Fits | Applications already on asyncio | Simple forwarding, writing to a queue |
| C API | None | Available |
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.
Related
- Subscribing With Callbacks: the callback form, and the only form the C API offers
- Adjusting Stream Rate: lower the firmware push rate
- Data Subscription: subscription model and lifecycle
ドキュメント