Skip to main content

Overview

Callback subscriptions push frames to you. Async subscriptions let you pull them. subscribe() returns a subscription handle, await sub.recv_async() takes one frame at a time, and six streams each run their own coroutine driven concurrently by asyncio.gather. Compared with callbacks, async gives you control over pacing: the code reads sequentially and can await other I/O directly. The cost is that the subscription queue fills up while you’re not reading, and silently drops frames once it’s full. This page measures exactly where that boundary sits. Matching example: examples/python/wuji_glove/1.subscribe_async.py. For field definitions of each stream, see SDK Reference. This page covers only how to run it and what the measurements show.
The C SDK glove examples offer callback subscriptions only, with no async counterpart. This page covers Python.

Prerequisites

Hardware

This example only reads. It writes no parameters.

Software

Before You Start

Read the callback page first. Frame structures for all six streams, the -1.0 invalid-channel marker, and how the numbers 744, 572, and 526 relate all live there, and this page doesn’t repeat them.

Data Flow

The queues live inside the SDK, filled by firmware pushes and decoupled from your consuming coroutines. That decoupling is where the frame-loss risk comes from.

Queue Measurements

The Queue Holds 1024 Frames

The queue fills from the moment a subscription opens, even if you never read. Measured on the tactile stream at 132 Hz: In the first two rows, the lag equals the production count, so nothing was lost. The third row stops at 1023, meaning everything beyond that was already discarded. The queue holds 1024 frames, matching the SDK’s internal broadcast channel setting.

Drops Take the Oldest Frames, Never a Sample Here and There

Across all three runs, reading 400 consecutive frames after the first one gives a header.seq step set of exactly [1]. On overflow the queue discards the oldest batch at its head. What remains stays contiguous, with no holes punched in the middle.

Drops Raise No Exception, Only a WARN Log

After a 12-second backlog, reading resumes and the SDK prints:
recv_async() returns the next frame normally, raises nothing, and carries no drop marker in its return value. Your application code has no visibility into those 549 lost frames. The log is the only signal. For work that depends on data completeness, set the SDK log level to WARN or higher and route it into your own logging system rather than letting it scroll past in a terminal.

Fast Consumers Lose Nothing

Keep up and you lose nothing. Reading 200 frames with consecutive await recv_async() calls takes 1.52 seconds, an effective 131 Hz that matches the stream’s own rate, with every seq step equal to 1.

Runtime Handling

The example’s while True loop has no exit condition and stops on KeyboardInterrupt. Its finally block only calls manager.disconnect_all(), leaving subscription handles to be reclaimed at process exit. A long-running service needs its own sub.close().

Integration Steps

subscribe() is a synchronous call, and the queue starts filling the moment it returns. The longer the gap between connecting and your first recv_async(), the older that first frame is. When you need data starting now, move subscribe() as close to the consuming loop as you can.

Running It

Measured on a right-hand Wuji Glove resting on a desk, unworn. Five seconds produced 3031 lines, with per-stream frame counts of 506, 506, 505, 502, 502, and 501. No two streams differ by more than 5 frames, so the event loop favors none of them and gather schedules fairly. The palm=-0.1 on the Zones line comes from the example’s avg() including -1.0 invalid channels in its mean, not from the glove sensing a reversed force. The callback page explains the cause and the fix.

Adapting It

Callback or Async

Queue semantics are identical, and the 1024-frame boundary applies to callbacks too. Callbacks keep your event loop free. Async composes naturally with coroutines such as HTTP requests and database writes.

Don’t Block Inside a Coroutine

The event loop is single-threaded. Calling time.sleep(), writing files synchronously, or making blocking HTTP requests inside a coroutine backs up all six streams at once. Hand blocking work to a thread pool:

Subscribe Only to What You Need

All six streams open means about 640 recv_async() wakeups per second at rest. For tactile analysis, keep tactile or tactile_point_cloud alone and event-loop pressure drops immediately.

Lower the Stream Rate

130 Hz is higher than most applications need. Subscription handles support rate control, described in Adjusting Stream Rate. At a lower rate, those 1024 queued frames cover a longer window, which also buys more tolerance for jitter.