1. 遊戲介紹
在這個充滿啟發性的程式碼研究室中,您將學習如何使用 Web Bluetooth API 控制 JavaScript 以外的 PLAYBULB LED 無燃蠟燭,過程中,您也可以使用 JavaScript ES2015 的功能,例如類別、箭頭函式、地圖和承諾。
課程內容
- 如何透過 JavaScript 與附近的藍牙裝置互動
- 如何使用 ES2015 類別、箭頭函式、地圖和 Promise
軟硬體需求
- 對網頁開發有基本瞭解
- 具備藍牙低功耗 (BLE) 和一般屬性設定檔 (GATT) 的基本知識
- 自選的文字編輯器
- 擁有 Chrome 瀏覽器應用程式和 USB micro 對 USB 傳輸線的 Mac、Chromebook 或 Android M 裝置。
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 網路伺服器後,請按一下書籤列上的「應用程式」捷徑:
在接下來的視窗中,按一下「網路伺服器」圖示:
您接下來會看到這個對話方塊,可讓您設定本機網路伺服器:
按一下「選擇資料夾」按鈕,然後選取複製 (或取消封存) 存放區的根目錄。這樣一來,您就能透過網路伺服器對話方塊中 (位於「網路伺服器網址」部分) 中醒目顯示的網址,提供處理中的工作。
在「選項」之下勾選「自動顯示 index.html」旁的方塊,如下所示:
現在請透過網路瀏覽器造訪您的網站 (點選醒目顯示的網路伺服器網址),您應該會看到類似下方的網頁:
如要查看這個應用程式在 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
物件。
如果關閉藍牙和/或 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.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;
}
現在,請在網路瀏覽器上造訪您的網站 (按一下網路伺服器應用程式中醒目顯示的網路伺服器網址),或是重新整理現有網頁。確認已開啟 PLAYBULB 蠟燭,然後按一下 [連線]你應該會在顏色挑選器下方看到裝置名稱。
查看電池電量
在包含裝置電池電量的 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 + '%';
}
現在,請在網路瀏覽器中造訪您的網站 (按一下網路伺服器應用程式中醒目顯示的網路伺服器網址),或是重新整理現有網頁。按一下 [連線]按鈕,就能看到裝置名稱和電池電量。
下一步
- 如何變更這個燈泡的顏色?這就是為什麼我會到這裡!
- 你真的很接近了...
常見問題
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 蠟燭的顏色。
馬達蠟燭效果
如果您先前已為蠟燭,那就知道指示燈沒有靜止不動。幸好,在宣傳為 0xFF02
的主要 GATT 服務中,還有另一個藍牙特性 (0xFFFB
),可讓使用者設定一些蠟燭效果。
設定「蠟燭效果」例如,您可以編寫 [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]);
}
接著更新 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;
}
}
現在,請在網路瀏覽器上造訪您的網站 (按一下網路伺服器應用程式中醒目顯示的網路伺服器網址),或是重新整理現有網頁。按一下 [連線]按鈕,並使用蠟燭和閃光效果。
下一步
- 就這樣?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]
基本上,也就是將新的 setPulseColor
、setRainbow
和 setRainbowFade
方法新增至 PlaybulbCandle
類別,並在 changeColor
中呼叫這些方法。
修正「沒有效果」
您可能已註意到,「沒有影響」這個選項不會重設任何進行中的效果,這項操作是輕微,但仍較不重要。接著就來解決這個問題。在 setColor
方法中,您需要先檢查某個效果是否正在透過新的類別變數 _isEffectSet
進行;如為 true
,請先關閉效果,再使用以下資料設定新顏色:[0x00, r, g, b, 0x05, 0x00, 0x01, 0x00]
。
寫入裝置名稱
這題非常簡單!撰寫自訂裝置名稱,就像寫出舊藍牙裝置名稱特性一樣簡單。建議您使用 TextEncoder
encode
方法取得包含裝置名稱的 Uint8Array
。
然後新增「輸入」eventListener
對 document.querySelector('#deviceName')
並呼叫 playbulbCandle.setDeviceName
來保持簡化。
我個人命名為「我的 Play💡?」!
8. 這樣就大功告成了!
您已經瞭解的內容
- 如何透過 JavaScript 與附近的藍牙裝置互動
- 如何使用 ES2015 類別、箭頭函式、地圖和 Promise
後續步驟
- 進一步瞭解 Web Bluetooth API
- 瀏覽官方的網路藍牙範例和示範
- 看看飛越毛茸茸的貓