1. על מה מדובר?

ב-codelab הזה תלמדו איך לשלוט בנר LED ללא להבה PLAYBULB באמצעות JavaScript בלבד, הודות ל-Web Bluetooth API. במהלך הדרך, תתנסו גם בתכונות של JavaScript ES2015 כמו Classes, Arrow functions, Map ו-Promises.
מה תלמדו
- איך מתקשרים עם מכשיר Bluetooth בקרבת מקום באמצעות JavaScript
- איך משתמשים ב-ES2015 Classes, בפונקציות חץ, ב-Map וב-Promises
מה תצטרכו
- הבנה בסיסית של פיתוח אתרים
- ידע בסיסי ב-Bluetooth Low Energy (BLE) וב-Generic Attribute Profile (GATT)
- עורך טקסט לפי בחירתכם
- מחשב Mac, Chromebook או מכשיר Android M עם אפליקציית דפדפן Chrome וכבל USB מיקרו ל-USB.
2. הפעלת הפריט הראשון
מומלץ לעיין בגרסה הסופית של האפליקציה שאתם עומדים ליצור בכתובת https://googlecodelabs.github.io/candle-bluetooth ולנסות את מכשיר ה-Bluetooth של PLAYBULB Candle שיש לכם, לפני שמתחילים את ה-codelab הזה.
אפשר גם לצפות בי משנה צבעים בכתובת https://www.youtube.com/watch?v=fBCPA9gIxlU
3. להגדרה
הורדת קוד לדוגמה
כדי להוריד את קוד הדוגמה הזה כקובץ ZIP, אפשר ללחוץ כאן:
או על ידי שיבוט של מאגר ה-git הזה:
git clone https://github.com/googlecodelabs/candle-bluetooth.git
אם הורדתם את המקור כקובץ ZIP, אחרי פתיחת הקובץ אמורה להיווצר תיקיית בסיס candle-bluetooth-master.
התקנה ואימות של שרת אינטרנט
אתם יכולים להשתמש בשרת האינטרנט שלכם, אבל ה-codelab הזה מיועד לעבוד בצורה טובה עם שרת האינטרנט של Chrome. אם האפליקציה עדיין לא מותקנת, אפשר להתקין אותה מחנות האינטרנט של Chrome.
אחרי שמתקינים את אפליקציית Web Server for Chrome, לוחצים על קיצור הדרך לאפליקציות בסרגל הסימניות:

בחלון שייפתח, לוחצים על סמל שרת האינטרנט:

בשלב הבא תופיע תיבת הדו-שיח הזו, שבה אפשר להגדיר את שרת האינטרנט המקומי:

לוחצים על הלחצן choose folder (בחירת תיקייה) ובוחרים את שורש המאגר המשוכפל (או שבוטל הארכיון שלו). כך תוכלו להציג את העבודה שלכם בתהליך באמצעות כתובת ה-URL שמודגשת בתיבת הדו-שיח של שרת האינטרנט (בקטע כתובות ה-URL של שרת האינטרנט).
בקטע 'אפשרויות', מסמנים את התיבה לצד הצגה אוטומטית של index.html, כמו שמוצג בהמשך:

עכשיו נכנסים לאתר בדפדפן האינטרנט (על ידי לחיצה על כתובת ה-URL של שרת האינטרנט שמודגשת) ורואים דף שנראה כך:

