iT邦幫忙

2025 iThome 鐵人賽

DAY 28
0
Modern Web

Web Bluetooth API 實戰:30 天打造通用 BLE 偵錯工具系列 第 28

Day 28:深入探索與強健性:讀取描述符與手動斷線

  • 分享至 

  • xImage
  •  

前言

昨天,我們為工具安裝了「被動安全系統」——當意外(藍牙斷線)來襲時,我們的應用程式能夠自動感知並優雅地回到港口(初始狀態)。

今天,我們將為這艘船安裝「主動控制系統」,並對島嶼進行最後、最精細的勘探。

首先,我們將賦予使用者主動結束的權力。一個專業的工具,不能只有開始,沒有結束。我們將新增一個「中斷連線」按鈕,讓使用者可以隨時、主動、乾淨地切斷藍牙連接,這將使我們的工具在可用性和強健性上,再上一個台階。

接著,我們將拿出探險家的「顯微鏡」,深入 GATT 結構的最後一層、也是最精細的一層——「描述符 (Descriptors)」。如果說「特徵」是寶箱,那麼「描述符」就是貼在寶箱上的標籤和說明書,它記錄了關於這個寶箱的元數據,例如「寶箱內物品的單位」或「開啟寶箱通知功能的開關」。探索它,將讓我們對裝置的理解達到極致。

內文

1. 強健性升級:新增「中斷連線」按鈕

一個完整的互動流程,必須有開始,也要有結束。

步驟一:在 HTML 中新增按鈕

打開 index.html,在「掃描」按鈕旁邊,加入一個預設為隱藏的「中斷連線」按鈕。

index.html


//index.html
<div class="control-section">
  <button id="scanButton">掃描並連接藍牙裝置</button>
  <button id="disconnectButton" class="hidden">中斷連線</button>
</div>

步驟二:用 CSS 定義樣式

打開 style.css,為新按鈕添加樣式,並定義 .hidden class。


//style.css
#disconnectButton {
  background-color: #dc3545; /* 紅色,表示危險或結束操作 */
  /* 其他樣式可以參考 scanButton */
  color: white;
  border: none;
  border-radius: 5px;
  padding: 10px 15px;
  font-size: 16px;
  cursor: pointer;
  transition: background-color 0.2s;
}

#disconnectButton:hover {
  background-color: #c82333;
}

.hidden {
  display: none; /* 讓元素完全從頁面上消失 */
}

步驟三:在 JavaScript 中注入靈魂

這是最核心的部分。我們需要在連接成功後,顯示「中斷連線」按鈕;在斷線後,再把它隱藏起來。

打開 app.js

// app.js

// [1] 在最上方選取 DOM 元素
const disconnectButton = document.getElementById('disconnectButton');

// [2] 在 onDisconnected 函式中,加入重置按鈕狀態的邏輯
function onDisconnected(event) {
  // ... 其他清理程式碼 ...
  
  // 顯示掃描按鈕,隱藏中斷連線按鈕
  scanButton.classList.remove('hidden');
  disconnectButton.classList.add('hidden');
}

// [3] 在全域新增 disconnectButton 的點擊事件
disconnectButton.onclick = () => {
  if (gattProfile.device && gattProfile.device.gatt.connected) {
    log('正在手動中斷連線...');
    gattProfile.device.gatt.disconnect();
  } else {
    log('沒有已連接的裝置。');
  }
};

// [4] 在 scanButton.onclick 的 try 區塊中,連接成功後,更新按鈕狀態
// ...
log('> 成功連接到 GATT 伺服器!');
// ...

// 顯示中斷連線按鈕,隱藏掃描按鈕
scanButton.classList.add('hidden');
disconnectButton.classList.remove('hidden');

device.addEventListener('gattserverdisconnected', onDisconnected);
// ...

