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

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

JPG_19700101_023537~2~2.jpg

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

מה תלמדו

  • איך לקיים אינטראקציה עם מכשיר Bluetooth בקרבת מקום ב-JavaScript
  • איך משתמשים בכיתות ES2015, בפונקציות חיצים, במפה ובהבטחות

מה צריך להכין

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

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

אפשר גם לצפות בי בזמן שינוי הצבעים בכתובת https://www.youtube.com/watch?v=fBCPA9gIxlU

3. להגדרה

להורדת הקוד לדוגמה

אפשר לקבל את הקוד לדוגמה של הקוד הזה על ידי הורדת המיקוד כאן:

או על ידי שכפול המאגר הזה של git:

git clone https://github.com/googlecodelabs/candle-bluetooth.git

אם הורדתם את המקור כקובץ ZIP, פורקים אותו אמור ליצור תיקיית בסיס: candle-bluetooth-master.

התקנה ואימות של שרת אינטרנט

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

לאחר התקנת האפליקציה 'שרת אינטרנט עבור Chrome', לוחצים על קיצור הדרך 'אפליקציות' בסרגל הסימניות:

צילום מסך מתאריך 2016-11-16 בשעה 16.10.42.png

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

9f3c21b2cf6cbfb5.png

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

צילום מסך משנת 2016-11-16 בשעה 3.40.47 PM.png

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

בקטע 'אפשרויות', מסמנים את התיבה לצד 'הצגה אוטומטית של index.html', כפי שמוצג בהמשך:

צילום מסך מתאריך 2016-11-16 בשעה 3.40.56.png

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

צילום מסך מתאריך 2016-11-16 בשעה 15.20.22.png

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

הנושא הבא

בשלב זה אפליקציית האינטרנט הזו לא עושה הרבה. נתחיל להוסיף תמיכה ב-Bluetooth!

4. מגלים את הנר

נתחיל בכתיבת ספרייה שמשתמשת ב-JavaScript ES2015 Class במכשיר PLAYBULB Candle Bluetooth.

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

בנוסף, נעקוב אחרי האובייקט 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 נרשם ביומן.

צילום מסך מתאריך 2016-11-16 בשעה 3.27.12 PM.png

אם ה-Bluetooth כבוי או אם מכשיר PLAYBULB Candle Bluetooth כבוי, תופיע הודעת שגיאה. במקרה כזה, צריך להפעיל אותו ולהמשיך שוב.

בונוס חובה

לא ידוע לי עליך, אבל אני כבר רואה יותר מדי 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 מההבטחה של navigator.bluetooth.requestDevice? כדי להתחבר לשרת GATT המרוחק, שמכיל את שירות ה-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) ששייך לשירות הזה. אפשר לעשות זאת בקלות על ידי הוספת שיטה חדשה 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 פועל ואז ללחוץ על 'התחברות' בדף, ושם המכשיר אמור להופיע מתחת לבורר הצבעים.

צילום מסך מתאריך 2016-11-16 בשעה 3.29.21 PM.png

קריאת נתוני הטעינה של הסוללה

קיים גם מאפיין Bluetooth סטנדרטי ברמת הסוללה שזמין במכשיר ה-Bluetooth עם נרתיק PLAYBULB שמכיל את רמת הטעינה של הסוללה. המשמעות היא שאנחנו יכולים להשתמש בשמות סטנדרטיים כמו battery_service עבור ה-UUID של שירות Bluetooth GATT ו-battery_level עבור ה-UUID של המאפיין Bluetooth GATT.

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

צילום מסך מתאריך 2016-11-16 בשעה 3.29.21 PM.png

הנושא הבא

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

- כל כך קרוב, מבטיח...

שאלות נפוצות

6. שינוי הצבע

שינוי הצבע פשוט כמו כתיבת קבוצה ספציפית של פקודות למאפיין Bluetooth (0xFFFC) בשירות GATT הראשי שמפורסם בשם 0xFF02. לדוגמה, כדי להפוך את PLAYBULB Candle לאדום, המשמעות היא שמערך מספרים שלמים לא חתומים בן 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 כאשר "No Impact" (ללא אפקט) לחצן הבחירה מסומן. משתני הצבע הגלובליים 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 כמה פעמים שרוצים.

צילום מסך משנת 2016-11-16 בשעה 3.31.37 PM.png

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

אם כבר הדלקתם נר בעבר, סימן שהנורה לא סטטית. למרבה המזל, בשירות GATT הראשי יש מאפיין Bluetooth נוסף (0xFFFB) שפורסם בתור 0xFF02, שמאפשר למשתמש להגדיר אפקטים של נרות.

הגדרת 'אפקט לנר' למשל אפשר להשיג על ידי כתיבת [0x00, r, g, b, 0x04, 0x00, 0x01, 0x00]. ואפשר גם להגדיר את 'אפקט ההבהוב' עם [0x00, r, g, b, 0x00, 0x00, 0x1F, 0x00].

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

צילום מסך מתאריך 2016-11-16 בשעה 3.33.23 PM.png

הנושא הבא

- זה הכול? 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]

בעיקרון, מדובר בהוספת שיטות חדשות setPulseColor, setRainbow ו-setRainbowFade לכיתה PlaybulbCandle ולהתקשר אליהן ב-changeColor.

איך פותרים את הבעיה 'ללא אפקט'

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

השלבים הבאים