שליטה בנר של PLAYBULB באמצעות Bluetooth מהאינטרנט

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

IMG_19700101_023537~2~2.jpg

בשיעור ה-Codelab הזה תלמדו איך לשלוט בנר LED ללא להבה של PLAYBULB באמצעות JavaScript בלבד, בזכות Web Bluetooth API. במהלך הפיתוח, תתנסו גם בתכונות של JavaScript ES2015, כמו Classes,‏ Arrow functions,‏ Map ו-Promises.

מה תלמדו

  • איך ליצור אינטראקציה עם מכשיר Bluetooth קרוב ב-JavaScript
  • איך משתמשים ב-ES2015 Classes, ב-Arrow functions, ב-Map וב-Promises

מה תצטרכו

2. הפעלה ראשונה

לפני שמתחילים את ה-Codelab, כדאי לבדוק את הגרסה הסופית של האפליקציה שאתם עומדים ליצור בכתובת https://googlecodelabs.github.io/candle-bluetooth ולהתנסות במכשיר ה-Bluetooth של PLAYBULB Candle שעומד לרשותכם.

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

Screen Shot 2016-11-16 at 4.10.42 PM.png

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

9f3c21b2cf6cbfb5.png

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

Screen Shot 2016-11-16 at 3.40.47 PM.png

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

בקטע Options, מסמנים את התיבה לצד Automatically show index.html, כמו שמוצג כאן:

Screen Shot 2016-11-16 at 3.40.56 PM.png

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

Screen Shot 2016-11-16 at 3.20.22 PM.png

אם אתם רוצים לראות איך האפליקציה הזו נראית בטלפון Android, תצטרכו להפעיל את האפשרות ניפוי באגים מרחוק ב-Android ולהגדיר העברה ליציאה אחרת (מספר היציאה שמוגדר כברירת מחדל הוא 8887). אחרי זה, פשוט פותחים כרטיסייה חדשה ב-Chrome בטלפון Android ועוברים לכתובת http://localhost:8887.

השלב הבא

בשלב הזה, אפליקציית האינטרנט הזו לא עושה הרבה. בואו נוסיף תמיכה ב-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 מפרסם באופן רציף (אם הוא עדיין לא מותאם) מזהה UUID קבוע של שירות Bluetooth GATT, שמוכר בצורה המקוצרת שלו בתור 0xFF02, אנחנו יכולים פשוט להגדיר קבוע ולהוסיף אותו לפרמטר שירותי המסננים ב-method ציבורי חדש connect של המחלקה PlaybulbCandle.

אנחנו גם נשמור את האובייקט BluetoothDevice באופן פנימי כדי שנוכל לגשת אליו מאוחר יותר במקרה הצורך. מכיוון שהפונקציה navigator.bluetooth.requestDevice מחזירה JavaScript ES2015 Promise, נעשה את זה ב-method‏ 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, צריך להפעיל את התכונה באמצעות תנועה של המשתמש, כמו נגיעה או לחיצה על העכבר. לכן, נקרא ל-method‏ connect כשהמשתמש ילחץ על הכפתור "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 של שרת האינטרנט שמודגשת באפליקציה של שרת האינטרנט) או פשוט מרעננים את הדף הקיים. לוחצים על הלחצן הירוק "Connect" (חיבור), בוחרים את המכשיר בבורר ופותחים את מסוף כלי הפיתוח המועדף עליכם באמצעות מקשי הקיצור Ctrl + Shift + J. שימו לב לאובייקט BluetoothDevice שנרשם ביומן.

Screen Shot 2016-11-16 at 3.27.12 PM.png

אם ה-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. עכשיו אנחנו רוצים לקבל את שירות ה-GATT הראשי (שפורסם קודם כ-0xFF02) ולקרוא את מאפיין שם המכשיר (0xFFFF) ששייך לשירות הזה. כדי לעשות את זה, פשוט מוסיפים שיטה חדשה 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);
      });
    }

בואו נוסיף את זה אל app.js על ידי התקשרות אל playbulbCandle.getDeviceName אחרי שנתחבר ונציג את שם המכשיר.

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 Candle מופעל, לוחצים על הכפתור "התחברות" בדף, ואז שם המכשיר אמור להופיע מתחת לכלי לבחירת צבעים.

Screen Shot 2016-11-16 at 3.29.21 PM.png

