02. Affichage VerseGrip
Affiche en continu l'orientation (quaternion + angles d'Euler Z-X-Y), le niveau du capteur à effet Hall et l'état du bouton depuis le premier VerseGrip filaire.
Ce que vous allez apprendre :
- Lire l'orientation
quaternionà partir de la trame d'état - Convertir un quaternion en angles d'Euler Z-X-Y en degrés (+X à droite, +Y vers l'avant, +Z vers le haut)
- Utiliser
probe_orientationcomme signal de maintien d'un observateur autonome - Le modèle de poignée de main, envoyé uniquement dans le premier message (identique à celui du tutoriel 01)
Déroulement
- Ouvrez une connexion WebSocket vers
ws://localhost:10001et attendez la première trame d'état. - Sélectionnez le
device_iddu premier VerseGrip filaire dans le tableauverse_grip. - Construisez une requête avec le profil de session et un signal de maintien
probe_orientationpar appareil (une commande à objet vide qui maintient le flux d'orientation de la poignée dans les trames d'état). - Envoyez la requête, puis retirez le champ
session: il s'agit d'une poignée de main ponctuelle. - À chaque trame suivante, convertissez le quaternion en angles d'Euler et affichez la télémétrie à cadence limitée. Renvoyez le signal de maintien à chaque cycle.
Paramètres
| Nom | Valeur par défaut | Rôle |
|---|---|---|
URI | ws://localhost:10001 | URL du canal de simulation WebSocket |
PRINT_EVERY_MS | 100 | Limite de cadence de l'affichage console |
| Nom du profil de session | co.haply.inverse.tutorials:print-verse-grip | Identifie cette simulation dans Haply Hub |
La conversion est une rotation intrinsèque Z-X-Y (lacet → tangage → roulis) dans le repère de l'application +X right, +Y forward, +Z up. N'utilisez pas glm::eulerAngles : cette fonction suit une autre convention et donnerait des valeurs erronées ici. Les trois variantes de langage implémentent les mêmes calculs ; consultez le code source pour la formule.
probe_orientation est réellement nécessaireprobe_orientation n'est utile que si votre session n'envoie aucune commande à un Inverse3. Dès que vous commandez un Inverse3 (force, position, couple...), le service diffuse automatiquement l'orientation de la VerseGrip associée dans chaque trame d'état : aucun signal de maintien n'est nécessaire. Utilisez probe_orientation uniquement pour les outils de suivi de poignée autonomes, comme ce tutoriel.
Champs d'état lus
Depuis data.verse_grip[0].state :
orientation—quaternion(w, x, y, z)hall— valeur entière du capteur à effet Hallbutton— booléentransform.rotation— rotation de l'espace de travail (quaternion) ; la position et l'échelle ne s'appliquent pas à un appareil qui ne fournit que l'orientation et ne sont jamais émises
transform.rotation est omis lorsqu'il est égal au quaternion identité {w:1,x:0,y:0,z:0}. Fournissez une valeur par défaut à la lecture. Activez serialization/explicit_fields pour le recevoir systématiquement.
Envoi / réception
La boucle WebSocket : recevez une trame d'état, puis construisez et renvoyez la poignée de main et le signal de maintien probe_orientation. Le premier message sortant contient le profil de session ; chaque trame suivante ne contient que le signal de maintien.
- Python
- C++ (nlohmann)
- C++ (Glaze)
Boucle asynchrone unique : recv() → construction de la commande → 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 pilote WebSocket sur son fil d'E/S — le traitement de chaque trame se fait dans ws.onmessage. Le fil principal reste bloqué jusqu'à ce qu'on appuie sur 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 libhv. Des 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 poignée de main ponctuelle : .reset() après le premier envoi l'exclut 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
Options de ligne de commande (Python)
La variante Python accepte les deux mêmes options que le tutoriel 01 :
--full— affiche en forme lisible la charge utile JSON brute, au lieu du résumé sur une seule ligne.--query-config— réinjectesession.force_render_full_stateà chaque cycle sortant, afin que chaque trame contienne le blocconfig(type d'appareil, micrologiciel, transformation de montage, …) au lieu du seul delta d'état. Utile pour déboguer les changements de paramètres en direct.
Les deux options se combinent. Les variantes C++ n'exposent pas ces options.
Le tutoriel 02 est également installé localement avec le SDK — consultez tutorials/02-haply-inverse-print-verse-grip/ dans le répertoire d'installation du service.
Source : Python · C++ · C++ Glaze
Voir aussi : Types (quaternion) · Commandes de contrôle (probe_orientation) · Protocole WebSocket · Tutoriel 03 (VG sans fil)