Reading retargeting data
Skeleton, dexterous hand output, target and package configuration.
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); });