透過網路藍牙控制 PLAYBULB 蠟燭

1. 遊戲介紹

IMG_19700101_023537 至 2~2.jpg

在這個充滿啟發性的程式碼研究室中,您將學習如何使用 Web Bluetooth API 控制 JavaScript 以外的 PLAYBULB LED 無燃蠟燭,過程中,您也可以使用 JavaScript ES2015 的功能,例如類別箭頭函式地圖承諾

課程內容

  • 如何透過 JavaScript 與附近的藍牙裝置互動
  • 如何使用 ES2015 類別、箭頭函式、地圖和 Promise

軟硬體需求

2. 先玩

建議您前往 https://googlecodelabs.github.io/candle-bluetooth 查看即將建立的最終應用程式版本,並在實際參與這個程式碼研究室前,使用現有的 PLAYBULB 蠟燭藍牙裝置進行遊戲。

你也可以前往 https://www.youtube.com/watch?v=fBCPA9gIxlU 觀看變色影片

3. 做好準備

下載程式碼範例

您可以在這裡下載 zip 檔案,取得這個程式碼的程式碼範例:

或是複製這個 Git 存放區:

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

如果將來源下載為 ZIP 檔案,解壓縮後應會顯示根資料夾 candle-bluetooth-master

安裝並驗證網路伺服器

雖然你可以免費使用自己的網路伺服器,但本程式碼研究室適用於 Chrome 網路伺服器。如果你尚未安裝這個應用程式,可以前往 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

按一下「選擇資料夾」按鈕,然後選取複製 (或取消封存) 存放區的根目錄。這樣一來,您就能透過網路伺服器對話方塊中 (位於「網路伺服器網址」部分) 中醒目顯示的網址,提供處理中的工作。

在「選項」之下勾選「自動顯示 index.html」旁的方塊,如下所示:

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

現在請透過網路瀏覽器造訪您的網站 (點選醒目顯示的網路伺服器網址),您應該會看到類似下方的網頁:

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

如要查看這個應用程式在 Android 手機上的顯示情形,您必須啟用 Android 的遠端偵錯功能,並設定通訊埠轉送 (預設通訊埠編號為 8887)。更新後,只要開啟新的 Chrome 分頁,即可在 Android 手機上前往 http://localhost:8887

下一步

目前這個應用程式沒什麼功能。現在就開始新增藍牙支援功能!

4. 探索蠟燭

我們會先編寫一個程式庫,使用適用於 PLAYBULB 蠟燭藍牙裝置的 JavaScript ES2015 類別。

保持冷靜,類別語法不會為 JavaScript 推出新的物件導向繼承模型。這只是提供更明確的語法來建立物件和處理繼承,如下方內容所示。

首先,請在 playbulbCandle.js 中定義 PlaybulbCandle 類別,並建立 playbulbCandle 例項,稍後可在 app.js 檔案中使用。

playbulbCandle.js

(function() {
  'use strict';

  class PlaybulbCandle {
    constructor() {
      this.device = null;
    }
  }

  window.playbulbCandle = new PlaybulbCandle();

})();

如要要求存取附近的藍牙裝置,需呼叫 navigator.bluetooth.requestDevice。由於 PLAYBULB Candle 裝置會持續通告 (如果尚未配對) 固定式藍牙 GATT 服務 UUID,簡稱為 0xFF02,我們只需定義常數,然後在 PlaybulbCandle 類別的新公開 connect 方法中,將這個常數新增至篩選器服務參數即可。

我們也會在 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();

})();

為了安全起見,必須使用觸控或滑鼠點擊等使用者手勢,才能利用 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);
  });
});

執行應用程式

現在,請在網路瀏覽器上造訪您的網站 (按一下網路伺服器應用程式中醒目顯示的網路伺服器網址),或是重新整理現有網頁。按一下綠色的「連線」按鈕,從選擇工具中選取裝置,然後使用 Ctrl + Shift + J 鍵盤快速鍵開啟慣用的開發人員工具控制台,接著您會發現系統記錄到 BluetoothDevice 物件。

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

如果關閉藍牙和/或 PLAYBULB 蠟燭藍牙裝置關閉,系統可能會顯示錯誤訊息。在這種情況下,請啟用裝置並繼續操作。

強制獎勵

我不知道你,但這個程式碼中有太多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. 朗讀內容

那麼,現在您是否已從 navigator.bluetooth.requestDevice 的承諾中傳回 BluetoothDevice?我們可以呼叫 device.gatt.connect() 來連線至保留藍牙服務和特性定義的藍牙遠端 GATT 伺服器:

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();
      });
    }
  }

讀出裝置名稱

我們已經連線至 PLAYBULB 蠟燭藍牙裝置的 GATT 伺服器。現在,我們要取得主要 GATT 服務 (先前誤稱 0xFF02),並讀取屬於這項服務的裝置名稱特性 (0xFFFF)。只要在 PlaybulbCandle 類別中新增 getDeviceName 方法,並使用 device.gatt.getPrimaryServiceservice.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;
}

現在,請在網路瀏覽器上造訪您的網站 (按一下網路伺服器應用程式中醒目顯示的網路伺服器網址),或是重新整理現有網頁。確認已開啟 PLAYBULB 蠟燭,然後按一下 [連線]你應該會在顏色挑選器下方看到裝置名稱。

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

查看電池電量

