Migration
API replacements and workflow adjustments for upgrading from SDK 1.3.0 to 1.4.0 and from earlier releases.
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_secinput temporarily, ensuretimestamp_msis not supplied with it - Pass absolute Unix milliseconds to
glove.sync_time()when an external time source is required - Use
CompositeExoGloveConfigonly 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 CaptureInteractionModeProject 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()todevice.start_capture() - Change
glove.capture_status()todevice.capture_status() - Change
glove.start_calibration()todevice.start_calibration() - Change
CalibrationInteractionModetoCaptureInteractionMode - Move
interactionandoperator_idfromCalibrationConfigtoCaptureConfig - 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 item | Previous usage | Current behavior | Migration action |
|---|---|---|---|
| Core and Glove installation packages | The wheels can be replaced separately, or an already packaged program can continue to carry the old version native library | Core and Glove belong to the same set of public API and native ABI, and mixing different release versions is not supported | Delete 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() lifecycle | connect() establishes the device connection, and start() then starts the data stream; after stopping, disconnect() can be called separately | connect() 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 stage | An 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 constructor | Prepare 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 device | Returns 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 verified | Do 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 parameters | capture_status() and start_capture() use candidate_ids | The parameter is changed to a non-empty package_ids, and each item must be a non-empty algorithm package ID | Change all candidate_ids= to package_ids=, and ensure the sequence is non-empty |
| Capture results and Session ownership | CaptureSession.result() returns CaptureSet; subsequent calibration receives that CaptureSet | result() returns a read-only summary CaptureResult; subsequent calibration directly receives the still-open and completed CaptureSession | Delete the CaptureSet / CaptureSetSnapshot logic; keep the original CaptureSession open until calibration is complete, and pass it to start_calibration() |
| Calibration session API | start_calibration(capture_set=..., package_id=..., target=...) returns one CalibrationJob for one target | start_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 completion | Delete 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 progress | device.calibration_control(name) submits confirmation per target; calibration_progress(name) includes action prompts, sample count, and save status at the same time | Source-level actions are now handled by device.capture_progress() and device.capture_control(); target-level device.calibration_progress(name) only reports the Calibration computation status | Change 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 fields | CaptureSessionState includes CREATED, AWAITING_CONFIRMATION, COLLECTING; CaptureStageState includes REUSED, CAPTURED, FAILED; the snapshot uses stage_runs and the string failure, and CapturePlan contains resolved_packages | The 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 removed | Update 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 fields | CalibrationJobState uses RUNNING, SAVING, COMPLETED, FAILED; CalibrationJobSnapshot and CalibrationResult contain package_id, package_version | CalibrationSessionState 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_at | Delete 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 configuration | The directory is specified through CalibrationStoreConfig or CalibrationConfig.store | These two public entry points have been removed, and calibration records are uniformly saved in <QNBOT_HOME>/glove/calibrations | Delete 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 directory | The directory is specified through DebugConfig.log_dir | DebugConfig.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 directory | Delete DebugConfig.log_dir; if the log root directory needs to be changed, set QNBOT_HOME before creating the Sdk |
| Debug detail and module | Use DebugDetail.RAW and an arbitrary module string, such as serial, glove, buffer | RAW has been removed; detailed recording uses FULL. The optional modules are changed to DebugModule.TRANSPORT, DEVICE, CAPTURE, CALIBRATION, RETARGETING, OUTPUT | Change DebugDetail.RAW to FULL or SUMMARY; replace the old module names with the corresponding DebugModule enum values |
| Skeleton joint angles and calibration | skeleton().joint_angles() uses 24 active joints, index 1 is the original second joint; existing calibration corresponds to the old Skeleton version | Joint 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 record | Update the fixed length, index mapping, and downstream protocol to 25 joints, and prefer reading by joint name; allow Skeleton recalibration after upgrading |