אם רוצים לראות איך האפליקציה הזו נראית בטלפון Android, צריך להפעיל את ניפוי באגים מרחוק ב-Android ולהגדיר העברה ליציאה אחרת (מספר היציאה הוא 8887 כברירת מחדל). אחרי זה, פשוט פותחים כרטיסייה חדשה ב-Chrome בכתובת http://localhost:8887 בטלפון Android.
הבא בתור
בשלב הזה, אפליקציית האינטרנט הזו לא עושה הרבה. נתחיל להוסיף תמיכה ב-Bluetooth.
4. גילוי הנר
נתחיל בכתיבת ספרייה שמשתמשת במחלקה JavaScript ES2015 למכשיר Bluetooth של PLAYBULB Candle.
אל דאגה. תחביר המחלקה לא מציג מודל חדש של ירושה מונחה-אובייקטים ב-JavaScript. הוא פשוט מספק תחביר ברור יותר ליצירת אובייקטים ולטיפול בהורשה, כפי שאפשר לקרוא בהמשך.
קודם נגדיר מחלקה PlaybulbCandle ב-playbulbCandle.js וניצור מופע playbulbCandle שיהיה זמין בקובץ app.js בהמשך.
playbulbCandle.js
(function() {
'use strict';
class PlaybulbCandle {
constructor() {
this.device = null;
}
}
window.playbulbCandle = new PlaybulbCandle();
})();
כדי לבקש גישה למכשיר Bluetooth בקרבת מקום, צריך להפעיל את navigator.bluetooth.requestDevice. מכשיר PLAYBULB Candle משדר פרסום באופן רציף (אם הוא לא משויך כבר), ולכן אפשר להגדיר קבוע ולהוסיף אותו לפרמטר services של המסננים בשיטה חדשה של connect class.0xFF02PlaybulbCandle
אנחנו גם נעקוב באופן פנימי אחרי אובייקט BluetoothDevice כדי שנוכל לגשת אליו בהמשך אם יהיה צורך. מכיוון שהפונקציה navigator.bluetooth.requestDevice מחזירה JavaScript ES2015 Promise, נשתמש בה בשיטה then.
playbulbCandle.js
(function() {
'use strict';
const CANDLE_SERVICE_UUID = 0xFF02;
class PlaybulbCandle {
constructor() {
this.device = null;
}
connect() {
let options = {filters:[{services:[ CANDLE_SERVICE_UUID ]}]};
return navigator.bluetooth.requestDevice(options)
.then(function(device) {
this.device = device;
}.bind(this));
}
}
window.playbulbCandle = new PlaybulbCandle();
})();
כאמצעי אבטחה, גילוי מכשירי Bluetooth בקרבת מקום באמצעות navigator.bluetooth.requestDevice חייב להתבצע באמצעות תנועת משתמש, כמו הקשה או לחיצה על העכבר. לכן נקרא לשיטה connect כשמשתמש לוחץ על הכפתור 'התחברות' בקובץ app.js:
app.js
document.querySelector('#connect').addEventListener('click', function(event) {
document.querySelector('#state').classList.add('connecting');
playbulbCandle.connect()
.then(function() {
console.log(playbulbCandle.device);
document.querySelector('#state').classList.remove('connecting');
document.querySelector('#state').classList.add('connected');
})
.catch(function(error) {
console.error('Argh!', error);
});
});
הפעלת האפליקציה
בשלב הזה, נכנסים לאתר בדפדפן האינטרנט (על ידי לחיצה על כתובת ה-URL של שרת האינטרנט שמודגשת באפליקציית שרת האינטרנט) או פשוט מרעננים את הדף הקיים. לוחצים על הלחצן הירוק 'חיבור', בוחרים את המכשיר בכלי לבחירת מכשירים ופותחים את מסוף כלי הפיתוח המועדף באמצעות מקשי הקיצור Ctrl + Shift + J. שימו לב לאובייקט BluetoothDevice שנרשם ביומן.

