SDKPython SDK

Migration

API replacements and workflow adjustments for upgrading from SDK 1.3.0 to 1.4.0 and from earlier releases.

Current version · 1.4.0

Migration

Upgrading from 1.3.0 to 1.4.0

The primary required migration in 1.4.0 is the unified time unit. An existing standalone Glove application does not need to become a composite or call sync_time() unless it uses an external time source, consumes numeric time fields, or is adopting Exo + Glove hardware.

Update Core and Glove together

Install qnbot-sdk-glove 1.4.0 and let pip resolve the matching Core dependency. Do not let a packaged application continue carrying the 1.3.0 native library:

python -m pip install --upgrade "qnbot-sdk-glove==1.4.0"

If the application bundles wheels, native libraries, or a virtual environment, remove the old artifacts before rebuilding and deploying it.

Change public numeric time values to milliseconds

Glove frames, runtime state, capture, calibration, and derived data use milliseconds in 1.4.0. New output exposes timestamp_ms only. Update code that reads, stores, subtracts, or forwards a time value as seconds.

Use timestamp_ms for a new external frame:

frame = ExternalGloveFrame(
    thumb=thumb,
    index=index,
    middle=middle,
    ring=ring,
    pinky=pinky,
    timestamp_ms=1_700_000_000_123.0,
)

The legacy timestamp_sec remains an explicit compatibility input and is converted as timestamp_ms = timestamp_sec * 1000. The SDK does not infer a unit from the numeric magnitude. Do not provide both fields: supplying timestamp_ms and timestamp_sec together is an argument error.

Set an external time reference when needed

Call the public Glove.sync_time() entry point when every Glove source needs one external absolute time reference. The argument is absolute Unix milliseconds, not seconds or nanoseconds:

import time

sdk = Sdk(devices=(GloveConfig(),))
glove = sdk.glove()
glove.sync_time(time.time() * 1_000.0)
glove.start()

sync_time() may be called again while running. Subsequent Glove frames without an explicit time advance from the new reference without resetting subscriptions or lifecycle state. An explicit timestamp_ms takes precedence. Without synchronization, the SDK uses host Unix milliseconds. The call changes the Glove domain only and does not modify Exo time.

Adopt an Exo + Glove composite only when needed

Composite support is additive and does not change an existing GloveConfig. For shared-serial hardware, use CompositeExoGloveConfig and obtain sdk.glove() and sdk.exo() from the same Sdk. See Glove + Exo composite solution for the full configuration and capability boundaries.

Migration checklist

  • Install matching Core and Glove 1.4.0 packages and remove bundled 1.3.0 native libraries
  • Convert second-valued output handling and downstream protocols to milliseconds and timestamp_ms
  • If retaining timestamp_sec input temporarily, ensure timestamp_ms is not supplied with it
  • Pass absolute Unix milliseconds to glove.sync_time() when an external time source is required
  • Use CompositeExoGloveConfig only for composite hardware
  • Re-test capture records, time alignment, reconnect recovery, and shutdown on real hardware

Upgrading from 1.2.x to 1.3.0

This section lists only what must change when upgrading the Glove SDK from 1.2.0 to 1.3.0. SerialConnection, background running, and the meaning of the algorithm package package_id are unchanged, so they are not part of this incompatible change set.

The capture entry points move from Glove to GloveDevice

In 1.2.0 the application called the capture entry points on Glove and passed the device selection along with the request:

capture = glove.start_capture(
    package_ids=["qnbot-dexhand"],
    operator_id="operator-001",
    source=None,
    side=Side.LEFT,
    force=False,
)

Here source selects the device by name and side selects it by hand; when source is empty, side decides.

In 1.3.0, select the target device first and then run the capture:

device = glove.device(side=Side.LEFT)

capture = device.start_capture(
    package_ids=["qnbot-dexhand"],
    operator_id="operator-001",
    force=False,
)

The device can also be selected by name:

device = glove.device("my-glove")

Device selection is now separate from the capture request: the application picks the GloveDevice explicitly first.

capture_status() moves to GloveDevice

1.2.0:

readiness = glove.capture_status(
    package_ids=["qnbot-dexhand"],
    operator_id="operator-001",
    source=None,
    side=Side.LEFT,
)

1.3.0:

device = glove.device(side=Side.LEFT)

readiness = device.capture_status(
    package_ids=["qnbot-dexhand"],
    operator_id="operator-001",
)

