Использовать Daydream Controller в HoloLens или вне Daydream?

Контроллер Daydream великолепен, и мы хотим использовать его в моем приложении дополненной реальности. Он отлично подключается через Bluetooth к HoloLens, но не уверен, смогу ли я просмотреть его в Unity.

И для HoloLens, и для Daydream требуются собственные предварительные технические версии Unity. код контроллера gvr находится в сети, но, похоже, говорите напрямую с GVR C api.

Любые мысли о том, возможен ли доступ к контроллеру Daydream в Unity за пределами предварительной версии Daydream Tech?


person Anand Agarawala    schedule 21.11.2016    source источник
comment
Кстати, г-н Дуб создал JS-версию удивительного ответа @Dotphracker, представленного ниже. github.com/mrdoob/daydream-controller.js   -  person Anand Agarawala    schedule 28.02.2017


Ответы (1)


Очень возможно получить доступ к контроллеру Daydream без служб GVR. На самом деле я работаю над этим сам и могу поделиться тем, что знаю.

Получение данных

Используя bluetooth gatt, вы можете просмотреть все доступные данные и подписаться на нужный вам идентификатор. Я не знаю, как бы вы сделали это конкретно в Hololens/Unity. В основном вы хотите:

  1. Подключиться к устройству
  2. Выберите услугу (0000fe55-0000-1000-8000-00805f9b34fb)
  3. Выберите характеристику (00000001-1000-1000-8000-00805f9b34fb)
  4. Запросить уведомления для него (00002902-0000-1000-8000-00805f9b34fb)

Пример Android:

