ה-Web Bluetooth API מאפשר לאתרים לתקשר עם מכשירי Bluetooth.
מה אם הייתי אומר לכם שאתרים יכולים לתקשר עם מכשירי Bluetooth בקרבת מקום באופן מאובטח ושומר על הפרטיות? כך, מכשירי מעקב אחר קצב הלב, נורות ששרות ואפילו צבים יכולים ליצור אינטראקציה ישירה עם אתר.
עד עכשיו, רק אפליקציות ספציפיות לפלטפורמה יכלו לקיים אינטראקציה עם מכשירי Bluetooth. ממשק Web Bluetooth API נועד לשנות את המצב הזה ולהביא את האפשרות הזו גם לדפדפני אינטרנט.
לפני שמתחילים
ההנחה במאמר הזה היא שיש לכם ידע בסיסי לגבי אופן הפעולה של Bluetooth Low Energy (BLE) ושל פרופיל התכונות הכללי.
למרות שהמפרט של Web Bluetooth API עדיין לא סופי, מחברי המפרט מחפשים באופן פעיל מפתחים נלהבים שינסו את ה-API הזה וישלחו משוב על המפרט ומשוב על ההטמעה.
קבוצת משנה של Web Bluetooth API זמינה ב-ChromeOS, ב-Chrome ל-Android 6.0, ב-Mac (Chrome 56) וב-Windows 10 (Chrome 70). המשמעות היא שתוכלו לשלוח בקשה ולהתחבר למכשירי Bluetooth Low Energy בקרבת מקום, לקרוא/לכתוב מאפייני Bluetooth, לקבל התראות GATT, לדעת מתי מכשיר Bluetooth מתנתק ואפילו לקרוא ולכתוב מתארים של Bluetooth. מידע נוסף זמין בטבלת תאימות הדפדפנים של MDN.
ב-Linux ובגרסאות קודמות של Windows, מפעילים את הדגל #experimental-web-platform-features ב-about://flags.
זמין לגרסאות מקור לניסיון
כדי לקבל כמה שיותר משוב ממפתחים שמשתמשים ב-Web Bluetooth API בשטח, Chrome הוסיף בעבר את התכונה הזו ב-Chrome 53 כגרסת מקור לניסיון ל-ChromeOS, ל-Android ול-Mac.
תקופת הניסיון הסתיימה בהצלחה בינואר 2017.
דרישות אבטחה
כדי להבין את ההשלכות על האבטחה, מומלץ לקרוא את הפוסט Web Bluetooth Security Model של ג'פרי יאסקין, מהנדס תוכנה בצוות Chrome, שעובד על מפרט ה-Web Bluetooth API.
רק HTTPS
ממשק ה-API הניסיוני הזה הוא תכונה חדשה ועוצמתית שנוספה לאינטרנט, ולכן הוא זמין רק בהקשרים מאובטחים. כלומר, צריך לבנות את האפליקציה תוך התחשבות ב-TLS.
נדרשת תנועת משתמש
כאמצעי אבטחה, גילוי מכשירי Bluetooth באמצעות navigator.bluetooth.requestDevice חייב להיות מופעל על ידי פעולת משתמש כמו מגע או לחיצה על העכבר. אנחנו מדברים על האזנה לאירועים pointerup, click ו-touchend.
button.addEventListener('pointerup', function(event) {
// Call navigator.bluetooth.requestDevice
});
התעמקות בקוד
ה-API של Bluetooth באינטרנט מסתמך במידה רבה על Promises של JavaScript. אם אתם לא מכירים את המושגים האלה, כדאי לעיין במדריך הזה בנושא Promises. עוד דבר אחד,
() => {} הן פונקציות חץ של ECMAScript 2015.
בקשה ממכשירי Bluetooth
הגרסה הזו של מפרט Web Bluetooth API מאפשרת לאתרים שפועלים בתפקיד Central להתחבר לשרתי GATT מרוחקים דרך חיבור BLE. הוא תומך בתקשורת בין מכשירים עם Bluetooth 4.0 ומעלה.
כשאתר מבקש גישה למכשירים בקרבת מקום באמצעות navigator.bluetooth.requestDevice, הדפדפן מציג למשתמש חלון לבחירת מכשיר, שבו הוא יכול לבחור מכשיר אחד או לבטל את הבקשה.
הפונקציה navigator.bluetooth.requestDevice() מקבלת אובייקט חובה שמגדיר מסננים. המסננים האלה משמשים להחזרת מכשירים שתואמים לחלק משירותי Bluetooth GATT שפורסמו או לשם המכשיר.
סינון לפי שירותים
לדוגמה, כדי לבקש ממכשירי Bluetooth לפרסם את שירות הסוללה של Bluetooth GATT :
navigator.bluetooth.requestDevice({ filters: [{ services: ['battery_service'] }] })
.then(device => { /* … */ })
.catch(error => { console.error(error); });
אם שירות ה-Bluetooth GATT שלכם לא מופיע ברשימה של שירותי Bluetooth GATT סטנדרטיים, אתם יכולים לספק את ה-UUID המלא של Bluetooth או טופס קצר של 16 או 32 ביט.
navigator.bluetooth.requestDevice({
filters: [{
services: [0x1234, 0x12345678, '99999999-0000-1000-8000-00805f9b34fb']
}]
})
.then(device => { /* … */ })
.catch(error => { console.error(error); });
מסנן שמות
אפשר גם לבקש מכשירי Bluetooth על סמך שם המכשיר שמתפרסם באמצעות מקש המסננים name, או אפילו קידומת של השם הזה באמצעות מקש המסננים namePrefix. שימו לב שבמקרה הזה, תצטרכו גם להגדיר את המפתח optionalServices כדי לקבל גישה לכל השירותים שלא נכללים במסנן שירותים. אם לא תעשו את זה, תופיע שגיאה מאוחר יותר כשנסו לגשת אליהם.
navigator.bluetooth.requestDevice({
filters: [{
name: 'Francois robot'
}],
optionalServices: ['battery_service'] // Required to access service later.
})
.then(device => { /* … */ })
.catch(error => { console.error(error); });
מסנן נתונים של יצרן
אפשר גם לבקש מכשירי Bluetooth על סמך נתונים ספציפיים של היצרן שמתפרסמים באמצעות מפתח המסננים manufacturerData. המפתח הזה הוא מערך של אובייקטים עם מפתח חובה Bluetooth company identifier בשם companyIdentifier. אפשר גם לספק קידומת נתונים שמסננת נתוני יצרן ממכשירי Bluetooth שמתחילים בה. שימו לב: תצטרכו גם להגדיר את מפתח optionalServices כדי לקבל גישה לכל השירותים שלא נכללים במסנן שירות. אם לא תעשו את זה, תופיע שגיאה בהמשך כשתנסו לגשת אליהם.
// Filter Bluetooth devices from Google company with manufacturer data bytes
// that start with [0x01, 0x02].
navigator.bluetooth.requestDevice({
filters: [{
manufacturerData: [{
companyIdentifier: 0x00e0,
dataPrefix: new Uint8Array([0x01, 0x02])
}]
}],
optionalServices: ['battery_service'] // Required to access service later.
})
.then(device => { /* … */ })
.catch(error => { console.error(error); });
אפשר להשתמש במסכה גם עם קידומת נתונים כדי להתאים לדפוסים מסוימים בנתונים של היצרן. מידע נוסף זמין בהסבר על מסנני נתונים של Bluetooth.
מסנני החרגה
האפשרות exclusionFilters בnavigator.bluetooth.requestDevice() מאפשרת להחריג מכשירים מסוימים מבורר הדפדפנים. אפשר להשתמש בו כדי להחריג מכשירים שתואמים למסנן רחב יותר אבל לא נתמכים.
// Request access to a bluetooth device whose name starts with "Created by".
// The device named "Created by Francois" has been reported as unsupported.
navigator.bluetooth.requestDevice({
filters: [{
namePrefix: "Created by"
}],
exclusionFilters: [{
name: "Created by Francois"
}],
optionalServices: ['battery_service'] // Required to access service later.
})
.then(device => { /* … */ })
.catch(error => { console.error(error); });
בלי פילטרים
במקום filters, אפשר להשתמש במקש acceptAllDevices כדי להציג את כל מכשירי ה-Bluetooth שבסביבה. תצטרכו גם להגדיר את המפתח optionalServices כדי לקבל גישה לשירותים מסוימים. אם לא תעשו את זה, תקבלו שגיאה בהמשך כשנסו לגשת אליהם.
navigator.bluetooth.requestDevice({
acceptAllDevices: true,
optionalServices: ['battery_service'] // Required to access service later.
})
.then(device => { /* … */ })
.catch(error => { console.error(error); });
התחברות למכשיר Bluetooth
אז מה עושים עכשיו שיש לכם BluetoothDevice? אנחנו מתחברים לשרת GATT של השלט ב-Bluetooth, שכולל את ההגדרות של השירות והמאפיינים.
navigator.bluetooth.requestDevice({ filters: [{ services: ['battery_service'] }] })
.then(device => {
// Human-readable name of the device.
console.log(device.name);
// Attempts to connect to remote GATT Server.
return device.gatt.connect();
})
.then(server => { /* … */ })
.catch(error => { console.error(error); });
קריאת מאפיין Bluetooth
כאן מתחברים לשרת GATT של מכשיר ה-Bluetooth המרוחק. עכשיו אנחנו רוצים לקבל שירות GATT ראשי ולקרוא מאפיין ששייך לשירות הזה. לדוגמה, ננסה לקרוא את רמת הטעינה הנוכחית של הסוללה במכשיר.
בדוגמה הבאה, battery_level הוא מאפיין רמת הטעינה הרגיל.
navigator.bluetooth.requestDevice({ filters: [{ services: ['battery_service'] }] })
.then(device => device.gatt.connect())
.then(server => {
// Getting Battery Service…
return server.getPrimaryService('battery_service');
})
.then(service => {
// Getting Battery Level Characteristic…
return service.getCharacteristic('battery_level');
})
.then(characteristic => {
// Reading Battery Level…
return characteristic.readValue();
})
.then(value => {
console.log(`Battery percentage is ${value.getUint8(0)}`);
})
.catch(error => { console.error(error); });
אם משתמשים במאפיין Bluetooth GATT מותאם אישית, אפשר לספק את מזהה ה-UUID המלא של Bluetooth או פורמט קצר של 16 או 32 ביט ל-service.getCharacteristic.
שימו לב שאפשר גם להוסיף פונקציית event listener מסוג characteristicvaluechanged למאפיין כדי לטפל בקריאת הערך שלו. כדאי לעיין בדוגמה לשינוי ערך מאפיין הקריאה כדי לראות איך אפשר לטפל גם בהתראות GATT הקרובות.
…
.then(characteristic => {
// Set up event listener for when characteristic value changes.
characteristic.addEventListener('characteristicvaluechanged',
handleBatteryLevelChanged);
// Reading Battery Level…
return characteristic.readValue();
})
.catch(error => { console.error(error); });
function handleBatteryLevelChanged(event) {
const batteryLevel = event.target.value.getUint8(0);
console.log('Battery percentage is ' + batteryLevel);
}
כתיבה למאפיין Bluetooth
כתיבה למאפיין Bluetooth GATT היא פשוטה כמו קריאה שלו. הפעם, נשתמש בנקודת הבקרה של קצב הלב כדי לאפס את הערך של השדה 'הוצאת אנרגיה' ל-0 במכשיר לניטור קצב הלב.
אני מבטיח שאין כאן קסם. הסבר מפורט מופיע בדף המאפיין של נקודת הבקרה של קצב הלב.
navigator.bluetooth.requestDevice({ filters: [{ services: ['heart_rate'] }] })
.then(device => device.gatt.connect())
.then(server => server.getPrimaryService('heart_rate'))
.then(service => service.getCharacteristic('heart_rate_control_point'))
.then(characteristic => {
// Writing 1 is the signal to reset energy expended.
const resetEnergyExpended = Uint8Array.of(1);
return characteristic.writeValue(resetEnergyExpended);
})
.then(_ => {
console.log('Energy expended has been reset.');
})
.catch(error => { console.error(error); });
קבלת התראות GATT
עכשיו נראה איך מקבלים הודעה כשהמאפיין Heart Rate Measurement משתנה במכשיר:
navigator.bluetooth.requestDevice({ filters: [{ services: ['heart_rate'] }] })
.then(device => device.gatt.connect())
.then(server => server.getPrimaryService('heart_rate'))
.then(service => service.getCharacteristic('heart_rate_measurement'))
.then(characteristic => characteristic.startNotifications())
.then(characteristic => {
characteristic.addEventListener('characteristicvaluechanged',
handleCharacteristicValueChanged);
console.log('Notifications have been started.');
})
.catch(error => { console.error(error); });
function handleCharacteristicValueChanged(event) {
const value = event.target.value;
console.log('Received ' + value);
// TODO: Parse Heart Rate Measurement value.
// See https://github.com/WebBluetoothCG/demos/blob/gh-pages/heart-rate-sensor/heartRateSensor.js
}
בדוגמה Notifications Sample מוסבר איך להפסיק את ההתראות באמצעות stopNotifications() ולהסיר בצורה נכונה את characteristicvaluechangedevent listener שנוסף.
התנתקות ממכשיר Bluetooth
כדי לספק חוויית משתמש טובה יותר, כדאי להאזין לאירועי ניתוק ולהזמין את המשתמש להתחבר מחדש:
navigator.bluetooth.requestDevice({ filters: [{ name: 'Francois robot' }] })
.then(device => {
// Set up event listener for when device gets disconnected.
device.addEventListener('gattserverdisconnected', onDisconnected);
// Attempts to connect to remote GATT Server.
return device.gatt.connect();
})
.then(server => { /* … */ })
.catch(error => { console.error(error); });
function onDisconnected(event) {
const device = event.target;
console.log(`Device ${device.name} is disconnected.`);
}
אפשר גם להתקשר אל device.gatt.disconnect() כדי לנתק את אפליקציית האינטרנט ממכשיר ה-Bluetooth. הפעולה הזו תפעיל מאזיני אירועים קיימים של gattserverdisconnected. שימו לב: הפעולה הזו לא תפסיק את התקשורת של מכשיר ה-Bluetooth אם אפליקציה אחרת כבר מתקשרת עם מכשיר ה-Bluetooth. כדי להעמיק בנושא, אפשר לעיין בדוגמה לניתוק מכשיר ובדוגמה לחיבור מחדש אוטומטי.
קריאה וכתיבה של תיאורי Bluetooth
מתארים של Bluetooth GATT הם מאפיינים שמתארים ערך של מאפיין. אפשר לקרוא ולכתוב אותם באופן דומה למאפייני Bluetooth GATT.
לדוגמה, נראה איך קוראים את תיאור המשתמש של מדד המרווח בין המדידות במדחום של תקינות המכשיר.
בדוגמה שלמטה, health_thermometer הוא השירות Health Thermometer, measurement_interval הוא המאפיין Measurement Interval ו-gatt.characteristic_user_description הוא המתאר Characteristic User Description.
navigator.bluetooth.requestDevice({ filters: [{ services: ['health_thermometer'] }] })
.then(device => device.gatt.connect())
.then(server => server.getPrimaryService('health_thermometer'))
.then(service => service.getCharacteristic('measurement_interval'))
.then(characteristic => characteristic.getDescriptor('gatt.characteristic_user_description'))
.then(descriptor => descriptor.readValue())
.then(value => {
const decoder = new TextDecoder('utf-8');
console.log(`User Description: ${decoder.decode(value)}`);
})
.catch(error => { console.error(error); });
אחרי שקראנו את תיאור המשתמש של מרווח המדידה של מד החום של תקינות המכשיר, נראה איך לעדכן אותו ולכתוב ערך מותאם אישית.
navigator.bluetooth.requestDevice({ filters: [{ services: ['health_thermometer'] }] })
.then(device => device.gatt.connect())
.then(server => server.getPrimaryService('health_thermometer'))
.then(service => service.getCharacteristic('measurement_interval'))
.then(characteristic => characteristic.getDescriptor('gatt.characteristic_user_description'))
.then(descriptor => {
const encoder = new TextEncoder('utf-8');
const userDescription = encoder.encode('Defines the time between measurements.');
return descriptor.writeValue(userDescription);
})
.catch(error => { console.error(error); });
דוגמאות, הדגמות ו-Codelabs
כל הדוגמאות של Web Bluetooth שבהמשך נבדקו בהצלחה. כדי ליהנות מהדוגמאות האלה בצורה מלאה, מומלץ להתקין את [אפליקציית Android של BLE Peripheral Simulator] שמדמה ציוד היקפי של BLE עם שירות סוללה, שירות מדידת דופק או שירות מדחום.
רמה למתחילים
- פרטי המכשיר – אחזור של פרטי מכשיר בסיסיים ממכשיר BLE.
- רמת הטעינה של הסוללה – אחזור מידע על הסוללה ממכשיר BLE שמפרסם מידע על הסוללה.
- איפוס האנרגיה – איפוס האנרגיה שהוצאה ממכשיר BLE שמפרסם את הדופק.
- מאפייני מאפיינים – הצגת כל המאפיינים של מאפיין ספציפי ממכשיר BLE.
- התראות – הפעלה והפסקה של התראות מאפיינים ממכשיר BLE.
- ניתוק מכשיר – ניתוק וקבלת התראה על ניתוק של מכשיר BLE אחרי החיבור אליו.
- Get Characteristics (קבלת מאפיינים) – קבלת כל המאפיינים של שירות שמפורסם ממכשיר BLE.
- קבלת תיאורים – קבלת כל תיאורי המאפיינים של שירות שמפורסם ממכשיר BLE.
- מסנן נתוני יצרן – אחזור של פרטי מכשיר בסיסיים ממכשיר BLE שתואמים לנתוני היצרן.
- מסנני החרגה – אחזור מידע בסיסי על מכשיר ממכשיר BLE עם מסנני החרגה בסיסיים.
שילוב של כמה פעולות
- GAP Characteristics – קבלת כל המאפיינים של פרופיל הגישה הכללי (GAP) של מכשיר BLE.
- מאפייני פרטי המכשיר – קבלת כל מאפייני פרטי המכשיר של מכשיר BLE.
- Link Loss (אובדן קישור) – הגדרה של מאפיין רמת ההתראה של מכשיר BLE (readValue ו-writeValue).
- גילוי שירותים ומאפיינים – גילוי כל השירותים הראשיים הנגישים והמאפיינים שלהם ממכשיר BLE.
- חיבור מחדש אוטומטי – חיבור מחדש למכשיר BLE מנותק באמצעות אלגוריתם של נסיגה אקספוננציאלית.
- קריאת שינוי ערך המאפיין – קריאת רמת הסוללה וקבלת הודעה על שינויים ממכשיר BLE.
- קריאת תיאורים – קריאת כל התיאורים של מאפיין של שירות ממכשיר BLE.
- Write Descriptor (כתיבת תיאור) – כתיבה לתיאור Characteristic User Description (תיאור מאפיין המשתמש) במכשיר BLE.
מומלץ לעיין גם בהדגמות של Web Bluetooth שנאספו במיוחד ובסדנאות התכנות הרשמיות של Web Bluetooth.
ספריות
- web-bluetooth-utils הוא מודול npm שמוסיף כמה פונקציות נוחות ל-API.
- Shim של Web Bluetooth API זמין ב-noble, המודול המרכזי הפופולרי ביותר של Node.js BLE. כך אפשר להשתמש ב-webpack/browserify noble בלי צורך בשרת WebSocket או בתוספים אחרים.
- angular-web-bluetooth הוא מודול ל-Angular שמבצע הפשטה של כל הקוד הסטנדרטי שנדרש להגדרת Web Bluetooth API.
כלים
- Get Started with Web Bluetooth היא אפליקציית אינטרנט פשוטה שיוצרת את כל קוד ה-JavaScript הסטנדרטי שצריך כדי להתחיל אינטראקציה עם מכשיר Bluetooth. מזינים שם למכשיר, לשירות או למאפיין, מגדירים את המאפיינים שלו וזהו.
- אם אתם כבר מפתחי Bluetooth, התוסף Web Bluetooth Developer Studio ייצור גם את קוד ה-JavaScript של Web Bluetooth למכשיר ה-Bluetooth שלכם.
טיפים
דף Bluetooth Internals זמין ב-Chrome בכתובת about://bluetooth-internals, כדי שתוכלו לבדוק את כל הפרטים על מכשירי Bluetooth סמוכים: סטטוס, שירותים, מאפיינים ותיאורים.
מומלץ גם לעיין בדף הרשמי How to file Web Bluetooth bugs (איך מדווחים על באגים ב-Web Bluetooth), כי לפעמים קשה לבצע ניפוי באגים ב-Bluetooth.
המאמרים הבאים
כדאי קודם לבדוק את סטטוס ההטמעה בדפדפן ובפלטפורמה כדי לדעת אילו חלקים של Web Bluetooth API מוטמעים כרגע.
הנה הצצה למה שצפוי בעתיד הקרוב, למרות שהרשימה עדיין לא מלאה:
- הסריקה לאיתור מודעות BLE בקרבת מקום תתבצע באמצעות
navigator.bluetooth.requestLEScan(). - אירוע חדש
serviceaddedיעקוב אחרי שירותי Bluetooth GATT חדשים שהתגלו, ואירועserviceremovedיעקוב אחרי שירותים שהוסרו. אירועservicechangedחדש יופעל כשמאפיין או תיאור יתווספו לשירות Bluetooth GATT או יוסרו ממנו.
תמיכה ב-API
האם אתם מתכננים להשתמש ב-Web Bluetooth API? התמיכה הציבורית שלכם עוזרת לצוות Chrome לתעדף תכונות ומראה לספקי דפדפנים אחרים עד כמה חשוב לתמוך בהן.
אפשר לשלוח ציוץ אל @ChromiumDev עם ההאשטאג #WebBluetooth ולספר לנו איפה ואיך אתם משתמשים בו.
משאבים
- Stack Overflow
- סטטוס התכונות של Chrome
- באגים בהטמעה של Chrome
- מפרט Web Bluetooth
- בעיות במפרט ב-GitHub
- אפליקציית סימולטור של מכשיר היקפי ב-BLE
תודות
תודה ל-Kayce Basques על הבדיקה.