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

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

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

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

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

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

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

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

מאפיינים ושדות נתונים

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

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

המאפיין Sensor Description‏ (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)

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

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

מכשיר מעקב עצמאי לראש הדף

אם המאפיין Persistent Unique ID‏ (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 Classic‏ (בפורמט HID v1.0) ו-Bluetooth LE‏ (בפורמט HID v2.0) חייבים לחשוף שני מתארי HID עם אותה כתובת זהות. במכשירים עם שני מצבים עם מכשירי ימין ושמאל נפרדים, צריך לחשוף את Bluetooth LE HID באמצעות המכשיר הראשי עם שני המצבים במקום המכשיר המשני עם LE בלבד.

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

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

מאפיין: סטטוס הדיווח (0x0316)

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

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

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

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

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

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

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

מאפיין: Vendor-reserved LE Transport‏ (0xF410)

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

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

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

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

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

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

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

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

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

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

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

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

מבנה הדוח

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

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

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

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

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

המאפיין Reporting Interval (מרווח הזמן לדיווח) קובע את התדירות שבה נשלחים הדוחות. אם אף אחד מהתנאים שלמעלה לא מתקיים, המכשיר לא יכול לשלוח דוחות.

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

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

כדי לקבוע אילו גרסאות נתמכות במכשיר, בודקים את המאפיין Sensor Description‏ (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 בגרסה 2.0

הדוגמה הבאה ממחישה מתאר HID בגרסה 2.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,
};