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

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

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

מה זה Generic Sensor API?

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

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

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

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

  • Generic Sensor API הוא מסגרת חיישנים שאפשר להרחיב בקלות באמצעות כיתות חיישנים חדשות, וכל אחת מהכיתות האלה תמשיך להשתמש בממשק הכללי. אפשר לעשות שימוש חוזר בקוד הלקוח שנכתב לסוג אחד של חיישן גם בסוג אחר, עם מעט מאוד שינויים.
  • אפשר להגדיר את החיישן. לדוגמה, אפשר להגדיר את תדירות הדגימה בהתאם לצרכים של האפליקציה.
  • אפשר לבדוק אם חיישן זמין בפלטפורמה.
  • לקריאות החיישן יש חותמות זמן מדויקות מאוד, שמאפשרות סנכרון טוב יותר עם פעילויות אחרות באפליקציה.
  • מודלים של נתוני חיישנים ומערכות קואורדינטות מוגדרים בבירור, ומאפשרים לספקי דפדפנים להטמיע פתרונות עם יכולת פעולה הדדית.
  • הממשקים מבוססי החיישן הגנרי לא מקושרים ל-DOM (כלומר, הם לא אובייקטים מסוג navigator או window), וכך נוצרות הזדמנויות עתידיות לשימוש ב-API בתוך שירותי עובדים או להטמיע אותו בסביבות זמן ריצה של JavaScript ללא ראש (headless), כמו מכשירים מוטמעים.
  • היבטי האבטחה והפרטיות הם בראש סדר העדיפויות של Generic Sensor 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;
  }
}

פוליפיל

לדפדפנים שלא תומכים ב-Generic Sensor API, זמין polyfill. ה-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 m/s2, וכשהמכשיר מונח ישר על שולחן, האצה בכיוון כלפי מעלה (ציר Z) תהיה שווה לכוח המשיכה של כדור הארץ, כלומר g ≈ +9.8 m/s2, כי הוא מודד את הכוח של השולחן שדוחף את המכשיר כלפי מעלה. אם דוחפים את המכשיר ימינה, האצה בציר X תהיה חיובית, או שלילית אם המכשיר מאיץ מימין לשמאל.

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

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

חיישן כבידה

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

ג'ירוסקופ

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

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

חיישני כיוון

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

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

כל המסגרות המודרניות של JavaScript ל-3D תומכות בקוואטרניונים ובמטריצות רוטציה כדי לייצג רוטציה. עם זאת, אם משתמשים ב-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();

BABYLON

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. הגישה הזו לא יעילה, והיא גם מגדילה באופן משמעותי את המורכבות של קוד אפליקציית האינטרנט. אפליקציית האינטרנט צריכה לעקוב אחרי שינויים בכיוון המסך ולבצע טרנספורמציות של קואורדינטות לקריאות החיישן, וזו לא פעולה פשוטה לביצוע עבור זוויות אוילר או קוואטרניונים.

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

// 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' });

קדימה, תכנות!

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

סביבת פיתוח

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

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

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

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

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

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

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

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

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

רק HTTPS

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

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

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

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

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

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

יכול להיות שהעברת הנתונים של מדידות החיישן תושהה

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

מה השלב הבא?

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

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

אתם יכולים לעזור!

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

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

משאבים

תודות

המאמר הזה נבדק על ידי Joe Medley ו-Kayce Basques.