02. Imprimer VerseGrip
Transmet l'orientation (quaternion + angles d'Euler Z-X-Y), le niveau du capteur à effet Hall et l'état des boutons du premier VerseGrip connecté par câble.
Ce que vous apprendrez :
- Lecture
quaternionorientation par rapport au repère de l'État - Conversion d'un quaternion en angles d'Euler Z-X-Y en degrés (+X à droite, +Y vers l'avant, +Z vers le haut)
- Utilisation de
probe_orientationen tant que mécanisme de maintien de connexion autonome - Le modèle de négociation de connexion par premier message uniquement (identique à celui du tutoriel 01)
Flux de travail
- Ouvrir un WebSocket vers
ws://localhost:10001et attendre la première trame d'état. - Choisissez les premiers VerseGrip filaires
device_idà partir deverse_griptableau. - Construisez une requête avec le profil de session et par appareil
probe_orientationkeepalive (une commande d'objet vide qui maintient la circulation de l'orientation de la prise dans les trames d'état). - Envoyez la requête, puis supprimez le
sessionchamp — il s'agit d'une poignée de main unique. - À chaque image suivante, convertissez le quaternion en angles d'Euler et affichez les données télémétriques avec un débit limité. Renvoyez le message de maintien de connexion à chaque tick.
Paramètres
| Nom | Par défaut | Objectif |
|---|---|---|
URI | ws://localhost:10001 | URL WebSocket du canal de simulation |
PRINT_EVERY_MS | 100 | Régulateur de débit via la console |
| Nom du profil de session | co.haply.inverse.tutorials:print-verse-grip | Identifie cette simulation dans Haply |
La conversion est intrinsèque Z-X-Y (lacet → tangage → roulis) dans le repère de l'application +X right, +Y forward, +Z up. Ne pas utiliser glm::eulerAngles — elle suit une convention différente et ne s'affichera pas correctement ici. Ces trois variantes de langage utilisent le même calcul ; consultez les sources pour voir la formule.
probe_orientation est en fait nécessaireprobe_orientation n'est utile que lorsque votre session pas envoyer n'importe quelle commande à un Inverse3. Dès que vous envoyez une commande à un Inverse3 force, position, couple...), le service transmet automatiquement l'orientation du VerseGrip appairé à chaque trame d'état — aucune sonde n'est nécessaire. Utilisez probe_orientation uniquement pour les outils autonomes de surveillance de la prise en main, comme dans ce tutoriel.
Champs d'état lus
De data.verse_grip[0].state:
orientation—quaternion(w, x, y, z)hall— valeur entière de la lecture du capteur Hallbutton— booléentransform.rotation— rotation de l'espace de travail (quaternion); la position et l'échelle ne s'appliquent pas à un périphérique à orientation unique et ne sont jamais transmises
transform.rotation est omis lorsqu'il est égal au quaternion identité {w:1,x:0,y:0,z:0}. Fournir une valeur par défaut lors de la lecture. Activer serialization/explicit_fields pour toujours le recevoir.
Envoyer / recevoir
La boucle WebSocket : réception d'une trame d'état, création et renvoi de la poignée de main + probe_orientation keepalive. Le premier message sortant contient le profil de session ; chaque trame suivante ne contient que le keepalive.
- Python
- C++ (nlohmann)
- C++ (Glaze)
Boucle asynchrone unique — recv() → commande de compilation → send() → répéter.
async with websockets.connect(URI) as websocket:
while True:
msg = await websocket.recv()
data = json.loads(msg)
if first_message:
first_message = False
device_id = data["verse_grip"][0]["device_id"]
request_msg = {
"session": {"configure": {"profile": {
"name": "co.haply.inverse.tutorials:print-verse-grip"}}},
"verse_grip": [{
"device_id": device_id,
"commands": {"probe_orientation": {}} # empty — keepalive
}]
}
await websocket.send(json.dumps(request_msg))
request_msg.pop("session", None) # one-shot handshake
libhv gère le WebSocket sur son thread d'E/S — le traitement par trame se fait dans ws.onmessage. Le thread principal se bloque à l'entrée.
ws.onmessage = [&](const std::string &msg) {
const json data = json::parse(msg);
if (first_message) {
first_message = false;
device_id = data["verse_grip"][0].at("device_id").get<std::string>();
request_msg = {
{"session", {{"configure", {{"profile",
{{"name", "co.haply.inverse.tutorials:print-versegrip"}}}}}}},
{"verse_grip", json::array({
{{"device_id", device_id},
{"commands", {{"probe_orientation", json::object()}}}},
})},
};
}
ws.send(request_msg.dump());
request_msg.erase("session"); // one-shot handshake
};
ws.open("ws://localhost:10001");
while (std::cin.get() != '\n') {} // block main thread
Même modèle de rappel que libhv. Les structures typées remplacent nlohmann::json — probe_orientation_cmd est une structure vide (Glaze l'écrit sous la forme {}). std::optional<session_cmd> gère la procédure de connexion ponctuelle : .reset() après le premier envoi, il est omis dans les sorties JSON suivantes.
// Struct models
struct quat { float w{1.0f}, x{}, y{}, z{}; };
struct grip_state { quat orientation{}; bool button{}; uint8_t hall{}; };
struct grip_device { std::string device_id; grip_state state; };
struct devices_message { std::vector<grip_device> verse_grip; };
struct probe_orientation_cmd {}; // empty object on the wire
struct commands_message {
std::optional<session_cmd> session; // one-shot — omitted when unset
std::vector<device_commands> verse_grip;
};
// Send / receive
ws.onmessage = [&](const std::string &msg) {
devices_message data{};
if (glz::read<glz_settings>(data, msg)) return;
if (first_message) {
first_message = false;
out_cmds.session = session_cmd{ /* profile = print-versegrip */ };
device_commands dc{ .device_id = data.verse_grip[0].device_id };
dc.commands.probe_orientation = probe_orientation_cmd{};
out_cmds.verse_grip.push_back(std::move(dc));
}
std::string out_json;
(void)glz::write_json(out_cmds, out_json);
ws.send(out_json);
out_cmds.session.reset(); // one-shot handshake
};
ws.open("ws://localhost:10001");
while (std::cin.get() != '\n') {} // block main thread
Options de ligne de commande (Python)
La variante Python accepte les deux mêmes indicateurs que le tutoriel 01:
--full— affiche le contenu JSON brut sous une forme lisible, au lieu du résumé sur une seule ligne.--query-config— réinjectesession.force_render_full_stateà chaque impulsion sortante, de sorte que chaque trame transporte leconfigbloc (type de périphérique, micrologiciel, transformation de montage, etc.) plutôt que le simple écart d'état. Utile pour déboguer les modifications de paramètres en temps réel.
Ces deux indicateurs sont combinés. Les variantes C++ ne prennent pas en charge ces indicateurs.
Le didacticiel 02 est également installé localement avec le SDK — regardez dans tutorials/02-haply-inverse-print-verse-grip/ dans le répertoire d'installation du service.
Source : Python · C++ · C++ Glaze
À lire également : Types (quaternion) · Commandes de contrôle (probe_orientation) · Protocole WebSocket · Tutoriel 03 (VG sans fil)