The open source OpenXR runtime
1// Copyright 2018, Philipp Zabel.
2// Copyright 2020-2021, N Madsen.
3// Copyright 2020-2023, Collabora, Ltd.
4// SPDX-License-Identifier: BSL-1.0
5/*!
6 * @file
7 * @brief Driver code for a WMR HMD.
8 * @author Philipp Zabel <philipp.zabel@gmail.com>
9 * @author nima01 <nima_zero_one@protonmail.com>
10 * @author Jakob Bornecrantz <jakob@collabora.com>
11 * @author Mateo de Mayo <mateo.demayo@collabora.com>
12 * @author Nova King <technobaboo@proton.me>
13 * @ingroup drv_wmr
14 */
15
16#include "xrt/xrt_config_drivers.h"
17#include "xrt/xrt_config_have.h"
18#include "xrt/xrt_config_build.h"
19#include "xrt/xrt_config_os.h"
20#include "xrt/xrt_defines.h"
21#include "xrt/xrt_device.h"
22#include "xrt/xrt_tracking.h"
23
24#include "os/os_time.h"
25#include "os/os_hid.h"
26
27#include "math/m_api.h"
28#include "math/m_mathinclude.h"
29#include "math/m_predict.h"
30#include "math/m_vec2.h"
31
32#include "util/u_var.h"
33#include "util/u_misc.h"
34#include "util/u_time.h"
35#include "util/u_debug.h"
36#include "util/u_device.h"
37#include "util/u_trace_marker.h"
38#include "util/u_distortion_mesh.h"
39#include "util/u_sink.h"
40
41#ifdef XRT_OS_LINUX
42#include "util/u_linux.h"
43#endif
44
45#include "tracking/t_tracking.h"
46
47#include "wmr_hmd.h"
48#include "wmr_common.h"
49#include "wmr_config_key.h"
50#include "wmr_protocol.h"
51#include "wmr_source.h"
52
53#include <stdio.h>
54#include <stdlib.h>
55#include <string.h>
56#include <assert.h>
57#ifndef XRT_OS_WINDOWS
58#include <unistd.h> // for sleep()
59#endif
60
61#ifdef XRT_BUILD_DRIVER_HANDTRACKING
62#include "../multi_wrapper/multi.h"
63#include "../drivers/ht/ht_interface.h"
64#endif
65
66// Unsure if these can change nor how to get them if so
67#define CAMERA_FREQUENCY 30 //!< Observed value (OV7251)
68#define IMU_FREQUENCY 1000 //!< Observed value (ICM20602)
69#define IMU_SAMPLES_PER_PACKET 4 //!< There are 4 samples for each USB IMU packet
70
71//! Specifies whether the user wants to use a SLAM tracker.
72DEBUG_GET_ONCE_BOOL_OPTION(wmr_slam, "WMR_SLAM", true)
73
74//! Specifies whether the user wants to use a SLAM tracker.
75DEBUG_GET_ONCE_NUM_OPTION(sleep_seconds, "WMR_DISPLAY_INIT_SLEEP_SECONDS", 4)
76
77//! Specifies whether the user wants to use the hand tracker.
78DEBUG_GET_ONCE_BOOL_OPTION(wmr_handtracking, "WMR_HANDTRACKING", true)
79
80#ifdef XRT_FEATURE_SLAM
81//! Whether to submit samples to the SLAM tracker from the start.
82DEBUG_GET_ONCE_OPTION(slam_submit_from_start, "SLAM_SUBMIT_FROM_START", NULL)
83#endif
84
85//! Specifies the y offset of the views.
86DEBUG_GET_ONCE_NUM_OPTION(left_view_y_offset, "WMR_LEFT_DISPLAY_VIEW_Y_OFFSET", 0)
87DEBUG_GET_ONCE_NUM_OPTION(right_view_y_offset, "WMR_RIGHT_DISPLAY_VIEW_Y_OFFSET", 0)
88
89
90#define WMR_TRACE(d, ...) U_LOG_XDEV_IFL_T(&d->base, d->log_level, __VA_ARGS__)
91#define WMR_DEBUG(d, ...) U_LOG_XDEV_IFL_D(&d->base, d->log_level, __VA_ARGS__)
92#define WMR_DEBUG_HEX(d, data, data_size) U_LOG_XDEV_IFL_D_HEX(&d->base, d->log_level, data, data_size)
93#define WMR_INFO(d, ...) U_LOG_XDEV_IFL_I(&d->base, d->log_level, __VA_ARGS__)
94#define WMR_WARN(d, ...) U_LOG_XDEV_IFL_W(&d->base, d->log_level, __VA_ARGS__)
95#define WMR_ERROR(d, ...) U_LOG_XDEV_IFL_E(&d->base, d->log_level, __VA_ARGS__)
96
97static int
98wmr_hmd_activate_reverb(struct wmr_hmd *wh);
99static void
100wmr_hmd_deactivate_reverb(struct wmr_hmd *wh);
101static void
102wmr_hmd_screen_enable_reverb(struct wmr_hmd *wh, bool enable);
103static int
104wmr_hmd_activate_odyssey_plus(struct wmr_hmd *wh);
105static void
106wmr_hmd_deactivate_odyssey_plus(struct wmr_hmd *wh);
107static void
108wmr_hmd_screen_enable_odyssey_plus(struct wmr_hmd *wh, bool enable);
109
110const struct wmr_headset_descriptor headset_map[] = {
111 {WMR_HEADSET_GENERIC, NULL, "Unknown WMR HMD", NULL, NULL, NULL}, /* Catch-all for unknown headsets */
112 {WMR_HEADSET_HP_VR1000, "HP Reverb VR Headset VR1000-1xxx", "HP VR1000", NULL, NULL, NULL}, /*! @todo init funcs */
113 {WMR_HEADSET_REVERB_G1, "HP Reverb VR Headset VR1000-2xxx", "HP Reverb", wmr_hmd_activate_reverb,
114 wmr_hmd_deactivate_reverb, wmr_hmd_screen_enable_reverb},
115 {WMR_HEADSET_REVERB_G2, "HP Reverb Virtual Reality Headset G2", "HP Reverb G2", wmr_hmd_activate_reverb,
116 wmr_hmd_deactivate_reverb, wmr_hmd_screen_enable_reverb},
117 {WMR_HEADSET_SAMSUNG_XE700X3AI, "Samsung Windows Mixed Reality XE700X3AI", "Samsung Odyssey",
118 wmr_hmd_activate_odyssey_plus, wmr_hmd_deactivate_odyssey_plus, wmr_hmd_screen_enable_odyssey_plus},
119 {WMR_HEADSET_SAMSUNG_800ZAA, "Samsung Windows Mixed Reality 800ZAA", "Samsung Odyssey+",
120 wmr_hmd_activate_odyssey_plus, wmr_hmd_deactivate_odyssey_plus, wmr_hmd_screen_enable_odyssey_plus},
121 {WMR_HEADSET_LENOVO_EXPLORER, "Lenovo VR-2511N", "Lenovo Explorer", NULL, NULL, NULL},
122 {WMR_HEADSET_MEDION_ERAZER_X1000, "Medion Erazer X1000", "Medion Erazer", NULL, NULL, NULL},
123 {WMR_HEADSET_DELL_VISOR, "DELL VR118", "Dell Visor", NULL, NULL, NULL},
124 {WMR_HEADSET_ACER_AH100, "Acer", "AH100", NULL, NULL, NULL},
125 {WMR_HEADSET_ACER_AH101, "Acer", "AH101", NULL, NULL, NULL},
126 {WMR_HEADSET_FUJITSU_FMVHDS1, "Fujitsu", "Fujitsu FMVHDS1", NULL, NULL, NULL},
127};
128const int headset_map_n = sizeof(headset_map) / sizeof(headset_map[0]);
129
130
131/*
132 *
133 * Hololens decode packets.
134 *
135 */
136
137static void
138hololens_sensors_decode_packet(struct wmr_hmd *wh,
139 struct hololens_sensors_packet *pkt,
140 const unsigned char *buffer,
141 int size)
142{
143 WMR_TRACE(wh, " ");
144
145 if (size != 497 && size != 381) {
146 WMR_ERROR(wh, "invalid hololens sensor packet size (expected 381 or 497 but got %d)", size);
147 return;
148 }
149
150 pkt->id = read8(&buffer);
151 for (int i = 0; i < 4; i++) {
152 pkt->temperature[i] = read16(&buffer);
153 }
154
155 for (int i = 0; i < 4; i++) {
156 pkt->gyro_timestamp[i] = read64(&buffer);
157 }
158
159 for (int i = 0; i < 3; i++) {
160 for (int j = 0; j < 32; j++) {
161 pkt->gyro[i][j] = read16(&buffer);
162 }
163 }
164
165 for (int i = 0; i < 4; i++) {
166 pkt->accel_timestamp[i] = read64(&buffer);
167 }
168
169 for (int i = 0; i < 3; i++) {
170 for (int j = 0; j < 4; j++) {
171 pkt->accel[i][j] = read32(&buffer);
172 }
173 }
174
175 for (int i = 0; i < 4; i++) {
176 pkt->video_timestamp[i] = read64(&buffer);
177 }
178}
179
180static void
181hololens_ensure_controller(struct wmr_hmd *wh, uint8_t controller_id, uint16_t vid, uint16_t pid)
182{
183 if (controller_id >= WMR_MAX_CONTROLLERS)
184 return;
185
186 if (wh->controller[controller_id] != NULL) {
187 return;
188 }
189
190 WMR_DEBUG(wh, "Adding controller device %d", controller_id);
191
192 enum xrt_device_type controller_type =
193 controller_id == 0 ? XRT_DEVICE_TYPE_LEFT_HAND_CONTROLLER : XRT_DEVICE_TYPE_RIGHT_HAND_CONTROLLER;
194 uint8_t hmd_cmd_base = controller_id == 0 ? 0x5 : 0xd;
195
196 struct wmr_hmd_controller_connection *controller =
197 wmr_hmd_controller_create(wh, hmd_cmd_base, controller_type, vid, pid, wh->log_level);
198
199 os_mutex_lock(&wh->controller_status_lock);
200 wh->controller[controller_id] = controller;
201 os_mutex_unlock(&wh->controller_status_lock);
202}
203
204/*
205 *
206 * Hololens packets.
207 *
208 */
209
210static void
211hololens_handle_unknown(struct wmr_hmd *wh, const unsigned char *buffer, int size)
212{
213 DRV_TRACE_MARKER();
214
215 WMR_DEBUG(wh, "Unknown hololens sensors message type: %02x, (%i)", buffer[0], size);
216}
217
218static void
219hololens_handle_control(struct wmr_hmd *wh, const unsigned char *buffer, int size)
220{
221 DRV_TRACE_MARKER();
222
223 WMR_DEBUG(wh, "WMR_MS_HOLOLENS_MSG_CONTROL: %02x, (%i)", buffer[0], size);
224}
225
226static void
227hololens_handle_controller_status_packet(struct wmr_hmd *wh, const unsigned char *buffer, int size)
228{
229 DRV_TRACE_MARKER();
230
231 if (size < 3) {
232 WMR_DEBUG(wh, "Got small packet 0x17 (%i)", size);
233 return;
234 }
235
236 uint8_t controller_id = buffer[1];
237 uint8_t pkt_type = buffer[2];
238
239 switch (pkt_type) {
240 case WMR_CONTROLLER_STATUS_UNPAIRED: {
241 WMR_TRACE(wh, "Controller %d is not paired", controller_id);
242 break;
243 }
244 case WMR_CONTROLLER_STATUS_OFFLINE: {
245 if (size < 7) {
246 WMR_TRACE(wh, "Got small controller offline status packet (%i)", size);
247 return;
248 }
249
250 /* Skip packet type, controller id, presence */
251 buffer += 3;
252
253 uint16_t vid = read16(&buffer);
254 uint16_t pid = read16(&buffer);
255 WMR_TRACE(wh, "Controller %d offline. VID 0x%04x PID 0x%04x", controller_id, vid, pid);
256 break;
257 }
258 case WMR_CONTROLLER_STATUS_ONLINE: {
259 if (size < 7) {
260 WMR_TRACE(wh, "Got small controller online status packet (%i)", size);
261 return;
262 }
263
264 /* Skip packet type, controller id, presence */
265 buffer += 3;
266
267 uint16_t vid = read16(&buffer);
268 uint16_t pid = read16(&buffer);
269
270 if (size >= 10) {
271 uint8_t unknown1 = read8(&buffer);
272 uint16_t unknown2160 = read16(&buffer);
273 WMR_TRACE(wh, "Controller %d online. VID 0x%04x PID 0x%04x val1 %u val2 %u", controller_id, vid,
274 pid, unknown1, unknown2160);
275 } else {
276 WMR_TRACE(wh, "Controller %d online. VID 0x%04x PID 0x%04x", controller_id, vid, pid);
277 }
278
279 hololens_ensure_controller(wh, controller_id, vid, pid);
280 break;
281 }
282 default: //
283 WMR_DEBUG(wh, "Unknown controller status packet (%i) type 0x%02x", size, pkt_type);
284 break;
285 }
286
287 os_mutex_lock(&wh->controller_status_lock);
288 if (controller_id == 0)
289 wh->have_left_controller_status = true;
290 else if (controller_id == 1)
291 wh->have_right_controller_status = true;
292 if (wh->have_left_controller_status && wh->have_right_controller_status)
293 os_cond_signal(&wh->controller_status_cond);
294 os_mutex_unlock(&wh->controller_status_lock);
295}
296
297static void
298hololens_handle_bt_iface_packet(struct wmr_hmd *wh, const unsigned char *buffer, int size)
299{
300 DRV_TRACE_MARKER();
301
302 int pkt_type;
303
304 if (size < 2)
305 return;
306
307 if (size < 6) {
308 WMR_DEBUG(wh, "Short Bluetooth interface packet (%d) type 0x%02x", size, buffer[1]);
309 return;
310 }
311
312 pkt_type = buffer[1];
313 if (pkt_type != WMR_BT_IFACE_MSG_DEBUG) {
314 WMR_DEBUG(wh, "Unknown Bluetooth interface packet (%d) type 0x%02x", size, pkt_type);
315 WMR_DEBUG_HEX(wh, buffer, size);
316 return;
317 }
318 buffer += 2;
319
320 uint16_t tag = read16(&buffer);
321 uint16_t msg_len = read16(&buffer);
322
323 if (size < msg_len + 6) {
324 WMR_DEBUG(wh, "Bluetooth interface debug packet (%d) too short. tag 0x%x msg len %u", size, tag,
325 msg_len);
326 return;
327 }
328
329 WMR_DEBUG(wh, "BT debug: tag %d: %.*s", tag, msg_len, buffer);
330}
331
332static void
333hololens_handle_controller_packet(struct wmr_hmd *wh, const unsigned char *buffer, int size)
334{
335 if (size < 45) {
336 WMR_TRACE(wh, "Got unknown short controller packet (%i)\n\t%02x", size, buffer[0]);
337 return;
338 }
339
340 uint8_t packet_id = buffer[0];
341 struct wmr_controller_connection *controller = NULL;
342
343 if (packet_id == WMR_MS_HOLOLENS_MSG_LEFT_CONTROLLER) {
344 controller = (struct wmr_controller_connection *)wh->controller[0];
345 } else if (packet_id == WMR_MS_HOLOLENS_MSG_RIGHT_CONTROLLER) {
346 controller = (struct wmr_controller_connection *)wh->controller[1];
347 }
348
349 if (controller == NULL)
350 return; /* Controller online message not yet seen */
351
352 uint64_t now_ns = os_monotonic_get_ns();
353 wmr_controller_connection_receive_bytes(controller, now_ns, (uint8_t *)buffer, size);
354}
355
356static void
357hololens_handle_debug(struct wmr_hmd *wh, const unsigned char *buffer, int size)
358{
359 DRV_TRACE_MARKER();
360
361 if (size < 12) {
362 WMR_TRACE(wh, "Got short debug packet (%i) 0x%02x", size, buffer[0]);
363 return;
364 }
365 buffer += 1;
366
367 uint32_t magic = read32(&buffer);
368 if (magic != WMR_MAGIC) {
369 WMR_TRACE(wh, "Debug packet (%i) 0x%02x had strange magic 0x%08x", size, buffer[0], magic);
370 return;
371 }
372 uint32_t timestamp = read32(&buffer);
373 uint16_t seq = read16(&buffer);
374 uint8_t src_tag = read8(&buffer);
375 int msg_len = size - 12;
376
377 WMR_DEBUG(wh, "HMD debug: TS %f seq %u src %d: %.*s", timestamp / 1000.0, seq, src_tag, msg_len, buffer);
378}
379
380static void
381hololens_handle_sensors_avg(struct wmr_hmd *wh, const unsigned char *buffer, int size)
382{
383 DRV_TRACE_MARKER();
384
385 // Get the timing as close to reading the packet as possible.
386 uint64_t now_ns = os_monotonic_get_ns();
387
388 hololens_sensors_decode_packet(wh, &wh->packet, buffer, size);
389
390 // Use a single averaged sample from all the samples in the packet
391 struct xrt_vec3 avg_raw_accel = XRT_VEC3_ZERO;
392 struct xrt_vec3 avg_raw_gyro = XRT_VEC3_ZERO;
393 for (int i = 0; i < IMU_SAMPLES_PER_PACKET; i++) {
394 struct xrt_vec3 a = XRT_VEC3_ZERO;
395 struct xrt_vec3 g = XRT_VEC3_ZERO;
396 vec3_from_hololens_accel(wh->packet.accel, i, &a);
397 vec3_from_hololens_gyro(wh->packet.gyro, i, &g);
398 math_vec3_accum(&a, &avg_raw_accel);
399 math_vec3_accum(&g, &avg_raw_gyro);
400 }
401 math_vec3_scalar_mul(1.0f / IMU_SAMPLES_PER_PACKET, &avg_raw_accel);
402 math_vec3_scalar_mul(1.0f / IMU_SAMPLES_PER_PACKET, &avg_raw_gyro);
403
404 // Calibrate averaged sample
405 struct xrt_vec3 avg_calib_accel = XRT_VEC3_ZERO;
406 struct xrt_vec3 avg_calib_gyro = XRT_VEC3_ZERO;
407 math_matrix_3x3_transform_vec3(&wh->config.sensors.accel.mix_matrix, &avg_raw_accel, &avg_calib_accel);
408 math_matrix_3x3_transform_vec3(&wh->config.sensors.gyro.mix_matrix, &avg_raw_gyro, &avg_calib_gyro);
409 math_vec3_accum(&wh->config.sensors.accel.bias_offsets, &avg_calib_accel);
410 math_vec3_accum(&wh->config.sensors.gyro.bias_offsets, &avg_calib_gyro);
411 math_quat_rotate_vec3(&wh->config.sensors.transforms.P_oxr_acc.orientation, &avg_calib_accel, &avg_calib_accel);
412 math_quat_rotate_vec3(&wh->config.sensors.transforms.P_oxr_gyr.orientation, &avg_calib_gyro, &avg_calib_gyro);
413
414 // Fusion tracking
415 os_mutex_lock(&wh->fusion.mutex);
416 timepoint_ns t = wh->packet.gyro_timestamp[IMU_SAMPLES_PER_PACKET - 1] * WMR_MS_HOLOLENS_NS_PER_TICK;
417 m_imu_3dof_update(&wh->fusion.i3dof, t, &avg_calib_accel, &avg_calib_gyro);
418 wh->fusion.last_imu_timestamp_ns = now_ns;
419 wh->fusion.last_angular_velocity = avg_calib_gyro;
420 os_mutex_unlock(&wh->fusion.mutex);
421
422 // SLAM tracking
423 wmr_source_push_imu_packet(wh->tracking.source, t, avg_raw_accel, avg_raw_gyro);
424}
425
426static void
427hololens_handle_sensors_all(struct wmr_hmd *wh, const unsigned char *buffer, int size)
428{
429 DRV_TRACE_MARKER();
430
431 // Get the timing as close to reading the packet as possible.
432 uint64_t now_ns = os_monotonic_get_ns();
433
434 hololens_sensors_decode_packet(wh, &wh->packet, buffer, size);
435
436 struct xrt_vec3 raw_gyro[IMU_SAMPLES_PER_PACKET];
437 struct xrt_vec3 raw_accel[IMU_SAMPLES_PER_PACKET];
438 struct xrt_vec3 calib_gyro[IMU_SAMPLES_PER_PACKET];
439 struct xrt_vec3 calib_accel[IMU_SAMPLES_PER_PACKET];
440
441 for (int i = 0; i < IMU_SAMPLES_PER_PACKET; i++) {
442 struct xrt_vec3 *rg = &raw_gyro[i];
443 struct xrt_vec3 *cg = &calib_gyro[i];
444 vec3_from_hololens_gyro(wh->packet.gyro, i, rg);
445 math_matrix_3x3_transform_vec3(&wh->config.sensors.gyro.mix_matrix, rg, cg);
446 math_vec3_accum(&wh->config.sensors.gyro.bias_offsets, cg);
447 math_quat_rotate_vec3(&wh->config.sensors.transforms.P_oxr_gyr.orientation, cg, cg);
448
449 struct xrt_vec3 *ra = &raw_accel[i];
450 struct xrt_vec3 *ca = &calib_accel[i];
451 vec3_from_hololens_accel(wh->packet.accel, i, ra);
452 math_matrix_3x3_transform_vec3(&wh->config.sensors.accel.mix_matrix, ra, ca);
453 math_vec3_accum(&wh->config.sensors.accel.bias_offsets, ca);
454 math_quat_rotate_vec3(&wh->config.sensors.transforms.P_oxr_acc.orientation, ca, ca);
455 }
456
457 // Fusion tracking
458 os_mutex_lock(&wh->fusion.mutex);
459 for (int i = 0; i < IMU_SAMPLES_PER_PACKET; i++) {
460 m_imu_3dof_update( //
461 &wh->fusion.i3dof, //
462 wh->packet.gyro_timestamp[i] * WMR_MS_HOLOLENS_NS_PER_TICK, //
463 &calib_accel[i], //
464 &calib_gyro[i]); //
465 }
466 wh->fusion.last_imu_timestamp_ns = now_ns;
467 wh->fusion.last_angular_velocity = calib_gyro[3];
468 os_mutex_unlock(&wh->fusion.mutex);
469
470 // SLAM tracking
471 for (int i = 0; i < IMU_SAMPLES_PER_PACKET; i++) {
472 timepoint_ns t = wh->packet.gyro_timestamp[i] * WMR_MS_HOLOLENS_NS_PER_TICK;
473 wmr_source_push_imu_packet(wh->tracking.source, t, raw_accel[i], raw_gyro[i]);
474 }
475}
476
477static void
478hololens_handle_sensors(struct wmr_hmd *wh, const unsigned char *buffer, int size)
479{
480 if (wh->average_imus) {
481 // Less overhead and jitter.
482 hololens_handle_sensors_avg(wh, buffer, size);
483 } else {
484 // More sophisticated fusion algorithms might work better with raw data.
485 hololens_handle_sensors_all(wh, buffer, size);
486 }
487}
488
489static bool
490hololens_sensors_read_packets(struct wmr_hmd *wh)
491{
492 DRV_TRACE_MARKER();
493
494 WMR_TRACE(wh, " ");
495
496 unsigned char buffer[WMR_FEATURE_BUFFER_SIZE];
497
498 // Block for 100ms
499 os_mutex_lock(&wh->hid_lock);
500 int size = os_hid_read(wh->hid_hololens_sensors_dev, buffer, sizeof(buffer), 100);
501 os_mutex_unlock(&wh->hid_lock);
502
503 if (size < 0) {
504 WMR_ERROR(wh, "Error reading from Hololens Sensors device. Call to os_hid_read returned %i", size);
505 return false;
506 }
507 if (size == 0) {
508 WMR_TRACE(wh, "No more data to read");
509 return true; // No more messages, return.
510 } else {
511 WMR_TRACE(wh, "Read %u bytes", size);
512 }
513
514 switch (buffer[0]) {
515 case WMR_MS_HOLOLENS_MSG_SENSORS: //
516 hololens_handle_sensors(wh, buffer, size);
517 break;
518 case WMR_MS_HOLOLENS_MSG_BT_IFACE: //
519 hololens_handle_bt_iface_packet(wh, buffer, size);
520 break;
521 case WMR_MS_HOLOLENS_MSG_LEFT_CONTROLLER:
522 case WMR_MS_HOLOLENS_MSG_RIGHT_CONTROLLER: //
523 hololens_handle_controller_packet(wh, buffer, size);
524 break;
525 case WMR_MS_HOLOLENS_MSG_CONTROLLER_STATUS: //
526 hololens_handle_controller_status_packet(wh, buffer, size);
527 break;
528 case WMR_MS_HOLOLENS_MSG_CONTROL: //
529 hololens_handle_control(wh, buffer, size);
530 break;
531 case WMR_MS_HOLOLENS_MSG_DEBUG: //
532 hololens_handle_debug(wh, buffer, size);
533 break;
534 default: //
535 hololens_handle_unknown(wh, buffer, size);
536 break;
537 }
538
539 return true;
540}
541
542
543/*
544 *
545 * Control packets.
546 *
547 */
548
549static void
550control_ipd_value_decode(struct wmr_hmd *wh, const unsigned char *buffer, int size)
551{
552 if (size != 2 && size != 4) {
553 WMR_ERROR(wh, "Invalid control ipd distance packet size (expected 4 but got %i)", size);
554 return;
555 }
556
557 uint8_t id = read8(&buffer);
558 if (id != 0x1) {
559 WMR_ERROR(wh, "Invalid control IPD distance packet ID (expected 0x1 but got %u)", id);
560 return;
561 }
562
563 uint8_t proximity = read8(&buffer);
564 uint16_t ipd_value = (size == 4) ? read16(&buffer) : wh->raw_ipd;
565
566 bool changed = (wh->raw_ipd != ipd_value) || (wh->proximity_sensor != proximity);
567
568 wh->raw_ipd = ipd_value;
569 wh->proximity_sensor = proximity;
570
571 if (changed) {
572 WMR_DEBUG(wh, "Proximity sensor %d IPD: %d", proximity, ipd_value);
573 }
574}
575
576static bool
577control_read_packets(struct wmr_hmd *wh)
578{
579 DRV_TRACE_MARKER();
580
581 unsigned char buffer[WMR_FEATURE_BUFFER_SIZE];
582
583 // Do not block
584 os_mutex_lock(&wh->hid_lock);
585 int size = os_hid_read(wh->hid_control_dev, buffer, sizeof(buffer), 0);
586 os_mutex_unlock(&wh->hid_lock);
587
588 if (size < 0) {
589 WMR_ERROR(wh, "Error reading from companion (HMD control) device. Call to os_hid_read returned %i",
590 size);
591 return false;
592 }
593 if (size == 0) {
594 WMR_TRACE(wh, "No more data to read");
595 return true; // No more messages, return.
596 } else {
597 WMR_TRACE(wh, "Read %u bytes", size);
598 }
599
600 DRV_TRACE_IDENT(control_packet_got);
601
602 switch (buffer[0]) {
603 case WMR_CONTROL_MSG_IPD_VALUE: //
604 control_ipd_value_decode(wh, buffer, size);
605 break;
606 case WMR_CONTROL_MSG_UNKNOWN_02: //
607 WMR_DEBUG(wh, "Unknown message type: %02x (size %i)", buffer[0], size);
608 if (size == 4) {
609 // Todo: Decode.
610 // On Reverb G1 this message is sometimes received right after a
611 // proximity/IPD message, and it always seems to be '02 XX 0d 26'.
612 WMR_DEBUG(wh, "---> Type and content bytes: %02x %02x %02x %02x", buffer[0], buffer[1],
613 buffer[2], buffer[3]);
614 }
615 break;
616 case WMR_CONTROL_MSG_DEVICE_STATUS: //
617 WMR_DEBUG(wh, "Device status message type: %02x (size %i)", buffer[0], size);
618 if (size != 11) {
619 WMR_DEBUG(wh,
620 "---> Unexpected message size. Expected 11 bytes incl. message type. Got %d bytes",
621 size);
622 WMR_DEBUG_HEX(wh, buffer, size);
623 if (size < 11) {
624 break;
625 }
626 }
627
628 // Todo: HMD state info to be decoded further.
629 // On Reverb G1 this message is received twice after having sent an 'enable screen' command to the HMD
630 // companion device. The first one is received promptly. The second one is received a few seconds later
631 // once the HMD screen backlight visibly powers on.
632 // 1st message: '05 00 01 01 00 00 00 00 00 00 00'
633 // 2nd message: '05 01 01 01 01 00 00 00 00 00 00'
634 WMR_DEBUG(wh, "---> Type and content bytes: %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x",
635 buffer[0], buffer[1], buffer[2], buffer[3], buffer[4], buffer[5], buffer[6], buffer[7],
636 buffer[8], buffer[9], buffer[10]);
637 WMR_DEBUG(wh,
638 "---> Flags decoded so far: [type: %02x] [display_ready: %02x] [?] [?] [display_ready: %02x] "
639 "[?] [?] [?] [?] [?] [?]",
640 buffer[0], buffer[1], buffer[4]);
641
642 break;
643 default: //
644 WMR_DEBUG(wh, "Unknown message type: %02x (size %i)", buffer[0], size);
645 WMR_DEBUG_HEX(wh, buffer, size);
646 break;
647 }
648
649 return true;
650}
651
652
653/*
654 *
655 * Helpers and internal functions.
656 *
657 */
658
659static void *
660wmr_run_thread(void *ptr)
661{
662 struct wmr_hmd *wh = (struct wmr_hmd *)ptr;
663
664 U_TRACE_SET_THREAD_NAME("WMR: USB-HMD");
665 os_thread_helper_name(&wh->oth, "WMR: USB-HMD");
666
667#ifdef XRT_OS_LINUX
668 // Try to raise priority of this thread.
669 u_linux_try_to_set_realtime_priority_on_thread(wh->log_level, "WMR: USB-HMD");
670#endif
671
672
673 os_thread_helper_lock(&wh->oth);
674 while (os_thread_helper_is_running_locked(&wh->oth)) {
675 os_thread_helper_unlock(&wh->oth);
676
677 // Does not block.
678 if (!control_read_packets(wh)) {
679 break;
680 }
681
682 // Does block for a bit.
683 if (!hololens_sensors_read_packets(wh)) {
684 break;
685 }
686 os_thread_helper_lock(&wh->oth);
687 }
688 os_thread_helper_unlock(&wh->oth);
689
690 WMR_DEBUG(wh, "Exiting reading thread.");
691
692 return NULL;
693}
694
695static void
696hololens_sensors_enable_imu(struct wmr_hmd *wh)
697{
698 DRV_TRACE_MARKER();
699
700 os_mutex_lock(&wh->hid_lock);
701 int size = os_hid_write(wh->hid_hololens_sensors_dev, hololens_sensors_imu_on, sizeof(hololens_sensors_imu_on));
702 os_mutex_unlock(&wh->hid_lock);
703
704 if (size <= 0) {
705 WMR_ERROR(wh, "Error writing to device");
706 return;
707 }
708}
709
710#define HID_SEND(hmd, HID, DATA, STR) \
711 do { \
712 os_mutex_lock(&hmd->hid_lock); \
713 int _ret = os_hid_set_feature(HID, DATA, sizeof(DATA)); \
714 os_mutex_unlock(&hmd->hid_lock); \
715 if (_ret < 0) { \
716 WMR_ERROR(wh, "Send (%s): %i", STR, _ret); \
717 } \
718 } while (false);
719
720#define HID_GET(hmd, HID, DATA, STR) \
721 do { \
722 os_mutex_lock(&hmd->hid_lock); \
723 int _ret = os_hid_get_feature(HID, DATA[0], DATA, sizeof(DATA)); \
724 os_mutex_unlock(&hmd->hid_lock); \
725 if (_ret < 0) { \
726 WMR_ERROR(wh, "Get (%s): %i", STR, _ret); \
727 } else { \
728 WMR_DEBUG(wh, "0x%02x HID feature returned", DATA[0]); \
729 WMR_DEBUG_HEX(wh, DATA, _ret); \
730 } \
731 } while (false);
732
733static int
734wmr_hmd_activate_reverb(struct wmr_hmd *wh)
735{
736 DRV_TRACE_MARKER();
737
738 struct os_hid_device *hid = wh->hid_control_dev;
739
740 WMR_TRACE(wh, "Activating HP Reverb G1/G2 HMD...");
741
742 // Hack to power up the Reverb G1 display, thanks to OpenHMD contributors.
743 // Sleep before we start seems to improve reliability.
744 // 300ms is what Windows seems to do, so cargo cult that.
745 os_nanosleep(U_TIME_1MS_IN_NS * 300);
746
747 for (int i = 0; i < 4; i++) {
748 unsigned char cmd[64] = {0x50, 0x01};
749 HID_SEND(wh, hid, cmd, "loop");
750
751 unsigned char data[64] = {0x50};
752 HID_GET(wh, hid, data, "loop");
753
754 os_nanosleep(U_TIME_1MS_IN_NS * 10); // Sleep 10ms
755 }
756
757 unsigned char data[64] = {0x09};
758 HID_GET(wh, hid, data, "data_1");
759
760 data[0] = 0x08;
761 HID_GET(wh, hid, data, "data_2");
762
763 data[0] = 0x06;
764 HID_GET(wh, hid, data, "data_3");
765
766 WMR_INFO(wh, "Sent activation report.");
767
768 // Enable the HMD screen now, if required. Otherwise, if screen should initially be disabled, then
769 // proactively disable it now. Why? Because some cases of irregular termination of Monado will
770 // leave either the 'Hololens Sensors' device or its 'companion' device alive across restarts.
771 wmr_hmd_screen_enable_reverb(wh, wh->hmd_screen_enable);
772
773 // Allow time for enumeration of available displays by host system, so the compositor can select among them.
774 WMR_INFO(wh,
775 "Sleep until the HMD display is powered up, so the available displays can be enumerated by the host "
776 "system.");
777
778 // Get the sleep amount, then sleep. One or two seconds was not enough.
779 uint64_t seconds = debug_get_num_option_sleep_seconds();
780 os_nanosleep(U_TIME_1S_IN_NS * seconds);
781
782 return 0;
783}
784
785static void
786wmr_hmd_refresh_debug_gui(struct wmr_hmd *wh)
787{
788 // Update debug GUI button labels.
789 if (wh) {
790 struct u_var_button *btn = &wh->gui.hmd_screen_enable_btn;
791 snprintf(btn->label, sizeof(btn->label),
792 wh->hmd_screen_enable ? "HMD Screen [On]" : "HMD Screen [Off]");
793 }
794}
795
796static void
797wmr_hmd_deactivate_reverb(struct wmr_hmd *wh)
798{
799 DRV_TRACE_MARKER();
800
801 // Turn the screen off
802 wmr_hmd_screen_enable_reverb(wh, false);
803
804 //! @todo Power down IMU, and maybe more.
805}
806
807static void
808wmr_hmd_screen_enable_reverb(struct wmr_hmd *wh, bool enable)
809{
810 DRV_TRACE_MARKER();
811
812 struct os_hid_device *hid = wh->hid_control_dev;
813
814 unsigned char cmd[2] = {0x04, 0x00};
815 if (enable) {
816 cmd[1] = enable ? 0x01 : 0x00;
817 }
818
819 HID_SEND(wh, hid, cmd, (enable ? "screen_on" : "screen_off"));
820
821 wh->hmd_screen_enable = enable;
822
823 // Update debug GUI button labels.
824 wmr_hmd_refresh_debug_gui(wh);
825}
826
827static int
828wmr_hmd_activate_odyssey_plus(struct wmr_hmd *wh)
829{
830 DRV_TRACE_MARKER();
831
832 struct os_hid_device *hid = wh->hid_control_dev;
833
834 WMR_TRACE(wh, "Activating Odyssey HMD...");
835
836 os_nanosleep(U_TIME_1MS_IN_NS * 300);
837
838 unsigned char data[64] = {0x16};
839 HID_GET(wh, hid, data, "data_1");
840
841 data[0] = 0x15;
842 HID_GET(wh, hid, data, "data_2");
843
844 data[0] = 0x14;
845 HID_GET(wh, hid, data, "data_3");
846
847 // Enable the HMD screen now, if required. Otherwise, if screen should initially be disabled, then
848 // proactively disable it now. Why? Because some cases of irregular termination of Monado will
849 // leave either the 'Hololens Sensors' device or its 'companion' device alive across restarts.
850 wmr_hmd_screen_enable_odyssey_plus(wh, wh->hmd_screen_enable);
851
852 // Allow time for enumeration of available displays by host system, so the compositor can select among them.
853 WMR_INFO(wh,
854 "Sleep until the HMD display is powered up, so the available displays can be enumerated by the host "
855 "system.");
856
857 os_nanosleep(3LL * U_TIME_1S_IN_NS);
858
859 return 0;
860}
861
862static void
863wmr_hmd_deactivate_odyssey_plus(struct wmr_hmd *wh)
864{
865 DRV_TRACE_MARKER();
866
867 // Turn the screen off
868 wmr_hmd_screen_enable_odyssey_plus(wh, false);
869
870 //! @todo Power down IMU, and maybe more.
871}
872
873static void
874wmr_hmd_screen_enable_odyssey_plus(struct wmr_hmd *wh, bool enable)
875{
876 DRV_TRACE_MARKER();
877
878 struct os_hid_device *hid = wh->hid_control_dev;
879
880 unsigned char cmd[2] = {0x12, 0x00};
881 if (enable) {
882 cmd[1] = enable ? 0x01 : 0x00;
883 }
884
885 HID_SEND(wh, hid, cmd, (enable ? "screen_on" : "screen_off"));
886
887 wh->hmd_screen_enable = enable;
888
889 // Update debug GUI button labels.
890 wmr_hmd_refresh_debug_gui(wh);
891}
892
893static void
894wmr_hmd_screen_enable_toggle(void *wh_ptr)
895{
896 struct wmr_hmd *wh = (struct wmr_hmd *)wh_ptr;
897 if (wh && wh->hmd_desc && wh->hmd_desc->screen_enable_func) {
898 wh->hmd_desc->screen_enable_func(wh, !wh->hmd_screen_enable);
899 }
900}
901
902/*
903 *
904 * Config functions.
905 *
906 */
907
908static int
909wmr_config_command_sync(struct wmr_hmd *wh, unsigned char type, unsigned char *buf, int len)
910{
911 DRV_TRACE_MARKER();
912
913 struct os_hid_device *hid = wh->hid_hololens_sensors_dev;
914
915 unsigned char cmd[64] = {0x02, type};
916 os_hid_write(hid, cmd, sizeof(cmd));
917
918 do {
919 int size = os_hid_read(hid, buf, len, 100);
920 if (size < 1) {
921 return -1;
922 }
923 if (buf[0] == WMR_MS_HOLOLENS_MSG_CONTROL) {
924 return size;
925 }
926 } while (true);
927
928 return -1;
929}
930
931static int
932wmr_read_config_part(struct wmr_hmd *wh, unsigned char type, unsigned char *data, int len)
933{
934 DRV_TRACE_MARKER();
935
936 unsigned char buf[33];
937 int offset = 0;
938 int size;
939
940 size = wmr_config_command_sync(wh, 0x0b, buf, sizeof(buf));
941 if (size != 33 || buf[0] != 0x02) {
942 WMR_ERROR(wh, "Failed to issue command 0b: %02x %02x %02x", buf[0], buf[1], buf[2]);
943 return -1;
944 }
945
946 size = wmr_config_command_sync(wh, type, buf, sizeof(buf));
947 if (size != 33 || buf[0] != 0x02) {
948 WMR_ERROR(wh, "Failed to issue command %02x: %02x %02x %02x", type, buf[0], buf[1], buf[2]);
949 return -1;
950 }
951
952 while (true) {
953 size = wmr_config_command_sync(wh, 0x08, buf, sizeof(buf));
954 if (size != 33 || (buf[1] != 0x01 && buf[1] != 0x02)) {
955 WMR_ERROR(wh, "Failed to issue command 08: %02x %02x %02x", buf[0], buf[1], buf[2]);
956 return -1;
957 }
958
959 if (buf[1] != 0x01) {
960 break;
961 }
962
963 if (buf[2] > len || offset + buf[2] > len) {
964 WMR_ERROR(wh, "Getting more information then requested");
965 return -1;
966 }
967
968 memcpy(data + offset, buf + 3, buf[2]);
969 offset += buf[2];
970 }
971
972 return offset;
973}
974
975XRT_MAYBE_UNUSED static int
976wmr_read_config_raw(struct wmr_hmd *wh, uint8_t **out_data, size_t *out_size)
977{
978 DRV_TRACE_MARKER();
979
980 unsigned char meta[84];
981 uint8_t *data;
982 int size;
983 int data_size;
984
985 size = wmr_read_config_part(wh, 0x06, meta, sizeof(meta));
986 WMR_DEBUG(wh, "(0x06, meta) => %d", size);
987
988 if (size < 0) {
989 return -1;
990 }
991
992 /*
993 * No idea what the other 64 bytes of metadata are, but the first two
994 * seem to be little endian size of the data store.
995 */
996 data_size = meta[0] | (meta[1] << 8);
997 data = calloc(1, data_size + 1);
998 if (!data) {
999 return -1;
1000 }
1001 data[data_size] = '\0';
1002
1003 size = wmr_read_config_part(wh, 0x04, data, data_size);
1004 WMR_DEBUG(wh, "(0x04, data) => %d", size);
1005 if (size < 0) {
1006 free(data);
1007 return -1;
1008 }
1009
1010 WMR_DEBUG(wh, "Read %d-byte config data", data_size);
1011
1012 *out_data = data;
1013 *out_size = size;
1014
1015 return 0;
1016}
1017
1018static int
1019wmr_read_config(struct wmr_hmd *wh)
1020{
1021 DRV_TRACE_MARKER();
1022
1023 unsigned char *data = NULL;
1024 unsigned char *config_json_block;
1025 size_t data_size;
1026 int ret;
1027
1028 // Read config
1029 ret = wmr_read_config_raw(wh, &data, &data_size);
1030 if (ret < 0)
1031 return ret;
1032
1033 /* De-obfuscate the JSON config */
1034 /* FIXME: The header contains little-endian values that need swapping for big-endian */
1035 struct wmr_config_header *hdr = (struct wmr_config_header *)data;
1036
1037 /* Take a copy of the header */
1038 memcpy(&wh->config_hdr, hdr, sizeof(struct wmr_config_header));
1039
1040 WMR_INFO(wh, "Manufacturer: %.*s", (int)sizeof(hdr->manufacturer), hdr->manufacturer);
1041 WMR_INFO(wh, "Device: %.*s", (int)sizeof(hdr->device), hdr->device);
1042 WMR_INFO(wh, "Serial: %.*s", (int)sizeof(hdr->serial), hdr->serial);
1043 WMR_INFO(wh, "UID: %.*s", (int)sizeof(hdr->uid), hdr->uid);
1044 WMR_INFO(wh, "Name: %.*s", (int)sizeof(hdr->name), hdr->name);
1045 WMR_INFO(wh, "Revision: %.*s", (int)sizeof(hdr->revision), hdr->revision);
1046 WMR_INFO(wh, "Revision Date: %.*s", (int)sizeof(hdr->revision_date), hdr->revision_date);
1047
1048 snprintf(wh->base.str, XRT_DEVICE_NAME_LEN, "%.*s", (int)sizeof(hdr->name), hdr->name);
1049
1050 if (hdr->json_start >= data_size || (data_size - hdr->json_start) < hdr->json_size) {
1051 WMR_ERROR(wh, "Invalid WMR config block - incorrect sizes");
1052 free(data);
1053 return -1;
1054 }
1055
1056 config_json_block = data + hdr->json_start + sizeof(uint16_t);
1057 for (unsigned int i = 0; i < hdr->json_size - sizeof(uint16_t); i++) {
1058 config_json_block[i] ^= wmr_config_key[i % sizeof(wmr_config_key)];
1059 }
1060
1061 WMR_DEBUG(wh, "JSON config:\n%s", config_json_block);
1062
1063 if (!wmr_hmd_config_parse(&wh->config, (char *)config_json_block, wh->log_level)) {
1064 free(data);
1065 return -1;
1066 }
1067
1068 free(data);
1069 return 0;
1070}
1071
1072/*
1073 *
1074 * Device members.
1075 *
1076 */
1077
1078static xrt_result_t
1079wmr_hmd_get_3dof_tracked_pose(struct xrt_device *xdev,
1080 enum xrt_input_name name,
1081 uint64_t at_timestamp_ns,
1082 struct xrt_space_relation *out_relation)
1083{
1084 DRV_TRACE_MARKER();
1085
1086 struct wmr_hmd *wh = wmr_hmd(xdev);
1087
1088 if (name != XRT_INPUT_GENERIC_HEAD_POSE) {
1089 U_LOG_XDEV_UNSUPPORTED_INPUT(&wh->base, wh->log_level, name);
1090 return XRT_ERROR_INPUT_UNSUPPORTED;
1091 }
1092
1093 // Variables needed for prediction.
1094 uint64_t last_imu_timestamp_ns = 0;
1095 struct xrt_space_relation relation = {0};
1096 relation.relation_flags = XRT_SPACE_RELATION_BITMASK_ALL;
1097 relation.pose.position = wh->pose.position;
1098 relation.linear_velocity = (struct xrt_vec3){0, 0, 0};
1099
1100 // Get data while holding the lock.
1101 os_mutex_lock(&wh->fusion.mutex);
1102 relation.pose.orientation = wh->fusion.i3dof.rot;
1103 relation.angular_velocity = wh->fusion.last_angular_velocity;
1104 last_imu_timestamp_ns = wh->fusion.last_imu_timestamp_ns;
1105 os_mutex_unlock(&wh->fusion.mutex);
1106
1107 // No prediction needed.
1108 if (at_timestamp_ns < last_imu_timestamp_ns) {
1109 *out_relation = relation;
1110 return XRT_SUCCESS;
1111 }
1112
1113 uint64_t prediction_ns = at_timestamp_ns - last_imu_timestamp_ns;
1114 double prediction_s = time_ns_to_s(prediction_ns);
1115
1116 m_predict_relation(&relation, prediction_s, out_relation);
1117 wh->pose = out_relation->pose;
1118
1119 return XRT_SUCCESS;
1120}
1121
1122//! Specific pose corrections for Basalt and a WMR headset
1123XRT_MAYBE_UNUSED static inline struct xrt_pose
1124wmr_hmd_correct_pose_from_basalt(struct xrt_pose pose)
1125{
1126 struct xrt_quat q = {0.70710678, 0, 0, 0.70710678};
1127 math_quat_rotate(&q, &pose.orientation, &pose.orientation);
1128 math_quat_rotate_vec3(&q, &pose.position, &pose.position);
1129
1130 // Correct swapped axes
1131 pose.position.y = -pose.position.y;
1132 pose.position.z = -pose.position.z;
1133 pose.orientation.y = -pose.orientation.y;
1134 pose.orientation.z = -pose.orientation.z;
1135 return pose;
1136}
1137
1138static void
1139wmr_hmd_get_slam_tracked_pose(struct xrt_device *xdev,
1140 enum xrt_input_name name,
1141 uint64_t at_timestamp_ns,
1142 struct xrt_space_relation *out_relation)
1143{
1144 DRV_TRACE_MARKER();
1145
1146 struct wmr_hmd *wh = wmr_hmd(xdev);
1147 xrt_tracked_slam_get_tracked_pose(wh->tracking.slam, at_timestamp_ns, out_relation);
1148
1149 int pose_bits = XRT_SPACE_RELATION_ORIENTATION_TRACKED_BIT | XRT_SPACE_RELATION_POSITION_TRACKED_BIT;
1150 bool pose_tracked = out_relation->relation_flags & pose_bits;
1151
1152 if (pose_tracked) {
1153#ifdef XRT_FEATURE_SLAM
1154 // !todo Correct pose depending on the VIT system in use, this should be done in the system itself.
1155 // For now, assume that we are using Basalt.
1156 wh->pose = wmr_hmd_correct_pose_from_basalt(out_relation->pose);
1157#else
1158 wh->pose = out_relation->pose;
1159#endif
1160 }
1161
1162 if (wh->tracking.imu2me) {
1163 math_pose_transform(&wh->pose, &wh->config.sensors.transforms.P_imu_me, &wh->pose);
1164 }
1165
1166 out_relation->pose = wh->pose;
1167 out_relation->relation_flags = (enum xrt_space_relation_flags)(
1168 XRT_SPACE_RELATION_ORIENTATION_VALID_BIT | XRT_SPACE_RELATION_POSITION_VALID_BIT |
1169 XRT_SPACE_RELATION_ORIENTATION_TRACKED_BIT | XRT_SPACE_RELATION_POSITION_TRACKED_BIT);
1170}
1171
1172static xrt_result_t
1173wmr_hmd_get_tracked_pose(struct xrt_device *xdev,
1174 enum xrt_input_name name,
1175 int64_t at_timestamp_ns,
1176 struct xrt_space_relation *out_relation)
1177{
1178 DRV_TRACE_MARKER();
1179
1180 struct wmr_hmd *wh = wmr_hmd(xdev);
1181
1182 at_timestamp_ns += (int64_t)(wh->tracked_offset_ms.val * (double)U_TIME_1MS_IN_NS);
1183
1184 xrt_result_t xret = XRT_SUCCESS;
1185 if (wh->tracking.slam_enabled && wh->slam_over_3dof) {
1186 wmr_hmd_get_slam_tracked_pose(xdev, name, at_timestamp_ns, out_relation);
1187 } else {
1188 xret = wmr_hmd_get_3dof_tracked_pose(xdev, name, at_timestamp_ns, out_relation);
1189 }
1190
1191 if (xret == XRT_SUCCESS) {
1192 math_pose_transform(&wh->offset, &out_relation->pose, &out_relation->pose);
1193 }
1194
1195 return xret;
1196}
1197
1198static void
1199wmr_hmd_destroy(struct xrt_device *xdev)
1200{
1201 DRV_TRACE_MARKER();
1202
1203 struct wmr_hmd *wh = wmr_hmd(xdev);
1204
1205 // Destroy the thread object.
1206 os_thread_helper_destroy(&wh->oth);
1207
1208 // Disconnect tunnelled controllers
1209 os_mutex_lock(&wh->controller_status_lock);
1210 if (wh->controller[0] != NULL) {
1211 struct wmr_controller_connection *wcc = (struct wmr_controller_connection *)wh->controller[0];
1212 wmr_controller_connection_disconnect(wcc);
1213 }
1214
1215 if (wh->controller[1] != NULL) {
1216 struct wmr_controller_connection *wcc = (struct wmr_controller_connection *)wh->controller[1];
1217 wmr_controller_connection_disconnect(wcc);
1218 }
1219 os_mutex_unlock(&wh->controller_status_lock);
1220
1221 os_mutex_destroy(&wh->controller_status_lock);
1222 os_cond_destroy(&wh->controller_status_cond);
1223
1224 if (wh->hid_hololens_sensors_dev != NULL) {
1225 os_hid_destroy(wh->hid_hololens_sensors_dev);
1226 wh->hid_hololens_sensors_dev = NULL;
1227 }
1228
1229 if (wh->hid_control_dev != NULL) {
1230 /* Do any deinit if we have a deinit function */
1231 if (wh->hmd_desc && wh->hmd_desc->deinit_func) {
1232 wh->hmd_desc->deinit_func(wh);
1233 }
1234 os_hid_destroy(wh->hid_control_dev);
1235 wh->hid_control_dev = NULL;
1236 }
1237
1238 // Destroy SLAM source and tracker
1239 xrt_frame_context_destroy_nodes(&wh->tracking.xfctx);
1240
1241 // Destroy the fusion.
1242 m_imu_3dof_close(&wh->fusion.i3dof);
1243
1244 os_mutex_destroy(&wh->fusion.mutex);
1245 os_mutex_destroy(&wh->hid_lock);
1246
1247 u_device_free(&wh->base);
1248}
1249
1250XRT_MAYBE_UNUSED static struct t_camera_calibration
1251wmr_hmd_get_cam_calib(struct wmr_hmd *wh, int cam_index)
1252{
1253 struct t_camera_calibration res;
1254 struct wmr_camera_config *wcalib = wh->config.tcams[cam_index];
1255 struct wmr_distortion_6KT *intr = &wcalib->distortion6KT;
1256
1257 res.image_size_pixels.h = wcalib->roi.extent.h;
1258 res.image_size_pixels.w = wcalib->roi.extent.w;
1259 res.intrinsics[0][0] = intr->params.fx * (double)wcalib->roi.extent.w;
1260 res.intrinsics[1][1] = intr->params.fy * (double)wcalib->roi.extent.h;
1261 res.intrinsics[0][2] = intr->params.cx * (double)wcalib->roi.extent.w;
1262 res.intrinsics[1][2] = intr->params.cy * (double)wcalib->roi.extent.h;
1263 res.intrinsics[2][2] = 1.0;
1264
1265 res.distortion_model = T_DISTORTION_WMR;
1266 res.wmr.k1 = intr->params.k[0];
1267 res.wmr.k2 = intr->params.k[1];
1268 res.wmr.p1 = intr->params.p1;
1269 res.wmr.p2 = intr->params.p2;
1270 res.wmr.k3 = intr->params.k[2];
1271 res.wmr.k4 = intr->params.k[3];
1272 res.wmr.k5 = intr->params.k[4];
1273 res.wmr.k6 = intr->params.k[5];
1274 res.wmr.codx = intr->params.dist_x;
1275 res.wmr.cody = intr->params.dist_y;
1276 res.wmr.rpmax = intr->params.metric_radius;
1277
1278 return res;
1279}
1280
1281XRT_MAYBE_UNUSED static struct xrt_vec2
1282wmr_hmd_camera_project(struct wmr_hmd *wh, struct xrt_vec3 p3d)
1283{
1284 float w = wh->config.cams[0].roi.extent.w;
1285 float h = wh->config.cams[0].roi.extent.h;
1286 float fx = wh->config.cams[0].distortion6KT.params.fx * w;
1287 float fy = wh->config.cams[0].distortion6KT.params.fy * h;
1288 float cx = wh->config.cams[0].distortion6KT.params.cx * w;
1289 float cy = wh->config.cams[0].distortion6KT.params.cy * h;
1290 float k1 = wh->config.cams[0].distortion6KT.params.k[0];
1291 float k2 = wh->config.cams[0].distortion6KT.params.k[1];
1292 float p1 = wh->config.cams[0].distortion6KT.params.p1;
1293 float p2 = wh->config.cams[0].distortion6KT.params.p2;
1294 float k3 = wh->config.cams[0].distortion6KT.params.k[2];
1295 float k4 = wh->config.cams[0].distortion6KT.params.k[3];
1296 float k5 = wh->config.cams[0].distortion6KT.params.k[4];
1297 float k6 = wh->config.cams[0].distortion6KT.params.k[5];
1298
1299 float x = p3d.x;
1300 float y = p3d.y;
1301 float z = p3d.z;
1302
1303 float xp = x / z;
1304 float yp = y / z;
1305 float rp2 = xp * xp + yp * yp;
1306 float cdist = (1 + rp2 * (k1 + rp2 * (k2 + rp2 * k3))) / (1 + rp2 * (k4 + rp2 * (k5 + rp2 * k6)));
1307#if 0 // OpenCV model
1308 float deltaX = 2 * p1 * xp * yp + p2 * (rp2 + 2 * xp * xp);
1309 float deltaY = 2 * p2 * xp * yp + p1 * (rp2 + 2 * yp * yp);
1310#else // Azure Kinect model (see comment in wmr_hmd_create_stereo_camera_calib)
1311 float deltaX = p1 * xp * yp + p2 * (rp2 + 2 * xp * xp);
1312 float deltaY = p2 * xp * yp + p1 * (rp2 + 2 * yp * yp);
1313#endif
1314 float xpp = xp * cdist + deltaX;
1315 float ypp = yp * cdist + deltaY;
1316 float u = fx * xpp + cx;
1317 float v = fy * ypp + cy;
1318
1319 struct xrt_vec2 p2d = {u, v};
1320 return p2d;
1321}
1322
1323
1324/*!
1325 * Creates an OpenCV-compatible @ref t_stereo_camera_calibration pointer from
1326 * the WMR config.
1327 *
1328 * Note that the camera model used on WMR headsets seems to be the same as the
1329 * one in Azure-Kinect-Sensor-SDK. That model is slightly different than
1330 * OpenCV's in the following ways:
1331 * 1. There are "center of distortion", codx and cody, parameters
1332 * 2. The terms that use the tangential parameters, p1 and p2, aren't multiplied by 2
1333 * 3. There is a "metric radius" that delimits a valid area of distortion/undistortion
1334 *
1335 * Thankfully, parameters of points 1 and 2 tend to be almost zero in practice. For 3, we place metric_radius into
1336 * the calibration struct so that downstream tracking algorithms can use it as needed.
1337 */
1338XRT_MAYBE_UNUSED static struct t_stereo_camera_calibration *
1339wmr_hmd_create_stereo_camera_calib(struct wmr_hmd *wh)
1340{
1341 struct t_stereo_camera_calibration *calib = NULL;
1342 t_stereo_camera_calibration_alloc(&calib, T_DISTORTION_WMR);
1343
1344
1345 // Intrinsics
1346 for (int i = 0; i < 2; i++) {
1347 calib->view[i] = wmr_hmd_get_cam_calib(wh, i);
1348 }
1349
1350 // Extrinsics
1351
1352 // Compute transform from HT1 to HT0 (HT0 space into HT1 space)
1353 struct wmr_camera_config *ht1 = &wh->config.cams[1];
1354 calib->camera_translation[0] = ht1->translation.x;
1355 calib->camera_translation[1] = ht1->translation.y;
1356 calib->camera_translation[2] = ht1->translation.z;
1357 calib->camera_rotation[0][0] = ht1->rotation.v[0];
1358 calib->camera_rotation[0][1] = ht1->rotation.v[1];
1359 calib->camera_rotation[0][2] = ht1->rotation.v[2];
1360 calib->camera_rotation[1][0] = ht1->rotation.v[3];
1361 calib->camera_rotation[1][1] = ht1->rotation.v[4];
1362 calib->camera_rotation[1][2] = ht1->rotation.v[5];
1363 calib->camera_rotation[2][0] = ht1->rotation.v[6];
1364 calib->camera_rotation[2][1] = ht1->rotation.v[7];
1365 calib->camera_rotation[2][2] = ht1->rotation.v[8];
1366
1367 return calib;
1368}
1369
1370//! Extended camera calibration info for SLAM
1371XRT_MAYBE_UNUSED static void
1372wmr_hmd_fill_slam_cams_calibration(struct wmr_hmd *wh)
1373{
1374 wh->tracking.slam_calib.cam_count = wh->config.tcam_count;
1375
1376 // Fill camera 0
1377 struct xrt_pose P_imu_c0 = wh->config.sensors.accel.pose;
1378 struct xrt_matrix_4x4 T_imu_c0;
1379 math_matrix_4x4_isometry_from_pose(&P_imu_c0, &T_imu_c0);
1380 wh->tracking.slam_calib.cams[0] = (struct t_slam_camera_calibration){
1381 .base = wmr_hmd_get_cam_calib(wh, 0),
1382 .T_imu_cam = T_imu_c0,
1383 .frequency = CAMERA_FREQUENCY,
1384 };
1385
1386 // Fill remaining cameras
1387 for (int i = 1; i < wh->config.tcam_count; i++) {
1388 struct xrt_pose P_ci_c0 = wh->config.tcams[i]->pose;
1389
1390 if (i == 2 || i == 3) {
1391 //! @note The calibration json for the reverb G2v2 (the only 4-camera wmr
1392 //! headset we know about) has the HT2 and HT3 extrinsics flipped compared
1393 //! to the order the third and fourth camera images come from usb.
1394 P_ci_c0 = wh->config.tcams[i == 2 ? 3 : 2]->pose;
1395 }
1396
1397 struct xrt_pose P_c0_ci;
1398 math_pose_invert(&P_ci_c0, &P_c0_ci);
1399
1400 struct xrt_pose P_imu_ci;
1401 math_pose_transform(&P_imu_c0, &P_c0_ci, &P_imu_ci);
1402
1403 struct xrt_matrix_4x4 T_imu_ci;
1404 math_matrix_4x4_isometry_from_pose(&P_imu_ci, &T_imu_ci);
1405
1406 wh->tracking.slam_calib.cams[i] = (struct t_slam_camera_calibration){
1407 .base = wmr_hmd_get_cam_calib(wh, i),
1408 .T_imu_cam = T_imu_ci,
1409 .frequency = CAMERA_FREQUENCY,
1410 };
1411 }
1412}
1413
1414XRT_MAYBE_UNUSED static struct t_imu_calibration
1415wmr_hmd_get_imu_calib(struct wmr_hmd *wh)
1416{
1417 float *at = wh->config.sensors.accel.mix_matrix.v;
1418 struct xrt_vec3 ao = wh->config.sensors.accel.bias_offsets;
1419 struct xrt_vec3 ab = wh->config.sensors.accel.bias_var;
1420 struct xrt_vec3 an = wh->config.sensors.accel.noise_std;
1421
1422 float *gt = wh->config.sensors.gyro.mix_matrix.v;
1423 struct xrt_vec3 go = wh->config.sensors.gyro.bias_offsets;
1424 struct xrt_vec3 gb = wh->config.sensors.gyro.bias_var;
1425 struct xrt_vec3 gn = wh->config.sensors.gyro.noise_std;
1426
1427 struct t_imu_calibration calib = {
1428 .accel =
1429 {
1430 .transform = {{at[0], at[1], at[2]}, {at[3], at[4], at[5]}, {at[6], at[7], at[8]}},
1431 .offset = {-ao.x, -ao.y, -ao.z}, // negative because slam system will add, not subtract
1432 .bias_std = {sqrt(ab.x), sqrt(ab.y), sqrt(ab.z)}, // sqrt because we want stdev not variance
1433 .noise_std = {an.x, an.y, an.z},
1434 },
1435 .gyro =
1436 {
1437 .transform = {{gt[0], gt[1], gt[2]}, {gt[3], gt[4], gt[5]}, {gt[6], gt[7], gt[8]}},
1438 .offset = {-go.x, -go.y, -go.z},
1439 .bias_std = {sqrt(gb.x), sqrt(gb.y), sqrt(gb.z)},
1440 .noise_std = {gn.x, gn.y, gn.z},
1441 },
1442 };
1443 return calib;
1444}
1445
1446//! Extended IMU calibration data for SLAM
1447XRT_MAYBE_UNUSED static void
1448wmr_hmd_fill_slam_imu_calibration(struct wmr_hmd *wh)
1449{
1450 //! @note `average_imus` might change during runtime but the calibration data will be already submitted
1451 double imu_frequency = wh->average_imus ? IMU_FREQUENCY / IMU_SAMPLES_PER_PACKET : IMU_FREQUENCY;
1452
1453 struct t_slam_imu_calibration imu_calib = {
1454 .base = wmr_hmd_get_imu_calib(wh),
1455 .frequency = imu_frequency,
1456 };
1457
1458 wh->tracking.slam_calib.imu = imu_calib;
1459}
1460
1461XRT_MAYBE_UNUSED static void
1462wmr_hmd_fill_slam_calibration(struct wmr_hmd *wh)
1463{
1464 wmr_hmd_fill_slam_imu_calibration(wh);
1465 wmr_hmd_fill_slam_cams_calibration(wh);
1466}
1467
1468static void
1469wmr_hmd_switch_hmd_tracker(void *wh_ptr)
1470{
1471 DRV_TRACE_MARKER();
1472
1473 struct wmr_hmd *wh = (struct wmr_hmd *)wh_ptr;
1474 wh->slam_over_3dof = !wh->slam_over_3dof;
1475 struct u_var_button *btn = &wh->gui.switch_tracker_btn;
1476
1477 if (wh->slam_over_3dof) { // Use SLAM
1478 snprintf(btn->label, sizeof(btn->label), "Switch to 3DoF Tracking");
1479 } else { // Use 3DoF
1480 snprintf(btn->label, sizeof(btn->label), "Switch to SLAM Tracking");
1481 os_mutex_lock(&wh->fusion.mutex);
1482 m_imu_3dof_reset(&wh->fusion.i3dof);
1483 wh->fusion.i3dof.rot = wh->pose.orientation;
1484 os_mutex_unlock(&wh->fusion.mutex);
1485 }
1486}
1487
1488static struct xrt_slam_sinks *
1489wmr_hmd_slam_track(struct wmr_hmd *wh)
1490{
1491 DRV_TRACE_MARKER();
1492
1493 struct xrt_slam_sinks *sinks = NULL;
1494
1495#ifdef XRT_FEATURE_SLAM
1496 struct t_slam_tracker_config config = {0};
1497 t_slam_fill_default_config(&config);
1498 config.cam_count = wh->config.slam_cam_count;
1499 wh->tracking.slam_calib.cam_count = wh->config.slam_cam_count;
1500 config.slam_calib = &wh->tracking.slam_calib;
1501 if (debug_get_option_slam_submit_from_start() == NULL) {
1502 config.submit_from_start = true;
1503 }
1504
1505 int create_status = t_slam_create(&wh->tracking.xfctx, &config, &wh->tracking.slam, &sinks);
1506 if (create_status != 0) {
1507 return NULL;
1508 }
1509
1510 int start_status = t_slam_start(wh->tracking.slam);
1511 if (start_status != 0) {
1512 return NULL;
1513 }
1514
1515 WMR_DEBUG(wh, "WMR HMD SLAM tracker successfully started");
1516#endif
1517
1518 return sinks;
1519}
1520
1521#ifdef XRT_BUILD_DRIVER_HANDTRACKING
1522static enum t_camera_orientation
1523wmr_hmd_guess_camera_orientation(struct wmr_hmd *wh)
1524{
1525 struct xrt_quat Q_ht0_me = wh->config.sensors.transforms.P_ht0_me.orientation;
1526 struct xrt_vec2 swing = {0};
1527 float twist = 0;
1528 math_quat_to_swing_twist(&Q_ht0_me, &swing, &twist);
1529 WMR_DEBUG(wh, "HT0 twist value is %f", twist);
1530
1531 float abstwist = fabsf(twist);
1532
1533 // Bottom quadrant
1534 if (abstwist < M_PI / 4) {
1535 WMR_DEBUG(wh, "I think this headset has CAMERA_ORIENTATION_0 front cameras!");
1536 return CAMERA_ORIENTATION_0;
1537 }
1538
1539 // Top quadrant
1540 if (abstwist > 3 * M_PI / 4) {
1541 WMR_DEBUG(wh, "I think this headset has CAMERA_ORIENTATION_180 front cameras!");
1542 return CAMERA_ORIENTATION_180;
1543 }
1544
1545 // Right quadrant
1546 if (twist < 0) {
1547 WMR_DEBUG(wh, "I think this headset has CAMERA_ORIENTATION_90 front cameras!");
1548 return CAMERA_ORIENTATION_90;
1549 }
1550
1551 // Left quadrant
1552 WMR_DEBUG(wh, "I think this headset has CAMERA_ORIENTATION_270 front cameras!");
1553 return CAMERA_ORIENTATION_270;
1554}
1555#endif
1556
1557static int
1558wmr_hmd_hand_track(struct wmr_hmd *wh,
1559 struct t_stereo_camera_calibration *stereo_calib,
1560 struct xrt_hand_masks_sink *masks_sink,
1561 struct xrt_slam_sinks **out_sinks,
1562 struct xrt_device **out_device)
1563{
1564 DRV_TRACE_MARKER();
1565
1566 struct xrt_slam_sinks *sinks = NULL;
1567 struct xrt_device *device = NULL;
1568
1569#ifdef XRT_BUILD_DRIVER_HANDTRACKING
1570
1571 struct t_camera_extra_info extra_camera_info = {0};
1572
1573 enum t_camera_orientation ori_guess = CAMERA_ORIENTATION_0;
1574
1575 if (wh->hmd_desc->hmd_type == WMR_HEADSET_GENERIC || //
1576 wh->hmd_desc->hmd_type == WMR_HEADSET_REVERB_G2) {
1577 ori_guess = wmr_hmd_guess_camera_orientation(wh);
1578 }
1579
1580 for (int i = 0; i < 2; i++) {
1581 extra_camera_info.views[i].camera_orientation = ori_guess;
1582 extra_camera_info.views[i].boundary_type = HT_IMAGE_BOUNDARY_CIRCLE;
1583 float w = wh->config.cams[i].roi.extent.w;
1584 float h = wh->config.cams[i].roi.extent.h;
1585 float cx = wh->config.cams[i].distortion6KT.params.cx * w;
1586 float cy = wh->config.cams[i].distortion6KT.params.cy * h;
1587 float rpmax = wh->config.cams[i].distortion6KT.params.metric_radius;
1588 struct xrt_vec3 p3d = {rpmax, 0, 1}; // Right-most border of the metric_radius circle in the Z=1 plane
1589 struct xrt_vec2 p2d = wmr_hmd_camera_project(wh, p3d);
1590 float radius = (p2d.x - cx) / w;
1591 extra_camera_info.views[i].boundary.circle.normalized_center = (struct xrt_vec2){cx / w, cy / h};
1592 extra_camera_info.views[i].boundary.circle.normalized_radius = radius;
1593 }
1594
1595 struct t_hand_tracking_create_info create_info = {.cams_info = extra_camera_info, .masks_sink = masks_sink};
1596
1597 int create_status = ht_device_create(&wh->tracking.xfctx, //
1598 stereo_calib, //
1599 create_info, //
1600 &sinks, //
1601 &device);
1602 if (create_status != 0) {
1603 return create_status;
1604 }
1605
1606 device = multi_create_tracking_override(XRT_TRACKING_OVERRIDE_ATTACHED, device, &wh->base,
1607 XRT_INPUT_GENERIC_HEAD_POSE, &wh->config.sensors.transforms.P_ht0_me);
1608
1609 WMR_DEBUG(wh, "WMR HMD hand tracker successfully created");
1610#endif
1611
1612 *out_sinks = sinks;
1613 *out_device = device;
1614
1615 return 0;
1616}
1617
1618static void
1619wmr_hmd_setup_ui(struct wmr_hmd *wh)
1620{
1621 u_var_add_root(wh, "WMR HMD", true);
1622
1623 u_var_add_gui_header(wh, NULL, "Tracking");
1624 if (wh->tracking.slam_enabled) {
1625 wh->gui.switch_tracker_btn.cb = wmr_hmd_switch_hmd_tracker;
1626 wh->gui.switch_tracker_btn.ptr = wh;
1627 u_var_add_button(wh, &wh->gui.switch_tracker_btn, "Switch to 3DoF Tracking");
1628 }
1629 u_var_add_pose(wh, &wh->pose, "Tracked Pose");
1630 u_var_add_pose(wh, &wh->offset, "Pose Offset");
1631 u_var_add_bool(wh, &wh->average_imus, "Average IMU samples");
1632 u_var_add_draggable_f32(wh, &wh->tracked_offset_ms, "Timecode offset(ms)");
1633
1634 u_var_add_gui_header(wh, NULL, "3DoF Tracking");
1635 m_imu_3dof_add_vars(&wh->fusion.i3dof, wh, "");
1636
1637 u_var_add_gui_header(wh, NULL, "SLAM Tracking");
1638 u_var_add_ro_text(wh, wh->gui.slam_status, "Tracker status");
1639 u_var_add_bool(wh, &wh->tracking.imu2me, "Correct IMU pose to middle of eyes");
1640
1641 u_var_add_gui_header(wh, NULL, "Hand Tracking");
1642 u_var_add_ro_text(wh, wh->gui.hand_status, "Tracker status");
1643
1644 u_var_add_gui_header(wh, NULL, "Hololens Sensors' Companion device");
1645 u_var_add_u8(wh, &wh->proximity_sensor, "HMD Proximity");
1646 u_var_add_u16(wh, &wh->raw_ipd, "HMD IPD");
1647
1648 if (wh->hmd_desc->screen_enable_func) {
1649 // Enabling/disabling the HMD screen at runtime is supported. Add button to debug GUI.
1650 wh->gui.hmd_screen_enable_btn.cb = wmr_hmd_screen_enable_toggle;
1651 wh->gui.hmd_screen_enable_btn.ptr = wh;
1652 u_var_add_button(wh, &wh->gui.hmd_screen_enable_btn, "HMD Screen [On/Off]");
1653 }
1654
1655 u_var_add_gui_header(wh, NULL, "Misc");
1656 u_var_add_log_level(wh, &wh->log_level, "log_level");
1657}
1658
1659/*!
1660 * Procedure to setup trackers: 3dof, SLAM and hand tracking.
1661 *
1662 * Determines which trackers to initialize and starts them.
1663 * Fills @p out_sinks to stream raw data to for tracking.
1664 * In the case of hand tracking being enabled, it returns a hand tracker device in @p out_handtracker.
1665 *
1666 * @param wh the wmr headset device
1667 * @param out_sinks sinks to stream video/IMU data to for tracking
1668 * @param out_handtracker a newly created hand tracker device
1669 * @return true on success, false when an unexpected state is reached.
1670 */
1671static bool
1672wmr_hmd_setup_trackers(struct wmr_hmd *wh, struct xrt_slam_sinks *out_sinks, struct xrt_device **out_handtracker)
1673{
1674 // We always have at least 3dof HMD tracking
1675 bool dof3_enabled = true;
1676
1677 // Decide whether to initialize the SLAM tracker
1678 bool slam_wanted = debug_get_bool_option_wmr_slam();
1679#ifdef XRT_FEATURE_SLAM
1680 bool slam_supported = true;
1681#else
1682 bool slam_supported = false;
1683#endif
1684 bool slam_enabled = slam_supported && slam_wanted;
1685
1686 // Decide whether to initialize the hand tracker
1687 bool hand_wanted = debug_get_bool_option_wmr_handtracking();
1688#ifdef XRT_BUILD_DRIVER_HANDTRACKING
1689 bool hand_supported = true;
1690#else
1691 bool hand_supported = false;
1692#endif
1693 bool hand_enabled = hand_supported && hand_wanted;
1694
1695 wh->base.supported.orientation_tracking = dof3_enabled || slam_enabled;
1696 wh->base.supported.position_tracking = slam_enabled;
1697 wh->base.supported.hand_tracking = false; // out_handtracker will handle it
1698
1699 wh->tracking.slam_enabled = slam_enabled;
1700 wh->tracking.hand_enabled = hand_enabled;
1701 wh->tracking.imu2me = true;
1702
1703 wh->slam_over_3dof = slam_enabled; // We prefer SLAM over 3dof tracking if possible
1704
1705 const char *slam_status = wh->tracking.slam_enabled ? "Enabled"
1706 : !slam_wanted ? "Disabled by the user (envvar set to false)"
1707 : !slam_supported ? "Unavailable (not built)"
1708 : NULL;
1709
1710 const char *hand_status = wh->tracking.hand_enabled ? "Enabled"
1711 : !hand_wanted ? "Disabled by the user (envvar set to false)"
1712 : !hand_supported ? "Unavailable (not built)"
1713 : NULL;
1714
1715 assert(slam_status != NULL && hand_status != NULL);
1716
1717 (void)snprintf(wh->gui.slam_status, sizeof(wh->gui.slam_status), "%s", slam_status);
1718 (void)snprintf(wh->gui.hand_status, sizeof(wh->gui.hand_status), "%s", hand_status);
1719
1720 struct t_stereo_camera_calibration *stereo_calib = wmr_hmd_create_stereo_camera_calib(wh);
1721 wmr_hmd_fill_slam_calibration(wh);
1722
1723 // Initialize 3DoF tracker
1724 m_imu_3dof_init(&wh->fusion.i3dof, M_IMU_3DOF_USE_GRAVITY_DUR_20MS);
1725
1726 // Initialize SLAM tracker
1727 struct xrt_slam_sinks *slam_sinks = NULL;
1728 if (wh->tracking.slam_enabled) {
1729 slam_sinks = wmr_hmd_slam_track(wh);
1730 if (slam_sinks == NULL) {
1731 WMR_WARN(wh, "Unable to setup the SLAM tracker");
1732 return false;
1733 }
1734 }
1735
1736 // Initialize hand tracker
1737 struct xrt_slam_sinks *hand_sinks = NULL;
1738 struct xrt_device *hand_device = NULL;
1739 struct xrt_hand_masks_sink *masks_sink = slam_sinks ? slam_sinks->hand_masks : NULL;
1740 if (wh->tracking.hand_enabled) {
1741 int hand_status = wmr_hmd_hand_track(wh, stereo_calib, masks_sink, &hand_sinks, &hand_device);
1742 if (hand_status != 0 || hand_sinks == NULL || hand_device == NULL) {
1743 WMR_WARN(wh, "Unable to setup the hand tracker");
1744 return false;
1745 }
1746 }
1747
1748 t_stereo_camera_calibration_reference(&stereo_calib, NULL);
1749
1750 // Setup sinks depending on tracking configuration
1751 struct xrt_slam_sinks entry_sinks = {0};
1752 if (slam_enabled && hand_enabled) {
1753 struct xrt_frame_sink *entry_cam0_sink = NULL;
1754 struct xrt_frame_sink *entry_cam1_sink = NULL;
1755
1756 u_sink_split_create(&wh->tracking.xfctx, slam_sinks->cams[0], hand_sinks->cams[0], &entry_cam0_sink);
1757 u_sink_split_create(&wh->tracking.xfctx, slam_sinks->cams[1], hand_sinks->cams[1], &entry_cam1_sink);
1758
1759 entry_sinks = *slam_sinks;
1760 entry_sinks.cams[0] = entry_cam0_sink;
1761 entry_sinks.cams[1] = entry_cam1_sink;
1762 } else if (slam_enabled) {
1763 entry_sinks = *slam_sinks;
1764 } else if (hand_enabled) {
1765 entry_sinks = *hand_sinks;
1766 } else {
1767 entry_sinks = (struct xrt_slam_sinks){0};
1768 }
1769
1770 *out_sinks = entry_sinks;
1771 *out_handtracker = hand_device;
1772 return true;
1773}
1774
1775static bool
1776wmr_hmd_request_controller_status(struct wmr_hmd *wh)
1777{
1778 DRV_TRACE_MARKER();
1779 unsigned char cmd[64] = {WMR_MS_HOLOLENS_MSG_BT_CONTROL, WMR_MS_HOLOLENS_MSG_CONTROLLER_STATUS};
1780 return wmr_hmd_send_controller_packet(wh, cmd, sizeof(cmd));
1781}
1782
1783static xrt_result_t
1784compute_distortion_wmr(struct xrt_device *xdev, uint32_t view, float u, float v, struct xrt_uv_triplet *out_result)
1785{
1786 struct wmr_hmd *wh = wmr_hmd(xdev);
1787
1788 u_compute_distortion_poly_3k(&wh->config.eye_params[view].poly_3k, view, u, v, out_result);
1789
1790 return XRT_SUCCESS;
1791}
1792
1793void
1794wmr_hmd_create(enum wmr_headset_type hmd_type,
1795 struct os_hid_device *hid_holo,
1796 struct os_hid_device *hid_ctrl,
1797 struct xrt_prober_device *dev_holo,
1798 enum u_logging_level log_level,
1799 struct xrt_device **out_hmd,
1800 struct xrt_device **out_handtracker,
1801 struct xrt_device **out_left_controller,
1802 struct xrt_device **out_right_controller)
1803{
1804 DRV_TRACE_MARKER();
1805
1806 enum u_device_alloc_flags flags =
1807 (enum u_device_alloc_flags)(U_DEVICE_ALLOC_HMD | U_DEVICE_ALLOC_TRACKING_NONE);
1808 int ret = 0;
1809 int i;
1810 int eye;
1811
1812 struct wmr_hmd *wh = U_DEVICE_ALLOCATE(struct wmr_hmd, flags, 1, 0);
1813 if (!wh) {
1814 return;
1815 }
1816
1817 // Populate the base members.
1818 wh->base.update_inputs = u_device_noop_update_inputs;
1819 wh->base.get_tracked_pose = wmr_hmd_get_tracked_pose;
1820 wh->base.get_view_poses = u_device_get_view_poses;
1821 wh->base.destroy = wmr_hmd_destroy;
1822 wh->base.name = XRT_DEVICE_GENERIC_HMD;
1823 wh->base.device_type = XRT_DEVICE_TYPE_HMD;
1824 wh->log_level = log_level;
1825
1826 wh->hid_hololens_sensors_dev = hid_holo;
1827 wh->hid_control_dev = hid_ctrl;
1828
1829 // Mutex before thread.
1830 ret = os_mutex_init(&wh->fusion.mutex);
1831 if (ret != 0) {
1832 WMR_ERROR(wh, "Failed to init fusion mutex!");
1833 wmr_hmd_destroy(&wh->base);
1834 wh = NULL;
1835 return;
1836 }
1837
1838 ret = os_mutex_init(&wh->hid_lock);
1839 if (ret != 0) {
1840 WMR_ERROR(wh, "Failed to init HID mutex!");
1841 wmr_hmd_destroy(&wh->base);
1842 wh = NULL;
1843 return;
1844 }
1845
1846 ret = os_mutex_init(&wh->controller_status_lock);
1847 if (ret != 0) {
1848 WMR_ERROR(wh, "Failed to init Controller status mutex!");
1849 wmr_hmd_destroy(&wh->base);
1850 wh = NULL;
1851 return;
1852 }
1853
1854 ret = os_cond_init(&wh->controller_status_cond);
1855 if (ret != 0) {
1856 WMR_ERROR(wh, "Failed to init Controller status cond!");
1857 wmr_hmd_destroy(&wh->base);
1858 wh = NULL;
1859 return;
1860 }
1861
1862 // Thread and other state.
1863 ret = os_thread_helper_init(&wh->oth);
1864 if (ret != 0) {
1865 WMR_ERROR(wh, "Failed to init threading!");
1866 wmr_hmd_destroy(&wh->base);
1867 wh = NULL;
1868 return;
1869 }
1870
1871 // Setup input.
1872 wh->base.inputs[0].name = XRT_INPUT_GENERIC_HEAD_POSE;
1873
1874 // Read config file from HMD
1875 if (wmr_read_config(wh) < 0) {
1876 WMR_ERROR(wh, "Failed to load headset configuration!");
1877 wmr_hmd_destroy(&wh->base);
1878 wh = NULL;
1879 return;
1880 }
1881
1882 wh->pose = (struct xrt_pose)XRT_POSE_IDENTITY;
1883 wh->offset = (struct xrt_pose)XRT_POSE_IDENTITY;
1884 wh->average_imus = true;
1885 wh->tracked_offset_ms = (struct u_var_draggable_f32){
1886 .val = 0.0,
1887 .min = -40.0,
1888 .step = 0.1,
1889 .max = +120.0,
1890 };
1891
1892 /* Now that we have the config loaded, iterate the map of known headsets and see if we have
1893 * an entry for this specific headset (otherwise the generic entry will be used)
1894 */
1895 for (i = 0; i < headset_map_n; i++) {
1896 const struct wmr_headset_descriptor *cur = &headset_map[i];
1897
1898 if (hmd_type == cur->hmd_type) {
1899 wh->hmd_desc = cur;
1900 if (hmd_type != WMR_HEADSET_GENERIC)
1901 break; /* Stop checking if we have a specific match, or keep going for the GENERIC
1902 catch-all type */
1903 }
1904
1905 if (cur->dev_id_str && strncmp(wh->config_hdr.name, cur->dev_id_str, 64) == 0) {
1906 hmd_type = cur->hmd_type;
1907 wh->hmd_desc = cur;
1908 break;
1909 }
1910 }
1911 assert(wh->hmd_desc != NULL); /* Each supported device MUST have a manually created entry in our headset_map */
1912
1913 WMR_INFO(wh, "Found WMR headset type: %s", wh->hmd_desc->debug_name);
1914
1915 wmr_config_precompute_transforms(&wh->config.sensors, wh->config.eye_params);
1916
1917 struct u_extents_2d exts;
1918 exts.w_pixels = (uint32_t)wh->config.eye_params[0].display_size.x;
1919 exts.h_pixels = (uint32_t)wh->config.eye_params[0].display_size.y;
1920 u_extents_2d_split_side_by_side(&wh->base, &exts);
1921
1922 // Fill in blend mode - just opqaue, unless we get Hololens support one day.
1923 size_t idx = 0;
1924 wh->base.hmd->blend_modes[idx++] = XRT_BLEND_MODE_OPAQUE;
1925 wh->base.hmd->blend_mode_count = idx;
1926
1927 wh->config.eye_params[0].poly_3k.y_offset = debug_get_num_option_left_view_y_offset();
1928 wh->config.eye_params[1].poly_3k.y_offset = debug_get_num_option_right_view_y_offset();
1929
1930 // Distortion information, fills in xdev->compute_distortion().
1931 for (eye = 0; eye < 2; eye++) {
1932 struct xrt_fov *fov = &wh->base.hmd->distortion.fov[eye];
1933 struct u_poly_3k_eye_values *poly_3k = &wh->config.eye_params[eye].poly_3k;
1934
1935 u_compute_distortion_bounds_poly_3k(&poly_3k->inv_affine_xform, poly_3k->channels, eye, fov,
1936 &poly_3k->tex_x_range, &poly_3k->tex_y_range);
1937
1938 WMR_INFO(wh, "FoV eye %d angles left %f right %f down %f up %f", eye, fov->angle_left, fov->angle_right,
1939 fov->angle_down, fov->angle_up);
1940
1941 WMR_INFO(wh, "Render texture range %f, %f to %f, %f", poly_3k->tex_x_range.x, poly_3k->tex_y_range.x,
1942 poly_3k->tex_x_range.y, poly_3k->tex_y_range.y);
1943 }
1944
1945 wh->base.hmd->distortion.models = XRT_DISTORTION_MODEL_COMPUTE;
1946 wh->base.hmd->distortion.preferred = XRT_DISTORTION_MODEL_COMPUTE;
1947 wh->base.compute_distortion = compute_distortion_wmr;
1948 u_distortion_mesh_fill_in_compute(&wh->base);
1949
1950 // Set HMD Scanout direction and time
1951 if (wh->hmd_desc->hmd_type == WMR_HEADSET_SAMSUNG_800ZAA ||
1952 wh->hmd_desc->hmd_type == WMR_HEADSET_SAMSUNG_XE700X3AI) {
1953
1954 wh->base.hmd->screens[0].scanout_direction = XRT_SCANOUT_DIRECTION_TOP_TO_BOTTOM;
1955 wh->base.hmd->screens[0].scanout_time_ns =
1956 wh->base.hmd->screens[0].nominal_frame_interval_ns * 1600.0 / 1624.0;
1957 } else {
1958 wh->base.hmd->screens[0].scanout_direction = XRT_SCANOUT_DIRECTION_NONE;
1959 wh->base.hmd->screens[0].scanout_time_ns = 0;
1960 }
1961
1962 // Set initial HMD screen power state.
1963 wh->hmd_screen_enable = true;
1964
1965 /* We're set up. Activate the HMD and turn on the IMU */
1966 if (wh->hmd_desc->init_func && wh->hmd_desc->init_func(wh) != 0) {
1967 WMR_ERROR(wh, "Activation of HMD failed");
1968 wmr_hmd_destroy(&wh->base);
1969 wh = NULL;
1970 return;
1971 }
1972
1973 // Switch on IMU on the HMD.
1974 hololens_sensors_enable_imu(wh);
1975
1976 // Switch on data streams on the HMD (only cameras for now as IMU is not yet integrated into wmr_source)
1977 wh->tracking.source = wmr_source_create(&wh->tracking.xfctx, dev_holo, wh->config);
1978
1979 struct xrt_slam_sinks sinks = {0};
1980 struct xrt_device *hand_device = NULL;
1981 bool success = wmr_hmd_setup_trackers(wh, &sinks, &hand_device);
1982 if (!success) {
1983 wmr_hmd_destroy(&wh->base);
1984 wh = NULL;
1985 return;
1986 }
1987
1988 // Stream data source into sinks (if populated)
1989 bool stream_started = xrt_fs_slam_stream_start(wh->tracking.source, &sinks);
1990 if (!stream_started) {
1991 //! @todo Could reach this due to !XRT_HAVE_LIBUSB but the HMD should keep working
1992 WMR_WARN(wh, "Failed to start WMR source");
1993 wmr_hmd_destroy(&wh->base);
1994 wh = NULL;
1995 return;
1996 }
1997
1998 // Hand over hololens sensor device to reading thread.
1999 ret = os_thread_helper_start(&wh->oth, wmr_run_thread, wh);
2000 if (ret != 0) {
2001 WMR_ERROR(wh, "Failed to start thread!");
2002 wmr_hmd_destroy(&wh->base);
2003 wh = NULL;
2004 return;
2005 }
2006
2007 /* Send controller status request to check for online controllers
2008 * and wait 250ms for the reports for Reverb G2 and Odyssey+ */
2009 if (wh->hmd_desc->hmd_type == WMR_HEADSET_REVERB_G2 || wh->hmd_desc->hmd_type == WMR_HEADSET_SAMSUNG_800ZAA) {
2010 bool have_controller_status = false;
2011
2012 os_mutex_lock(&wh->controller_status_lock);
2013 if (wmr_hmd_request_controller_status(wh)) {
2014 /* @todo: Add a timed version of os_cond_wait and a timeout? */
2015 /* This will be signalled from the reader thread */
2016 while (!wh->have_left_controller_status && !wh->have_right_controller_status) {
2017 os_cond_wait(&wh->controller_status_cond, &wh->controller_status_lock);
2018 }
2019 have_controller_status = true;
2020 }
2021 os_mutex_unlock(&wh->controller_status_lock);
2022
2023 if (!have_controller_status) {
2024 WMR_WARN(wh, "Failed to request controller status from HMD");
2025 }
2026 }
2027
2028 wmr_hmd_setup_ui(wh);
2029
2030 *out_hmd = &wh->base;
2031 *out_handtracker = hand_device;
2032
2033 os_mutex_lock(&wh->controller_status_lock);
2034 if (wh->controller[0] != NULL) {
2035 *out_left_controller = wmr_hmd_controller_connection_get_controller(wh->controller[0]);
2036 } else {
2037 *out_left_controller = NULL;
2038 }
2039
2040 if (wh->controller[1] != NULL) {
2041 *out_right_controller = wmr_hmd_controller_connection_get_controller(wh->controller[1]);
2042 } else {
2043 *out_right_controller = NULL;
2044 }
2045 os_mutex_unlock(&wh->controller_status_lock);
2046}
2047
2048bool
2049wmr_hmd_send_controller_packet(struct wmr_hmd *hmd, const uint8_t *buffer, uint32_t buf_size)
2050{
2051 os_mutex_lock(&hmd->hid_lock);
2052 int ret = os_hid_write(hmd->hid_hololens_sensors_dev, buffer, buf_size);
2053 os_mutex_unlock(&hmd->hid_lock);
2054
2055 return ret != -1 && (uint32_t)(ret) == buf_size;
2056}
2057
2058/* Called from WMR controller implementation only during fw reads. @todo: Refactor
2059 * controller firmware reads to happen from a state machine and not require this blocking method */
2060int
2061wmr_hmd_read_sync_from_controller(struct wmr_hmd *hmd, uint8_t *buffer, uint32_t buf_size, int timeout_ms)
2062{
2063 os_mutex_lock(&hmd->hid_lock);
2064 int res = os_hid_read(hmd->hid_hololens_sensors_dev, buffer, buf_size, timeout_ms);
2065 os_mutex_unlock(&hmd->hid_lock);
2066
2067 return res;
2068}