Add XREAL XBX (A01 / A01+) glasses support - #133
Conversation
Registers the XREAL XBX A01 (PID 0x0440) and XBX A01 Plus (PID 0x0442) and
wires up their connect flow so head tracking works.
Unlike the Air/One devices, XBX gates its IMU stream behind an MCU
SDK-identification handshake plus a continuous 100 ms heartbeat, and it returns
an error status for R_ACTIVATION_TIME. So the MCU must be opened and
bootstrapped (heartbeat running) *before* the IMU is opened. This commit
reorders xreal_device_connect() accordingly for XBX and keeps the background
heartbeat thread as the sole owner of the MCU handle while streaming (the
controller poll loop skips MCU reads for XBX).
Display-mode/SBS control uses an XBX-specific ("Helen") encoding that isn't
implemented yet, so it is disabled for XBX; only IMU head tracking is exposed.
The MCU bootstrap + heartbeat protocol itself lives in the
xrealInterfaceLibrary submodule and is provided as a companion patch (see PR
description) — this repo change depends on it.
Verified on real XBX A01+ hardware (IMU streams ~1000 Hz; accel = 1 g
stationary; gyro tracks head rotation).
7c3bd3a to
82da43e
Compare
| // XBX (A01 / A01 Plus) glasses gate their IMU stream behind an MCU SDK handshake + heartbeat, | ||
| // so they need the MCU opened and bootstrapped before the IMU is opened (see xreal_device_connect). | ||
| #define XREAL_XBX_A01_ID_PRODUCT 0x0440 | ||
| #define XREAL_XBX_A01_PLUS_ID_PRODUCT 0x0442 |
There was a problem hiding this comment.
Let's make this a little more generic. I'm thinking we create a list of id_product values that require an MCU-first approach.
| 25.0, // XREAL One S | ||
| 25.0 // XREAL One S | ||
| 25.0, // XREAL One S | ||
| 10.0, // XREAL XBX A01 |
There was a problem hiding this comment.
Did you verify that this default look-ahead creates the best pinning effect? It looks like in general you copied the values for the Air glasses, which is probably the safe bet, but it's worth playing around with the look-ahead slider in the settings to see if any other values appear to work better (easier to use that dynamic slider than tinker with it here and recompile each time to test, then you can hardcode the best default). The way I usually test is to pin the screen with an edge of the screen near something else in the background (with light passthrough so I can see the background). Then move my head somewhat quickly and see how well the pinning resists following the quick movement (it will sort of jump in the direction you move and then quickly go back to where it was pinned). The best look-ahead value will create the most solid pinning (none will be perfect, though).
There was a problem hiding this comment.
Ah yes that's a good idea. I've concluded on this value based on a custom software i've made for the glasses, so they might not be perfect indeed, i'll try out your method to refine it
| static bool is_xreal_xbx(uint16_t product_id) { | ||
| return product_id == XREAL_XBX_A01_ID_PRODUCT || product_id == XREAL_XBX_A01_PLUS_ID_PRODUCT; | ||
| } |
There was a problem hiding this comment.
Have this check the list of mcu-first id_products, give it a more generic name (like requires_mcu_connection_first or something similar).
| if (device_is_xbx && !connected && glasses_controller) { | ||
| device_mcu_close(glasses_controller); | ||
| free(glasses_controller); | ||
| glasses_controller = NULL; | ||
| mcu_enabled = false; | ||
| } |
There was a problem hiding this comment.
This can live inside the if-block where it's relevant.
| // must not read/poll the MCU; it just stays alive until disconnect (detected via the IMU). | ||
| while (connected && glasses_imu && mcu_enabled && | ||
| (device_is_xbx || device_mcu_read(glasses_controller, 100) == DEVICE_MCU_ERROR_NO_ERROR)) { | ||
| if (!device_is_xbx) { |
There was a problem hiding this comment.
if (device->sbs_mode_supported) seems more appropriate
|
@wheaney thank you for your feedback ! I'll check them asap :) |
|
@wheaney how would you handle changes in the submodule ? Clicking it through github doesn't let me access it. It there a specific process ? |
|
@SolberLight I've refactored and merged your patch into my fork of the nrealAirLinuxDriver package in gitlab. I added a write lock that allows callers to issue MCU commands without risk of colliding with the heartbeat logic (the heartbeat no longer does HID read calls after the initial "open"). That should allow us to continue to support switching to SBS mode and any other MCU commands we want. Please pull in this latest commit and let me know if it doesn't work the same as it did with your patch. Also, I think we may be able to update XRLinuxDriver's xreal.c implementation so that it always attempts to open the MCU first (no conditional logic, just swap the ordering for every device). The only conditional check we'd need to add is that the device connection should fail if the MCU fails to open for devices that require a heartbeat (right now it allows MCU connection to be skipped if the IMU connection opens fine). |
|
Oh, but with that said, I also have a much larger refactor in place that makes this unusable at the moment. I'll need to update XRLinuxDriver to fully incorporate. |
|
Okay, the latest version (2.10.0) has your MCU-heartbeat driver changes plus the larger refractor I was referring to. You should be able to rebase with minimal-to-no conflicts and make the changes I described above. |
|
I rebased this branch on latest master (using Claude - sorry, not good enough with C) and it doesn't work for me. I have glasses and can relatively easy build and test xrlinuxdriver - please let me know if I can help |
Add XREAL XBX (A01 / A01+) support
This adds head-tracking support for the XREAL XBX A01 (PID
0x0440) and XBX A01 Plus (PID0x0442), mirroring how the existing Air/One devices work. Verified working on real XBX A01+ hardware: the IMU streams at ~1000 Hz, accelerometer reads ~1 g while stationary, and the gyro tracks head rotation.Huge thanks to @taowen 🙏
The key discovery — how to get the XBX MCU to unlock its IMU stream — comes from @taowen and their reverse-engineering work in taowen/ar-glass-lib (see
XrealXbxSession.kt). This PR is essentially a C port of that bootstrap.Why the driver doesn't already work on XBX
The XBX IMU acks
START_IMU_DATAbut streams nothing under the normal XREAL init, because its firmware gates the IMU stream behind an SDK-identification handshake + a continuous heartbeat on the MCU that the current driver never performs. On top of that, the standarddevice_mcu_openreadsR_ACTIVATION_TIME(0x29), which on XBX returns an error status — so the standard MCU open path fails on XBX and has to be replaced.What this does
hid_ids.c/.h): registers0x0440/0x0442withimu_protocol_hid(same0xAAframing as the Air), IMU on interface 1, MCU on interface 0, 64-byte reports.device_mcu.c): a dedicated open path for XBX that runs the bootstrap commands (0x26,0x57,0x12,0x02,0x34,0x35), the SDK-version handshake (0x31= ASCII"3.1.1") — this is the gate — then two acknowledged heartbeats, and finally starts a background thread that sends a heartbeat (0x1a) every 100 ms for the whole session.R_ACTIVATION_TIMEis skipped for XBX.src/devices/xreal.c): for XBX the MCU is opened and bootstrapped (heartbeat running) before the IMU is opened; the IMU then uses the existingimu_protocol_hidpath unchanged. The controller poll loop leaves the MCU handle to the heartbeat thread on XBX.The protocol code (
hid_ids,device_mcu) lives in thexrealInterfaceLibrarysubmodule, which is hosted separately atgitlab.com/wheaney/nrealAirLinuxDriver. This PR only contains the top-level (XRLinuxDriver) changes —src/devices/xreal.cand the README. The submodule changes can't appear in this PR's diff and are included below as a patch. A complete merge needs a companion change to the submodule (on GitLab) and then a submodule-pointer bump here. Happy to open the GitLab MR — just let me know the preferred workflow.The
src/devices/xreal.cchange is written to compile against the current submodule (it detects XBX by PID locally), but XBX won't actually stream until the submodule patch below lands.Build / testing status
Opened as a draft because it was not compile-verified in CI (developed on a machine without the Linux build deps). The protocol itself is verified end-to-end on XBX A01+ hardware via a Python proof-of-concept and taowen's Android implementation. Note: the submodule patch adds a
pthreaddependency tointerface_lib(Threads::Threads) for the heartbeat thread.Companion patch for the
xrealInterfaceLibrarysubmodule (apply inmodules/xrealInterfaceLibrary)