חיישנים לאינטרנט

אפשר להשתמש ב-General Sensor API כדי לקבל גישה לחיישנים במכשיר כמו מדי תאוצה, ג'ירוסקופים ומגנטומטרים.

Alex Shalamov
Alex Shalamov
Mikhail Pozdnyakov
Mikhail Pozdnyakov

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

מה זה General Sensor API?

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

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

אתם עשויים לחשוב שפלטפורמת האינטרנט כבר מספקת נתוני חיישנים, ואתם צודקים לגמרי! במכונה, אירועים של DeviceMotion ו-DeviceOrientation חושפים נתונים מחיישני התנועה. אז למה אנחנו צריכים API חדש?

בהשוואה לממשקים הקיימים, ה-General Sensor API מספק מספר רב של יתרונות:

  • ממשק API גנרי של חיישן הוא מסגרת חיישנים שאפשר להרחיב בקלות עם סיווגי חיישנים חדשים, וכל אחד מהסיווגים האלה ישמור על הממשק הגנרי. אפשר להשתמש שוב בקוד הלקוח שנכתב בסוג חיישן אחד כדי לעשות שימוש חוזר בקוד אחר, עם מעט מאוד שינויים.
  • אפשר להגדיר את החיישן. לדוגמה, תוכלו להגדיר את תדירות הדגימה בהתאם לצורכי האפליקציה שלכם.
  • אפשר לזהות אם חיישן זמין בפלטפורמה.
  • לקריאות מהחיישנים יש חותמות זמן מדויקות יותר, כדי לאפשר סנכרון טוב יותר עם פעילויות אחרות באפליקציה.
  • מודלים של נתונים ומערכות קואורדינטות מוגדרים בבירור, וכך ספקי דפדפנים יכולים להטמיע פתרונות עם יכולת פעולה הדדית.
  • הממשקים מבוססי החיישנים הגנריים לא קשורים ל-DOM (כלומר הם לא אובייקטים מסוג navigator ולא אובייקטים מסוג window), ולכן פותחות הזדמנויות עתידיות לשימוש ב-API עם קובצי שירות (service worker) או להטמעה שלו בסביבות זמן ריצה של JavaScript ללא GUI, כמו מכשירים מוטמעים.
  • היבטי אבטחה ופרטיות נמצאים בראש סדר העדיפויות של ממשק ה-API של חיישן גנרי, ומספקים אבטחה הרבה יותר טובה בהשוואה לממשקי API ישנים יותר של חיישנים. יש שילוב עם Permissions API.
  • סנכרון אוטומטי עם קואורדינטות של המסך זמין עבור Accelerometer, Gyroscope, LinearAccelerationSensor, AbsoluteOrientationSensor, RelativeOrientationSensor ו-Magnetometer.

ממשקי API של חיישן גנריים זמינים

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

חיישני תנועה:

  • Accelerometer
  • Gyroscope
  • LinearAccelerationSensor
  • AbsoluteOrientationSensor
  • RelativeOrientationSensor
  • GravitySensor

