02. Imprimer VerseGrip
À partir du premier VerseGrip filaire, il transmet en temps réel la posture (quaternions + angles de Euler Z-X-Y), les niveaux des capteurs à effet Hall ainsi que l'état des boutons.
Contenu pédagogique :
- Lecture à partir de la trame d'état
quaternionPosture - Convertir un quaternion en angles d'Euler Z-X-Y exprimés en degrés (+X à droite, +Y vers l'avant, +Z vers le haut)
- Va
probe_orientationCommande de maintien en vie en tant qu'observateur indépendant - Mode dans lequel seul le premier message nécessite une poignée de main (identique au tutoriel 01 )
Processus de travail
- Ouvrir le lien vers
ws://localhost:10001WebSocket, puis attendre la première trame d'état. - Depuis
verse_gripSélectionner le premier VerseGrip filaire dans le tableaudevice_id。 - Utilisation Configuration de la session ainsi que, pour chaque appareil,
probe_orientationDemande de création d'une commande de maintien de la posture (il s'agit d'une commande d'objet vide, destinée à garantir que les données de posture continuent d'apparaître dans les images d'état). - Envoyer la demande, puis supprimer
sessionChamp — Il sert uniquement à la poignée de main initiale. - Chaque image suivante convertit les quaternions en angles d'Euler et affiche les données de télémétrie de manière économe. Une commande de maintien de connexion est renvoyée à chaque cycle.
Paramètres
| Nom | Valeur par défaut | Utilisation |
|---|---|---|
URI | ws://localhost:10001 | URL du canal de simulation WebSocket |
PRINT_EVERY_MS | 100 | Intervalle d'affichage des données sur la console |
| Nom de la configuration de session | co.haply.inverse.tutorials:print-verse-grip | Permet d'identifier cette simulation dans le Hub « Haply » |
La conversion s'effectue dans le système de coordonnées de l'application +X right, +Y forward, +Z up Séquence de rotation interne Z-X-Y (cap → tangage → roulis).Veuillez ne pasUtilisation glm::eulerAngles——Il suit des conventions différentes, ce qui donne ici un résultat erroné. Les calculs mathématiques sont identiques dans les trois versions linguistiques ; pour les formules détaillées, veuillez vous reporter au code source.
probe_orientationUniquement lorsque la sessionnonLorsque vous envoyez une commande à Inverse3 ,probe_orientation C'est là que cela prend tout son sens. Dès que vous envoyez une commande (force, position, couple, etc.) à Inverse3 , le service transmet automatiquement, dans chaque trame d'état, les données d'attitude du VerseGrip appairé — sans qu'aucune détection ne soit nécessaire.probe_orientation S'applique uniquement aux outils de surveillance de la prise en main autonomes, tels que celui présenté dans ce tutoriel.
Champs d'état lus
Source : data.verse_grip[0].state:
orientation—quaternion(w, x, y, z)hall— Valeurs de lecture des capteurs à effet Hall de type entierbutton— Valeurs booléennestransform.rotation— Rotation de l'espace de travail (quaternion) ; pour les appareils qui ne fournissent que des données d'orientation, les paramètres de position et de mise à l'échelle ne s'appliquent pas et ne sont donc jamais fournis
Lorsque transform.rotation Équivaut à un quaternion unitaire {w:1,x:0,y:0,z:0} sera omis. Veuillez fournir une valeur par défaut lors de la lecture. Pour toujours recevoir ce champ, activez serialization/explicit_fields。
Envoyer / Recevoir
Boucle WebSocket : réception d'une trame d'état, construction et renvoi du message de poignée de main ainsi que probe_orientation Commande de maintien de connexion. Le premier message émis contient les paramètres de session ; chaque trame suivante ne contient que la commande de maintien de connexion.
- Python
- C++ (nlohmann)
- C++ (Glaze)
Boucle asynchrone unique ——recv() → Commande de compilation → send() → Répétition.
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 les WebSockets sur son thread d'E/S — la logique de traitement de chaque trame se trouve dans ws.onmessage . Le thread principal est bloqué, en attente d'une pression sur la touche 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
Le modèle de rappel libhv reste inchangé. Des structures à typage fort remplacent nlohmann::json——probe_orientation_cmd pour une structure vide (Glaze l'écrit sous la forme {}).std::optional<session_cmd> À utiliser pour gérer une poignée de main ponctuelle : appelé après le premier envoi .reset(), ce qui permettra de l'exclure des 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
Paramètres de ligne de commande (Python)
La variante Python accepte les deux mêmes paramètres que le tutoriel 01:
--full— Afficher les données brutes de la charge JSON sous forme formatée, et non sous forme de résumé sur une seule ligne.--query-config— Réinjection à chaque cycle de diffusionsession.force_render_full_state, afin que chaque image contienneconfigblocs (type d'appareil, micrologiciel, transformations d'installation, etc.), et non pas uniquement des incréments d'état. Cela s'avère très utile lors du débogage de modifications en temps réel des paramètres.
Ces deux paramètres peuvent être utilisés conjointement. La variante C++ ne propose pas ces paramètres.
Le tutoriel 02 est également installé localement avec le SDK — veuillez vous rendre dans le répertoire d'installation du service, sous tutorials/02-haply-inverse-print-verse-grip/ Rechercher dans.
Source : Python · C++ · C++ Glaze
Contenu associé : Type (quaternion) · Commandes de contrôle (probe_orientation) · Protocole WebSocket · Tutoriel 03 (VG sans fil)