start_calibration() moves to GloveDevice

1.2.0:

calibration = glove.start_calibration(
    completed_capture_session=capture,
    targets=targets,
)

1.3.0:

calibration = device.start_calibration(
    completed_capture_session=capture,
    targets=targets,
)

The capture session and the calibration session are managed by the same GloveDevice:

device = glove.device(side=Side.LEFT)

capture = device.start_capture(
    package_ids=["qnbot-dexhand"],
    operator_id="operator-001",
)

calibration = device.start_calibration(
    completed_capture_session=capture,
    targets=targets,
)

CalibrationInteractionMode is renamed

The 1.2.0 CalibrationInteractionMode is renamed to CaptureInteractionMode in 1.3.0, with the same values (AUTO_TERMINAL, EXTERNAL):

from qnbot_sdk import CaptureInteractionMode

Project code, configuration mappings, and custom wrappers that use the old enum name must all be updated.

Capture interaction configuration moves from CalibrationConfig to CaptureConfig

1.3.0 separates capture-stage configuration from calibration-stage configuration: interaction and operator_id belong to the capture stage and have moved from CalibrationConfig to CaptureConfig, leaving only force on CalibrationConfig.

If a 1.2.0 project wrote those two fields on CalibrationConfig:

algorithms = AlgorithmsConfig(
    calibration=CalibrationConfig(
        interaction=CalibrationInteractionMode.EXTERNAL,
        operator_id="operator-001",
        force=False,
    ),
)

1.3.0 requires:

algorithms = AlgorithmsConfig(
    capture=CaptureConfig(
        interaction=CaptureInteractionMode.EXTERNAL,
        operator_id="operator-001",
    ),
    calibration=CalibrationConfig(force=False),
)

The meaning and ownership of the two force flags are unchanged; see CaptureConfig and CalibrationConfig for the field definitions.

Migration checklist

  • Change glove.start_capture() to device.start_capture()
  • Change glove.capture_status() to device.capture_status()
  • Change glove.start_calibration() to device.start_calibration()
  • Change CalibrationInteractionMode to CaptureInteractionMode
  • Move interaction and operator_id from CalibrationConfig to CaptureConfig
  • Make sure capture and calibration use the same GloveDevice

Upgrading from 1.1.x to 1.2.0

The following lists only the incompatible changes that require modifying existing code or rebuilding when upgrading. For complete new features, fixes, and version changes, see the Python SDK changelog.

