Diagnostics and Logs
SDK Reference
Fault Logs
After connection, the SDK logs faults without a joint_diagnostics subscription. For log locations, fault states, and code lookup, see Fault Logs and Error Codes.
After the device restarts, the SDK’s first received status records faults that the device saved as active before power loss, marked state=active_before_power_off. The device saves up to 15 active fault-code records and records how many did not fit. These records describe the state before power loss. They don’t establish that a fault is still active after restart or provide a complete event history from before connection.
When a reporting list is truncated, the log reports omissions. The SDK doesn’t treat an omitted fault as cleared solely because the list was truncated. Retain these messages and check the current state when troubleshooting.
joint_diagnostics (SUB)
Joint diagnostics stream: subscribe to per-joint status word, current, bus voltage, temperature, and error code. Frames are variable-length and contain only online joints — identify each entry by its nid. This stream and joint_states ride the same device stream, so lowering the push rate of one lowers both — see joint_states (SUB).
sub = hand.joint_diagnostics().subscribe() # → Subscription[JointDiagnosticsFrame]
frame = await sub.recv_async()
for j in frame.joints:
print(j.nid, j.vbus_v_fb, j.mcu_temp_c_fb, f"0x{j.error_code_current:04X}")
sub.close() Decode Error Codes
Use WujiHand2.describe_error(code) to get the fault cause and recommended action. Unknown codes return None. See Device Fault Codes.
Object Display Format
Python uses the following format for error_code_current in the repr() of JointDiagnosticsEntry:
| Error Code | repr() Display |
|---|---|
| Known nonzero value | 0x2102(Overcurrent) |
| Zero | 0x0000 |
| Unknown value | 0xFFFF(Unknown) |
This changes only the display format. error_code_current remains an integer that you can pass to describe_error().
error_code_current is a native integer fault code. Look it up with WujiHand2.describe_error(code). Unknown codes return None. Printing a diagnostics entry shows the error code in hexadecimal.
For joint angles, use position from the joint_states stream — the diagnostics stream doesn’t carry joint angles.
comm_diag (GET, 1 Hz)
Communication diagnostics: returns the throughput / error rate of the 5 fingers, once per second.
diag = hand.comm_diag().get() # → HandCommunicationDiagnostics
for finger in diag.fingers: # throughput / error rate for each of the 5 fingers
print(finger.tx_kbps, finger.rx_kbps, finger.error_per_sec) Use it to troubleshoot communication between the joints and the control board.
SDK Log Level
Adjust the SDK log level for debugging:
import wuji_sdk
wuji_sdk.set_log_level("debug") Choose one log level per call:
| Level | Output |
|---|---|
trace | All logs |
debug | Debug and higher-severity logs |
info | Info and higher-severity logs (default) |
warn | Warnings and errors |
error | Errors only |
off | No logs |
The SDK writes its log to the terminal and to ~/.wuji/logs/sdk_<date>.log. The kp / kd readback record for each connection uses the info level. For the format, see mit_params.
Retrieve the device firmware’s own runtime logs with Flash Log Export.
Flash Log Export (Diagnostics)
# This call blocks until the export finishes.
result = hand.export_flash_logs("./logs")
print(result["path"]) # str: absolute path to the exported JSONL file
print(result["frames"]) # int: number of exported log records The SDK writes one flash_<serial>_<YYYY-MM-DD_HHMMSS>.jsonl file to out_dir. Omit out_dir to use ~/.wuji/logs.
The SDK writes to a temporary file and makes the JSONL file available only after the export finishes. If the export fails, the SDK removes the temporary file, so the output directory contains no partial export.
A device supports concurrent log exports.
When multiple sessions export logs from the same device, the SDK retries automatically. If the export still can’t finish, the call reports that the device is busy. The SDK removes the temporary files and doesn’t keep an unfinished log.
Data Types
JointDiagnosticsFrame / JointDiagnosticsEntry
Joint diagnostics frame: frame type of the hand.joint_diagnostics().subscribe() stream. Variable-length, online joints only.
class JointDiagnosticsFrame:
header: FrameHeader # seq / timestamp_us / frame_id
num_joints: int
joints: list[JointDiagnosticsEntry]
comm: Hand2CommSummary # frame-level comm health summary, 1 Hz refresh (see Hand2CommSummary)
class JointDiagnosticsEntry:
nid: int # node ID — identifies the joint across frames
status_word: StatusWord # status word
current: float # current (A)
vbus_v_fb: float # bus voltage (V)
mcu_temp_c_fb: float # MCU temperature (°C)
error_code_current: int # current error code (integer value, shown in hex when the entry is printed) — decode with describe_error()
comm_response_rate_pct: int # bus response rate over the last second, 0–100
comm_timeout_total: int # cumulative bus timeouts for this joint HandCommunicationDiagnostics
Communication diagnostics data.
class HandCommunicationDiagnostics:
fingers: list[FingerCommunicationDiagnostics] # length 5
class FingerCommunicationDiagnostics:
tx_frame_total: int
rx_frame_total: int
tx_kbps: int
rx_kbps: int
error_per_sec: int
crc_error_total: int
frame_format_error_total: int
uart_hw_error_total: int
transfer_stats: list[TransferStats]
nodes: list[NodeDiagnostics] # per-node: online / ms_since_last_response / response_rate_pct StatusWord
Status word: the type of the status_word field on JointDiagnosticsEntry, the public status view exposed by the SDK.
| Field | Type | Description |
|---|---|---|
ext_state | int | Extended state value (such as Init / Ready / Enabled / Stopped) |
ext_state_name | str | Semantic name of ext_state |
position_limit_active | bool | Position limit triggered |
velocity_limit_active | bool | Velocity limit triggered |
current_limit_active | bool | Current limit triggered |
Hand2CommSummary
Frame-level communication health summary: the type of the comm field on JointDiagnosticsFrame, combining the device-internal bus view with the SDK-local end-to-end (Ethernet) view. The device-internal part comes from a snapshot the SDK refreshes automatically at 1 Hz — no polling required.
| Field | Type | Description |
|---|---|---|
age_ms | int | Age of the device-internal snapshot in milliseconds. 65535 = never sampled successfully (the device-internal fields are zeros). 65534 = saturated, the snapshot is 65.5 s or older |
e2e_received | int | Frames received across all subscribed streams (cumulative) |
e2e_lost | int | Frames lost on the Ethernet segment, detected via sequence gaps (cumulative) |
e2e_reordered | int | Late/reordered frames (cumulative) |
e2e_duplicates | int | Duplicate frames (cumulative) |
e2e_window_loss_x100 | int | Loss rate over the last second, in 0.01% units |
rpc_total | int | Requests sent (cumulative). 0 means this transport does not report RPC statistics (only the wuji-proto transport does, and a live wuji-proto connection has always sent at least one request), so a zero here is not the same as “no retries happened” |
rpc_retries | int | Request retransmissions (cumulative) |
rpc_timeouts | int | Requests that ultimately timed out (cumulative) |
comm_get_failures | int | Failed internal snapshot refreshes (cumulative) |
sdk_dropped | int | Frames dropped inside the SDK because one of its in-process hops fell behind (cumulative) — either your subscription consumer or the internal stream handler. Distinct from e2e_lost, which is network loss |
Reading the device-internal fields. age_ms and the per-joint comm_* fields
all come from one snapshot the SDK refreshes at 1 Hz. The refresh is deliberately
skipped while a firmware upgrade is running, so a briefly stale snapshot is expected
rather than a fault. Tell the two cases apart with
comm_get_failures: a growing age_ms while comm_get_failures stays flat means the
refresh is being skipped on purpose, while comm_get_failures increasing means the refresh
itself is failing.
Counters saturate, they do not wrap. Every counter in this table stops at its
maximum (65535 for the 16-bit ones — e2e_reordered, e2e_duplicates,
rpc_retries, rpc_timeouts, comm_get_failures — and 4294967295 for the 32-bit ones)
and stays there. On a 1 kHz stream a long-running session can reach those ceilings, so
read these as deltas between two samples rather than as absolute lifetime totals.
Exception Handling
All SDK operation errors raise a unified WujiException whose message carries an error-type prefix (Disconnected, Timeout, PathNotFound, SchemaMismatch, SerializeError, …). Use try / except as needed.
from wuji_sdk import WujiException
try:
hand.joint_states().subscribe()
except WujiException as e:
print(f"SDK error: {e}") For the full list of exception types, see the Wuji SDK appendix — Python Exception Types. For reconnecting after a drop, see Wuji SDK best practices — Reconnection Strategy.
Device Fault Codes
error_code_current in the joint diagnostics and error_code in the error history are both u16 device fault codes. Decode them with the static method WujiHand2.describe_error(code). The returned object contains:
code, the error name, anddesc: error code, name, and descriptioncauseandresolution: fault cause and recommended actionseverity:Warning/DeferredStop/ImmediateStop/Fatalclear_policy:AutoClear/ManualClear/NonClearable
Device fault codes lay their hex digits out as 0xSCNN, but don’t decode the digits yourself — the severity and clear_policy fields returned by describe_error() carry the same information as plain strings and stay correct if the layout ever changes. Treat the code as an opaque identifier: log it, show it to the user, quote it in a bug report. When decoding succeeds, use the returned severity and clear_policy to decide how to react. When describe_error() returns None (an unknown code), don’t access those fields — keep the original value for logging and bug reports.
Python
C
Python: See the Python API examples earlier on this page.
The C API matches the Python API on this page and uses the wuji_hand_2_ prefix. For initialization conventions, memory management, error codes, and build setup, see the C API Reference.
Subscribe to Joint Diagnostics
wuji_hand_2_subscribe_joint_diagnostics has the same callback shape as wuji_hand_2_subscribe_joint_states. Each WujiJointDiagnosticsEntry carries status_word, current, vbus_v_fb, mcu_temp_c_fb, error_code_current, and per-joint bus comm quality (comm_response_rate_pct, comm_timeout_total). The frame-level comm field (WujiHand2CommSummary) adds end-to-end stream loss, RPC retry and timeout counters.
Decode Joint Status Words
wuji_hand_2_decode_joint_status decodes WujiJointDiagnosticsEntry.status_word into WujiJointStatus. ext_state is 0 for initializing, 1 for ready, 2 for enabled, or 3 for stopped. enabled is true only when ext_state == 2. The remaining fields show whether the position, velocity, and current limits are active. The function doesn’t require a device connection.
void print_joint_status(uint32_t status_word) {
WujiJointStatus status = {0};
if (wuji_hand_2_decode_joint_status(status_word, &status) != WUJI_STATUS_OK) {
return;
}
printf("state=%u enabled=%d limits=%d/%d/%d\n",
status.ext_state,
status.enabled,
status.position_limit_active,
status.velocity_limit_active,
status.current_limit_active);
}The function returns WUJI_STATUS_ERR_INVALID_ARG when out is NULL.
Flash Log Export
The C SDK provides the Flash log export API. After calling wuji_hand_2_export_flash_logs(), the current thread waits until the SDK reads the device logs and writes the output file before the function returns a result struct with path and frame_count:
WujiFlashLogExport result = {0};
if (wuji_hand_2_export_flash_logs(dev, NULL, &result) == WUJI_STATUS_OK) {
printf("%u frames -> %s\n", result.frame_count, result.path);
wuji_hand_2_flash_log_export_free(&result);
} else {
fprintf(stderr, "export failed: %s\n", wuji_last_error());
}Pass NULL as out_dir to write flash_<serial>_<date>_<time>.jsonl under ~/.wuji/logs/, or pass a directory path to choose the destination. Each call creates a new file. Exports created within the same second receive a numbered suffix instead of replacing an existing file.
The returned frame count can be 0 when the device log ring is empty. The empty output file is still a successful export.
Handle common export errors as follows:
- C returns
WUJI_STATUS_ERR_INVALID_ARGwhendevoroutisNULL, or whenout_dirisn’t valid UTF-8. - C returns
WUJI_STATUS_ERR_UNSUPPORTEDwhen the device isn’t Wuji Hand 2 or its firmware doesn’t support the current protocol. - C returns
WUJI_STATUS_ERR_TIMEOUTif another export is running or if log data is overwritten during export and repeated retries still can’t complete. Checkwuji_last_error()for details. - C returns
WUJI_STATUS_ERR_DISCONNECTEDif the device disconnects during export. - C returns
WUJI_STATUS_ERR_PROTOCOLwhen the device reports a flash-log protocol error or returns malformed data. - C returns
WUJI_STATUS_ERR_INTERNALwhen a file-system I/O operation fails. Checkwuji_last_error()for details.
For other status values, call wuji_last_error() to get the cause. A failed export doesn’t create an output file.
Call wuji_hand_2_flash_log_export_free only after a successful export.
Documentation