Protokół HID śledzenia ruchów głowy

Protokół urządzenia interfejsu (HID) do śledzenia ruchu głowy, dostępny na urządzeniach z Androidem 13 lub nowszym, umożliwia podłączenie urządzenia do śledzenia ruchu głowy do urządzenia z Androidem przez USB lub Bluetooth i udostępnienie go w ramach Androida i aplikacji za pomocą platformy czujników. Ten protokół służy do sterowania efektem wirtualizacji dźwięku (dźwięk 3D). Na tej stronie używamy terminów urządzeniehost w kontekście Bluetootha. Urządzenie oznacza urządzenie śledzące ruchy głowy, a host oznacza hosta z Androidem.

Producenci urządzeń z Androidem muszą skonfigurować je tak, aby obsługiwały protokół HID trackera głowy. Szczegółowe informacje o konfiguracji znajdziesz w pliku README dotyczącym czujników dynamicznych.

Na tej stronie przyjęto założenie, że znasz te zasoby:

Struktura najwyższego poziomu

Platforma Androida identyfikuje urządzenie śledzące ruchy głowy jako urządzenie HID.

Pełny przykład prawidłowego deskryptora HID znajdziesz w Dodatku 1: Przykład deskryptora HID.

Na najwyższym poziomie urządzenie śledzące ruchy głowy to zbiór aplikacji z Sensorsstroną0x20Other: Customużyciem0xE1. W tym zbiorze znajduje się kilka pól danych (dane wejściowe) i właściwości (funkcje).

Usługi i pola danych

W tej sekcji opisano właściwości i pola danych w kolekcji aplikacji urządzenia śledzącego ruchy głowy.

Usługa: opis czujnika (0x0308)

Właściwość Sensor Description (0x0308) to ciąg znaków ASCII (8-bitowy) tylko do odczytu, który musi zawierać te wartości:

Śledzenie głowy w wersji 1.0:

#AndroidHeadTracker#1.0

Śledzenie ruchu głowy w wersji 2.0 (dostępne w Androidzie 15 lub nowszym), które obejmuje obsługę LE Audio:

#AndroidHeadTracker#2.0#x

Wartość x to liczba całkowita (1, 2, 3) wskazująca obsługiwany transport:

  • 1. ACL
  • 2: ISO
  • 3. ACL + ISO

Nie oczekuje się znaku null-terminator, co oznacza, że łączny rozmiar tej właściwości w przypadku wersji 1.0 wynosi 23 znaki 8-bitowe.

Ta właściwość służy jako rozróżnienie, aby uniknąć kolizji z innymi czujnikami niestandardowymi.

Właściwość: trwały unikalny identyfikator (0x0302)

Właściwość Trwały unikalny identyfikator (0x0302) to tablica tylko do odczytu składająca się z 16 elementów po 8 bitów każdy (łącznie 128 bitów). Nie oczekuje się znaku null. Ta właściwość jest opcjonalna.

Ta właściwość umożliwia urządzeniom śledzącym ruch głowy zintegrowanym z urządzeniami audio odwoływanie się do urządzenia audio, do którego są podłączone. Obsługiwane są te schematy:

Samodzielny tracker głowy

Jeśli właściwość Trwały unikalny identyfikator (0x0302) nie istnieje lub jest ustawiona na same zera, oznacza to, że urządzenie śledzące ruchy głowy nie jest trwale przymocowane do urządzenia audio i może być używane oddzielnie, np. użytkownik może ręcznie powiązać urządzenie śledzące ruchy głowy z oddzielnym urządzeniem audio.

Odwołanie do adresu MAC Bluetootha

Octet 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
Wartość 0 0 0 0 0 0 0 0 B T Adres MAC Bluetooth

W tym schemacie pierwsze 8 oktetów musi mieć wartość 0, oktety 8 i 9 muszą zawierać odpowiednio wartości ASCII BT, a kolejne 6 oktetów jest interpretowanych jako adres MAC Bluetooth, przy założeniu, że urządzenie śledzące ruchy głowy ma zastosowanie do każdego urządzenia audio z tym adresem MAC. Musi to być adres tożsamości, nawet jeśli urządzenie używa losowego adresu MAC do nawiązywania połączeń. Urządzenia dwutrybowe łączące się przez Bluetooth Classic (format HID w wersji 1.0) i Bluetooth LE (format HID w wersji 2.0) muszą udostępniać 2 deskryptory HID z tym samym adresem tożsamości. Urządzenia dwumodowe z oddzielnymi urządzeniami lewym i prawym muszą udostępniać interfejs HID Bluetooth LE za pomocą głównego urządzenia dwumodowego, a nie urządzenia dodatkowego obsługującego tylko LE.

