02. Sortie de VerseGrip
À partir du VerseGrip initialement connecté par câble, le système récupère et transmet les données relatives à l'orientation (quaternion + angles d'Euler Z-X-Y), au niveau du capteur à effet Hall et à l'état des boutons.
Ce que vous apprendrez dans ce tutoriel :
- À partir du cadre d'état
quaterniondécrypter son attitude - 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)
- En tant que « keep-alive » pour un observateur autonome
probe_orientationutiliser - Modèle de « handshake » utilisant uniquement le premier message (identique à celui dututoriel 01 )
Flux de travail
ws://localhost:10001Ouvrez une connexion WebSocket et attendez la première trame d'état.verse_gripDans la liste, le premier VerseGrip connecté par câbledevice_idest récupérée.- Profil de session et, pour chaque appareil,
probe_orientationCréez une requête incluant la commande « Keep Alive » (commande destinée aux objets dans l'espace, qui permet à la posture de la poignée de continuer à s'afficher dans le cadre d'état). - Après avoir envoyé la demande,
sessionSupprimez ce champ. En effet, il s'agit d'une procédure d'établissement de connexion ponctuelle. - À chaque image suivante, le quaternion est converti en angles d'Euler, puis des données de télémétrie à fréquence limitée sont générées. À chaque tick, un message de maintien de connexion est renvoyé.
Paramètres
| Nom | Par défaut | Utilisation |
|---|---|---|
URI | ws://localhost:10001 | URL du canal de simulation WebSocket |
PRINT_EVERY_MS | 100 | Limitation de la fréquence des sorties sur la console |
| Nom du profil de session | co.haply.inverse.tutorials:print-verse-grip | Haply Identifiez cette simulation au sein du Hub |
La transformation s'effectue dans le système de coordonnées de l'application +X right, +Y forward, +Z up Il s'agit des coordonnées Z-X-Y (yaw → tangage → roulis) propres à ce système.glm::eulerAngles Ne pas utiliser. Cette fonction obéissant à d'autres conventions, une valeur erronée est lue ici. Les trois versions linguistiques implémentent toutes le même calcul. Veuillez vous reporter au code source pour consulter la formule de calcul.
probe_orientation lorsque cela s'avère réellement nécessaireprobe_orientation Ce qui est utile, c'est que la session soit transmise à Inverse3Ne pas envoyer la moindre commandeUniquement dans ce cas. Dès l'envoi d'une commande aux capteurs (Inverse3, par exemple : force, position, couple, etc.), le service transmet automatiquement l'orientation des deux VerseGrip appariés dans tous les repères d'état. Aucune sonde n'est donc nécessaire.probe_orientation Veuillez l'utiliser uniquement avec des outils autonomes de surveillance de la prise, comme celui présenté dans ce tutoriel.
Champ d'état de lecture
data.verse_grip[0].state On lit ce qui suit :
orientation—quaternion(w, x, y, z)hall— Valeur entière du capteur Hallbutton— Valeur booléennetransform.rotation— Rotation de l'espace de travail (quaternion). La position et l'échelle ne s'appliquant pas aux appareils qui ne traitent que l'orientation, elles ne sont pas transmises.
transform.rotation est un quaternion unitaire {w:1,x:0,y:0,z:0} est omis s'il est égal à . Veuillez spécifier une valeur par défaut lors de la lecture. Pour recevoir en permanence,serialization/explicit_fields Veuillez l'activer.
Émission et réception
Boucle WebSocket : réception d'une trame d'état, établissement de la connexion et probe_orientation Nous construisons un paquet « keep-alive » et le renvoyons. Le premier message envoyé contient le profil de session, tandis que les trames suivantes ne contiennent que des paquets « keep-alive ».
- Python
- C++ (nlohmann)
- C++ (Glaze)
Une seule boucle asynchrone — recv() → Construction des commandes → 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 un thread d'E/S. Le traitement de chaque trame se fait ws.onmessage comme indiqué ci-dessus. Le thread principal attend la pression de 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
Le modèle de rappel de libhv est identique. Les structures typées nlohmann::json est utilisé à la place de.probe_orientation_cmd est une structure vide (Glaze l'appelle {} (on l'écrit ainsi).std::optional<session_cmd> qui gère une poignée de main unique, et après la première transmission, .reset() Si vous l'appelez, il sera 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 quele tutoriel 01:
--full— Il ne s'agit pas d'un résumé d'une ligne, mais d'une sortie formatée à partir de la charge utile JSON brute.--query-config— À chaque tick envoyé,session.force_render_full_statele retransmettre, à chaque trameconfigCela inclut les blocs (type de périphérique, micrologiciel, conversion de montage, etc.). Vous recevez à chaque fois des informations complètes, et pas seulement les différences d'état. C'est très pratique pour déboguer les modifications de configuration en temps réel.
Ces deux indicateurs peuvent être utilisés conjointement. Dans la variante C++, ces indicateurs ne sont pas disponibles.
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/ Veuillez vérifier.
Source : Python · C++ · C++ Glaze
À lire également : Type (quaternion) · Commandes de contrôle (probe_orientation) · Protocole WebSocket · Tutoriel n° 3 (Wireless VG)