יכול להיות שתקבלו שגיאה אם ה-Bluetooth כבוי או אם מכשיר ה-Bluetooth של PLAYBULB Candle כבוי. במקרה כזה, צריך להפעיל אותו ולהמשיך.
בונוס חובה
לא יודע מה איתך, אבל אני כבר רואה יותר מדי function() {} בקוד הזה. במקום זאת, נשתמש ב() => {}פונקציות חץ של JavaScript ES2015. הן מצילות חיים: כל היופי של פונקציות אנונימיות, בלי העצב של הקישור.
playbulbCandle.js
(function() {
'use strict';
const CANDLE_SERVICE_UUID = 0xFF02;
class PlaybulbCandle {
constructor() {
this.device = null;
}
connect() {
let options = {filters:[{services:[ CANDLE_SERVICE_UUID ]}]};
return navigator.bluetooth.requestDevice(options)
.then(device => {
this.device = device;
});
}
}
window.playbulbCandle = new PlaybulbCandle();
})();
app.js
document.querySelector('#connect').addEventListener('click', event => {
playbulbCandle.connect()
.then(() => {
console.log(playbulbCandle.device);
document.querySelector('#state').classList.remove('connecting');
document.querySelector('#state').classList.add('connected');
})
.catch(error => {
console.error('Argh!', error);
});
});
הבא בתור
– אוקיי… אפשר לדבר עם הנר הזה או לא?
– בטח... אפשר לעבור לשלב הבא
שאלות נפוצות
5. לקרוא משהו
אז מה עושים עכשיו כשמקבלים BluetoothDevice שהוחזר מאובייקט ה-promise של navigator.bluetooth.requestDevice? כדי להתחבר לשרת GATT של השלט הרחוק ב-Bluetooth שמכיל את שירות ה-Bluetooth ואת הגדרות המאפיינים, קוראים ל-device.gatt.connect():
playbulbCandle.js
class PlaybulbCandle {
constructor() {
this.device = null;
}
connect() {
let options = {filters:[{services:[ CANDLE_SERVICE_UUID ]}]};
return navigator.bluetooth.requestDevice(options)
.then(device => {
this.device = device;
return device.gatt.connect();
});
}
}
קריאת שם המכשיר
כאן אנחנו מחוברים לשרת GATT של מכשיר ה-Bluetooth PLAYBULB Candle. עכשיו אנחנו רוצים לקבל את שירות ה-GATT הראשי (שפורסם בעבר כ-0xFF02) ולקרוא את מאפיין שם המכשיר (0xFFFF) ששייך לשירות הזה. אפשר לעשות את זה בקלות על ידי הוספת method חדשה getDeviceName למחלקה PlaybulbCandle ושימוש ב-device.gatt.getPrimaryService וב-service.getCharacteristic. השיטה characteristic.readValue תחזיר למעשה DataView, ואנחנו פשוט נפענח אותה באמצעות TextDecoder.
playbulbCandle.js
const CANDLE_DEVICE_NAME_UUID = 0xFFFF;
...
getDeviceName() {
return this.device.gatt.getPrimaryService(CANDLE_SERVICE_UUID)
.then(service => service.getCharacteristic(CANDLE_DEVICE_NAME_UUID))
.then(characteristic => characteristic.readValue())
.then(data => {
let decoder = new TextDecoder('utf-8');
return decoder.decode(data);
});
}
אחרי שנתחבר, נתקשר אל playbulbCandle.getDeviceName כדי להוסיף אותו אל app.js ונציג את שם המכשיר.
app.js
document.querySelector('#connect').addEventListener('click', event => {
playbulbCandle.connect()
.then(() => {
console.log(playbulbCandle.device);
document.querySelector('#state').classList.remove('connecting');
document.querySelector('#state').classList.add('connected');
return playbulbCandle.getDeviceName().then(handleDeviceName);
})
.catch(error => {
console.error('Argh!', error);
});
});
function handleDeviceName(deviceName) {
document.querySelector('#deviceName').value = deviceName;
}
בשלב הזה, נכנסים לאתר בדפדפן האינטרנט (על ידי לחיצה על כתובת ה-URL של שרת האינטרנט שמודגשת באפליקציית שרת האינטרנט) או פשוט מרעננים את הדף הקיים. מוודאים שהנר PLAYBULB מופעל, ואז לוחצים על הלחצן Connect (חיבור) בדף. שם המכשיר אמור להופיע מתחת לכלי לבחירת צבעים.