חיישנים סביבתיים:

  • AmbientLightSensor (מאחורי הדגל #enable-generic-sensor-extra-classes ב-Chromium).
  • Magnetometer (מאחורי הדגל #enable-generic-sensor-extra-classes ב-Chromium).

זיהוי תכונות

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

if ('Accelerometer' in window) {
  // The `Accelerometer` interface is supported by the browser.
  // Does the device have an accelerometer, though?
}

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

let accelerometer = null;
try {
  accelerometer = new Accelerometer({ frequency: 10 });
  accelerometer.onerror = (event) => {
    // Handle runtime errors.
    if (event.error.name === 'NotAllowedError') {
      console.log('Permission to access sensor was denied.');
    } else if (event.error.name === 'NotReadableError') {
      console.log('Cannot connect to the sensor.');
    }
  };
  accelerometer.onreading = (e) => {
    console.log(e);
  };
  accelerometer.start();
} catch (error) {
  // Handle construction errors.
  if (error.name === 'SecurityError') {
    console.log('Sensor construction was blocked by the Permissions Policy.');
  } else if (error.name === 'ReferenceError') {
    console.log('Sensor is not supported by the User Agent.');
  } else {
    throw error;
  }
}

פוליפיל

אפשר להשתמש ב-polyfill לדפדפנים שלא תומכים ב-General Sensor API. ה-Polyfill מאפשר לכם לטעון רק את היישומים של החיישנים הרלוונטיים.

// Import the objects you need.
import { Gyroscope, AbsoluteOrientationSensor } from './src/motion-sensors.js';

// And they're ready for use!
const gyroscope = new Gyroscope({ frequency: 15 });
const orientation = new AbsoluteOrientationSensor({ frequency: 60 });

מהם כל החיישנים האלה? איך אוכל להשתמש בהם?

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

מד התאוצה וחיישן תאוצה ליניארית

מדידות בחיישן מד התאוצה

החיישן Accelerometer מודד את התאוצה של מכשיר שמארח את החיישן בשלושה צירים (X, Y ו-Z). החיישן הזה הוא חיישן אינרטיבי. כלומר, כשהמכשיר נמצא בנפילה חופשית ליניארית, התאוצה הכוללת שנמדדת תהיה 0 מ"ש2, וכשמכשיר מונח במצב שטוח על שולחן, התאוצה כלפי מעלה (ציר ה-Z) תהיה שווה לכוח הכבידה של כדור הארץ, כלומר g g / s +9.8 כלפי מעלה. אם דוחפים את המכשיר ימינה, התאוצה בציר ה-X תהיה חיובית או שלילית אם המכשיר מואץ מימין לשמאל.

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

LinearAccelerationSensor מודדת את התאוצה שמופעלת על המכשיר שמארח את החיישן, לא כולל התרומה של כוח הכבידה. כשהמכשיר במצב מנוחה, למשל כשהוא מונח על השולחן, החיישן ימדוד תאוצה של ≈ 0 מטר לשנייה2 בשלושה צירים.

חיישן כבידה

משתמשים כבר יכולים להסיק באופן ידני קריאות שקרובות לקריאות של חיישן כוח הכבידה על ידי בדיקה ידנית של קריאות Accelerometer ו-LinearAccelerometer, אבל זו יכולה להיות משימה מורכבת ותלויה בדיוק הערכים שמספקים החיישנים האלה. פלטפורמות כמו Android יכולות לספק קריאות של כוח הכבידה כחלק ממערכת ההפעלה, שאמורות להיות זולות יותר מבחינת החישוב, לספק ערכים מדויקים יותר בהתאם לחומרה של המשתמש, ולהיות קל יותר לשימוש במונחים של ארגונומיה של API. הפונקציה GravitySensor מחזירה את אפקט התאוצה לאורך ציר ה-X, ה-Y ו-Z של המכשיר עקב כוח הכבידה.

ג'ירוסקופ

מדידות בחיישן הג'ירוסקופ

החיישן Gyroscope מודד את המהירות הזוויתית ברדיאנים לשנייה סביב ציר X, Y ו-Z המקומי של המכשיר. ברוב המכשירים לצרכנים יש ג'ירוסקופים מכניים (MEMS), שהם חיישנים אינרטיביים שמודדים את קצב הסיבוב על סמך כוח קוריוליס האינרטיבי. בג'ירוסקופים של MEMS יש נטייה לסחף בגלל רגישות הכבידה של החיישן, שמשפיעה על המערכת המכנית הפנימית של החיישן. הג'ירוסקופים נעים בתדירויות גבוהות יחסית. לדוגמה, 10 שניות של קילו-הרץ ולכן יכול להיות שתצרוך יותר חשמל בהשוואה לחיישנים אחרים.

חיישני כיוון

מדידות של חיישן הכיוון המוחלט

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

כל המסגרות המודרניות של JavaScript בתלת-ממד תומכות בקווטרניונים ובמטריצות סיבוב על מנת לייצג סיבוב. עם זאת, אם משתמשים ב-WebGL ישירות, ל-OrientationSensor יש גם נכס quaternion וגם שיטתpopulateMatrix(). הנה כמה קטעי טקסט:

three.js

let torusGeometry = new THREE.TorusGeometry(7, 1.6, 4, 3, 6.3);
let material = new THREE.MeshBasicMaterial({ color: 0x0071c5 });
let torus = new THREE.Mesh(torusGeometry, material);
scene.add(torus);

// Update mesh rotation using quaternion.
const sensorAbs = new AbsoluteOrientationSensor();
sensorAbs.onreading = () => torus.quaternion.fromArray(sensorAbs.quaternion);
sensorAbs.start();

// Update mesh rotation using rotation matrix.
const sensorRel = new RelativeOrientationSensor();
let rotationMatrix = new Float32Array(16);
sensor_rel.onreading = () => {
  sensorRel.populateMatrix(rotationMatrix);
  torus.matrix.fromArray(rotationMatrix);
};
sensorRel.start();

בבילון

const mesh = new BABYLON.Mesh.CreateCylinder('mesh', 0.9, 0.3, 0.6, 9, 1, scene);
const sensorRel = new RelativeOrientationSensor({ frequency: 30 });
sensorRel.onreading = () => mesh.rotationQuaternion.FromArray(sensorRel.quaternion);
sensorRel.start();

WebGL

// Initialize sensor and update model matrix when new reading is available.
let modMatrix = new Float32Array([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]);
const sensorAbs = new AbsoluteOrientationSensor({ frequency: 60 });
sensorAbs.onreading = () => sensorAbs.populateMatrix(modMatrix);
sensorAbs.start();

// Somewhere in rendering code, update vertex shader attribute for the model
gl.uniformMatrix4fv(modMatrixAttr, false, modMatrix);

חיישני הכיוון מאפשרים תרחישי שימוש שונים, כמו גיימינג סוחף, מציאות רבודה ומציאות מדומה.

למידע נוסף על חיישני תנועה, תרחישים מתקדמים לדוגמה ודרישות, ניתן לעיין במסמך הסבר על חיישני תנועה.

סנכרון עם קואורדינטות של המסך

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

מערכת הקואורדינטות של המכשיר
מערכת הקואורדינטות של המכשיר

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

מערכת קואורדינטות מסך
מערכת קואורדינטות המסך

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

ה-General Sensor API מספק פתרון פשוט ואמין הרבה יותר! אפשר להגדיר את מערכת הקואורדינטות המקומית בכל סיווגי החיישנים המרחביים המוגדרים: Accelerometer, Gyroscope, LinearAccelerationSensor, AbsoluteOrientationSensor, RelativeOrientationSensor ו-Magnetometer. על ידי העברת האפשרות referenceFrame לבונה האובייקטים של החיישן, המשתמש מגדיר אם הקריאות שהוחזרו יטופלו בקואורדינטות של מכשיר או מסך.

// Sensor readings are resolved in the Device coordinate system by default.
// Alternatively, could be RelativeOrientationSensor({referenceFrame: "device"}).
const sensorRelDevice = new RelativeOrientationSensor();

// Sensor readings are resolved in the Screen coordinate system. No manual remapping is required!
const sensorRelScreen = new RelativeOrientationSensor({ referenceFrame: 'screen' });

קדימה, מתחילים!

ה-General Sensor API הוא פשוט וקל מאוד לשימוש! בממשק החיישן יש שיטות start() ו-stop() לשליטה במצב החיישנים, וכמה גורמים מטפלים באירועים לקבלת התראות לגבי הפעלת חיישנים, שגיאות וקריאות חדשות זמינות. בדרך כלל, סיווגי החיישנים הבטון מוסיפים את מאפייני הקריאה הספציפיים שלהם לסיווג הבסיס.

סביבת פיתוח

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

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

סיבוב מודל תלת-ממדי

בדוגמה הפשוטה הזו אנחנו משתמשים בנתונים מחיישן כיוון מוחלט כדי לשנות את סיבוב הסיבוב של מודל תלת-ממדי. model הוא מכונה של המחלקה 3.js מסוג Object3D, שיש לה נכס quaternion. קטע הקוד הבא מההדגמה של טלפון הכיוון מדגים איך אפשר להשתמש בחיישן הכיוון המוחלט כדי לסובב מודל תלת-ממדי.

function initSensor() {
  sensor = new AbsoluteOrientationSensor({ frequency: 60 });
  sensor.onreading = () => model.quaternion.fromArray(sensor.quaternion);
  sensor.onerror = (event) => {
    if (event.error.name == 'NotReadableError') {
      console.log('Sensor is not available.');
    }
  };
  sensor.start();
}

כיוון המכשיר ישתקף בסיבוב תלת-ממדי model בתוך סצנת WebGL.

כיוון עדכוני החיישן של המודל התלת-ממדי
עדכון הכיוון של מודל תלת-ממדי באמצעות החיישן

מחורר

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

this.maxSpeed = 0;
this.vx = 0;
this.ax = 0;
this.t = 0;

/* … */

this.accel.onreading = () => {
  let dt = (this.accel.timestamp - this.t) * 0.001; // In seconds.
  this.vx += ((this.accel.x + this.ax) / 2) * dt;

  let speed = Math.abs(this.vx);

  if (this.maxSpeed < speed) {
    this.maxSpeed = speed;
  }

  this.t = this.accel.timestamp;
  this.ax = this.accel.x;
};

המהירות הנוכחית מחושבת כקירוב לאינטגרל של פונקציית התאוצה.

אפליקציית אינטרנט להדגמה של מהירות האגרוף
מדידת מהירות ניקוב

ניפוי באגים וביטול חיישנים באמצעות כלי הפיתוח ל-Chrome

במקרים מסוימים לא צריך מכשיר פיזי כדי לשחק עם ה-General Sensor API. בכלי הפיתוח ל-Chrome יש תמיכה מעולה בסימולציה של כיוון המכשיר.

כלי הפיתוח ל-Chrome משמשים לביטול נתוני הכיוון המותאם אישית של טלפון וירטואלי
סימולציה של כיוון המכשיר בעזרת כלי הפיתוח ל-Chrome

פרטיות ואבטחה

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

רק HTTPS

מכיוון שממשק API גנרי של חיישן הוא תכונה חזקה, הדפדפן מאפשר זאת רק בהקשרים מאובטחים. בפועל, המשמעות היא שכדי להשתמש ב-General Sensor API, תצטרכו לגשת לדף דרך HTTPS. במהלך הפיתוח תוכלו לעשות זאת דרך http://localhost, אבל כדי להשתמש בסביבת הייצור תצטרכו HTTPS בשרת. למידע על שיטות מומלצות והנחיות, קראו את האוסף בטוח ומאובטח.

שילוב מדיניות ההרשאות

השילוב של מדיניות ההרשאות ב-API גנרי של חיישן שולט בגישה לנתוני החיישנים של פריים.

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

קטע הקוד הבא מתאר איך להעניק גישה לנתוני מד התאוצה ל-iframe ממקורות שונים, כלומר עכשיו ניתן ליצור שם אובייקטים Accelerometer או LinearAccelerationSensor.

<iframe src="https://third-party.com" allow="accelerometer" />

ניתן להשעות את ההעברה של קריאות חיישנים

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

מה השלב הבא?

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

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

יש לך אפשרות לעזור!

מפרטי החיישנים הגיע לשכבת הגיל של Candidate Recommendation, ולכן אנחנו מעריכים מאוד את המשוב שקיבלנו ממפתחי האתרים והדפדפנים. ספרו לנו אילו תכונות כדאי להוסיף או אם אתם רוצים לשנות משהו ב-API הנוכחי.

אתם יכולים לשלוח בעיות במפרט וגם bugs לגבי ההטמעה של Chrome.

משאבים

אישורים

המאמר הזה נכתב על ידי Joe Medley ו-Kayce Basques. תמונה ראשית (Hero) של Misko דרך Wikimedia Commons.