פרוטוקול ה-HID של מכשיר מעקב אחר תנועות הראש, שזמין במכשירים עם Android מגרסה 13 ואילך, מאפשר לחבר מכשיר מעקב אחר תנועות הראש למכשיר Android באמצעות USB או Bluetooth, ולהציג אותו למסגרת ולאפליקציות של Android דרך המסגרת sensors. הפרוטוקול הזה משמש לשליטה באפקט של וירטואליזציה של אודיו (אודיו תלת-ממדי). בדף הזה נעשה שימוש במונחים מכשיר ומארח במובן של Bluetooth, כאשר מכשיר הוא מכשיר מעקב אחר תנועות הראש ומארח הוא מארח Android.
יצרני המכשירים חייבים להגדיר את מכשירי Android שלהם כך שיאפשרו תמיכה בפרוטוקול HID של מכשיר מעקב אחר תנועות הראש. למידע מפורט יותר על ההגדרה, אפשר לעיין בקובץ ה-README של Dynamic Sensors.
בדף הזה נדרש להכיר את המשאבים הבאים:
המבנה ברמה העליונה
מסגרת 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
אוקטט | 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
).
הערך הראשוני בשדה הזה חייב להיות 'No Events' (ללא אירועים), ואי אפשר לשנות אותו במכשיר, אלא רק במארח.
מאפיין: מצב הסוללה (0x0319
)
המאפיין Power State (0x0319
) הוא מאפיין קריאה/כתיבה שיש לו את הסמנטיקה הרגילה כפי שמוגדרת במפרט HID. המארח משתמש במאפיין הזה כדי לציין למכשיר באיזה מצב צריך להיות המכשיר. נעשה שימוש רק בערכים 'הספק מלא' (0x0851
) ו'כיבוי' (0x0855
).
הערך הראשוני של השדה הזה נקבע על ידי המכשיר, ואסור לשנות אותו במכשיר, אלא רק במארח.
מאפיין: Report Interval (מרווח הזמן בין הדוחות, 0x030E
)
המאפיין Report Interval (0x030E
) הוא מאפיין לקריאה/כתיבה שיש לו את הסמנטיקה הרגילה כפי שמוגדרת במפרט HID. המארח משתמש במאפיין הזה כדי לציין למכשיר באיזו תדירות לדווח על קריאות הנתונים שלו.
היחידות הן שניות. הטווח החוקי של הערך הזה נקבע על ידי המכשיר ומתואר באמצעות מנגנון Physical Min/Max. צריכה להיות תמיכה בתדירות דיווח של 50 Hz לפחות, ותדירות הדיווח המקסימלית המומלצת היא 100 Hz. לכן, מרווח הדיווח המינימלי צריך להיות קטן מ-20 ms או שווה לו, והוא מומלץ להיות גדול מ-10 ms או שווה לו.
מאפיין: 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
)
השדה Custom Value 2 (0x0545
) הוא שדה קלט שמשמש לדיווח על פרטי המעקב אחר תנועות הראש בפועל. זהו מערך של 3 רכיבים בנקודת קבועה, שמתורגם בהתאם לכללי ה-HID הרגילים לערכים פיזיים.
היחידות הן תמיד רדיאנים לשנייה.
הרכיבים מפורשים כך: [vx, vy, vz]
, כאשר [vx, vy, vz]
הוא וקטור סיבוב שמייצג את המהירות הזוויתית של מסגרת הראש (ביחס לעצמה).
שדה נתונים: ערך מותאם אישית 3 (0x0546
)
השדה 'ערך מותאם אישית 3' (0x0546
) הוא שדה קלט המשמש למעקב אחרי הפסקות במסגרת העזר. זהו מספר שלם סקלר בגודל 8 ביט. המכשיר צריך להגדיל אותו (עם חזרה לתחילת המחרוזת) בכל פעם שמתבצע שינוי במסגרת העזר, למשל אם המצב של אלגוריתם מסנן הכיוון שמשמש לקביעת הכיוון אופס. הערך הזה מפורש בהתאם לכללי HID הרגילים לערכים פיזיים. עם זאת, הערך הפיזי והיחידות לא חשובים. המידע היחיד שרלוונטי למארח הוא ערך שהשתנה. כדי למנוע בעיות מספריות שקשורות לאובדן דיוק במהלך המרה מיחידות לוגיות ליחידות פיזיות, מומלץ להגדיר את הערכים של הערך המינימלי הפיזי, הערך המקסימלי הפיזי והחזקה של היחידות לאפס בשדה הזה.
מבנה הדוח
אפשר לקבץ נכסים בדוחות (באמצעות הקצאת מזהי דוחות) באופן גמיש. כדי לשפר את היעילות, מומלץ להפריד בין הנכסים לקריאה בלבד לבין הנכסים לקריאה/כתיבה.
בשדות הנתונים, השדות 'ערך מותאם אישית 1', 'ערך מותאם אישית 2' ו'ערך מותאם אישית 3' חייבים להופיע באותו דוח, ובדוח אחד בלבד לכל מכשיר נתון (אוסף אפליקציות).
שליחת דוחות קלט
המכשיר צריך לשלוח דוחות קלט באופן סדיר ולא סינכרוני (באמצעות הודעות HID INPUT) כשכל התנאים הבאים מתקיימים:
- הנכס Power State מוגדר ל-Full Power.
- הערך של המאפיין Reporting State מוגדר ל-All Events (כל האירועים).
- הערך של המאפיין Reporting Interval שונה מאפס.
המאפיין 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 Sensor Usages (קטע 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,
};