SDKPython SDK

Diagnostics & errors

Device status, automatic connection recovery and error handling.

Current version · 1.4.0

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 fieldMeaning
connectedWhether it is currently connected
staleWhether the current data is already expired or has not yet started flowing
last_errorThe most recent device error
reconnect_countThe 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.

Rendering diagram

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 fieldMeaning
okWhether the current summary status is healthy
warning_count / error_countThe number of warnings and errors in the current summary
devicesThe mapping from source ID to the health status of each device
last_errorThe most recent SDK error
runtime_overrunWhether an overrun has occurred in SDK scheduling
topic_callback_errorWhether an unhandled exception has occurred in a callback

Common fields of a single device's status:

Common fieldMeaning
connectedWhether the device is connected
staleWhether the device data is expired or has not yet started flowing
last_errorThe 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:

ErrorTypical meaning
InvalidArgumentErrorThe argument or configuration value is invalid
ClosedHandleErrorThe SDK or Glove has been closed
StaleUpdateErrorThe update wait token has already been used or is no longer valid
LifecycleErrorThe lifecycle order or run mode is invalid
UnavailableFeatureErrorThe current platform, installation package or feature is unavailable
CommandRejectedErrorThe SDK rejected the command
GloveControlErrorThe device control command failed: not connected, device busy, timed out; the reason is in kind (GloveControlErrorKind)
NotFoundSelectionErrorNo matching device or target
AmbiguousSelectionErrorThe selection criteria match multiple devices or targets
InternalErrorThe SDK cannot complete the current operation
PanicErrorAn 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()