פרוטוקול ממשק אנושי (HID) למעקב אחר ראש, שזמין למכשירים עם Android מגרסה 13 ואילך, מאפשר חיבור של מכשיר מעקב ראש למכשיר Android באמצעות USB או Bluetooth, ולחשוף אותו למסגרת ולאפליקציות של Android דרך מסגרת החיישנים. הפרוטוקול הזה משמש לשליטה באפקט של וירטואליזציה של אודיו (אודיו תלת-ממדי). הדף הזה משתמש במונחים מכשיר ומארח בחוש ה-Bluetooth. המשמעות של מכשיר היא המכשיר למעקב אחרי הראש ומארח הוא המארח של Android.
יצרני המכשירים חייבים להגדיר את מכשירי Android שלהם כך שיתמכו בפרוטוקול HID של מכשיר מעקב הראש. למידע מפורט יותר על הגדרה, ראו חיישנים דינמיים README.
בדף הזה אנחנו יוצאים מנקודת הנחה שאתם מכירים את המשאבים הבאים:
המבנה ברמה העליונה
מסגרת 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, כלומר, הנפח הכולל של המאפיין הזה הוא 23 תווים של 8 ביט בגרסה 1.0.
המאפיין הזה משמש כמאפיין מבדיל כדי למנוע התנגשויות עם חיישנים מותאמים אישית אחרים.
מאפיין: מזהה ייחודי קבוע (0x0302
)
המאפיין Persistent Unique ID (0x0302
) הוא מערך לקריאה בלבד של 16 רכיבים, כל אחד באורך 8 ביט (סה"כ 128 ביט). לא צפוי סימן סיום null. המאפיין הזה הוא אופציונלי.
המאפיין הזה מאפשר למכשירים למעקב אחר תנועות הראש שמשולבים בהתקני אודיו להתייחס להתקן האודיו שאליו הם מחוברים. יש תמיכה בסכמות הבאות.
מכשיר מעקב עצמאי לראש הדף
אם המאפיין Persistent Unique ID (0x0302
) לא קיים או מוגדר לכל אפסים, המשמעות היא שמכשיר מעקב הראש לא מחובר באופן קבוע למכשיר אודיו, וניתן להשתמש בו בנפרד. לדוגמה, אפשר לאפשר למשתמש לשייך באופן ידני את מכשיר מעקב הראש למכשיר אודיו נפרד.
הפניה באמצעות כתובת MAC של Bluetooth
Octet | 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
)
המאפיין 'מצב דיווח' (0x0316
) הוא מאפיין קריאה/כתיבה שכולל את הסמנטיקה הסטנדרטית כפי שהוגדרה במפרט ממשק ה-HID. המארח משתמש במאפיין הזה כדי לציין למכשיר על אילו אירועים לדווח. נעשה שימוש רק בערכים 'אין אירועים' (0x0840
) ו'כל האירועים' (0x0841
).
הערך הראשוני בשדה הזה חייב להיות 'No Events' (ללא אירועים), ואי אפשר לשנות אותו במכשיר, רק במארח.
מאפיין: מצב צריכת חשמל (0x0319
)
המאפיין Power State (0x0319
) הוא מאפיין קריאה/כתיבה שיש לו את הסמנטיקה הרגילה כפי שמוגדרת במפרט HID. המארח משתמש במאפיין הזה כדי לציין למכשיר באיזה מצב חשמל הוא צריך להיות. נעשה שימוש רק בערכים 'הספק מלא' (0x0851
) ו'כיבוי' (0x0855
).
הערך הראשוני של השדה הזה נקבע על ידי המכשיר, ואסור לשנות אותו במכשיר, אלא רק במארח.
מאפיין: Report Interval (מרווח הזמן בין הדוחות, 0x030E
)
המאפיין Report Interval (0x030E
) הוא מאפיין לקריאה/כתיבה שיש לו את הסמנטיקה הרגילה כפי שמוגדרת במפרט HID. המארח משתמש במאפיין הזה כדי לציין למכשיר באיזו תדירות לדווח על קריאות הנתונים שלו.
היחידות הן שניות. הטווח התקף של הערך הזה נקבע על ידי המכשיר ומתואר באמצעות המנגנון של תפקוד מינימלי/מקסימלי. צריכה להיות תמיכה בתדירות דיווח של 50 Hz לפחות, ותדירות הדיווח המקסימלית המומלצת היא 100 Hz. לכן, מרווח הדיווח המינימלי צריך להיות קטן מ-20ms או שווה לו, והוא מומלץ להיות גדול מ-10ms או שווה לו.
מאפיין: Vendor-reserved LE Transport (0xF410
)
המאפיין LE Transport (0xF410
) ששמור לשימוש היצרן הוא מאפיין קריאה/כתיבה שיש לו את הסמנטיקה הרגילה כפי שמוגדרת במפרט HID. המארח משתמש במאפיין הזה כדי לציין את התעבורה שנבחרה (ACL או ISO). המערכת משתמשת רק בערכים ACL (0xF800
) ו-ISO (0xF801
), וצריך לכלול את שניהם באוסף הלוגי.
הנכס הזה מוגדר לפני המצבים של מצב ההפעלה או של הדיווח.
שדה נתונים: ערך מותאם אישית 1 (0x0544
)
השדה Custom Value 1 (0x0544
) הוא שדה קלט שמשמש לדיווח על המידע בפועל של מעקב אחר תנועות הראש. זוהי מערך של 3 רכיבים, שמתפרש בהתאם לכללי ה-HID הרגילים לערכים פיזיים כפי שמפורט בקטע 6.2.2.7 במפרט ה-HID. הטווח החוקי לכל רכיב הוא [-π, π] rad. היחידות תמיד רדיאנים.
הרכיבים מפורשים כך: [rx, ry, rz]
, כך ש-[rx, ry, rz]
הוא וקטור סיבוב, שמייצג את הטרנספורמציה ממסגרת ההפניה למסגרת הראשית.
העוצמה חייבת להיות בטווח [0..Org].
מסגרת העזר היא שרירותית, אבל בדרך כלל היא קבועה וחובה שהיא תהיה ימנית. אפשר להשתמש בכמות קטנה של סחף. צירי הראש הם:
- 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 State מוגדר כ-All Events.
- הנכס 'מרווח דיווח' אינו אפס.
המאפיין 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, המפורטות בקטע שימושים בחיישני 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,
};