昨天,我們學習了 Web Bluetooth API 的三大黃金法則,如同飛行員在起飛前 meticulously 檢查儀表板,確保了萬無一失。我們的 scanButton.onclick
函式已經整裝待發,try...catch
這個強大的安全網也已鋪設完畢,就等待著那條能喚醒魔法的指令。
今天,所有的理論和等待都將告一段落。我們將寫下並執行自專案啟動以來,最令人激動的一行程式碼:navigator.bluetooth.requestDevice()
。這行程式碼將會打通虛擬與現實的壁壘,讓我們的網頁第一次真正地「感知」到周遭的物理世界。
在發射火箭之前,我們需要為它設定航向。requestDevice()
中的設定選項,決定了我們要尋找什麼樣的裝置。今天,我們將學習其中最簡單、最直接,也最符合我們「通用偵錯工具」目標的選項:acceptAllDevices: true
。我們將一同見證它「萬物皆可連」的強大之處,並探討其背後,身為開發者需要承擔的責任與權衡。
準備好,點火程序,正式開始!
navigator.bluetooth.requestDevice(options)
函式詳解這是我們與藍牙世界互動的唯一入口。
功能:向使用者請求權限,並顯示一個由瀏覽器原生提供的 UI 視窗,讓使用者可以從附近掃描到的藍牙裝置列表中,選擇一個來進行配對。
非同步:它是一個標準的非同步函式,會立即回傳一個 Promise。
使用者手勢:再次強調,它必須在 click
等使用者手勢事件中被呼叫。
options
物件:它接收一個設定物件作為參數,用來告訴瀏覽器,我們對什麼樣的裝置感興趣。
acceptAllDevices: true
的力量這是 options
物件中最簡單直接的一個屬性。
作用:當你設定 acceptAllDevices: true
時,你等於在告訴瀏覽器:「別過濾了,把附近所有正在廣播的低功耗藍牙裝置,全都顯示在列表裡給我看!」
適用場景:這對於我們的「通用偵錯工具」來說,是完美的選項。因為我們的目標,就是要能探索和連接任何未知的裝置。
現在,是時候將這行魔法指令,放入我們昨天的 try...catch
區塊中了。
打開 app.js
,修改 scanButton.onclick
函式:
// app.js
scanButton.onclick = async () => {
// 我們的預檢程式碼...
if (!navigator.bluetooth) {
statusText.textContent = 'Error: Web Bluetooth is not supported in this browser.';
return;
}
console.log('瀏覽器支援 Web Bluetooth API!');
try {
// ---- ↓↓↓ 今天新增的核心程式碼 ↓↓↓ ----
console.log('Requesting Bluetooth device...');
statusText.textContent = '正在請求藍牙裝置...請在彈出視窗中選擇';
// 呼叫 API,並等待使用者選擇一個裝置
const device = await navigator.bluetooth.requestDevice({
acceptAllDevices: true
});
console.log('使用者已選擇裝置:', device);
// 更新 UI,顯示已選擇的裝置名稱(如果裝置有名稱的話)
statusText.textContent = `已選擇裝置: ${device.name || `ID: ${device.id}`}`;
// 將獲取到的真實裝置資訊,存入我們之前設計好的 gattProfile 物件中
gattProfile.device.name = device.name;
gattProfile.device.id = device.id;
console.log('GATT Profile 已更新:', gattProfile);
// TODO: 明天的任務 -> 連接到這個裝置...
// ---- ↑↑↑ 今天新增的核心程式碼 ↑↑↑ ----
} catch(error) {
// 這裡將會捕捉到使用者點擊「取消」時的錯誤
console.error('掃描時發生錯誤:', error);
statusText.textContent = `錯誤: ${error.message}`;
}
};
確保你使用的是支援的瀏覽器 (Chrome/Edge)。
確保你已經透過 "Live Server" 或其他方式,在 localhost
下運行你的網頁。
確保你電腦或手機的藍牙已經開啟。
確保附近有一個正在廣播的 BLE 裝置(例如,你前幾天用手機 App 創建的虛擬裝置,或者一個藍牙滑鼠、手環等)。
點擊「掃描藍牙裝置」按鈕!
你應該會看到瀏覽器彈出一個原生的視窗,上面列出了附近可用的藍牙裝置。
成功路徑:選擇一個裝置,點擊「配對 (Pair)」。彈窗消失,await
順利完成,回傳一個 device
物件。你的網頁狀態文字會更新,Console 也會印出裝置的詳細資訊。
失敗路徑:在彈窗中,點擊「取消 (Cancel)」。requestDevice()
的 Promise 會立刻變為 rejected
狀態,await
將拋出一個錯誤。我們的 catch
區塊會立刻捕獲到這個錯誤,並在網頁和 Console 中顯示 DOMException: User cancelled the requestDevice()
這樣的訊息。這完美地展示了 try...catch
的作用!
acceptAllDevices
的責任「萬物皆可連」的能力非常強大,但它並非萬靈丹。
對於我們的工具:它是正確的選擇,因為我們的目標就是探索一切。
對於專業的、單一用途的應用:它是一個糟糕的選擇。
想像一下,你正在為一個「智慧咖啡杯」開發專用的網頁 App。你希望使用者在點擊「連接」時,只看到附近的「智慧咖啡杯」,而不是他鄰居的智慧電視、辦公室的藍牙鍵盤和路上行人的運動手環。向使用者展示一個包含數十個無關裝置的混亂列表,會造成極大的困擾,這是非常差的使用者體驗。
在這種情況下,我們應該使用「過濾器 (Filters)」來代替 acceptAllDevices
。過濾器可以讓我們精準地告訴瀏覽器:「我只對那些廣播自己擁有『咖啡杯服務 UUID』的裝置感興趣」。這將是我們後續課程會探討的進階主題。
今天我們已經成功地「發現」了島嶼,並將它(device
物件)標記在了我們的地圖 (gattProfile
) 上。但是,我們人還在船上,尚未登陸。
明天,我們的任務就是建立連接。我們將學習如何使用 device.gatt.connect()
這個方法,在我們的網頁和使用者選擇的裝置之間,建立一條穩定可靠的通訊鏈路,為接下來探索裝置內部的服務與特徵,做好萬全準備。
那麼今天的內容就到這邊,感謝你能看到這裡,在這邊祝你早安、午安、晚安,我們明天見。