昨天,我們取得了歷史性的突破。透過 navigator.bluetooth.requestDevice()
,我們的網頁第一次「看見」了真實世界中的藍牙裝置,並成功捕獲了代表它的 device
物件。我們找到了傳說中的寶藏島,正站在船頭,滿懷期待地眺望著海岸。
但發現島嶼只是第一步。要尋寶,我們必須踏上陸地,建立一個穩固的營地,作為所有探索行動的基地。在 Web Bluetooth 的世界裡,這個「登陸」的動作,就是連接到裝置的 GATT 伺服器。GATT 伺服器是裝置上所有資料(服務與特徵)的管理者,建立與它的連接,是我們後續所有讀寫操作的堅實基礎。
同時,在我們探索未知島嶼的過程中,詳細記錄每一步的足跡至關重要。「我們在什麼時候發現了島嶼?」「什麼時候成功登陸?」「探索過程中發生了什麼錯誤?」為了讓我們的工具更加專業,今天,我們不僅要學會如何連接,還將親手打造一個專屬的「航海日誌」系統,將每一步操作都清晰地顯示在介面上。
準備好,讓我們正式登陸!
device.gatt.connect()
- 登陸島嶼的指令我們昨天獲取的 device
物件,本身還不能用來讀寫資料。它身上有一個非常重要的屬性叫做 gatt
,這就是通往裝置內部資料世界的大門。而 device.gatt.connect()
就是打開這扇大門的鑰匙。
device.gatt
:BluetoothDevice
物件的一個屬性,它回傳一個 BluetoothRemoteGATT
物件,提供了連接到 GATT 伺服器的功能。
.connect()
:device.gatt
物件的方法。
非同步:它是一個非同步操作,會立即回傳一個 Promise
。
回傳值:當 Promise 成功解析 (fulfilled) 時,它會回傳一個 BluetoothRemoteGATTServer
物件。這個物件就代表了我們與裝置 GATT 伺服器的有效連接,是我們後續探索服務的起點。
失敗:如果連接失敗(例如裝置突然關機或超出範圍),Promise 將會被拒絕 (rejected),我們的 try...catch
區塊會捕捉到這個錯誤。
在修改連接邏輯之前,我們先把日誌系統建立好。一個好的日誌,能讓開發者(也就是我們自己)清楚地看到每一步發生了什麼。
打開 index.html
,在 <div id="gattContainer">
的下方,加入日誌的區塊。
index.html
</div>
<hr> <h3>日誌記錄 (Log)</h3>
<div id="logContainer">
</div>
<script src="app.js"></script>
</body>
</html>
打開 style.css
,在檔案末尾加入新的樣式,讓它看起來像一個專業的日誌面板。
style.css
/* --- 日誌容器樣式 --- */
#logContainer {
background-color: #2c3e50; /* 深色背景 */
color: #ecf0f1; /* 淺色文字 */
font-family: 'Courier New', Courier, monospace; /* 等寬字體 */
font-size: 14px;
padding: 15px;
height: 250px; /* 固定高度 */
overflow-y: scroll; /* 當內容超出高度時,顯示垂直滾動條 */
border-radius: 5px;
border: 1px solid #34495e;
}
#logContainer p {
margin: 0 0 5px 0; /* 每條日誌之間的間距 */
word-break: break-all; /* 長字串自動換行 */
}
log
函式這是最核心的一步。我們將創建一個萬用的 log
函式,它會同時在開發者控制台和我們的 UI 上輸出訊息。
打開 app.js
,在檔案的最上方,選取我們新的 DOM 元素,並定義 log
函式。
app.js
// --- [1] 選取核心 DOM 元素 ---
const scanButton = document.getElementById('scanButton');
const statusText = document.getElementById('statusText');
const gattContainer = document.getElementById('gattContainer');
const logContainer = document.getElementById('logContainer'); // 新增!
// --- [2] 日誌記錄函式 ---
/**
* 在 UI 和 Console 中記錄訊息
* @param {string} message - 要記錄的訊息
*/
function log(message) {
const now = new Date();
const timeString = now.toLocaleTimeString(); // 取得 HH:MM:SS 格式的時間
// 1. 在開發者控制台印出
console.log(message);
// 2. 在 UI 上創建一個新的 p 元素來顯示日誌
const logEntry = document.createElement('p');
logEntry.innerHTML = `<strong>[${timeString}]</strong> ${message}`;
// 3. 將新的日誌項目附加到日誌容器中
logContainer.appendChild(logEntry);
// 4. 自動將滾動條滾動到最底部,以便永遠顯示最新的日誌
logContainer.scrollTop = logContainer.scrollHeight;
}
// --- [3] GATT Profile 資料結構 ---
// ... (gattProfile 物件) ...
// ... (UI 生成函式) ...
logContainer.scrollTop = logContainer.scrollHeight;
是一個非常實用的小技巧,它能確保每次新增日誌後,滾動條都會自動滾動到底部,讓我們總能看到最新的訊息。萬事俱備!現在我們回到 scanButton.onclick
函式,用我們全新的 log
函式來記錄流程,並加入 device.gatt.connect()
的呼叫。
修改 app.js
中的 scanButton.onclick
函式:
// app.js
scanButton.onclick = async () => {
// 清空舊的日誌和 GATT 資訊
gattContainer.innerHTML = '';
logContainer.innerHTML = '';
if (!navigator.bluetooth) {
log('錯誤: 您的瀏覽器不支援 Web Bluetooth API。');
return;
}
try {
log('正在請求藍牙裝置...');
const device = await navigator.bluetooth.requestDevice({
acceptAllDevices: true
});
log(`> 已選擇裝置: ${device.name || `ID: ${device.id}`}`);
gattProfile.device = device;
// ---- ↓↓↓ 今天新增的核心程式碼 ↓↓↓ ----
log('正在連接到 GATT 伺服器...');
const server = await device.gatt.connect();
log('> 成功連接到 GATT 伺服器!');
gattProfile.server = server;
statusText.textContent = `已連接至 ${device.name}`;
// TODO: 明天的任務 -> 探索這個伺服器上的服務...
// ---- ↑↑↑ 今天新增的核心程式碼 ↑↑↑ ----
} catch(error) {
log(`錯誤: ${error.message}`);
}
};
我們將 console.log
和一部分 statusText.textContent = ...
都替換成了我們的 log()
函式,讓日誌記錄更統一、更強大。
我們在 await device.gatt.connect()
前後都加入了日誌,這樣使用者就能清楚地看到程式正在進行哪一步。
現在,重新整理頁面並點擊掃描按鈕。選擇一個裝置後,你將會在 UI 的日誌區看到類似這樣的輸出:
[17:05:10] 正在請求藍牙裝置...
[17:05:15] > 已選擇裝置: My ESP32
[17:05:15] 正在連接到 GATT 伺服器...
[17:05:16] > 成功連接到 GATT 伺服器!
今天,我們成功地從「發現」邁向了「連接」!
我們已經成功登陸了寶藏島,並在海灘上建立了一個堅固的營地(GATT Server 連接)。我們的航海日誌(Log System)也已準備就見證著我們的偉大航程。
營地已經紮好,下一步就是拿出藏寶圖,開始真正的探索了。明天,我們將從 server
物件出發,學習如何使用 server.getPrimaryService()
和 server.getPrimaryServices()
來發現這個裝置到底提供了哪些「服務」(藏寶圖上的山洞)。我們將第一次窺見裝置內部的秘密。
那麼今天的內容就到這邊,感謝你能看到這裡,在這邊祝你早安、午安、晚安,我們明天見。