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

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

Alex Shalamov
Alex Shalamov
Mikhail Pozdnyakov
Mikhail Pozdnyakov

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

מהו ה-General Sensor API?

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

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

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

בהשוואה לממשקים הקיימים, General 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 יש סחף שנובעת מרגישות הכבידה של החיישן, וכתוצאה מכך מעוותת את המערכת המכנית הפנימית של החיישן. גירוסקופים תנודעים בתדרים יחסית גבוהים, למשל 10 שניות של קילו-הרץ (kHz) ולכן עשויים לצרוך יותר חשמל בהשוואה לחיישנים אחרים.

חיישני כיוון

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

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 של האובייקטים של החיישן, המשתמש מגדיר אם הקריאות שמוחזרות יתקבלו בקואורדינטות device או screen.

// 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 הוא מופע מחלקה של שלושה.js Object3D שיש לו נכס 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.

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

Punchmeter

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

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.