קריאת רמת הטעינה
במכשיר ה-Bluetooth של PLAYBULB Candle יש גם מאפיין Bluetooth רגיל של רמת הטעינה של הסוללה, שמכיל את רמת הטעינה של הסוללה במכשיר. כלומר, אפשר להשתמש בשמות סטנדרטיים כמו battery_service בשביל מזהה ה-UUID של שירות Bluetooth GATT ו-battery_level בשביל מזהה ה-UUID של מאפיין Bluetooth GATT.
נוסיף את ה-method החדש getBatteryLevel למחלקה PlaybulbCandle ונקרא את רמת הטעינה באחוזים.
playbulbCandle.js
getBatteryLevel() {
return this.device.gatt.getPrimaryService('battery_service')
.then(service => service.getCharacteristic('battery_level'))
.then(characteristic => characteristic.readValue())
.then(data => data.getUint8(0));
}
בנוסף, צריך לעדכן את אובייקט ה-JavaScript options כדי לכלול את שירות הסוללה במפתח optionalServices, כי הוא לא מוצג במכשיר ה-Bluetooth של נר PLAYBULB, אבל עדיין חובה לגשת אליו.
playbulbCandle.js
let options = {filters:[{services:[ CANDLE_SERVICE_UUID ]}],
optionalServices: ['battery_service']};
return navigator.bluetooth.requestDevice(options)
כמו קודם, נחבר את זה ל-app.js על ידי קריאה ל-playbulbCandle.getBatteryLevel אחרי שנקבל את שם המכשיר ונציג את רמת הטעינה.
app.js
document.querySelector('#connect').addEventListener('click', event => {
playbulbCandle.connect()
.then(() => {
console.log(playbulbCandle.device);
document.querySelector('#state').classList.remove('connecting');
document.querySelector('#state').classList.add('connected');
return playbulbCandle.getDeviceName().then(handleDeviceName)
.then(() => playbulbCandle.getBatteryLevel().then(handleBatteryLevel));
})
.catch(error => {
console.error('Argh!', error);
});
});
function handleDeviceName(deviceName) {
document.querySelector('#deviceName').value = deviceName;
}
function handleBatteryLevel(batteryLevel) {
document.querySelector('#batteryLevel').textContent = batteryLevel + '%';
}
בשלב הזה, נכנסים לאתר בדפדפן האינטרנט (על ידי לחיצה על כתובת ה-URL של שרת האינטרנט שמודגשת באפליקציית שרת האינטרנט) או פשוט מרעננים את הדף הקיים. לוחצים על הלחצן 'חיבור' בדף, ואז אמורים לראות את שם המכשיר ואת רמת הטעינה.
הבא בתור
– איך אפשר לשנות את הצבע של הנורה הזו? לכן אני כאן!
- כמעט סיימת, אני מבטיח...
שאלות נפוצות
6. שינוי הצבע
כדי לשנות את הצבע, צריך לכתוב קבוצה ספציפית של פקודות למאפיין Bluetooth (0xFFFC) בשירות GATT הראשי שמופיע כ-0xFF02. לדוגמה, כדי להפוך את נר PLAYBULB לאדום, צריך לכתוב מערך של מספרים שלמים לא מסומנים בני 8 ביט ששווים ל-[0x00, 255, 0, 0], כאשר 0x00 הוא הרוויה הלבנה ו-255, 0, 0 הם הערכים של אדום, ירוק וכחול, בהתאמה .
נשתמש ב-characteristic.writeValue כדי לכתוב נתונים בפועל למאפיין Bluetooth בשיטה הציבורית החדשה setColor של המחלקה PlaybulbCandle. בנוסף, כשההבטחה תתממש, נחזיר את הערכים האמיתיים של אדום, ירוק וכחול כדי שנוכל להשתמש בהם ב-app.js בהמשך:
playbulbCandle.js
const CANDLE_COLOR_UUID = 0xFFFC;
...
setColor(r, g, b) {
let data = new Uint8Array([0x00, r, g, b]);
return this.device.gatt.getPrimaryService(CANDLE_SERVICE_UUID)
.then(service => service.getCharacteristic(CANDLE_COLOR_UUID))
.then(characteristic => characteristic.writeValue(data))
.then(() => [r,g,b]);
}
נעדכן את הפונקציה changeColor בקובץ app.js כדי לקרוא לפונקציה playbulbCandle.setColor כשמסמנים את לחצן הבחירה 'ללא אפקט'. משתני הצבע הגלובליים r, g, b כבר מוגדרים כשהמשתמש לוחץ על אזור הציור של הכלי לבחירת צבעים.
app.js
function changeColor() {
var effect = document.querySelector('[name="effectSwitch"]:checked').id;
if (effect === 'noEffect') {
playbulbCandle.setColor(r, g, b).then(onColorChanged);
}
}
בשלב הזה, נכנסים לאתר בדפדפן האינטרנט (על ידי לחיצה על כתובת ה-URL של שרת האינטרנט שמודגשת באפליקציית שרת האינטרנט) או פשוט מרעננים את הדף הקיים. לוחצים על הלחצן 'התחברות' בדף ולוחצים על כלי לבחירת צבעים כדי לשנות את הצבע של נר PLAYBULB כמה פעמים שרוצים.
עוד אפקטים של נרות
אם כבר הדלקתם נר בעבר, אתם יודעים שהאור לא קבוע. למזלנו, יש מאפיין Bluetooth נוסף (0xFFFB) בשירות ה-GATT הראשי שמפורסם כ-0xFF02, שמאפשר למשתמש להגדיר כמה אפקטים של נרות.
לדוגמה, כדי להגדיר אפקט של נר, כותבים [0x00, r, g, b, 0x04, 0x00, 0x01, 0x00]. אפשר גם להגדיר את האפקט 'הבהוב' באמצעות [0x00, r, g, b, 0x00, 0x00, 0x1F, 0x00].
נוסיף את ה-methods setCandleEffectColor ו-setFlashingColor למחלקה PlaybulbCandle.
playbulbCandle.js
const CANDLE_EFFECT_UUID = 0xFFFB;
...
setCandleEffectColor(r, g, b) {
let data = new Uint8Array([0x00, r, g, b, 0x04, 0x00, 0x01, 0x00]);
return this.device.gatt.getPrimaryService(CANDLE_SERVICE_UUID)
.then(service => service.getCharacteristic(CANDLE_EFFECT_UUID))
.then(characteristic => characteristic.writeValue(data))
.then(() => [r,g,b]);
}
setFlashingColor(r, g, b) {
let data = new Uint8Array([0x00, r, g, b, 0x00, 0x00, 0x1F, 0x00]);
return this.device.gatt.getPrimaryService(CANDLE_SERVICE_UUID)
.then(service => service.getCharacteristic(CANDLE_EFFECT_UUID))
.then(characteristic => characteristic.writeValue(data))
.then(() => [r,g,b]);
}
בנוסף, נעדכן את הפונקציה changeColor ב-app.js כדי לקרוא ל-playbulbCandle.setCandleEffectColor כשמסומן לחצן הבחירה 'אפקט נר', ול-playbulbCandle.setFlashingColor כשמסומן לחצן הבחירה 'הבהוב'. הפעם נשתמש בכתובת switch, אם זה בסדר מבחינתך.
app.js
function changeColor() {
var effect = document.querySelector('[name="effectSwitch"]:checked').id;
switch(effect) {
case 'noEffect':
playbulbCandle.setColor(r, g, b).then(onColorChanged);
break;
case 'candleEffect':
playbulbCandle.setCandleEffectColor(r, g, b).then(onColorChanged);
break;
case 'flashing':
playbulbCandle.setFlashingColor(r, g, b).then(onColorChanged);
break;
}
}
בשלב הזה, נכנסים לאתר בדפדפן האינטרנט (על ידי לחיצה על כתובת ה-URL של שרת האינטרנט שמודגשת באפליקציית שרת האינטרנט) או פשוט מרעננים את הדף הקיים. לוחצים על הלחצן 'התחברות' בדף ומשחקים עם האפקטים של הנר וההבהוב.
הבא בתור
– זה הכול? 3 אפקטים של נרות באיכות ירודה? האם זאת הסיבה שאני כאן?
– יש עוד, אבל הפעם תצטרך להסתדר לבד.
7. להמשיך לרמה הבאה
אז הנה אנחנו! יכול להיות שנדמה לכם שהאפליקציה עומדת להסתיים, אבל היא עדיין לא נגמרה. בואו נבדוק אם באמת הבנתם את מה שהעתקתם והדבקתם במהלך ה-codelab הזה. אלה הפעולות שאתם יכולים לעשות בעצמכם כדי לשפר את האפליקציה.
הוספת אפקטים חסרים
אלה הנתונים של האפקטים החסרים:
- דופק:
[0x00, r, g, b, 0x01, 0x00, 0x09, 0x00](אולי תרצו לשנות את הערכים שלr, g, b) - קשת בענן:
[0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x01, 0x00](אנשים עם אפילפסיה צריכים להימנע מהאמוג'י הזה) - העברה הדרגתית של צבעי הקשת:
[0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x26, 0x00]
המשמעות היא שנוספו methods חדשים מסוג setPulseColor, setRainbow ו-setRainbowFade למחלקה PlaybulbCandle, והם נקראים ב-changeColor.
פתרון הבעיה 'אין השפעה'
יכול להיות ששמתם לב שהאפשרות 'ללא השפעה' לא מאפסת אפקטים שכבר פועלים. זה לא משמעותי, אבל עדיין. בוא נפתור את הבעיה. בשיטה setColor, קודם צריך לבדוק אם אפקט נמצא בתהליך באמצעות משתנה מחלקה חדש _isEffectSet, ואם כן true, להשבית את האפקט לפני שמגדירים צבע חדש באמצעות הנתונים האלה: [0x00, r, g, b, 0x05, 0x00, 0x01, 0x00].
הזנת שם המכשיר
זה קל! כדי לכתוב שם מכשיר מותאם אישית, פשוט כותבים למאפיין של שם מכשיר ה-Bluetooth הקודם. מומלץ להשתמש בשיטה TextEncoder encode כדי לקבל Uint8Array שמכיל את שם המכשיר.
אחר כך אוסיף רכיב eventListener מסוג input ל-document.querySelector('#deviceName') ואקרא לו playbulbCandle.setDeviceName כדי שיהיה פשוט.
אני קראתי לשלי PLAY💡 CANDLE!
8. זהו!
מה למדתם
- איך מתקשרים עם מכשיר Bluetooth בקרבת מקום באמצעות JavaScript
- איך משתמשים ב-ES2015 Classes, בפונקציות חץ, ב-Map וב-Promises
השלבים הבאים
- מידע נוסף על Web Bluetooth API
- אפשר לעיין בדוגמאות של Web Bluetooth ובהדגמות הרשמיות
- כדאי לראות את החתול הכועס המעופף