程式碼解析:

  • 狀態切換scanButtondisconnectButton 就像蹺蹺板,一個顯示時另一個就隱藏。我們在「連接成功」和「斷線時」這兩個關鍵節點,更新它們的 .hidden class 來實現這個效果。

  • 主動斷線disconnectButton.onclick 呼叫了 gattProfile.device.gatt.disconnect()。這會主動觸發藍牙斷線。

  • 架構的優雅:我們不需要disconnectButton.onclick 裡手動清理 UI!因為 disconnect() 會觸發我們昨天寫好的 gattserverdisconnected 事件,從而自動呼叫 onDisconnected 函式,完成所有的清理工作。這證明了我們事件驅動的架構是多麼穩健!

2. 深入探索:讀取描述符

描述符是 GATT 結構中最精細的單位,它為特徵提供上下文。

  • characteristic.getDescriptors():

    • 非同步方法,回傳一個 Promise,解析後得到一個包含所有 BluetoothGATTDescriptor 物件的陣列。
  • descriptor.readValue():

    • characteristic.readValue() 類似,用於讀取描述符的值,同樣回傳一個包含 DataViewPromise

程式碼實戰:

我們將在探索「特徵」的迴圈內部,再嵌套一層迴圈來探索「描述符」。

app.jsscanButton.onclick 函式中,找到遍歷特徵的 for...of 迴圈,並在其中加入以下程式碼:

// 在遍歷 characteristics 的 for...of 迴圈內部
for (const characteristic of characteristics) {
    // ... 之前處理特徵和渲染特徵面板的程式碼 ...
    renderCharacteristic(service.uuid, { /*...*/ }, serviceCard);
    
    // ---- ↓↓↓ 今天新增的核心程式碼 (2) ↓↓↓ ----

    try {
        log(`------> 正在探索描述符...`);
        const descriptors = await characteristic.getDescriptors();

        for (const descriptor of descriptors) {
            log(`---------> 描述符 UUID: ${descriptor.uuid}`);
            
            // 讀取描述符的值
            const valueDataView = await descriptor.readValue();
            
            // 我們使用 Day 28 的 parseValue 函式來解析
            const parsedValue = parseValue(valueDataView); 
            log(`---------> 描述符值: ${parsedValue}`);

            // TODO: (可選) 創建一個 renderDescriptor 的 UI 函式並呼叫它
        }
    } catch (error) {
        log(`------> 探索描述符失敗: ${error.message}`);
    }
    // ---- ↑↑↑ 今天新增的核心程式碼 (2) ↑↑↑ ----
}

程式碼解析:

  • 我們在 characteristic 迴圈內,呼叫了 getDescriptors()

  • 接著遍歷返回的 descriptors 陣列。

  • 對於每一個 descriptor,我們呼叫 readValue() 並用我們之前寫好的 parseValue 函式來解析和記錄它的值。

現在,當你連接到一個裝置(例如包含標準心率服務的虛擬裝置),你將會在日誌中看到,它不僅探索到了服務和特徵,還進一步探索到了 Heart Rate Measurement 特徵下的 Client Characteristic Configuration 描述符 (2902)!


後續

今天,我們WEB工具的所有核心功能都已打造完畢。它已經是一個可靠、功能完備的實用工具。我們幾乎掌握了 Web Bluetooth API 中所有關於探索互動的關鍵部分。

在我們為這趟精彩的旅程畫上句點之前,還有最後一個錦上添花的重要主題值得我們探討:效能與最佳化。一個好的工具不僅要功能完整,還應該運行流暢、反應迅速,尤其是在處理大量數據流時。
明天,我們將為這次鐵人賽衝刺畫上一個完美的句號。我們將回顧整個專案,並專注於程式碼的最佳化與重構。我們將探討如何讓日誌系統更有效率、如何避免不必要的 DOM 操作,以及如何讓我們的程式碼架構更清晰、更易於未來的擴展。這將是從「能用」到「好用」的最後一步昇華。

那麼今天的內容就到這邊,感謝你能看到這裡,在這邊祝你早安、午安、晚安,我們明天見。


上一篇
Day 27 強健性工程 (1):處理斷線與解析標準服務名稱
下一篇
Day 29:效能與最佳化:打造流暢的日誌與高頻率通知體驗
系列文
Web Bluetooth API 實戰:30 天打造通用 BLE 偵錯工具29
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言