הקראת רמת הטעינה

יש גם מאפיין Bluetooth סטנדרטי של רמת הטעינה שזמין במכשיר ה-Bluetooth של PLAYBULB Candle, שמכיל את רמת הטעינה של המכשיר. זה אומר שאנחנו יכולים להשתמש בשמות סטנדרטיים כמו 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));
    }

אנחנו צריכים גם לעדכן את האובייקט options ב-JavaScript כדי לכלול את שירות הסוללה במפתח optionalServices, כי הוא לא מפורסם על ידי מכשיר ה-Bluetooth של PLAYBULB Candle, אבל עדיין חובה לגשת אליו.

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 של שרת האינטרנט שמודגשת באפליקציה של שרת האינטרנט) או פשוט מרעננים את הדף הקיים. לוחצים על הכפתור "חיבור" בדף. שם המכשיר ורמת הטעינה אמורים להופיע.

Screen Shot 2016-11-16 at 3.29.21 PM.png

השלב הבא

- איך אפשר לשנות את הצבע של הנורה הזו? בשביל זה אני פה!

- את ממש קרובה, אני מבטיח...

שאלות נפוצות

6. שינוי הצבע

כדי לשנות את הצבע, פשוט כותבים קבוצה ספציפית של פקודות למאפיין Bluetooth‏ (0xFFFC) בשירות ה-GATT הראשי שמוצג בתור 0xFF02. לדוגמה, כדי להפוך את PLAYBULB Candle לאדום, צריך לכתוב מערך של מספרים שלמים ללא סימן של 8 ביט ששווים ל-[0x00, 255, 0, 0], כאשר 0x00 הוא הרוויה הלבנה ו-255, 0, 0 הם, בהתאמה, הערכים של אדום, ירוק וכחול .

נשתמש ב-characteristic.writeValue כדי לכתוב נתונים למאפיין ה-Bluetooth ב-method הציבורי החדש 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 כשכפתור הבחירה "No Effect" מסומן. משתני הצבע הגלובליים של 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 של שרת האינטרנט שמודגשת באפליקציה של שרת האינטרנט) או פשוט מרעננים את הדף הקיים. לוחצים על הכפתור "Connect" (התחברות) בדף ועל הכלי לבחירת צבעים כדי לשנות את הצבע של PLAYBULB Candle כמה פעמים שרוצים.

Screen Shot 2016-11-16 at 3.31.37 PM.png

עוד אפקטים של נרות

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

Screen Shot 2016-11-16 at 3.33.23 PM.png

השלב הבא

- זה הכל? ‫3 אפקטים של נרות לא טובים? בגלל זה אני כאן?

- יש עוד, אבל הפעם תצטרכו להסתדר לבד.

7. השקיעו מאמץ נוסף

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

הוספת אפקטים חסרים

אלה הנתונים של האפקטים החסרים:

  • ‫Pulse: [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.

תיקון הבעיה "אין אפקט"

כמו שאולי שמתם לב, האפשרות "ללא אפקט" לא מאפסת אפקט שפועל כרגע. זה עניין קטן, אבל עדיין. בואו נפתור את זה. ב-method‏ setColor, קודם צריך לבדוק אם אפקט נמצא בתהליך באמצעות משתנה class חדש _isEffectSet, ואם true, להשבית את האפקט לפני שמגדירים צבע חדש עם הנתונים האלה: [0x00, r, g, b, 0x05, 0x00, 0x01, 0x00].

צריך לכתוב את שם המכשיר

זה קל! כדי לכתוב שם מותאם אישית למכשיר, פשוט כותבים למאפיין הקודם של שם מכשיר ה-Bluetooth. מומלץ להשתמש בשיטה TextEncoder encode כדי לקבל Uint8Array שמכיל את שם המכשיר.

אחר כך, כדי לשמור על פשטות, הייתי מוסיף "קלט" eventListener ל-document.querySelector('#deviceName') וקורא ל-playbulbCandle.setDeviceName.

אני קראתי לשלי PLAY💡 CANDLE!

8. זהו!

מה למדתם

  • איך ליצור אינטראקציה עם מכשיר Bluetooth קרוב ב-JavaScript
  • איך משתמשים ב-ES2015 Classes, ב-Arrow functions, ב-Map וב-Promises

השלבים הבאים