SDKC++ SDK

Reading retargeting data

Skeleton, dexterous hand output, target and package configuration.

Current version · 1.4.0

Reading retargeting data

Read first: Data channels & read methods · After this page you can: Configure a target and read Skeleton or dexterous hand output

Retargeting data is produced by SDK scheduling and includes Skeleton and the dexterous hand output you configure. To receive these outputs continuously, the application must choose a runtime mode.

Configure a target

A target decides which algorithm package receives the glove data and how many results come out. The minimum is an algorithm package ID:

qnbot::TargetConfig target_config;
target_config.algorithms = {qnbot::TargetAlgorithm{"<package-id>"}};

type, name, side and source can all be omitted: the SDK fills them in when the result is unambiguous and fails to create the Sdk when it is not. Fill them in when you need to pin a device or name an output; the field definitions are in Configuration reference.

The algorithm package must be installed before the Sdk is created: install it on this machine with the Qnbot CLI, then put the package_id shown by qnbot algorithm list into TargetAlgorithm. Installing, upgrading and uninstalling are owned by the CLI guide, see Package management. A running Sdk does not refresh the package list, so recreate it after installing.

Skeleton

The device's pose() returns the raw pose captured by the glove; skeleton() returns the dynamic OpenXR skeleton capability. Its joint_angles() returns the angles of 25 active joints, where the newly added thumb_cmc_flexion is at native joint definition index 1; pose() returns a 25-node HandSkeletonPose including the wrist (in the wrist_local coordinate frame).

skeleton() is pre-start configuration: it must be called after SDK creation and before start(); repeated calls before starting reuse the same capability, and calling it after the device has already started reports an explicit lifecycle error.

auto skeleton = glove.device().skeleton();
auto angles_subscription = skeleton.joint_angles().subscribe(
    [](const qnbot::Sample<qnbot::HandJointCommand>& sample) {
        std::cout << sample.sequence << ' ' << sample.value.joints.size() << '\n';
    });
auto pose_subscription = skeleton.pose().subscribe(
    [](const qnbot::Sample<qnbot::HandSkeletonPose>& sample) {
        std::cout << sample.sequence << ' ' << sample.value.names.size() << '\n';
    });
glove.start();

How calibration is guided depends on the interaction mode: the default auto_terminal guides it in an interactive terminal and reuses a compatible calibration, while external leaves it to the application; see Guided capture interaction and External takeover of guided interaction. In external mode, select this capability with qnbot::TargetName::skeleton. For a complete example, see skeleton.cpp.

The Skeleton API described above is supported on Linux x86_64, Linux ARM64, Windows x86_64, and macOS ARM64. For the returned fields and the complete joint names, see HandJointCommand and HandSkeletonPose.

Dexterous hand output

After a target is configured, read HandJointCommand through device.output(). The name can be omitted when there is only one target; when the same source has multiple targets, the output must be selected by TargetConfig::name.

Each TargetConfig binds to only one source; the SDK does not fuse multiple gloves into one target. For the output fields, see HandJointCommand.

One-to-one

When one glove corresponds to one dexterous hand, configure only one source and one target; the names can be omitted when both are unique:

qnbot::TargetConfig target;
target.algorithms = {qnbot::TargetAlgorithm{"<package-id>"}};

qnbot::SdkConfig config;
config.devices = {qnbot::GloveConfig{}};
config.targets = {target};
qnbot::Sdk sdk(std::move(config));

auto device = sdk.glove().device();
auto output = device.output();
auto subscription = output.subscribe([](const auto& sample) {
    use_joint_command(sample.value);
});

One-to-many

When one glove drives multiple dexterous hands, configure multiple named targets for the same source, then read the outputs by name:

qnbot::GloveConfig source;
source.name = "primary";

qnbot::TargetConfig dexhand;
dexhand.name = "dexhand";
dexhand.source = qnbot::DeviceSelector{"glove", "primary", std::nullopt};
dexhand.algorithms = {qnbot::TargetAlgorithm{"<dexhand-package-id>"}};

qnbot::TargetConfig backup = dexhand;
backup.name = "backup";
backup.algorithms = {qnbot::TargetAlgorithm{"<backup-package-id>"}};

qnbot::SdkConfig config;
config.devices = {source};
config.targets = {dexhand, backup};
qnbot::Sdk sdk(std::move(config));

auto device = sdk.glove().device("primary");
auto dexhand_output = device.output("dexhand");
auto backup_subscription = device.output("backup").subscribe(
    [](const auto& sample) { use_backup_command(sample.value); });

Many-to-many

When multiple gloves drive multiple dexterous hands, name the sources and targets separately, and explicitly select a source in each target:

qnbot::GloveConfig left;
left.name = "left-glove";
left.side = qnbot::Side::left;
qnbot::GloveConfig right;
right.name = "right-glove";
right.side = qnbot::Side::right;

qnbot::TargetConfig left_hand;
left_hand.name = "left-hand";
left_hand.source = qnbot::DeviceSelector{"glove", "left-glove", qnbot::Side::left};
left_hand.algorithms = {qnbot::TargetAlgorithm{"<package-id>"}};
qnbot::TargetConfig right_hand;
right_hand.name = "right-hand";
right_hand.source = qnbot::DeviceSelector{"glove", "right-glove", qnbot::Side::right};
right_hand.algorithms = {qnbot::TargetAlgorithm{"<package-id>"}};

qnbot::SdkConfig config;
config.devices = {left, right};
config.targets = {left_hand, right_hand};
qnbot::Sdk sdk(std::move(config));

auto glove = sdk.glove();
auto left_subscription = glove.device("left-glove").output("left-hand").subscribe(
    [](const auto& sample) { use_left_command(sample.value); });
auto right_output = glove.device("right-glove").output("right-hand");
auto right_subscription = right_output.subscribe(
    [](const auto& sample) { use_right_command(sample.value); });