פרוטוקול HID של מכשיר מעקב תנועות הראש

פרוטוקול למכשיר מעקב אנושי (HID), שזמין למכשירים עם Android מגרסה 13 ואילך, מכשיר מעקב ראש לחיבור למכשיר Android באמצעות USB או להתחבר ל-Bluetooth ולאפליקציות של Android דרך sensors. הפרוטוקול הזה משמש שליטה אפקט וירטואליזציה של אודיו (אודיו בתלת-ממד). הדף הזה משתמש במונחים מכשיר ו host בחוש ה-Bluetooth שלהם. המשמעות של device היא המכשיר למעקב אחרי הראש. ו-host הוא המארח של Android.

יצרני מכשירים חייבים להגדיר את מכשירי Android שלהם כך שיוכלו להפעיל תמיכה את פרוטוקול ממשק אנושי (HID) של מכשיר המעקב אחר תנועות הראש. לקבלת מידע מפורט יותר על של ההגדרות האישיות, אפשר לעיין חיישנים דינמיים README.

הדף הזה מבוסס על ההנחה שאתם מכירים את המשאבים הבאים:

מבנה ברמה העליונה

ה-framework של Android מזהה את המכשיר למעקב אחרי הראש כמכשיר ממשק אנושי (HID).

הדוגמה המלאה של מתאר HID תקין זמינה כאן: נספח 1: דוגמה לתיאור HID.

ברמה העליונה, מכשיר המעקב אחרי הראש הוא אוסף אפליקציות עם דף אחד (Sensors) (0x20) והשימוש ב-Other: Custom (0xE1). בתוך איסוף הם מספר שדות נתונים (קלט) ומאפיינים (תכונות).

שדות נתונים ונכסים

בקטע הזה מתוארים שדות הנתונים והמאפיינים באפליקציה של מכשיר למעקב אחר תנועת הראש.

מאפיין: תיאור החיישן (0x0308)

מאפיין תיאור החיישן (0x0308) הוא מחרוזת ASCII לקריאה בלבד (8 ביט) חייב להכיל את הערכים הבאים:

מכשיר מעקב תנועות הראש גרסה 1.0:

#AndroidHeadTracker#1.0

מכשיר מעקב אחר תנועות הראש גרסה 2.0 (זמין ב-Android 15 או גבוהה יותר), שכוללת תמיכה ב-LE audio:

#AndroidHeadTracker#2.0#x

הערך x הוא מספר שלם (1, 2, 3) שמציין את העברת התמיכה:

  • 1: רשימת ACL
  • 2: ISO
  • 3: ACL + ISO

לא צפוי ערך null null כלומר הגודל הכולל של הנכס הזה. הוא 23 תווים של 8 ביט לגרסה 1.0.

הנכס הזה משמש כגורם מבדילה כדי למנוע התנגשויות עם בהתאמה אישית.

מאפיין: מזהה ייחודי קבוע (0x0302)

