// Copyright 2016-2019, Philipp Zabel // Copyright 2020, Collabora, Ltd. // SPDX-License-Identifier: BSL-1.0 /*! * @file * @brief Vive Lighthouse Watchman implementation * @author Lubosz Sarnecki * @ingroup drv_vive */ // IWYU pragma: no_include // IWYU pragma: no_include #include #include #include #include #include "math/m_api.h" #include "xrt/xrt_byte_order.h" #include "util/u_debug.h" #include "util/u_logging.h" #include "vive_lighthouse.h" static enum u_logging_level log_level; #define LH_TRACE(...) U_LOG_IFL_T(log_level, __VA_ARGS__) #define LH_DEBUG(...) U_LOG_IFL_D(log_level, __VA_ARGS__) #define LH_INFO(...) U_LOG_IFL_I(log_level, __VA_ARGS__) #define LH_WARN(...) U_LOG_IFL_W(log_level, __VA_ARGS__) #define LH_ERROR(...) U_LOG_IFL_E(log_level, __VA_ARGS__) DEBUG_GET_ONCE_LOG_OPTION(vive_log, "VIVE_LOG", U_LOGGING_WARN) struct lighthouse_ootx_report { __le16 version; __le32 serial; __le16 phase[2]; __le16 tilt[2]; __u8 reset_count; __u8 model_id; __le16 curve[2]; __s8 gravity[3]; __le16 gibphase[2]; __le16 gibmag[2]; } __attribute__((packed)); static unsigned int watchman_id; float _f16_to_float(uint16_t f16) { unsigned int sign = f16 >> 15; unsigned int exponent = (f16 >> 10) & 0x1f; unsigned int mantissa = f16 & 0x3ff; union { float f32; uint32_t u32; } u; if (exponent == 0) { if (!mantissa) { /* zero */ u.u32 = sign << 31; } else { /* subnormal */ exponent = 127 - 14; mantissa <<= 23 - 10; /* * convert to normal representation: * shift up mantissa and drop MSB */ while (!(mantissa & (1 << 23))) { mantissa <<= 1; exponent--; } mantissa &= 0x7fffffu; u.u32 = (sign << 31) | (exponent << 23) | mantissa; } } else if (exponent < 31) { /* normal */ exponent += 127 - 15; mantissa <<= 23 - 10; u.u32 = (sign << 31) | (exponent << 23) | mantissa; } else if (mantissa == 0) { /* infinite */ u.u32 = (sign << 31) | (255 << 23); } else { /* NaN */ u.u32 = 0x7fffffffu; } return u.f32; } static inline float __le16_to_float(__le16 le16) { return _f16_to_float(__le16_to_cpu(le16)); } static inline bool pulse_in_this_sync_window(int32_t dt, uint16_t duration) { return dt > -duration && (dt + duration) < (6500 + 250); } static inline bool pulse_in_next_sync_window(int32_t dt, uint16_t duration) { int32_t dt_end = dt + duration; /* * Allow 2000 pulses (40 µs) deviation from the expected interval * between bases, and 1000 pulses (20 µs) for a single base. */ return (dt > (20000 - 2000) && (dt_end) < (20000 + 6500 + 2000)) || (dt > (380000 - 2000) && (dt_end) < (380000 + 6500 + 2000)) || (dt > (400000 - 1000) && (dt_end) < (400000 + 6500 + 1000)); } static inline bool pulse_in_sweep_window(int32_t dt, uint16_t duration) { /* * The J axis (horizontal) sweep starts 71111 ticks after the sync * pulse start (32°) and ends at 346667 ticks (156°). * The K axis (vertical) sweep starts at 55555 ticks (23°) and ends * at 331111 ticks (149°). */ return dt > (55555 - 1000) && (dt + duration) < (346667 + 1000); } static void _handle_ootx_frame(struct lighthouse_base *base) { struct lighthouse_ootx_report *report = (void *)(base->ootx + 2); uint16_t len = (__le16)*base->ootx; uint32_t crc = crc32(0L, Z_NULL, 0); bool serial_changed = false; uint32_t ootx_crc; uint16_t version; int ootx_version; struct xrt_vec3 gravity; int i; if (len != 33) { LH_WARN("Lighthouse Base %X: unexpected OOTX payload length: %d", base->serial, len); return; } ootx_crc = (__le32) * ((__le32 *)(base->ootx + 36)); /* (len+3)/4*4 */ crc = crc32(crc, base->ootx + 2, 33); if (ootx_crc != crc) { LH_ERROR("Lighthouse Base %X: CRC error: %08x != %08x", base->serial, crc, ootx_crc); return; } version = __le16_to_cpu(report->version); ootx_version = version & 0x3f; if (ootx_version != 6) { LH_ERROR("Lighthouse Base %X: unexpected OOTX frame version: %d", base->serial, ootx_version); return; } base->firmware_version = version >> 6; if (base->serial != __le32_to_cpu(report->serial)) { base->serial = __le32_to_cpu(report->serial); serial_changed = true; } for (i = 0; i < 2; i++) { struct lighthouse_rotor_calibration *rotor; rotor = &base->calibration.rotor[i]; rotor->tilt = __le16_to_float(report->tilt[i]); rotor->phase = __le16_to_float(report->phase[i]); rotor->curve = __le16_to_float(report->curve[i]); rotor->gibphase = __le16_to_float(report->gibphase[i]); rotor->gibmag = __le16_to_float(report->gibmag[i]); } base->model_id = report->model_id; if (serial_changed) { LH_INFO( "Lighthouse Base %X: firmware version: %d, model id: %d, " "channel: %c", base->serial, base->firmware_version, base->model_id, base->channel); for (i = 0; i < 2; i++) { struct lighthouse_rotor_calibration *rotor; rotor = &base->calibration.rotor[i]; LH_INFO( "Lighthouse Base %X: rotor %d: [ %12.9f %12.9f " "%12.9f %12.9f %12.9f ]", base->serial, i, rotor->tilt, rotor->phase, rotor->curve, rotor->gibphase, rotor->gibmag); } } gravity.x = report->gravity[0]; gravity.y = report->gravity[1]; gravity.z = report->gravity[2]; math_vec3_normalize(&gravity); if (gravity.x != base->gravity.x || gravity.y != base->gravity.y || gravity.z != base->gravity.z) { base->gravity = gravity; LH_INFO("Lighthouse Base %X: gravity: [ %9.6f %9.6f %9.6f ]", base->serial, gravity.x, gravity.y, gravity.z); } if (base->reset_count != report->reset_count) { base->reset_count = report->reset_count; LH_INFO("Lighthouse Base %X: reset count: %d", base->serial, base->reset_count); } } static void lighthouse_base_reset(struct lighthouse_base *base) { base->data_sync = 0; base->data_word = -1; base->data_bit = 0; memset(base->ootx, 0, sizeof(base->ootx)); } static void _handle_ootx_data_word(struct lighthouse_watchman *watchman, struct lighthouse_base *base) { uint16_t len = (__le16)*base->ootx; /* After 4 OOTX words we have received the base station serial number */ if (base->data_word == 4) { struct lighthouse_ootx_report *report = (void *)(base->ootx + 2); uint16_t ootx_version = __le16_to_cpu(report->version) & 0x3f; uint32_t serial = __le32_to_cpu(report->serial); if (len != 33) { LH_WARN("%s: unexpected OOTX frame length %d", watchman->name, len); return; } if (ootx_version == 6 && serial != base->serial) { LH_DEBUG("%s: spotted Lighthouse Base %X", watchman->name, serial); } } if (len == 33 && base->data_word == 20) { /* (len + 3)/4 * 2 + 2 */ _handle_ootx_frame(base); } } static void lighthouse_base_handle_ootx_data_bit(struct lighthouse_watchman *watchman, struct lighthouse_base *base, bool data) { if (base->data_word >= (int)sizeof(base->ootx) / 2) { base->data_word = -1; } else if (base->data_word >= 0) { if (base->data_bit == 16) { /* Sync bit */ base->data_bit = 0; if (data) { base->data_word++; _handle_ootx_data_word(watchman, base); } else { LH_WARN("%s: Missed a sync bit, restarting", watchman->name); /* Missing sync bit, restart */ base->data_word = -1; } } else if (base->data_bit < 16) { /* * Each 16-bit payload word contains two bytes, * transmitted MSB-first. */ if (data) { int idx = 2 * base->data_word + (base->data_bit >> 3); base->ootx[idx] |= 0x80 >> (base->data_bit % 8); } base->data_bit++; } } /* Preamble detection */ if (data) { if (base->data_sync > 16) { /* Preamble detected, restart bit capture */ memset(base->ootx, 0, sizeof(base->ootx)); base->data_word = 0; base->data_bit = 0; } base->data_sync = 0; } else { base->data_sync++; } } static void lighthouse_base_handle_frame(struct lighthouse_watchman *watchman, struct lighthouse_base *base, uint32_t sync_timestamp) { struct lighthouse_frame *frame = &base->frame[base->active_rotor]; (void)watchman; if (!frame->sweep_ids) return; frame->frame_duration = sync_timestamp - frame->sync_timestamp; /* * If a single base station is running in 'B' mode, skipped frames * will still contain old data. */ if (frame->frame_duration > 1000000) return; // telemetry_send_lighthouse_frame(watchman->id, frame); } /* * The pulse length encodes three bits. The skip bit indicates whether the * emitting base will enable the sweeping laser in the next sweep window. * The data bit is collected to eventually assemble the OOTX frame. The rotor * bit indicates whether the next sweep will be horizontal (0) or vertical (1): * * duration 3000 3500 4000 4500 5000 5500 6000 6500 (in 48 MHz ticks) * skip 0 0 0 0 1 1 1 1 * data 0 0 1 1 0 0 1 1 * rotor 0 1 0 1 0 1 0 1 */ #define SKIP_BIT 4 #define DATA_BIT 2 #define ROTOR_BIT 1 static void _handle_sync_pulse(struct lighthouse_watchman *watchman, struct lighthouse_pulse *sync) { struct lighthouse_base *base; unsigned char channel; int32_t dt; unsigned int code; if (!sync->duration) return; if (sync->duration < 2750 || sync->duration > 6750) { LH_WARN("%s: Unknown pulse length: %d", watchman->name, sync->duration); return; } code = (sync->duration - 2750) / 500; dt = sync->timestamp - watchman->last_timestamp; /* 48 MHz / 120 Hz = 400000 cycles per sync pulse */ if (dt > (400000 - 1000) && dt < (400000 + 1000)) { /* Observing a single base station, channel A (or B, actually) */ channel = 'A'; } else if (dt > (380000 - 1000) && dt < (380000 + 1000)) { /* Observing two base stations, this is channel B */ channel = 'B'; } else if (dt > (20000 - 1000) && dt < (20000 + 1000)) { /* Observing two base stations, this is channel C */ channel = 'C'; } else { if (dt > -1000 && dt < 1000) { /* * Ignore, this means we prematurely finished * assembling the last sync pulse. */ } else { /* Irregular sync pulse */ if (watchman->last_timestamp) LH_WARN( "%s: Irregular sync pulse: %08x -> %08x " "(%+d)", watchman->name, watchman->last_timestamp, sync->timestamp, dt); lighthouse_base_reset(&watchman->base[0]); lighthouse_base_reset(&watchman->base[1]); } watchman->last_timestamp = sync->timestamp; return; } base = &watchman->base[channel == 'C']; base->channel = channel; base->last_sync_timestamp = sync->timestamp; lighthouse_base_handle_ootx_data_bit(watchman, base, (code & DATA_BIT)); lighthouse_base_handle_frame(watchman, base, sync->timestamp); base->active_rotor = (code & ROTOR_BIT); if (!(code & SKIP_BIT)) { struct lighthouse_frame *frame = &base->frame[code & ROTOR_BIT]; watchman->active_base = base; frame->sync_timestamp = sync->timestamp; frame->sync_duration = sync->duration; frame->sweep_ids = 0; } watchman->last_timestamp = sync->timestamp; } static void _handle_sweep_pulse(struct lighthouse_watchman *watchman, uint8_t id, uint32_t timestamp, uint16_t duration) { struct lighthouse_base *base = watchman->active_base; struct lighthouse_frame *frame; int32_t offset; (void)id; if (!base) { LH_WARN("%s: sweep without sync", watchman->name); return; } frame = &base->frame[base->active_rotor]; offset = timestamp - base->last_sync_timestamp; /* Ignore short sync pulses or sweeps without a corresponding sync */ if (offset > 379000) return; if (!pulse_in_sweep_window(offset, duration)) { LH_WARN( "%s: sweep offset out of range: rotor %u offset %u " "duration %u", watchman->name, base->active_rotor, offset, duration); return; } if (frame->sweep_ids & (1 << id)) { LH_WARN("%s: sensor %u triggered twice per frame, assuming reflection", watchman->name, id); return; } frame->sweep_duration[id] = duration; frame->sweep_offset[id] = offset; frame->sweep_ids |= (1 << id); } static void accumulate_sync_pulse(struct lighthouse_watchman *watchman, uint8_t id, uint32_t timestamp, uint16_t duration) { int32_t dt = timestamp - watchman->last_sync.timestamp; if (dt > watchman->last_sync.duration || watchman->last_sync.duration == 0) { watchman->seen_by = 1 << id; watchman->last_sync.timestamp = timestamp; watchman->last_sync.duration = duration; watchman->last_sync.id = id; } else { watchman->seen_by |= 1 << id; if (timestamp < watchman->last_sync.timestamp) { watchman->last_sync.duration += watchman->last_sync.timestamp - timestamp; watchman->last_sync.timestamp = timestamp; } if (duration > watchman->last_sync.duration) watchman->last_sync.duration = duration; watchman->last_sync.duration = duration; } } void lighthouse_watchman_handle_pulse(struct lighthouse_watchman *watchman, uint8_t id, uint16_t duration, uint32_t timestamp) { int32_t dt; dt = timestamp - watchman->last_sync.timestamp; if (watchman->sync_lock) { if (watchman->seen_by && dt > watchman->last_sync.duration) { _handle_sync_pulse(watchman, &watchman->last_sync); watchman->seen_by = 0; } if (pulse_in_this_sync_window(dt, duration) || pulse_in_next_sync_window(dt, duration)) { accumulate_sync_pulse(watchman, id, timestamp, duration); } else if (pulse_in_sweep_window(dt, duration)) { _handle_sweep_pulse(watchman, id, timestamp, duration); } else { /* * Spurious pulse - this could be due to a reflection or * misdetected sync. If dt > period, drop the sync lock. * Maybe we should ignore a single missed sync. */ if (dt > 407500) { watchman->sync_lock = false; LH_WARN("%s: late pulse, lost sync", watchman->name); } else { LH_WARN("%s: spurious pulse: %08x (%02x %d %u)", watchman->name, timestamp, id, dt, duration); } watchman->seen_by = 0; } } else { /* * If we've not locked onto the periodic sync signals, try to * treat all pulses within the right duration range as potential * sync pulses. * This is still a bit naive. If the sensors are moved too * close to the lighthouse base station, sweep pulse durations * may fall into this range and sweeps may be misdetected as * sync floods. */ if (duration >= 2750 && duration <= 6750) { /* * Decide we've locked on if the pulse falls into any * of the expected time windows from the last * accumulated sync pulse. */ if (pulse_in_next_sync_window(dt, duration)) { LH_WARN("%s: sync locked", watchman->name); watchman->sync_lock = true; } accumulate_sync_pulse(watchman, id, timestamp, duration); } else { /* Assume this is a sweep, ignore it until we lock */ } } } void lighthouse_watchman_init(struct lighthouse_watchman *watchman, const char *name) { watchman->id = watchman_id++; watchman->name = name; watchman->seen_by = 0; watchman->last_timestamp = 0; watchman->last_sync.timestamp = 0; watchman->last_sync.duration = 0; log_level = debug_get_log_option_vive_log(); }