Incompatible itemPrevious usageCurrent behaviorMigration action
Core and Glove installation packagesThe wheels can be replaced separately, or an already packaged program can continue to carry the old version native libraryCore and Glove belong to the same set of public API and native ABI, and mixing different release versions is not supportedDelete the old version wheels and install the Core and Glove of the same release version at the same time; do not mix them. If the program packages the SDK into an executable, image, or other deliverable, clean up the old build contents and rebuild
connect() / start() / stop() lifecycleconnect() establishes the device connection, and start() then starts the data stream; after stopping, disconnect() can be called separatelyconnect() is deprecated and retained as a successful no-op; start() is responsible for connecting and starting, stop() stops and disconnects at the same time; disconnect() delegates to stop()No longer rely on connect() to determine connection success; after preparing data reading and subscription, call start() directly, call stop() when finishing, and finally call close()
SDK creation stageAn SDK that has not completed device resolution can be created first, and then the device is discovered, the identity is read, and the configuration is completed at connect()Creating an Sdk completes device discovery and identity probing, and resolves the source, target, and algorithm package; no device, port occupied, identity mismatch, or missing algorithm package may throw an error in the constructorPrepare the device and algorithm package before creating the SDK, and put both construction and start() into exception handling; recreate the SDK after the device, configuration, or installed algorithm package changes
get_device_info()Each call initiates a new id identity transaction with the connected deviceReturns the verified identity snapshot saved during SDK creation, and can be called before start(); throws UnavailableFeatureError when explicitly falling back to a port but the identity is not verifiedDo not rely on this call to refresh the device identity; read the snapshot when the identity needs to be displayed, and handle UnavailableFeatureError in deployments that allow unverified devices
Capture request parameterscapture_status() and start_capture() use candidate_idsThe parameter is changed to a non-empty package_ids, and each item must be a non-empty algorithm package IDChange all candidate_ids= to package_ids=, and ensure the sequence is non-empty
Capture results and Session ownershipCaptureSession.result() returns CaptureSet; subsequent calibration receives that CaptureSetresult() returns a read-only summary CaptureResult; subsequent calibration directly receives the still-open and completed CaptureSessionDelete the CaptureSet / CaptureSetSnapshot logic; keep the original CaptureSession open until calibration is complete, and pass it to start_calibration()
Calibration session APIstart_calibration(capture_set=..., package_id=..., target=...) returns one CalibrationJob for one targetstart_calibration(completed_capture_session=..., targets=(...)) returns one CalibrationSession, and processes a non-empty and non-duplicate target sequence in order; result() returns a result tuple, and wait() can block waiting for completionDelete the standalone CalibrationJob flow and the package_id parameter, and instead create one CalibrationSession, managed through snapshot(), result(), wait(), cancel(), and close()
Capture control and progressdevice.calibration_control(name) submits confirmation per target; calibration_progress(name) includes action prompts, sample count, and save status at the same timeSource-level actions are now handled by device.capture_progress() and device.capture_control(); target-level device.calibration_progress(name) only reports the Calibration computation statusChange calibration_control() to capture_control(); read action and sample progress from CaptureProgress.stage, and read each target status from CalibrationProgress.job; confirm(), skip(), and cancel() use request_id, and retry() uses stage_id
Capture states and fieldsCaptureSessionState includes CREATED, AWAITING_CONFIRMATION, COLLECTING; CaptureStageState includes REUSED, CAPTURED, FAILED; the snapshot uses stage_runs and the string failure, and CapturePlan contains resolved_packagesThe in-progress state of a Session is unified as RUNNING; action states go into CaptureStageProgress, completion uses COMPLETED, and reuse is indicated by origin=REUSED; the snapshot uses stage_progress and a structured CaptureFailure, and resolved_packages has been removedUpdate state branches and UI fields: change CAPTURED to COMPLETED, determine reuse from origin, determine failure from the Session's structured failure, and change stage_runs to stage_progress; do not read resolved_packages anymore
Calibration states and result fieldsCalibrationJobState uses RUNNING, SAVING, COMPLETED, FAILED; CalibrationJobSnapshot and CalibrationResult contain package_id, package_versionCalibrationSessionState and CalibrationJobState support cancellation, and job also includes PENDING; the save process is grouped under RUNNING; progress uses CalibrationJobProgress / CalibrationSessionSnapshot, and CalibrationResult retains only record_id, source_id, target_id, created_atDelete the SAVING and CalibrationJobSnapshot branches, and switch to the two-level Session/job state; if the algorithm package ID or version is needed, maintain it from your own target configuration instead of reading it from CalibrationResult
Calibration storage configurationThe directory is specified through CalibrationStoreConfig or CalibrationConfig.storeThese two public entry points have been removed, and calibration records are uniformly saved in <QNBOT_HOME>/glove/calibrationsDelete the old storage configuration; if the data root directory needs to be changed, set QNBOT_HOME before creating the Sdk, and ensure the CLI and SDK use the same value
Debug log directoryThe directory is specified through DebugConfig.log_dirDebugConfig.log_dir has been removed; QNBOT_HOME determines the log root directory, logs are located in <QNBOT_HOME>/logs, and each diagnostic run uses an independent session directoryDelete DebugConfig.log_dir; if the log root directory needs to be changed, set QNBOT_HOME before creating the Sdk
Debug detail and moduleUse DebugDetail.RAW and an arbitrary module string, such as serial, glove, bufferRAW has been removed; detailed recording uses FULL. The optional modules are changed to DebugModule.TRANSPORT, DEVICE, CAPTURE, CALIBRATION, RETARGETING, OUTPUTChange DebugDetail.RAW to FULL or SUMMARY; replace the old module names with the corresponding DebugModule enum values
Skeleton joint angles and calibrationskeleton().joint_angles() uses 24 active joints, index 1 is the original second joint; existing calibration corresponds to the old Skeleton versionJoint angles are increased to 25, index 1 newly adds <side>_thumb_cmc_flexion, and the original indices 1–23 shift by one; the Skeleton pose is still 25 nodes, and the old version calibration will not be reused as a currently compatible recordUpdate the fixed length, index mapping, and downstream protocol to 25 joints, and prefer reading by joint name; allow Skeleton recalibration after upgrading