Odwołanie za pomocą identyfikatora UUID

Gdy najbardziej znaczący bit (MSB) oktetu 8 jest ustawiony (≥0x80), pole jest interpretowane jako identyfikator UUID zgodnie z RFC-4122. Odpowiednie urządzenie audio udostępnia ten sam identyfikator UUID, który jest rejestrowany w ramach Androida za pomocą nieokreślonego mechanizmu, który jest specyficzny dla używanego typu transportu.

Właściwość: stan raportowania (0x0316)

Właściwość Stan raportowania (0x0316) to właściwość do odczytu i zapisu, która ma standardową semantykę zdefiniowaną w specyfikacji HID. Host używa tej właściwości, aby wskazać urządzeniu, które zdarzenia ma zgłaszać. Używane są tylko wartości Brak zdarzeń (0x0840) i Wszystkie zdarzenia (0x0841).

Początkowa wartość tego pola musi wynosić „No Events” (Brak zdarzeń) i nie może być modyfikowana przez urządzenie, tylko przez hosta.

Właściwość: Stan zasilania (0x0319)

Właściwość Stan zasilania (0x0319) to właściwość do odczytu i zapisu, która ma standardową semantykę zdefiniowaną w specyfikacji HID. Host używa tej właściwości, aby wskazać urządzeniu, w jakim stanie zasilania ma się znajdować. Używane są tylko wartości Pełna moc (0x0851) i Wyłącz (0x0855).

Wartość początkowa tego pola jest określana przez urządzenie i nigdy nie może być modyfikowana przez urządzenie, a jedynie przez hosta.

Usługa: przedział raportowania (0x030E)

Właściwość Report Interval (0x030E) jest właściwością do odczytu i zapisu, która ma standardową semantykę zdefiniowaną w specyfikacji HID. Host używa tej właściwości, aby określić, jak często urządzenie ma raportować odczyty danych. Jednostką są sekundy. Prawidłowy zakres tej wartości jest określany przez urządzenie i opisywany za pomocą mechanizmu Physical Min/Max. Musi być obsługiwana częstotliwość raportowania wynosząca co najmniej 50 Hz, a zalecana maksymalna częstotliwość raportowania to 100 Hz. Minimalny interwał raportowania musi być zatem mniejszy lub równy 20 ms, a zalecany jest interwał większy lub równy 10 ms.

Usługa: Transport LE zarezerwowany przez dostawcę (0xF410)

Właściwość Vendor-reserved LE Transport (0xF410) jest właściwością do odczytu i zapisu, która ma standardową semantykę zdefiniowaną w specyfikacji HID. Host używa tej właściwości, aby wskazać wybrany transport (ACL lub ISO). Używane są tylko wartości ACL (0xF800) i ISO (0xF801), a obie muszą być uwzględnione w kolekcji logicznej.

Ta właściwość jest konfigurowana przed stanami zasilania lub raportowania.

Pole danych: Wartość niestandardowa 1 (0x0544)

Pole Wartość niestandardowa 1 (0x0544) to pole wejściowe służące do raportowania rzeczywistych informacji o śledzeniu ruchu głowy. Jest to tablica 3-elementowa interpretowana zgodnie ze zwykłymi regułami HID dotyczącymi wartości fizycznych, jak określono w sekcji 6.2.2.7 specyfikacji HID. Prawidłowy zakres dla każdego elementu to [-π, π] rad. Jednostki są zawsze podawane w radianach.

Elementy są interpretowane jako [rx, ry, rz], gdzie [rx, ry, rz] to wektor rotacji reprezentujący transformację z ramy odniesienia do ramy głowy. Wartość musi mieścić się w zakresie [0..π].

Układ odniesienia jest dowolny, ale zwykle jest stały i musi być prawoskrętny. Niewielki dryf jest dopuszczalny. Osie głowy to:

  • X od lewego do prawego ucha
  • Y od tyłu głowy do nosa (od tyłu do przodu)
  • Z od szyi do czubka głowy

Pole danych: Wartość niestandardowa 2 (0x0545)

Pole Wartość niestandardowa 2 (0x0545) to pole wejściowe służące do raportowania rzeczywistych informacji o śledzeniu ruchu głowy. Jest to 3-elementowa tablica stałoprzecinkowa, interpretowana zgodnie ze standardowymi regułami HID dotyczącymi wartości fizycznych. Jednostką jest zawsze radian na sekundę.

