02. Impression VerseGrip
Le premier VerseGrip filaire transmet en continu les informations d'orientation (quaternions + angles d'Euler Z-X-Y), le niveau du capteur à effet Hall et l'état des boutons.
Contenu pédagogique :
- Dans le cadre d'état
quaternionOrientation - Conversion d'un quaternion en angles d'Euler Z-X-Y exprimés en degrés (+X vers la droite, +Y vers l'avant, +Z vers le haut)
probe_orientationUtilisation de 을 comme keepalive pour un observateur unique- Modèle de « handshake » envoyé uniquement lors du premier message (identiqueau tutoriel 01)
Flux de travail
ws://localhost:10001Ouvre un WebSocket et attend la première trame d'état.verse_gripLe premier VerseGrip filaire de la gammedevice_idSélectionnez .- Profil de sessionet par appareil
probe_orientationCrée une requête incluant un « keepalive » (commande d'objet vide permettant d'assurer la transmission continue des données de direction de la prise dans le cadre d'état). - Après avoir envoyé la demande
sessionSupprime ce champ. Ce champ est destiné à une poignée de main ponctuelle. - Par la suite, à chaque image, les quaternions sont convertis en angles d'Euler et les données de télémétrie filtrées sont transmises. À chaque tick, un message de keepalive est renvoyé.
Paramètres
| Nom | Valeur par défaut | Utilisation |
|---|---|---|
URI | ws://localhost:10001 | URL WebSocket du canal de simulation |
PRINT_EVERY_MS | 100 | Limitation de la sortie de la console |
| Nom du profil de session | co.haply.inverse.tutorials:print-verse-grip | Haply Identifier cette simulation dans Hub |
La conversion s'effectue au niveau du cadre de l'application +X right, +Y forward, +Z upIl s'agit d'un système Z-X-Y (lacet → tangage → roulis) intrinsèque. glm::eulerAnglesNe l'utilisez pas. Cette fonction suit une autre convention et ne s'affiche donc pas correctement ici. Les trois versions linguistiques implémentant la même formule, veuillez vous reporter au code source pour la consulter.
probe_orientationSi cela s'avère réellement nécessaireprobe_orientationLa session a envoyé une commande à Inverse3 qui n'est pas envoyé Cela n'est utile que dans certains cas. Dès que vous envoyez une commande aux Inverse3(puissance, position, couple…), le service diffuse automatiquement les informations d'orientation du VerseGrip associé dans chaque trame d'état ; aucune sonde n'est donc nécessaire. probe_orientationUtilisez-le uniquement avec des outils de surveillance de la prise en main autonomes, comme celui présenté dans ce tutoriel.
Champ « État de lecture »
data.verse_grip[0].stateExtrait de :
orientation—quaternion(w, x, y, z)hall— Valeur du capteur Hall de type entierbutton— Booletransform.rotation— Rotation de l'espace de travail (quaternion). Sur les appareils à orientation fixe, la position et l'échelle ne correspondent pas ; ces informations ne sont donc jamais affichées.
transform.rotationquaternion identique {w:1,x:0,y:0,z:0}Elle est omise lorsque la condition est remplie. Lors de la lecture, veuillez fournir la valeur par défaut. Pour toujours recevoir une valeur, serialization/explicit_fieldsVeuillez l'activer.
Émission / Réception
Boucle WebSocket : réception d'une trame d'état, établissement de la connexion et probe_orientation Configurez le keepalive pour effectuer une nouvelle transmission. Le premier message transmis contient le profil de session ; tous les paquets suivants ne contiennent que le keepalive.
- Python
- C++ (nlohmann)
- C++ (Glaze)
Boucle asynchrone unique — recv() → Configuration des commandes → 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 exécute WebSocket dans un thread d'E/S — les opérations par trame sont ws.onmessageest traitée ici. Le thread principal est bloqué en attendant la touche ENTER.
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
Il utilise le même modèle de rappel libhv. Une structure de type spécifié nlohmann::jsonremplace — probe_orientation_cmdest une structure vide (Glaze l'appelle {}(on l'écrit ainsi). std::optional<session_cmd>qui gère la procédure d'établissement de connexion effectuée une seule fois, après la première transmission .reset()Si vous appelez cette fonction, cet élément sera omis dans la sortie JSON suivante.
// 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 prend en charge les deux indicateurs présentés dans le tutoriel 01:
--full— Au lieu d'afficher un résumé en une ligne, il affiche le contenu JSON brut de manière claire et lisible.--query-config— À chaque tick sortantsession.force_render_full_stateen réinjectant, de sorte que chaque trame ne transmette plus que le delta d'état, au lieu deconfigVeillez à inclure les blocs (type de périphérique, micrologiciel, conversion de montage, etc.). Cela s'avère utile pour le débogage en temps réel des modifications de configuration.
Ces deux options peuvent être utilisées conjointement. La version C++ ne propose pas cette option.
Le tutoriel 02 est également installé localement avec le SDK — dans le répertoire d'installation du service, sous tutorials/02-haply-inverse-print-verse-grip/Vous pouvez le consulter ici.
Source : Python · C++ · C++ Glaze
À ce sujet : Type (quaternion) · Commandes de contrôle (probe_orientation) · Protocole WebSocket · Tutoriel 03 (VG sans fil)