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

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

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

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

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

מסגרת 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, כלומר, הנפח הכולל של המאפיין הזה הוא 23 תווים של 8 ביט בגרסה 1.0.

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

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

המאפיין Persistent Unique ID‏ (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, שמירשם במסגרת Android באמצעות מנגנון לא ידוע שספציפי לסוג התעבורה שבו נעשה שימוש.

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

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

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

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

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

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

מאפיין: Report Interval (מרווח הזמן בין הדוחות, 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. הטווח החוקי לכל רכיב הוא [-π, π] rad. יחידות הן תמיד רדיאנים.

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

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

  • 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) כשכל התנאים הבאים מתקיימים:

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

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

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

פרוטוקול ה-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, המפורטות בקטע שימושים בחיישני HID (סעיף 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 למכשיר שתומך רק בתעבורת 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,
};