Elementy są interpretowane jako [vx, vy, vz], gdzie [vx, vy, vz] to wektor rotacji reprezentujący prędkość kątową ramki głowy (względem samej siebie).

Pole danych: Wartość niestandardowa 3 (0x0546)

Pole Wartość niestandardowa 3 (0x0546) to pole wejściowe służące do śledzenia nieciągłości w układzie odniesienia. Jest to 8-bitowa liczba całkowita skalarna. Musi być zwiększany (z zawijaniem) przez urządzenie za każdym razem, gdy zmienia się układ odniesienia, np. jeśli algorytm filtra orientacji używany do określania orientacji został zresetowany. Ta wartość jest interpretowana zgodnie ze standardowymi regułami HID dotyczącymi wartości fizycznych. Jednak wartość fizyczna i jednostki nie mają znaczenia. Jedyną informacją istotną dla hosta jest zmieniona wartość. Aby uniknąć problemów z liczbami związanych z utratą precyzji podczas konwersji z jednostek logicznych na fizyczne, zalecamy ustawienie wartości fizycznej minimalnej, fizycznej maksymalnej i wykładnika jednostki na zero w przypadku tego pola.

Struktura raportu

Grupowanie usług w raportach (przez przypisywanie identyfikatorów raportów) jest elastyczne. Aby zwiększyć wydajność, zalecamy oddzielenie właściwości tylko do odczytu od właściwości do odczytu i zapisu.

W przypadku pól danych pola Wartość niestandardowa 1, 2 i 3 muszą znajdować się w tym samym raporcie i tylko w jednym raporcie dla danego urządzenia (zbieranie danych z aplikacji).

Wysyłanie raportów o danych wejściowych

Urządzenie musi okresowo i asynchronicznie (za pomocą komunikatów HID INPUT) wysyłać raporty wejściowe, gdy spełnione są wszystkie te warunki:

  • Właściwość Stan zasilania jest ustawiona na Pełna moc.
  • Właściwość Stan raportowania ma wartość Wszystkie zdarzenia.
  • Właściwość przedziału raportowania ma wartość różną od zera.

Właściwość „Interwał raportowania” określa, jak często mają być wysyłane raporty. Jeśli którykolwiek z powyższych warunków nie zostanie spełniony, urządzenie nie może wysyłać żadnych raportów.

Zgodność w przód i wstecz

Protokół HID śledzenia głowy używa schematu wersji, który umożliwia aktualizacje, a jednocześnie zapewnia interoperacyjność między hostem a urządzeniem, które używają różnych wersji protokołu. Wersje protokołu są identyfikowane za pomocą 2 liczb: głównej i pomocniczej, które mają odmienne znaczenie, jak opisano w kolejnych sekcjach.

Wersje obsługiwane przez urządzenie można określić, sprawdzając właściwość Opis czujnika (0x0308).

Zgodność z wersją podrzędną

Zmiany w wersji podrzędnej są zgodne wstecznie z wcześniejszymi wersjami podrzędnymi opartymi na tej samej wersji głównej. W przypadku aktualizacji wersji pomocniczej host ignoruje dodatkowe pola danych i właściwości. Na przykład urządzenie korzystające z protokołu w wersji 1.6 jest zgodne z hostem obsługującym protokół w wersji 1.x, w tym w wersji 1.5.

Zgodność z wersją główną

W przypadku zmian w głównych wersjach dozwolone są zmiany, które nie są wstecznie zgodne. Aby obsługiwać wiele głównych wersji w celu zapewnienia interoperacyjności ze starymi i nowymi hostami, urządzenia mogą określać w deskryptorach raportów wiele kolekcji aplikacji. Na przykład:

const unsigned char ReportDescriptor[] = {
    HID_USAGE_PAGE_SENSOR,
    HID_USAGE_SENSOR_TYPE_OTHER_CUSTOM,

    HID_COLLECTION(HID_APPLICATION),
        // Feature report 2 (read-only).
        HID_REPORT_ID(2),

        // Magic value: "#AndroidHeadTracker#1.5"
        HID_USAGE_SENSOR_PROPERTY_SENSOR_DESCRIPTION,
        HID_LOGICAL_MIN_8(0),
        HID_LOGICAL_MAX_8(0xFF),
        HID_REPORT_SIZE(8),
        HID_REPORT_COUNT(23),
        HID_FEATURE(HID_CONST_VAR_ABS),

      ...

    HID_END_COLLECTION,

    HID_COLLECTION(HID_APPLICATION),
        // Feature report 12 (read-only).
        HID_REPORT_ID(12),

        // Magic value: "#AndroidHeadTracker#2.4"
        HID_USAGE_SENSOR_PROPERTY_SENSOR_DESCRIPTION,
        HID_LOGICAL_MIN_8(0),
        HID_LOGICAL_MAX_8(0xFF),
        HID_REPORT_SIZE(8),
        HID_REPORT_COUNT(23),
        HID_FEATURE(HID_CONST_VAR_ABS),

      ...

    HID_END_COLLECTION,
};

W takim przypadku host może wyliczyć wszystkie różne kolekcje aplikacji reklamowane przez urządzenie, sprawdzając ich właściwość Sensor Description (Opis czujnika), aby określić wersje protokołu, które każda z nich implementuje, a następnie wybrać najnowszą wersję protokołu obsługiwaną przez hosta. Po wybraniu tej opcji host będzie działać z jednym protokołem wybranym na cały okres połączenia urządzenia.

Dodatek: przykład deskryptora HID

Poniższy przykład przedstawia typowy prawidłowy deskryptor HID. Korzysta z często używanych makr języka C, które są dostępne w sekcji 4.1 dokumentu HID Sensor Usages.

const unsigned char ReportDescriptor[] = {
    HID_USAGE_PAGE_SENSOR,
    HID_USAGE_SENSOR_TYPE_OTHER_CUSTOM,
    HID_COLLECTION(HID_APPLICATION),
        // Feature report 2 (read-only).
        HID_REPORT_ID(2),

        // Magic value: "#AndroidHeadTracker#1.0"
        HID_USAGE_SENSOR_PROPERTY_SENSOR_DESCRIPTION,
        HID_LOGICAL_MIN_8(0),
        HID_LOGICAL_MAX_8(0xFF),
        HID_REPORT_SIZE(8),
        HID_REPORT_COUNT(23),
        HID_FEATURE(HID_CONST_VAR_ABS),

        // UUID.
        HID_USAGE_SENSOR_PROPERTY_PERSISTENT_UNIQUE_ID,
        HID_LOGICAL_MIN_8(0),
        HID_LOGICAL_MAX_8(0xFF),
        HID_REPORT_SIZE(8),
        HID_REPORT_COUNT(16),
        HID_FEATURE(HID_CONST_VAR_ABS),

        // Feature report 1 (read/write).
        HID_REPORT_ID(1),

        // 1-bit on/off reporting state.
        HID_USAGE_SENSOR_PROPERTY_REPORTING_STATE,
        HID_LOGICAL_MIN_8(0),
        HID_LOGICAL_MAX_8(1),
        HID_REPORT_SIZE(1),
        HID_REPORT_COUNT(1),
        HID_COLLECTION(HID_LOGICAL),
            HID_USAGE_SENSOR_PROPERTY_REPORTING_STATE_NO_EVENTS,
            HID_USAGE_SENSOR_PROPERTY_REPORTING_STATE_ALL_EVENTS,
            HID_FEATURE(HID_DATA_ARR_ABS),
        HID_END_COLLECTION,

        // 1-bit on/off power state.
        HID_USAGE_SENSOR_PROPERTY_POWER_STATE,
        HID_LOGICAL_MIN_8(0),
        HID_LOGICAL_MAX_8(1),
        HID_REPORT_SIZE(1),
        HID_REPORT_COUNT(1),
        HID_COLLECTION(HID_LOGICAL),
            HID_USAGE_SENSOR_PROPERTY_POWER_STATE_D4_POWER_OFF,
            HID_USAGE_SENSOR_PROPERTY_POWER_STATE_D0_FULL_POWER,
            HID_FEATURE(HID_DATA_ARR_ABS),
        HID_END_COLLECTION,

        // 6-bit reporting interval, with values [0x00..0x3F] corresponding to [10ms..100ms].
        HID_USAGE_SENSOR_PROPERTY_REPORT_INTERVAL,
        HID_LOGICAL_MIN_8(0x00),
        HID_LOGICAL_MAX_8(0x3F),
        HID_PHYSICAL_MIN_8(10),
        HID_PHYSICAL_MAX_8(100),
        HID_REPORT_SIZE(6),
        HID_REPORT_COUNT(1),
        HID_USAGE_SENSOR_UNITS_SECOND,
        HID_UNIT_EXPONENT(0xD),  // 10^-3
        HID_FEATURE(HID_DATA_VAR_ABS),

        // Input report 1

        // Orientation as rotation vector (scaled to [-pi..pi] rad).
        HID_USAGE_SENSOR_DATA_CUSTOM_VALUE_1,
        HID_LOGICAL_MIN_16(0x01, 0x80), // LOGICAL_MINIMUM (-32767)
        HID_LOGICAL_MAX_16(0xFF, 0x7F), // LOGICAL_MAXIMUM (32767)
        HID_PHYSICAL_MIN_32(0x60, 0x4F, 0x46, 0xED),  // -314159265
        HID_PHYSICAL_MAX_32(0xA1, 0xB0, 0xB9, 0x12),  // 314159265
        HID_UNIT_EXPONENT(0x08),  // 10^-8
        HID_REPORT_SIZE(16),
        HID_REPORT_COUNT(3),
        HID_INPUT(HID_DATA_VAR_ABS),

        // Angular velocity as rotation vector (scaled to [-32..32] rad/sec).
        HID_USAGE_SENSOR_DATA_CUSTOM_VALUE_2,
        HID_LOGICAL_MIN_16(0x01, 0x80), // LOGICAL_MINIMUM (-32767)
        HID_LOGICAL_MAX_16(0xFF, 0x7F), // LOGICAL_MAXIMUM (32767)
        HID_PHYSICAL_MIN_8(0xE0),
        HID_PHYSICAL_MAX_8(0x20),
        HID_UNIT_EXPONENT(0x00),  // 10^0
        HID_REPORT_SIZE(16),
        HID_REPORT_COUNT(3),
        HID_INPUT(HID_DATA_VAR_ABS),

        // Reference frame reset counter.
        HID_USAGE_SENSOR_DATA_CUSTOM_VALUE_3,
        HID_LOGICAL_MIN_16(0x00, 0x00), // LOGICAL_MINIMUM (0)
        HID_LOGICAL_MAX_16(0xFF, 0x00), // LOGICAL_MAXIMUM (255)
        HID_PHYSICAL_MIN_8(0x00),
        HID_PHYSICAL_MAX_8(0x00),
        HID_UNIT_EXPONENT(0x00),  // 10^0
        HID_REPORT_SIZE(8),
        HID_REPORT_COUNT(1),
        HID_INPUT(HID_DATA_VAR_ABS),

    HID_END_COLLECTION,
};