מאפיין המזהה הייחודי הקבוע (0x0302) הוא מערך לקריאה בלבד של 16 8 סיביות לכל אחד (סה"כ 128 ביט). לא צפוי תו null. הזה הוא אופציונלי.

המאפיין הזה מאפשר מכשירים למעקב אחר תנועות הראש שמשולבים באודיו כדי להפנות להתקן האודיו שאליו הם מחוברים. הסכימות הבאות נתמכות.

מכשיר מעקב עצמאי אחר הראש

אם המאפיין 'מזהה ייחודי קבוע' (0x0302) לא קיים או שהוא מוגדר כ'הכול' אפסים, המשמעות היא שמכשיר המעקב אחר הראש לא מחובר באופן קבוע התקן אודיו ואפשר להשתמש בו בנפרד, לדוגמה, על ידי מתן אפשרות למשתמש לשייך באופן ידני את מכשיר המעקב הראש למכשיר אודיו נפרד.

הפניה לכתובת MAC של Bluetooth

אוקטט 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
ערך 0 0 0 0 0 0 0 0 B T כתובת MAC של Bluetooth

בסכימה הזו, 8 האוקטטים הראשונים חייבים להיות 0, אוקטטים 8 ו-9 חייבים להכיל ערכי ASCII B ו-T בהתאמה, ו-6 אוקטטים הבאים מפורשת ככתובת MAC של Bluetooth, בהנחה שמכשיר המעקב אחר ראש המכשיר חל על כל מכשיר אודיו עם כתובת ה-MAC הזו. הכתובת הזאת צריכה להיות גם אם המכשיר משתמש בכתובת MAC אקראית בחיבורים. מכשירים דו-מצביים מתחברים באמצעות Bluetooth הקלאסי (פורמט HID v1.0) ו-Bluetooth LE (פורמט HID v2.0) חייבים לחשוף שני מכשירי HID תיאורים עם אותה כתובת זהות. מכשירים דו-מצביים עם הפרדה מכשירים שמאלה וימינה חייבים לחשוף את Bluetooth LE HID באמצעות במצב המכשיר ולא במכשיר המשני LE בלבד.

קובץ עזר באמצעות UUID

בכל פעם שמוגדר הביט המשמעותי ביותר (MSB) של 8 תווים (≥0x80), השדה מפורשות כ-UUID, כפי שמצוין RFC-4122. מכשיר האודיו התואם מספק את אותו UUID, שרשום על באמצעות מנגנון לא מוגדר שספציפי סוג התעבורה שבו נעשה שימוש.

נכס: מצב הדיווח (0x0316)

המאפיין 'מצב דיווח' (0x0316) הוא נכס קריאה/כתיבה שכולל את סמנטיקה סטנדרטית כפי שמוגדר במפרט ממשק אנושי (HID). המארח משתמש כדי לציין למכשיר על אילו אירועים לדווח. רק הערכים מסוג 'לא' נעשה שימוש באירועים (0x0840) וב'כל האירועים' (0x0841).

הערך הראשוני בשדה הזה חייב להיות 'ללא אירועים', ולעולם לא להיות שונה על ידי המכשיר, רק על ידי המארח.

מאפיין: מצב חשמל (0x0319)

מאפיין מצב Power (0x0319) הוא מאפיין קריאה/כתיבה עם סמנטיקה סטנדרטית כפי שמוגדר במפרט ממשק אנושי (HID). המארח משתמש כדי לציין למכשיר באיזה מצב חשמל הוא צריך להיות. רק ערכים של 'הפעלה מלאה' (0x0851) ו'כיבוי' (0x0855).

הערך הראשוני בשדה הזה נקבע על ידי המכשיר, ולעולם לא צריך להיות שונה על ידי המכשיר, רק על ידי המארח.

מאפיין: מרווח בין דוחות (0x030E)

המאפיין 'מרווח בין דוחות' (0x030E) הוא מאפיין קריאה/כתיבה שכולל את סמנטיקה סטנדרטית כפי שמוגדר במפרט ממשק אנושי (HID). המארח משתמש כדי לציין למכשיר באיזו תדירות לדווח על קריאות הנתונים שלו. היחידות הן שניות. הטווח החוקי של הערך הזה נקבע לפי המכשיר ומתואר באמצעות מנגנון מינימום/מקסימום פיזית. לפחות 50 Hz שיעור הדיווח חייב להיות נתמך, ושיעור הדיווח המקסימלי המומלץ הוא 100 Hz לכן, המרווח המינימלי בין הדיווח חייב להיות שווה או קטן מ- עד 20 אלפיות השנייה, ומומלץ שהאורך יהיה גדול מ-10 אלפיות השנייה או שווה לו.

נכס: LE Transport בהזמנה של ספק (0xF410)

מאפיין LE Transport (0xF410) שמור לספק הוא נכס קריאה/כתיבה שכולל את הסמנטיקה הסטנדרטית כפי שמוגדר במפרט ממשק אנושי (HID). המארח/ת. משתמשת במאפיין הזה כדי לציין את התעבורה שנבחרה (ACL או ISO). רק נעשה שימוש בערכי ACL (0xF800) ו-ISO (0xF801), וצריך לכלול את שניהם באוסף הלוגי.

הנכס הזה מוגדר לפני מצב ההפעלה או הדיווח.

שדה נתונים: ערך מותאם אישית 1 (0x0544)

השדה 'ערך מותאם אישית 1' (0x0544) הוא שדה להזנת קלט שמשמש לדיווח על מידע בפועל על מעקב אחר תנועות הראש. זהו מערך של 3 רכיבים, מפרש לפי כללי ממשק אנושי (HID) הרגילים לערכים הפיזיים כפי שמפורט בסעיף 6.2.2.7 של ממפרט ממשק אנושי (HID). הטווח התקף לכל רכיב הוא [- הלמידה האישית, לחצן העדכני]. יחידות הם תמיד רדיאנים.

הרכיבים מפורשים כך: [rx, ry, rz], כך ש-[rx, ry, rz] הוא וקטור הסיבוב, שמייצג את הטרנספורמציה ממסגרת העזר למסגרת הראש. העוצמה חייבת להיות בטווח [0..Sequence].

מסגרת העזר שרירותית, אבל בדרך כלל היא קבועה וחייבת להיות ליד ימין. אפשר להשתמש בכמות קטנה של סחף. צירי הראש הם:

  • X מאוזן שמאל לימין
  • Y מהחלק האחורי של הראש אל האף (חזרה לחזית)
  • Z מהצוואר ועד לראש הראש

שדה נתונים: ערך מותאם אישית 2 (0x0545)

השדה 'ערך מותאם אישית 2' (0x0545) הוא שדה להזנת קלט שמשמש לדיווח על מידע בפועל על מעקב אחר תנועות הראש. זה מערך בן 3 רכיבים בנקודות קבועות, מפורשים בהתאם לכללי ממשק אנושי (HID) הרגילים לגבי ערכים פיזיים. היחידות הן תמיד רדיאנים/שנייה.

הרכיבים מפורשים כך: [vx, vy, vz], כך ש-[vx, vy, vz] הוא וקטור הסיבוב, שמייצג את המהירות הזוויתית של מסגרת הראש (יחסית לעצמו).

שדה נתונים: ערך מותאם אישית 3 (0x0546)

השדה 'ערך מותאם אישית 3' (0x0546) הוא שדה להזנת קלט שמשמש למעקב חוסר עקביות במסגרת ההפניה. זהו מספר שלם סקלרי באורך 8 ביט גודל. צריך להגדיל את הערך הזה (בעיגול) על ידי המכשיר בכל פעם מסגרת הייחוס משתנה, לדוגמה, אם אלגוריתם של מסנן כיוון ששימש לקביעת הכיוון שבו בוצע איפוס. הערך הזה הוא מפורשים בהתאם לכללי ממשק אנושי (HID) הרגילים לגבי ערכים פיזיים. אבל, לפעמים הערך הפיזי והיחידות לא חשובים. המידע היחיד שרלוונטי המארח הוא ערך שהשתנה. כדי להימנע מבעיות מספריות שקשורות לאובדן הדיוק בזמן ההמרה מיחידות לוגיות ליחידות פיזיות, מומלץ להגדיר עבור מינימום פיזי, מקסימום פיזי ומעריך יחידה לאפס עבור שדה זה.

מבנה הדוח

הקיבוץ של נכסים לדוחות (לפי הקצאה של מזהי דוחות) הוא גמישה. כדי לשפר את היעילות, מומלץ להפריד בין המאפיינים לקריאה בלבד. ממאפייני קריאה/כתיבה.

בשדות הנתונים, השדות 'ערך מותאם אישית 1', '2 ו-3' חייבים להיות זהים להיות כלולים בדוח אחד בלבד לגבי מכשיר נתון (אוסף אפליקציות).

שליחת דוחות על קלט

המכשיר חייב מעת לעת ובאופן אסינכרוני (באמצעות הודעות HID INPUT) שליחת דוחות קלט כאשר כל התנאים הבאים מתקיימים:

  • המאפיין 'מצב כוח' מוגדר כ'חשמל מלא'.
  • הנכס 'מצב דיווח' מוגדר כ'כל האירועים'.
  • הנכס 'מרווח הזמן לדיווח' אינו אפס.

הנכס 'מרווח דיווח' קובע את התדירות שבה הדוחות יישלחו. מתי אף אחד מהתנאים שלמעלה לא מתקיים, המכשיר לא יכול לשלוח דיווחים.

תאימות קדימה ואחורה

פרוטוקול ממשק אנושי (HID) של מכשיר מעקב ראש משתמש בסכמת ניהול גרסאות שמאפשרת ובמקביל לאפשר יכולת פעולה הדדית בין מארח למכשיר שמשתמש גרסאות שונות של הפרוטוקול. המערכת מזהה גרסאות של הפרוטוקול בשני מספרים, ראשי וקטין, שיש להם סמנטיקה מובהקת שמתוארים בסעיפים הבאים.

אפשר לקבוע מהן הגרסאות שנתמכות במכשיר על ידי עיון מאפיין תיאור החיישן (0x0308) שלו.

תאימות לגרסאות משניות

שינויים בגרסה המשנית תואמים לאחור עם גרסאות משניות קודמות גרסאות שמבוססות על אותה הגרסה הראשית. בעדכונים לגבי הקטין או הקטינה אבל המארח מתעלם משדות נתונים נוספים וממאפיינים נוספים. לדוגמה, מכשיר שמשתמש בפרוטוקול גרסה 1.6 תואם למארח שתומך גרסה 1.x של פרוטוקול, כולל גרסה 1.5.

תאימות לגרסאות ראשיות

אפשר לבצע שינויים שלא תואמים לאחור עבור שינויים בגרסאות הראשיות. שפת תרגום לתמוך בכמה גרסאות ראשיות בשביל יכולת פעולה הדדית עם מארחים ישנים וחדשים, מכשירים יכולים לציין כמה אוספים של אפליקציות בדוח שלהם ומתארים. לדוגמה:

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,
};

במקרה הזה, המארח יכול לספור את כל האוספים השונים של האפליקציות. שפורסמו על ידי המכשיר, תוך בחינת נכס תיאור החיישן לקבוע אילו גרסאות פרוטוקול כל אחת מהן מטמיעה, ואז לבחור גרסת הפרוטוקול העדכנית ביותר שהמארח תומך בה. כשהמארח נבחר, הוא פועל באמצעות הפרוטוקול היחיד שנבחר לכל משך החיים של המכשיר. חיבור כזה.

נספח: דוגמה לתיאור HID

הדוגמה הבאה ממחישה מתאר מתאר HID חוקי טיפוסי. היא משתמשת ב בפקודות מאקרו C, שימוש בחיישן ממשק אנושי (סעיף 4.1).

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,
};

נספח 2: דוגמה למתאר HID v2.0

הדוגמה הבאה ממחישה מתאר HID v2.0 עבור מכשיר שתומך רק את התעבורה של Bluetooth LE ACL.

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,
};