03. Sortie du VerseGrip sans fil
Il s'agit du même modèle que le tutoriel 02, mais destiné au Wireless VerseGrip. Nous allons ajouter l'état de diffusion en continu ainsi que les boutons (A/B/C) et le niveau de batterie.
Ce que vous apprendrez dans ce tutoriel :
- Lecture des champs d'état propres au réseau sans fil :
buttons.{a,b,c},battery_level,hall - En tant que « keep-alive » destiné aux observateurs autonomes
probe_orientationutiliser - Comme dans le tutoriel 02, modèle de négociation de connexion (handshake) s'effectuant uniquement lors du premier message
Flux de travail
ws://localhost:10001Ouvrez une connexion WebSocket vers cette adresse et attendez la première trame d'état.wireless_verse_gripDans la gamme, le premier Wireless VerseGripdevice_idest récupérée.- Profil de session et, pour chaque appareil,
probe_orientationCréez une requête incluant « KeepAlive ». - Après avoir envoyé la demande,
sessionSupprimer le champ. Il s'agit d'une procédure de connexion ponctuelle. - À chaque image suivante,le quaternionest converti en angles d'Euler, puis les données de télémétrie, comprenant l'état des boutons et le niveau de la batterie, sont échantillonnées et transmises. Le signal de maintien de connexion est renvoyé à chaque tick.
Paramètres
| Nom | Par défaut | Utilisation |
|---|---|---|
URI | ws://localhost:10001 | URL du canal de simulation WebSocket |
PRINT_EVERY_MS | 100 | Intervalle entre les sorties sur la console |
| Nom du profil de session | co.haply.inverse.tutorials:print-wireless-verse-grip | Haply Identifiez cette simulation dans Hub |
La conversion s'effectue dans le cadre de l'application +X right, +Y forward, +Z up Il s'agit des mouvements intrinsèques Z-X-Y (yaw → pitch → roll) dans ce contexte.glm::eulerAngles Ne pas utiliser. Cette fonction obéissant à d'autres conventions, elle renvoie ici une valeur erronée. 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 dans les cas où cela s'avère réellement nécessaireprobe_orientation Ce qui est utile, c'est d'envoyer une commande depuis la session vers Inverse3Ne rien envoyerUniquement dans ce cas. Dès l'envoi d'une commande aux capteurs (Inverse3, force, position, couple, etc.), le service diffuse automatiquement l'attitude du VerseGrip apparié dans toutes les trames 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.wireless_verse_grip[0].state Extrait de :
orientation—quaternion(w, x, y, z)hall— Valeur lue par le capteur à effet Hall pour les nombres entiersbuttons.a,buttons.b,buttons.c— Valeur booléennebattery_level— Nombre à virgule flottante (0,0 à 1,0)transform.rotation— Rotation de l'espace de travail (quaternion). Sur les appareils qui ne mesurent que l'orientation, la position et l'échelle ne s'appliquent pas et ne sont donc pas fournies.
transform.rotation est un quaternion unitaire {w:1,x:0,y:0,z:0} Elle est omise si elle est égale à . Veuillez définir une valeur par défaut lors de la lecture. Pour recevoir en permanence, serialization/explicit_fields Veuillez l'activer.
Envoyer / Recevoir
wireless_verse_grip Si l'on fait abstraction de l'utilisation d'un réseau de périphériques,Tutoriel 02 C'est le même format. La boucle WebSocket reçoit des trames d'état, effectue la procédure de handshake et probe_orientation Il renvoie un message « keep-alive ». Le premier message envoyé contient le profil de session, tandis que les trames suivantes ne contiennent que des messages « keep-alive ».
- Python
- C++ (nlohmann)
- C++ (Glaze)
Il s'agit d'une boucle asynchrone unique.
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["wireless_verse_grip"][0]["device_id"]
request_msg = {
"session": {"configure": {"profile": {
"name": "co.haply.inverse.tutorials:print-wireless-verse-grip"}}},
"wireless_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
ws.onmessage = [&](const std::string &msg) {
const json data = json::parse(msg);
if (first_message) {
first_message = false;
device_id = data["wireless_verse_grip"][0].at("device_id").get<std::string>();
request_msg = {
{"session", {{"configure", {{"profile",
{{"name", "co.haply.inverse.tutorials:print-wireless-verse-grip"}}}}}}},
{"wireless_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 s'agit d'une structure typée.button_state (buttons (et non pas) en leur attribuant explicitement un nom. Cela,wvg_state の buttons C'est pour éviter le « shadowing » des champs. Comme Glaze effectue le mappage à partir des noms de champs, vous pouvez choisir librement le nom du type de la structure.
// Struct models
struct quat { float w{1.0f}, x{}, y{}, z{}; };
struct button_state { bool a{}, b{}, c{}; };
struct wvg_state {
quat orientation{};
uint8_t hall{};
button_state buttons{};
float battery_level{};
};
struct wvg_device { std::string device_id; wvg_state state; };
struct devices_message { std::vector<wvg_device> wireless_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> wireless_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;
request_msg.session = session_cmd{ /* profile = print-wireless-verse-grip */ };
device_commands dc{ .device_id = data.wireless_verse_grip[0].device_id };
dc.commands.probe_orientation = probe_orientation_cmd{};
request_msg.wireless_verse_grip.push_back(std::move(dc));
}
std::string out_json;
(void)glz::write_json(request_msg, out_json);
ws.send(out_json);
request_msg.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 version Python accepte les deux mêmes indicateurs que letutoriel 01:
--full— Au lieu d'un résumé d'une ligne, le programme affiche la charge utile JSON brute mise en forme.--query-config— À chaque tick envoyésession.force_render_full_stateen réinjectant, de sorte que chaque trame ne reflète pas seulement la différence d'état, mais aussiconfigVeillez à inclure les blocs.
--query-config est particulièrement utile avec le VerseGrip sans fil.config Outre les champs de configuration habituels, les blocs comportent des champs spécifiques à WVG : sub_type Champ (stylus / prototype / ruko / kingfisher(ajouté dans la version 3.5) y figure.python 03-haply-inverse-print-wireless-verse-grip.py --full --query-config Une fois cette commande exécutée, elle s'affiche aux côtés des autres valeurs de configuration config.sub_type Vous pouvez voir comment les données sont mises à jour en temps réel.
Dans la version C++, ces indicateurs ne sont pas exposés.
Le tutoriel 03 est également installé localement avec le SDK. Dans le répertoire d'installation du service, sous tutorials/03-haply-inverse-print-wireless-verse-grip/ Veuillez consulter [...].
Source : Python · C++ · C++ Glaze
À lire également : Tutoriel 02 (Wired VG) · Type (quaternion) · Commandes de contrôle (probe_orientation) · Protocole WebSocket