06. Intégration (Inverse3 + VerseGrip sans fil)
Ce tutoriel concerne deux appareils. En orientant la poignée dans une direction et en maintenant le bouton enfoncé, le curseur de Inverse3 se déplace dans cette direction. Le curseur reste confiné à l'intérieur de l'espace de travail sphérique.
Contenu pédagogique :
- Deux types d'appareils au sein d'un même état (
inverse3etwireless_verse_grip) lire - quaternion À partir de l'orientation de la poignée (locale)
+YRetirer l'axe set_cursor_positiondéplacer le curseur vers la cible calculée à l'aide de- Limiter la cible à l'intérieur d'une sphère de travail sécurisée. La fonction «Minverse » utilise un rayon inférieur à celui de « Inverse3 ».
- Préréglages de l'espace de travail(
arm_front_centered) et faire en sorte que le point d'origine se trouve au centre de la zone d'atteinte
Flux de travail
- Détecte les deux appareils :
- C++ La version est chargée via HTTP au démarrage
GET /devicesConsultez la valeur, affichez l'invite de calibrage et attendez que l'utilisateur appuie sur la touche ENTER. - Python lit les identifiants des deux appareils à partir de la première trame d'état WebSocket.
- C++ La version est chargée via HTTP au démarrage
- Dans le premier message, Profil de session en l'enregistrant,
configure.preset: arm_front_centeredConfigurer (poignée de main en une seule étape). - À chaque clic, la poignée
orientationetbuttons.{a, b}Il lit l'état. - Si le bouton de déplacement est enfoncé, l'orientation dans l'espace mondial de la poignée (
R(q) · ĵ, calculer l'unité après rotation (axe +Y), etSPEEDOn ajoute la valeur mise à l'échelle à la position cible. - Limiter la cible à l'intérieur de la sphère de l'espace de travail, et
set_cursor_positionJe l'enverrai par... - (Python uniquement) Les périphériques
config.typeOn ajuste le rayon de la sphère en fonction de cela.minverseDans ce cas, elle est de 0,04 m ; dans les autres cas, elle est de 0,10 m.
Paramètres
| Nom | Par défaut | Utilisation |
|---|---|---|
SPEED | 0.01 m/tick | Étapes de déplacement pendant que le bouton est enfoncé |
RADIUS_INVERSE3 | 0.10 m | Inverse3 / Rayon limite de l'espace de travail pour Inverse3x |
RADIUS_MINVERSE | 0.04 m | Minverse Rayon limite de l'espace de travail (Python uniquement — pour C++, c'est 0.10 (fixe) |
PRINT_EVERY_MS | 200 | Limite de fréquence d'envoi des données de télémétrie |
| Nom du profil de session | co.haply.inverse.tutorials:combined | Haply Identifier cette simulation dans Hub |
- Inverse3 Effectuez l'auto-étalonnage (ou placez le stylo dans le réservoir d'encre et attendez que la LED s'allume).
- Retirez la poignée de l'encrier.
- Faites pivoter la molette tout en maintenant la touche A ou B enfoncée. Le curseur se déplace dans la direction vers laquelle est orientée la molette.
Lire le champ « state »
On lit les éléments suivants dans le cadre « state » de chaque tick :
data.inverse3[0].state.cursor_position—vec3data.wireless_verse_grip[0].state.orientation—quaterniondata.wireless_verse_grip[0].state.buttons.{a, b, c}— Valeur booléenne- (Python, première image uniquement)
data.inverse3[0].config.type— Choisir entre les rayons Inverse3 et Minverse - (Python, première image uniquement)
data.inverse3[0].status.calibrated— Si la valeur est « false », afficher une invite à l'utilisateur
Envoyer / Recevoir
Calcul permettant de déterminer une direction à partir d'un quaternion (R(q) et +Y (rotation) et le serrage par sphère constituent des calculs classiques de l'algèbre linéaire. Pour plus de détails, veuillez consulter le fichier source. Du côté de l'API inverse, la négociation de connexion et, à chaque tick, set_cursor_position effectue l'appel.
- Python
- C++ (nlohmann)
- C++ (Glaze)
Il s'agit d'une boucle asynchrone unique. Python lit les identifiants des deux périphériques à partir de la première trame d'état. Lors de la négociation de connexion, la première set_cursor_position avec le profil et configure.preset: arm_front_centered est attribué.
async with websockets.connect(URI) as websocket:
while True:
msg = await websocket.recv()
data = json.loads(msg)
if first_message:
first_message = False
inverse3_id = data["inverse3"][0]["device_id"]
grip_id = data["wireless_verse_grip"][0]["device_id"]
radius = get_workspace_radius(data["inverse3"][0].get("config", {}))
# Handshake: profile + preset + first position command
request_msg = {
"session": {"configure": {"profile": {"name": SLUG}}},
"inverse3": [{
"device_id": inverse3_id,
"configure": {"preset": {"preset": "arm_front_centered"}},
"commands": {"set_cursor_position": {"position": position}},
}],
}
else:
# Per tick: update position from grip pointing direction (classic math, not shown), send
request_msg = {
"inverse3": [{
"device_id": inverse3_id,
"commands": {"set_cursor_position": {"position": position}},
}],
}
await websocket.send(json.dumps(request_msg))
Le C++, au démarrage, GET /devices On récupère ensuite les identifiants des deux appareils (via HTTP), puis on ouvre une connexion WebSocket.onmessage Il s'exécute sur le thread d'E/S de libhv, tandis que le thread principal est bloqué en attente d'une entrée ENTER.
// Startup (synchronous):
const std::string inv3_device_id = get_first_device_id("inverse3");
const std::string grip_device_id = get_first_device_id("wireless_verse_grip");
// Per tick:
ws.onmessage = [&](const std::string &msg) {
json data = json::parse(msg);
// ... classic math (not shown): update local Inverse3State + WirelessVerseGripState,
// compute new position from grip orientation, clamp to sphere ...
json command;
command["inverse3"] = json::array();
command["inverse3"].push_back({
{"device_id", inv3_device_id},
{"commands", {{"set_cursor_position",
{{"position", {{"x", pos.x}, {"y", pos.y}, {"z", pos.z}}}}}}}
});
if (first_message) {
first_message = false;
command["session"] = {{"configure", {{"profile",
{{"name", "co.haply.inverse.tutorials:combined"}}}}}};
command["inverse3"][0]["configure"] = {
{"preset", {{"preset", "arm_front_centered"}}}};
}
ws.send(command.dump());
};
ws.open("ws://localhost:10001");
while (std::cin.get() != '\n') {} // block main thread
Le modèle de rappel de libhv est identique. Il s'agit d'une structure typée qui représente à la fois la trame d'état et la commande à envoyer.devices_message les deux std::vector<> ce qui permet d'obtenir un seul glz::read Cela permet d'obtenir les deux types d'appareils.
// Struct models
struct quat { float w{1.0f}, x{}, y{}, z{}; };
struct button_state { bool a{}, b{}, c{}; };
struct wvg_state { quat orientation{}; uint8_t hall{}; button_state buttons{}; };
struct wvg_device { std::string device_id; wvg_state state; };
struct inverse_state { vec3 cursor_position{}, cursor_velocity{}; };
struct inverse_device { std::string device_id; inverse_state state; };
struct devices_message {
std::vector<inverse_device> inverse3;
std::vector<wvg_device> wireless_verse_grip;
};
struct set_cursor_position_cmd { vec3 position; };
struct commands_message {
std::optional<session_cmd> session;
std::vector<device_commands> inverse3;
};
// Send / receive
ws.onmessage = [&](const std::string &msg) {
devices_message data{};
if (glz::read<glz_settings>(data, msg)) return;
const auto &wvg = data.wireless_verse_grip[0].state;
cursor_pos = data.inverse3[0].state.cursor_position;
// ... classic math (not shown): if (wvg.buttons.a || wvg.buttons.b)
// move in pointing dir; clamp to sphere ...
commands_message out_cmds{};
device_commands dc{ .device_id = inv3_device_id };
dc.commands.set_cursor_position = set_cursor_position_cmd{cursor_pos};
if (first_message) {
first_message = false;
out_cmds.session = session_cmd{ /* profile = combined */ };
dc.configure = device_configure{ .preset = preset_cfg{"arm_front_centered"} };
}
out_cmds.inverse3.push_back(std::move(dc));
std::string out_json;
(void)glz::write_json(out_cmds, out_json);
ws.send(out_json);
};
ws.open("ws://localhost:10001");
while (std::cin.get() != '\n') {} // block main thread
Le tutoriel 06 est également installé localement avec le SDK. Dans le répertoire d'installation du service, sous tutorials/06-haply-inverse-combined/ Veuillez consulter [...].
Source : Python · C++ · C++ Glaze
À lire également : Commandes de contrôle (set_cursor_position) · type (quaternion, vec3) · Support et espace de travail (préréglages) · Tutoriel 03 (VG sans fil) · Tutoriel 05 (Contrôle de position)