Diagnostics & errors
Device status, automatic connection recovery and error handling.
Diagnostics & errors
Read first: Debug logs (turn on logs before reproducing the problem) · What you get: understand device status, automatic connection recovery and error types
To locate a symptom, see Common questions; this chapter covers the tools for reading device status and handling errors.
Device status and diagnostics
Device status can be continuously subscribed to, or the latest value can be read in a non-blocking way:
from qnbot_sdk import Sdk
from qnbot_sdk.glove import GloveConfig
sdk = Sdk(devices=(GloveConfig(),))
glove = sdk.glove()
try:
status = glove.device().status()
status.subscribe(
lambda sample: print(sample.value.connected, sample.value.last_error)
)
glove.start()
glove.run_forever()
finally:
glove.close()Common fields:
| Common field | Meaning |
|---|---|
connected | Whether it is currently connected |
stale | Whether the current data is already expired or has not yet started flowing |
last_error | The most recent device error |
reconnect_count | The cumulative number of successful connection recoveries |
For complete fields, types and nullability behavior, see GloveStatus.
Automatic connection recovery
Automatic recovery is only used for gloves that have already started successfully and then disconnected unexpectedly; a failed first connection is returned directly by start(). During recovery, the affected glove enters connected=false, stale=true, and reports the cause through last_error, while other gloves continue running. After the identity is confirmed and the connection is recovered, the status becomes connected=true again, clears
last_error and increments reconnect_count. When a glove that has already called start() receives data again,
stale=false; a glove that has not yet called start() still remains stale=true after the connection is recovered, until it starts receiving data.
When haptics().set() is called during a disconnection to set vibration, or other device commands are sent, they fail immediately with a not-connected error, are not queued, and are not replayed. The current vibration setting is cleared; after recovery it must be set again. Explicitly calling stop()
or close() cancels recovery. For the same confirmed device, SDK-processed data that is already ready and still valid can continue to be used after recovery, and is not recalibrated merely because of a reconnect.
Health status
Health status can be read by other logic during runtime, or read after run_forever() returns and before close():
health = glove.health()
print(health.ok)
print(health.warning_count)
print(health.error_count)
print(health.last_error)
print(health.runtime_overrun)
print(health.topic_callback_error)
for source_id, device_health in health.devices.items():
print(source_id, device_health.connected, device_health.stale)The keys of health.devices are the source_id values returned by the SDK. When you need to locate a specific glove, you can match that key against the device.source_id of a selected device; device selection still uses name or side, and do not parse the string format of source_id.
sdk.health() returns the health status of the entire SDK; glove.health() returns the health status of all configured gloves. To inspect one glove, select it with glove.device(side=Side.LEFT) or its configured name and call device.health(). Match device.source_id to a key in the aggregate health.devices mapping when needed.
Common fields of the summary status:
| Common field | Meaning |
|---|---|
ok | Whether the current summary status is healthy |
warning_count / error_count | The number of warnings and errors in the current summary |
devices | The mapping from source ID to the health status of each device |
last_error | The most recent SDK error |
runtime_overrun | Whether an overrun has occurred in SDK scheduling |
topic_callback_error | Whether an unhandled exception has occurred in a callback |
Common fields of a single device's status:
| Common field | Meaning |
|---|---|
connected | Whether the device is connected |
stale | Whether the device data is expired or has not yet started flowing |
last_error | The most recent device error |
For complete fields, types and nullability behavior, see Health and
DeviceHealth.
Recommended usage: To determine whether it is healthy during runtime, you should read and record the running health before the stop request. The following background mode, after starting, first waits for its own first frame or readiness condition; after the active stop completes, read the stopped lifecycle health before closing, to confirm the post-stop connected/stale status:
glove.run_background()
# First wait for its own first frame or readiness condition.
running_health = glove.health()
print("running health", running_health)
glove.request_stop()
glove.join()
stopped_health = glove.health()
print("stopped lifecycle health", stopped_health)After telemetry stops, the device enters stale=true, so even if warning_count=0 and error_count=0, ok
may also be ok=false. This is the expected state after stopping, does not indicate a runtime failure, and does not change Health.ok
meaning. Foreground run_forever() blocks the current thread, so when you need to check status during runtime, you should handle it through an already registered
callback or session. The complete foreground and background sequences are shown respectively in
foreground_runtime.py
and background_runtime.py.
Error handling
Catch the most specific exception type according to the failure scenario, and preferentially read the structured fields provided by the exception:
| Error | Typical meaning |
|---|---|
InvalidArgumentError | The argument or configuration value is invalid |
ClosedHandleError | The SDK or Glove has been closed |
StaleUpdateError | The update wait token has already been used or is no longer valid |
LifecycleError | The lifecycle order or run mode is invalid |
UnavailableFeatureError | The current platform, installation package or feature is unavailable |
CommandRejectedError | The SDK rejected the command |
GloveControlError | The device control command failed: not connected, device busy, timed out; the reason is in kind (GloveControlErrorKind) |
NotFoundSelectionError | No matching device or target |
AmbiguousSelectionError | The selection criteria match multiple devices or targets |
InternalError | The SDK cannot complete the current operation |
PanicError | An SDK execution exception has been converted to a Python error |
Selection errors also provide structured fields:
from qnbot_sdk import AmbiguousSelectionError
try:
glove = sdk.glove()
device = glove.device()
except AmbiguousSelectionError as error:
print(error.selector_kind)
print(error.selector_value)
print(error.candidates)Do not determine the error type or selection result by parsing error messages. For argument/configuration problems, usually handle
InvalidArgumentError first; for device or target selection handle NotFoundSelectionError and
AmbiguousSelectionError; for runtime order and background errors handle LifecycleError; only when you merely need to uniformly log unclassified SDK failures, additionally catch QnBotError in the outer layer.
Integration flow: Catch SDK errors in the outer layer, and keep resource release in finally:
from qnbot_sdk import QnBotError
try:
glove.start()
glove.run_forever()
except QnBotError as error:
print(error.message)
finally:
glove.close()