Dodatek 2. Przykład deskryptora HID w wersji 2.0

Poniższy przykład przedstawia deskryptor HID w wersji 2.0 dla urządzenia obsługującego tylko transport ACL Bluetooth LE.

const unsigned char ReportDescriptor[] = {
    HID_USAGE_PAGE_SENSOR,
    HID_USAGE_SENSOR_TYPE_OTHER_CUSTOM,
    HID_COLLECTION(HID_APPLICATION),
        // Feature report 2 (read-only).
        HID_REPORT_ID(2),

        // Magic value: "#AndroidHeadTracker#2.0#1"
        HID_USAGE_SENSOR_PROPERTY_SENSOR_DESCRIPTION,
        HID_LOGICAL_MIN_8(0),
        HID_LOGICAL_MAX_8(0xFF),
        HID_REPORT_SIZE(8),
        HID_REPORT_COUNT(25),
        HID_FEATURE(HID_CONST_VAR_ABS),

        // UUID.
        HID_USAGE_SENSOR_PROPERTY_PERSISTENT_UNIQUE_ID,
        HID_LOGICAL_MIN_8(0),
        HID_LOGICAL_MAX_8(0xFF),
        HID_REPORT_SIZE(8),
        HID_REPORT_COUNT(16),
        HID_FEATURE(HID_CONST_VAR_ABS),

        // Feature report 1 (read/write).
        HID_REPORT_ID(1),

        // 1-bit on/off reporting state.
        HID_USAGE_SENSOR_PROPERTY_REPORTING_STATE,
        HID_LOGICAL_MIN_8(0),
        HID_LOGICAL_MAX_8(1),
        HID_REPORT_SIZE(1),
        HID_REPORT_COUNT(1),
        HID_COLLECTION(HID_LOGICAL),
            HID_USAGE_SENSOR_PROPERTY_REPORTING_STATE_NO_EVENTS,
            HID_USAGE_SENSOR_PROPERTY_REPORTING_STATE_ALL_EVENTS,
            HID_FEATURE(HID_DATA_ARR_ABS),
        HID_END_COLLECTION,

        // 1-bit on/off power state.
        HID_USAGE_SENSOR_PROPERTY_POWER_STATE,
        HID_LOGICAL_MIN_8(0),
        HID_LOGICAL_MAX_8(1),
        HID_REPORT_SIZE(1),
        HID_REPORT_COUNT(1),
        HID_COLLECTION(HID_LOGICAL),
            HID_USAGE_SENSOR_PROPERTY_POWER_STATE_D4_POWER_OFF,
            HID_USAGE_SENSOR_PROPERTY_POWER_STATE_D0_FULL_POWER,
            HID_FEATURE(HID_DATA_ARR_ABS),
        HID_END_COLLECTION,

        // 6-bit reporting interval, with values [0x00..0x3F] corresponding to [10ms..100ms].
        HID_USAGE_SENSOR_PROPERTY_REPORT_INTERVAL,
        HID_LOGICAL_MIN_8(0x00),
        HID_LOGICAL_MAX_8(0x3F),
        HID_PHYSICAL_MIN_8(10),
        HID_PHYSICAL_MAX_8(100),
        HID_REPORT_SIZE(6),
        HID_REPORT_COUNT(1),
        HID_USAGE_SENSOR_UNITS_SECOND,
        HID_UNIT_EXPONENT(0xD),  // 10^-3
        HID_FEATURE(HID_DATA_VAR_ABS),

        // 1-bit transport selection
        HID_USAGE_SENSOR_PROPERTY_VENDOR_LE_TRANSPORT,
        HID_LOGICAL_MIN_8(0),
        HID_LOGICAL_MAX_8(1),
        HID_REPORT_SIZE(1),
        HID_REPORT_COUNT(1),
        HID_COLLECTION(HID_LOGICAL),
            HID_USAGE_SENSOR_PROPERTY_VENDOR_LE_TRANSPORT_ACL,
            HID_USAGE_SENSOR_PROPERTY_VENDOR_LE_TRANSPORT_ISO,
            HID_FEATURE(HID_DATA_ARR_ABS),
        HID_END_COLLECTION,

        // Input report 1

        // Orientation as rotation vector (scaled to [-pi..pi] rad).
        HID_USAGE_SENSOR_DATA_CUSTOM_VALUE_1,
        HID_LOGICAL_MIN_16(0x01, 0x80), // LOGICAL_MINIMUM (-32767)
        HID_LOGICAL_MAX_16(0xFF, 0x7F), // LOGICAL_MAXIMUM (32767)
        HID_PHYSICAL_MIN_32(0x60, 0x4F, 0x46, 0xED),  // -314159265
        HID_PHYSICAL_MAX_32(0xA1, 0xB0, 0xB9, 0x12),  // 314159265
        HID_UNIT_EXPONENT(0x08),  // 10^-8
        HID_REPORT_SIZE(16),
        HID_REPORT_COUNT(3),
        HID_INPUT(HID_DATA_VAR_ABS),

        // Angular velocity as rotation vector (scaled to [-32..32] rad/sec).
        HID_USAGE_SENSOR_DATA_CUSTOM_VALUE_2,
        HID_LOGICAL_MIN_16(0x01, 0x80), // LOGICAL_MINIMUM (-32767)
        HID_LOGICAL_MAX_16(0xFF, 0x7F), // LOGICAL_MAXIMUM (32767)
        HID_PHYSICAL_MIN_8(0xE0),
        HID_PHYSICAL_MAX_8(0x20),
        HID_UNIT_EXPONENT(0x00),  // 10^0
        HID_REPORT_SIZE(16),
        HID_REPORT_COUNT(3),
        HID_INPUT(HID_DATA_VAR_ABS),

        // Reference frame reset counter.
        HID_USAGE_SENSOR_DATA_CUSTOM_VALUE_3,
        HID_LOGICAL_MIN_16(0x00, 0x00), // LOGICAL_MINIMUM (0)
        HID_LOGICAL_MAX_16(0xFF, 0x00), // LOGICAL_MAXIMUM (255)
        HID_PHYSICAL_MIN_8(0x00),
        HID_PHYSICAL_MAX_8(0x00),
        HID_UNIT_EXPONENT(0x00),  // 10^0
        HID_REPORT_SIZE(8),
        HID_REPORT_COUNT(1),
        HID_INPUT(HID_DATA_VAR_ABS),

    HID_END_COLLECTION,
};