00. Device List
Discovers all connected Haply devices via the HTTP REST API — no WebSocket needed. A simple smoke test that the runtime is up and detecting hardware.
What you'll learn:
- Querying
GET /devicesto list Inverse3, VerseGrip, and Wireless VerseGrip devices - Reading device IDs from the JSON response, grouped by device type
- Using a session selector to switch between raw device coordinates and a session's application-space view
Workflow
- Send
GET http://localhost:10001/devices. - Parse the JSON response — three top-level arrays:
inverse3,verse_grip,wireless_verse_grip. - Print the device IDs grouped by type. Empty arrays mean no device of that type is detected.
- If a session selector is appended as
?session=<selector>, the response is filtered to that session's devices and coordinates are converted to its application space (basis, mount, workspace applied). The Python sample exposes this as a CLI argument; the C++ samples query without a selector.
Parameters
| Parameter | Default | Purpose |
|---|---|---|
| Service URL | http://localhost:10001 | HTTP base URL |
| Endpoint | /devices | Device discovery endpoint |
| Session selector | (none) | ?session=:0, ?session=:-1, ?session=:my_profile:0 — see Selectors. Exposed as a CLI argument in the Python sample. |
Key code
- Python
- C++ (nlohmann)
- C++ (Glaze)
The Python sample exposes the session selector as a CLI argument. Without it, devices are returned in raw device-space coordinates; with it, they're filtered to the selected session and converted to application space.
def main():
session = sys.argv[1] if len(sys.argv) > 1 else None
url = f"{BASE_URL}/devices"
if session:
url += f"?session={session}"
print(f"Querying devices for session '{session}' (application space)\n")
else:
print("Querying all detected devices (device space)\n")
r = requests.get(url, timeout=3)
r.raise_for_status()
data = r.json()
print_devices("Inverse3", data.get("inverse3"))
print_devices("Wired Verse Grip", data.get("verse_grip"))
print_devices("Wireless Verse Grip", data.get("wireless_verse_grip"))
The C++ variant uses libhv for HTTP and nlohmann/json for parsing. It manually checks each top-level key with contains() and iterates the array if non-empty. The C++ samples don't expose the session-selector CLI argument — append ?session=<selector> to the URL if you need application-space coordinates.
HttpResponsePtr resp = requests::get("localhost:10001/devices");
if (resp == nullptr) { /* ... error ... */ return 1; }
json data = json::parse(resp->body);
if (data.contains("inverse3") && !data["inverse3"].empty()) {
printf("Inverse3 found:\n");
for (auto &element : data["inverse3"]) {
printf(" (id: %s)\n", element["device_id"].get<std::string>().c_str());
}
}
// ... same pattern for verse_grip and wireless_verse_grip
The Glaze variant declares plain structs that mirror the JSON shape — Glaze fills them via compile-time reflection. No contains() checks needed: missing arrays come back as empty std::vector<>.
static constexpr glz::opts glz_settings{.error_on_unknown_keys = false};
struct device_entry { std::string device_id; };
struct devices_response {
std::vector<device_entry> inverse3;
std::vector<device_entry> verse_grip;
std::vector<device_entry> wireless_verse_grip;
};
// In main():
HttpResponsePtr resp = requests::get("localhost:10001/devices");
devices_response data;
if (auto err = glz::read<glz_settings>(data, resp->body)) {
printf("JSON parse error: %s\n", glz::format_error(err, resp->body).c_str());
return 1;
}
for (const auto &dev : data.inverse3) {
printf(" (id: %s)\n", dev.device_id.c_str());
}
Source: Python · C++ · C++ Glaze
Related: JSON Conventions · Selectors · Sessions