Requirements
Fingertip tactile requires Wuji Hand 2.2 (Beta) hardware equipped with fingertip tactile sensors, firmware v2.1.0 or later, and Wuji SDK v2026.7.21 or later.
Each stream has a native output rate of 100 Hz. After subscribing, use
Subscription.set_rate() to lower the output rate, and pass 0 to restore the device default. The C SDK uses wuji_sub_set_rate(). Rate control requires Wuji Hand 2 firmware v2.3.0 or later and Wuji SDK v2026.8.17 or later.
API Overview
Python’s
fingertip_<finger>_info() and fingertip_<finger>_data() name the finger in the method: thumb, index, middle, ring, or pinky. tactile_status() uses "thumb", "index", "middle", "ring", or "pinky". The C wuji_hand_2_tactile_status() takes a finger argument from 0 through 4 for thumb, index, middle, ring, and pinky.
Self-Describing Data Format
Read the finger’sFingertipSensorInfo first, then decode FingertipSensorData.data from its format JSON. Don’t hardcode the payload length, field offsets, scales, or units.
WujiException in that case. Skip that finger and keep decoding the others. This is an expected state, not a fault.
Metadata and Data Frames
The
v, encoding, point_count, point_stride, point_fields, aggregate_stride, aggregate_fields, and unit keys in format describe the payload. Check that v is 1 and encoding is point_array before decoding. The payload length equals point_count * point_stride + aggregate_stride, so use it to validate each frame. Each sensing point contains fx, fy, and fz. Aggregate fields provide the three-axis resultant force and temperature.
Starting with firmware v2.4.0, per-point force is normalized against the sensor’s full scale. The normal axis ranges from 0 to 1, and the two tangential axes range from -1 to 1, clamped beyond that. Earlier firmware reports per-point force in newtons. Aggregate force stays in newtons, and temperature stays in degrees Celsius. Read scale and unit from format to support both formats. The normalized format requires Wuji SDK v2026.8.17 or later.
Place Points on the Hand Model
Starting with firmware v2.5.0,format includes the mounting-pose fields point_rpy, base_xyz, and base_rpy. The format can include positions without the other mounting-pose fields. Treat the mounting pose as available only when point_rpy, base_xyz, and base_rpy are all present. positions and point_rpy must each contain point_count rows of three numbers. base_xyz and base_rpy must each contain three numbers. Treat a partial set or a shape mismatch as an invalid format instead of placing points with incomplete metadata.
positions and point_rpy use the sensor module frame. base_xyz and base_rpy describe that frame relative to the finger’s tip_sensor_frame. Convert each contact point and its force axes with:
R(rpy) uses URDF fixed-axis roll, pitch, and yaw: Rz(yaw) * Ry(pitch) * Rx(roll). format reports positions in meters and angles in radians. Both formulas transform values from the sensor module frame into the finger’s tip_sensor_frame.
The device reports the mounting pose that matches its handedness, so the same transform works for left and right hands. When none of the three mounting-pose keys is present, force data remains available, but the points can’t be placed on the hand model.
Locate the Resultant Force
Starting with firmware v2.6.0,format includes aggregate_xyz and aggregate_rpy. aggregate_xyz is the fixed geometric centroid of the sensor pad in the sensor module frame, and the resultant force from aggregate_fields is applied at that point. The point doesn’t follow the center of pressure from frame to frame. Convert it to the finger link with:
aggregate_rpy describes the resultant-force axes in the same sensor module frame. Convert the force and its moment about the finger link origin with:
aggregate_xyz is fixed, moment_link is the moment of the resultant force about a fixed lever arm, not a measured contact moment.
Firmware earlier than v2.6.0 still reports the resultant force, but doesn’t provide its point of application or moment. When aggregate_rpy is absent, use the sensor mounting orientation R(base_rpy) alone for the force axes, whether or not aggregate_xyz is present. If aggregate_xyz or aggregate_rpy is present but doesn’t hold exactly three numbers, treat the whole format as invalid rather than as a missing field.
Read and Display Examples
The Python and C examples read metadata for all five fingers and build decoders that match the currentdigest. Python subscribes only to the fingers that report metadata, while C subscribes to all five streams and skips fingers without metadata when displaying. The terminal refreshes the same display area at 100 Hz to show:
- Resultant force, temperature, contact count, and strongest contact for each finger
- Three-axis force for every sensing point, where
fxandfyare tangential components along the local x- and y-axes, andfzis the normal component along the local z-axis - Position and force direction of the strongest contact, starting with firmware v2.5.0
- Position of the aggregate force point and its moment about the origin of the finger link frame, starting with firmware v2.6.0
Frame Handling and Status
The Python example drains every subscription queue on each refresh and displays only the newest frame for each finger. The C example stores the latest frame for each finger, then refreshes the display from the main thread. Wheninfo_digest differs from the current metadata, the corresponding status line tells you to read FingertipSensorInfo again and rebuild the decoder. When the payload length doesn’t match format, the example skips that finger’s frame while the other fingers keep updating.
The C example reports waiting, lagged, stream ended, stream error, metadata changed, and unexpected payload length on each finger’s status line. The Python example also keeps running after a payload-length error for one finger.
Format Validation
Both examples requireaggregate_fields to contain fx, fy, fz, and temperature. Before subscribing, the C example validates point counts, strides, field offsets, and scales. It rejects nonfinite values, noninteger layout values, out-of-range fields, and per-finger layouts larger than 2048 bytes.
When a force value exceeds the expected column width, the table widens the row instead of truncating it.
The examples select a contact threshold from the unit declared in format. They use 0.02 for normalized data and 0.2 N for data in newtons. These values serve the example display and aren’t part of the SDK data contract.
- Python:
3.fingertip_typed.py - C:
3_fingertip.c
C subscription callbacks run on SDK worker threads. The frame and its
data stay valid only for the callback duration. The example copies each payload into mutex-protected storage before the callback returns, then renders a five-finger snapshot on the main thread. Build it with the provided CMakeLists.txt, which links the math library and Threads/pthread. Close each subscription with wuji_sub_close() when the example exits.Zero-Baseline Calibration and Status
Keep all five sensor surfaces unloaded before calibration. One call triggers every sensor from thumb through pinky. With firmware v2.6.0 or later and Wuji SDK v2026.8.31 or later, the call raisesNodeOfflineError on the first offline finger, and later fingers aren’t triggered. The C SDK returns WUJI_STATUS_ERR_NODE_OFFLINE for that finger. Earlier versions raise WujiException instead.
TactileState.Ready, and set an overall timeout for the wait. Each status query blocks for up to about one second, so one round over five fingers can take about five seconds, and the deadline is checked between rounds. tactile_status() raises NodeOfflineError when that finger’s tactile module is offline, under the same version requirement. The example treats that finger as not ready until the deadline.
TactileType.Thumb identifies the 40-point thumb sensor, and TactileType.Standard identifies a 34-point standard sensor. Runtime states include TactileState.Calibrating and TactileState.Ready.
Handle an Offline Sensor
A fingertip data subscription can still be created while its sensor is offline. If the subscription doesn’t receive any frames, calltactile_status for that finger to check whether the sensor is offline.
With firmware v2.6.0 or later and Wuji SDK v2026.8.31 or later:
- Python
fingertip_<finger>_info().get(),tactile_status, andtactile_calibrateraiseNodeOfflineError. - The corresponding C calls return
WUJI_STATUS_ERR_NODE_OFFLINE (-12). - Generic GET, SET, and EXEC requests return the same typed result when their target is offline.
NodeOfflineError inherits from WujiException, not RuntimeError. Existing code that catches WujiException continues to work. Code that caught RuntimeError for these operations must catch NodeOfflineError or WujiException instead.
Tactile Communication Diagnostics
Subscribe withhand.joint_diagnostics().subscribe(), then read JointDiagnosticsFrame.comm from each frame that recv() or recv_async() returns:
comm includes tactile communication status:
Use
tactile_online_mask to locate an offline finger, then check the response rate and timeout count for continuing communication degradation.
The mask and both per-finger arrays come from a device-internal snapshot. When that snapshot is unavailable, all three read as zeros. Check age_ms before reading them as five offline fingers. A growing age_ms with a flat comm_get_failures means the SDK is skipping the refresh on purpose, which happens during tactile calibration and firmware upgrades. A growing age_ms with an increasing comm_get_failures means the refresh itself is failing.
For the full Hand2CommSummary field list, see Wuji Hand 2 SDK Reference — Hand2CommSummary.
For a measured run, how to detect the capability, and the interface change in SDK v2026.9.14, see Wuji Hand 2 Fingertip Tactile in practice.