static final UUID DAYDREAM_CUSTOM_SERVICE = UUID.fromString("0000fe55-0000-1000-8000-00805f9b34fb");
static final UUID DAYDREAM_CHARACTERISTIC = UUID.fromString("00000001-1000-1000-8000-00805f9b34fb");
static final UUID CHARACTERISTIC_UPDATE_NOTIFICATION_DESCRIPTOR_UUID = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb");
...
BluetoothGattService service = gatt.getService(DAYDREAM_CUSTOM_SERVICE);
BluetoothGattCharacteristic characteristic = service.getCharacteristic(DAYDREAM_CHARACTERISTIC);
gatt.setCharacteristicNotification(characteristic, true);
BluetoothGattDescriptor descriptor = characteristic.getDescriptor(CHARACTERISTIC_UPDATE_NOTIFICATION_DESCRIPTOR_UUID);
descriptor.setValue( BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
gatt.writeDescriptor(descriptor);

Я предлагаю поискать Bluetooth Gatt, чтобы узнать больше об услугах и характеристиках. Я также использовал приложение BLE Scanner в магазине игр, чтобы просмотреть большую часть этой информации, прежде чем начать с кода.

Разбор данных

Устройство дает 20 байт данных для работы. Он состоит из времени, ориентации, ускорения, необработанного гироскопа, положения касания и флажков кнопок.

Пример (лежа на столе):

5BEBFFB825FDB000041000B00000000000000000
63EFFFB825FDB000041000B00000000000000008
6C73FFB825FDB000041000B00000000000000038

Пример (с использованием сенсорной панели):

480BFE87EB00E801841000B00000000191FBA008
4F8FFE47EB00E800441000B0000003FEB1FBA038
5893FE27EB00EFFF041000B0000003FF51FBA000

Определение байта ниже:

Bytes:

  - 1: TTTT TTTT * T for time, loops
  - 2: TNNN NNKK * N is sequence number
  - 3: KKKK KKKK * IJK is orientation
  - 4: KKKI IIII
  - 5: IIII IIII
  - 6: JJJJ JJJJ
  - 7: JJJJ JOOO * MNO is acceleration
  - 8: OOOO OOOO
  - 9: OONN NNNN
  -10: NNNN NNNM
  -11: MMMM MMMM
  -12: MMMM CCCC * CDE for raw gyro
  -13: CCCC CCCC
  -14: CDDD DDDD
  -15: DDDD DDEE
  -16: EEEE EEEE
  -17: EEEX XXXX * All the X is the X touch position (8 bits)
  -18: XXXY YYYY * Y the Y touch position (8 bits)
  -19: YYYB BBBB * B the buttons (5 bits | [+][-][App][Home][Click])
  -20: Values vary

Благодаря этому у меня есть сенсорная панель и кнопки, способные работать с любым устройством Bluetooth, для которого я могу создавать приложения. Кроме того, вам нужно будет добавить обратно функциональные возможности для сброса положения устройства, управления звуком и т. д.

Используя это определение на Android:

static final int CLICK_BTN = 0x1;
static final int HOME_BTN = 0x2;
static final int APP_BTN = 0x4;
static final int VOL_DOWN_BTN = 0x8;
static final int VOL_UP_BTN = 0x10;
float xTouch=0, yTouch=0;
...
final boolean isClickDown = (data[18] & CLICK_BTN) > 0;
final boolean isHomeDown = (data[18] & HOME_BTN) > 0;
final boolean isAppDown = (data[18] & APP_BTN) > 0;
final boolean isVolMinusDown = (data[18] & VOL_DOWN_BTN) > 0;
final boolean isVolPlusDown = (data[18] & VOL_UP_BTN) > 0;

final int time = ((data[0] & 0xFF) << 1 | (data[1] & 0x80) >> 7 );

final int seq = (data[1] & 0x7C) >> 2;

int xOri = (data[1] & 0x03) << 11 | (data[2] & 0xFF) << 3 | (data[3] & 0xE0) >> 5;
xOri = (xOri << 19) >> 19;

int yOri = (data[3] & 0x1F) << 8 | (data[4] & 0xFF);
yOri = (yOri << 19) >> 19;

int zOri = (data[5] & 0xFF) << 5 | (data[6] & 0xF8) >> 3;
zOri = (zOri << 19) >> 19;

int xAcc = (data[6] & 0x07) << 10 | (data[7] & 0xFF) << 2 | (data[8] & 0xC0) >> 6;
xAcc = (xAcc << 19) >> 19;

int yAcc = (data[8] & 0x3F) << 7 | (data[9] & 0xFE) >>> 1;
yAcc = (yAcc << 19) >> 19;

int zAcc = (data[9] & 0x01) << 12 | (data[10] & 0xFF) << 4 | (data[11] & 0xF0) >> 4;
zAcc = (zAcc << 19) >> 19;

int xGyro = ((data[11] & 0x0F) << 9 | (data[12] & 0xFF) << 1 | (data[13] & 0x80) >> 7);
xGyro = (xGyro << 19) >> 19;

int yGyro = ((data[13] & 0x7F) << 6 | (data[14] & 0xFC) >> 2 );
yGyro = (yGyro << 19) >> 19;

int zGyro = ((data[14] & 0x03) << 11 | (data[15] & 0xFF) << 3 | (data[16] & 0xE0) >> 5);
zGyro = (zGyro << 19) >> 19;

xTouch = ((data[16] & 0x1F) << 3 | (data[17] & 0xE0) >> 5) / 255.0f;
yTouch = ((data[17] & 0x1F) << 3 | (data[18] & 0xE0) >> 5) / 255.0f;

Это можно оптимизировать, но присваиваются все биты, кроме последнего байта. Код value = (value << 19) >> 19 также может быть value = (value >> 12) == 0 ? value : ~0x1FFF | value. Это просто расширение подписанного бита до 32-битного целого числа со знаком.

Я надеюсь, что это поможет, и с нетерпением жду дополнительных ответов.

-- Обновление 2 --

Посмотрев на код gvr, я обнаружил, что у меня есть некоторые проблемы с моими предыдущими предположениями. На самом деле это ориентация/ускорение/гироскоп. Также был на 1 бит больше для последовательности и на 1 меньше для времени. Я обновил определение байта и пример Android.

Кроме того, значения X, Y, Z необходимо преобразовать в числа с плавающей запятой. Для Unity вы можете поместить целые числа в Vector3s, а затем использовать следующее. Я также отрицал x и y в oriVector для Unity.

Vector3 oriVector = new Vector3 (-xOri, -yOri, zOri);
...
oriVector *= (2 * Mathf.PI / 4095.0);
accVector *= (8 * 9.8 / 4095.0);
gyroVector *= (2048 / 180 * Mathf.PI / 4095.0);

Затем, чтобы получить вращение, вам просто нужен oriVector. Который на самом деле представляет собой угол оси, хранящийся как: единичный вектор * угол.

public Quaternion orientation = Quaternion.identity;
private Quaternion controllerPoseInSensorSpace = Quaternion.identity;
private Quaternion startFromSensorTransformation = Quaternion.identity;
...
// do this bit after getting the data and scaling it
float sqrMagnitude = oriVector.sqrMagnitude;
if (sqrMagnitude > 0) {
    // extract radian angle
    float w = Mathf.Sqrt (sqrMagnitude);
    // normalize vector
    oriVector /= w;
    // set orientation space
    setOrientationInSensorSpace (w,oriVector);
}
...
// then assign to a Transform
controller.localRotation = this.orientation;
...
// sets orientation with rotation offset
void setOrientationInSensorSpace(float angle, Vector3 axis) {
    // set orientation space
    this.controllerPoseInSensorSpace = Quaternion.AngleAxis(angle*Mathf.Rad2Deg,axis);
    // rotate based on centered offset
    this.orientation = this.startFromSensorTransformation * this.controllerPoseInSensorSpace;
}
...
// after holding home for 600 milliseconds
private void setStartFromSensorTransformation() {
    Vector3 angles = this.controllerPoseInSensorSpace.eulerAngles;
    // reset rotation on Y
    this.startFromSensorTransformation.Set(0,Mathf.Sin(-angles.y * Mathf.Deg2Rad / 2f), 0, Mathf.Cos(angles.y * Mathf.Deg2Rad / 2f));
    // could also reset all, easier to work with
    //this.startFromSensorTransformation = Quaternion.Inverse (this.controllerPoseInSensorSpace);
}

Это все, что связано с работой Daydream с обычными Bluetooth-устройствами. Я также использовал приведенный выше код C# в Unity3D.

-- Обновление 1 --

Добавлено более полное определение байта. Раньше отсутствовали данные гироскопа, магнитометра и ускорения. У каждого из них есть три 13-битных целых числа со знаком. Также кажется, что порядковый номер спрятан вместе с битами времени.

Идти вперед

Чтобы использовать данные устройства с другими платформами, вам нужно будет провести данные через аналогичные уравнения, используемые для устройств 9DoF/IMU. Я не знаю, как именно это решить.

Последний байт

Это, вероятно, зарезервировано для флагов, и я не уверен в значении, но у меня есть некоторые выводы для перечисления. Номер версии — это версия микропрограммы контроллера.

1.0.10 (out of the box): 0xF0/0xF8
1.0.10 (previously used with gvr): 0x00/0x08/0x38/0x51
1.0.15: 0x00/0x70
person Forrest Porter    schedule 22.11.2016
comment
Спасибо, что поделились своими выводами! Было бы здорово, если бы вы могли поделиться своим кодом, если вы добьетесь большего прогресса. ???????? - person Anand Agarawala; 23.11.2016
comment
Спасибо за это! Достигли ли вы какого-либо прогресса в определении байта? - person Alexandre Bulté; 28.11.2016
comment
Да, я обновил данные и считаю, что они полны, кроме последнего байта. Чтобы он полностью работал вне GVR, потребуется немного работы, но, кажется, есть несколько видео, которые я погуглил, которые показывают использование устройств 9DoF/IMU (по крайней мере, arduino), которые используются даже в Unity. - person Forrest Porter; 28.11.2016
comment
Кстати, @Dotphracker, мы сейчас начинаем переносить ваш код в HoloLens, поскольку встроенные методы ввода не очень надежны. Есть ли у вас какие-либо обновления с момента вашего последнего сообщения, которые могут быть полезны? Или даже ссылку на код/github, которой вы могли бы поделиться? Мы были бы очень благодарны. - person Anand Agarawala; 10.12.2016
comment
@Dotphracker хорошие новости, мы считываем байты в HoloLens! В настоящее время мы пытаемся проанализировать массив байтов, который мы получаем. На самом деле не в состоянии понять многое из того, что именно означают значения. Вот пример массива байтов, который мы получаем. 118,19,225,25,31,23,88,35,194,153,237,112,13,127,184,8,0,0,0,0 Удалось ли вам получить из этого соответствующие значения Vector3? - person Anand Agarawala; 14.12.2016
comment
@AnandAgarawala Вам нужно извлечь данные из байтов, используя определение, которое я определил. Я обновил ответ, чтобы включить пример. Значения не дадут вам значение Vector3 для использования. Вам нужно использовать какой-то алгоритм слияния датчиков, я не могу объяснить их сам, но это было бы похоже на отслеживание положения IMU. - person Forrest Porter; 20.12.2016
comment
@Dotphracker, спасибо за обновление вашего кода! Все очень полезно. Мы используем код объединения датчиков JS здесь и по-прежнему какая-то беда. Любопытно, не могли бы вы поделиться кодом всего вашего проекта на github? - person Anand Agarawala; 21.12.2016
comment
И кстати, это, вероятно, самый подробный/лучший ответ, который существует во всем Интернете, на чей-то общий вопрос. Вы, сэр, заслуживаете трофея! - person Anand Agarawala; 21.12.2016
comment
@AnandAgarawala Ссылка мне очень помогла, и я получил рабочий пример. Если кто-то хочет найти соответствующий код после использования jd-gui, просто найдите в файлах соответствующий сервисный uuid (fe55). Обновленный ответ должен быть завершен сейчас. - person Forrest Porter; 25.12.2016
comment
Ваш обновленный ответ потрясающий !! Большое спасибо, обновим наш код соответствующим образом. - person Anand Agarawala; 10.01.2017
comment
@AnandAgarawala Например, вот как это работает на unity/hololens [ссылка на видео ] используя все, что я определил в своем ответе. - person Forrest Porter; 11.01.2017
comment
@Dotphracker Не могли бы вы уточнить, что такое rawOrientation в вашем коде? - person JibranAhmed; 12.04.2017
comment
@jeniusj Это была ошибка копирования и вставки из моего кода. Я переименовал его, чтобы ответ был более ясным. Исправил ответ. - person Forrest Porter; 19.04.2017
comment
Пожалуйста, посмотрите, как вы разбираете файл oriX. Вы написали 4: KKKI IIII. Битовая маска для первых 3 битов байта — 0xE0, но в вашем коде — int xOri = (data[1] & 0x03) << 11 | (data[2] & 0xFF) << 3 | (data[3] & 0x80) >> 5. Вы используете битовую маску 0x80. Кстати, Как я взломал контроллер Google Daydream, что вы назвали ориентация в этой статье — гироскоп. - person Vertex; 29.04.2017
comment
@Vertex, ты прав. Я использую часть nodejs, которая имеет 0xE0, но каким-то образом она стала 0x80 для моих примеров Android/Unity. Я использовал то, что указано в ядре Google vr. В этой статье действительно есть событие ориентации на изображении кода JD-GUI для того, что он называет событием магнитометра. Все, что действительно необходимо, — это часть ориентации, потому что она дает вам кватернион устройства без дополнительных алгоритмов. - person Forrest Porter; 04.05.2017