在包含裝置電池電量的 PLAYBULB 蠟燭藍牙裝置設有標準電池電量藍牙特性。這表示我們可以使用標準名稱,如 battery_service Bluetooth GATT Service UUID,battery_level 用於藍牙 GATT 特性 UUID。

讓我們在 PlaybulbCandle 類別中新增 getBatteryLevel 方法,並讀取電池電量 (以百分比表示)。

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 金鑰納入電池服務,因為 PLAYBULB 燭台藍牙裝置並未通告,但仍須使用這類物件。

playbulbCandle.js

      let options = {filters:[{services:[ CANDLE_SERVICE_UUID ]}],
                     optionalServices: ['battery_service']};
      return navigator.bluetooth.requestDevice(options)

和先前一樣,讓我們在取得裝置名稱並顯示電池電量後,呼叫 playbulbCandle.getBatteryLevel 來將其插入 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)
    .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 + '%';
}

現在,請在網路瀏覽器中造訪您的網站 (按一下網路伺服器應用程式中醒目顯示的網路伺服器網址),或是重新整理現有網頁。按一下 [連線]按鈕,就能看到裝置名稱和電池電量。

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

下一步

- 如何變更這個燈泡的顏色?這就是為什麼我會到這裡!

- 你真的很接近了...

常見問題

6. 變更顏色

變更顏色就像在宣傳為 0xFF02 的主要 GATT 服務中,將一組特定指令寫成藍牙特性 (0xFFFC) 一樣簡單。舉例來說,將 PLAYBULB 蠟燭轉成紅色將寫入等於 [0x00, 255, 0, 0] 的 8 位元無正負號整數陣列,其中 0x00 是白色的飽和度,255, 0, 0 則分別為紅色、綠色和藍色值。

我們會使用 characteristic.writeValue,在 PlaybulbCandle 類別的新 setColor 公開方法中將部分資料寫入藍牙特性。此外,我們會在履行承諾時傳回實際的紅色、綠色和藍色值,方便稍後在 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]);
    }

請更新 app.js 中的 changeColor 函式,以便在「無效果」時呼叫 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);
  }
}

現在,請在網路瀏覽器上造訪您的網站 (按一下網路伺服器應用程式中醒目顯示的網路伺服器網址),或是重新整理現有網頁。按一下 [連線]並按一下顏色挑選器,即可變更 PLAYBULB 蠟燭的顏色。

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

馬達蠟燭效果

如果您先前已為蠟燭,那就知道指示燈沒有靜止不動。幸好,在宣傳為 0xFF02 的主要 GATT 服務中,還有另一個藍牙特性 (0xFFFB),可讓使用者設定一些蠟燭效果。

設定「蠟燭效果」例如,您可以編寫 [0x00, r, g, b, 0x04, 0x00, 0x01, 0x00] 來達成此目的您也可以設定「閃爍效果」只在 [0x00, r, g, b, 0x00, 0x00, 0x1F, 0x00]

讓我們將 setCandleEffectColorsetFlashingColor 方法新增至 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]);
    }

接著更新 app.js 中的 changeColor 函式,以便在「Candle Effect」時呼叫 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;
  }
}

現在,請在網路瀏覽器上造訪您的網站 (按一下網路伺服器應用程式中醒目顯示的網路伺服器網址),或是重新整理現有網頁。按一下 [連線]按鈕,並使用蠟燭和閃光效果。

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

下一步

- 就這樣?3 道蠟燭效果很差?那我就是在這裡嗎?

- 還有更多內容,但這次你可自己動手。

7. 再努力一下

終於!您可能會以為快已接近尾聲,但應用程式尚未推出。讓我們看看您是否確實瞭解在本程式碼研究室中複製貼上的內容。為了讓這個應用程式脫穎而出,立即採取下列行動。

新增缺少的特效

以下是缺少效果的資料:

  • Pulse:[0x00, r, g, b, 0x01, 0x00, 0x09, 0x00] (建議在該處調整 r, g, b 值)
  • 彩虹:[0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x01, 0x00] (Epeption 使用者可能不應該避免這個情況)
  • 彩虹淡出:[0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x26, 0x00]

基本上,也就是將新的 setPulseColorsetRainbowsetRainbowFade 方法新增至 PlaybulbCandle 類別,並在 changeColor 中呼叫這些方法。

修正「沒有效果」

您可能已註意到,「沒有影響」這個選項不會重設任何進行中的效果,這項操作是輕微,但仍較不重要。接著就來解決這個問題。在 setColor 方法中,您需要先檢查某個效果是否正在透過新的類別變數 _isEffectSet 進行;如為 true,請先關閉效果,再使用以下資料設定新顏色:[0x00, r, g, b, 0x05, 0x00, 0x01, 0x00]

寫入裝置名稱

這題非常簡單!撰寫自訂裝置名稱,就像寫出舊藍牙裝置名稱特性一樣簡單。建議您使用 TextEncoder encode 方法取得包含裝置名稱的 Uint8Array

然後新增「輸入」eventListenerdocument.querySelector('#deviceName') 並呼叫 playbulbCandle.setDeviceName 來保持簡化。

我個人命名為「我的 Play💡?」!

8. 這樣就大功告成了!

您已經瞭解的內容

  • 如何透過 JavaScript 與附近的藍牙裝置互動
  • 如何使用 ES2015 類別、箭頭函式、地圖和 Promise

後續步驟