藍牙 GATT 用戶端
本文示範如何將藍牙通用屬性 (GATT) 用戶端 API 用於通用 Windows 平台 (UWP) 應用程式。
重要
您必須在 Package.appxmanifest 中宣告 "bluetooth" 功能。
<Capabilities> <DeviceCapability Name="bluetooth" /> </Capabilities>
重要 API
概觀
開發人員可以使用 Windows.Devices.Bluetooth.GenericAttributeProfile 命名空間中的 API 來存取藍牙 LE 裝置。 藍牙 LE 裝置透過以下集合公開其功能:
- 服務
- 特性
- 描述項
服務定義 LE 裝置的功能合約,並包含定義服務的特性集合。 這些特性又包含描述這些特性的描述項。 這 3 個詞彙通常稱為裝置的屬性。
藍牙 LE GATT API 公開物件和函式,而不是存取原始傳輸。 GATT API 還使開發人員能夠使用能夠執行以下工作的藍牙 LE 裝置:
- 執行屬性探索
- 讀取和寫入屬性值
- 註冊 Feature ValueChanged 事件的回呼
為了建立有用的實作,開發人員必須事先了解應用程式打算使用的 GATT 服務和特性,並處理特定的特性值,以便 API 提供的二進位資料在呈現給使用者之前轉換為有用的資料。 藍牙 GATT API 僅公開與藍牙 LE 裝置通訊所需的基本原語。 要解釋資料,必須透過藍牙 SIG 標準設定檔或裝置廠商實作的自訂設定檔來定義應用程式設定檔。 設定檔在應用程式和裝置之間建立一個綁定合約,以規定交換的資料代表什麼以及如何解釋它。
為了方便起見,藍牙 SIG 會維護可用的公共設定檔清單。
範例
有關完整範例,請參閱藍牙低功耗範例。
查詢附近的裝置
查詢附近裝置主要有兩種方法:
- Windows.Devices.Enumeration 中的 DeviceWatcher
- BluetoothLEAdvertisementWatcher in Windows.Devices.Bluetooth.Advertisement
第二種方法在通告文件中進行了詳細討論,因此這裡不會對其進行過多討論,但基本蓋念是尋找滿足特定通告篩選器的附近裝置的藍牙地址。 取得位址後,您可以呼叫 BluetoothLEDevice.FromBluetoothAddressAsync 來取得對裝置的參考。
現在,回到 DeviceWatcher 方法。 藍牙 LE 裝置就像 Windows 中的任何其他裝置一樣,可以使用列舉 API 進行查詢。 使用 DeviceWatcher 類別並傳遞指定要尋找裝置的查詢字串:
// Query for extra properties you want returned
string[] requestedProperties = { "System.Devices.Aep.DeviceAddress", "System.Devices.Aep.IsConnected" };
DeviceWatcher deviceWatcher =
DeviceInformation.CreateWatcher(
BluetoothLEDevice.GetDeviceSelectorFromPairingState(false),
requestedProperties,
DeviceInformationKind.AssociationEndpoint);
// Register event handlers before starting the watcher.
// Added, Updated and Removed are required to get all nearby devices
deviceWatcher.Added += DeviceWatcher_Added;
deviceWatcher.Updated += DeviceWatcher_Updated;
deviceWatcher.Removed += DeviceWatcher_Removed;
// EnumerationCompleted and Stopped are optional to implement.
deviceWatcher.EnumerationCompleted += DeviceWatcher_EnumerationCompleted;
deviceWatcher.Stopped += DeviceWatcher_Stopped;
// Start the watcher.
deviceWatcher.Start();
啟動 DeviceWatcher 後,您將收到滿足相關裝置的已新增事件處理常式中查詢的每個裝置的 DeviceInformation。 如需 DeviceWatcher 的詳細資料,請參閱 Github 上的完整範例。
連線裝置
發現所需裝置後,使用 DeviceInformation.Id 取得相關裝置的藍牙 LE 裝置物件:
async void ConnectDevice(DeviceInformation deviceInfo)
{
// Note: BluetoothLEDevice.FromIdAsync must be called from a UI thread because it may prompt for consent.
BluetoothLEDevice bluetoothLeDevice = await BluetoothLEDevice.FromIdAsync(deviceInfo.Id);
// ...
}
另一方面,處理對裝置的 BluetoothLEDevice 物件的所有參考 (並且如果系統上沒有其他應用程式具有對該裝置的參考) 將在短暫的逾時時間後,觸發自動中斷連線。
bluetoothLeDevice.Dispose();
如果應用程式需要再次存取裝置,只需重新建立裝置物件並存取特性 (在下一節中討論) 將觸發作業系統在必要時重新連接。 如果裝置在附近,您將可以存取該裝置,否則它將傳回 DeviceUnreachable 錯誤。
注意
單獨呼叫此方法建立 BluetoothLEDevice 物件不會 (不一定) 啟動連線。 若要啟動連接,請將 GattSession.MaintainConnection 設定為 true
,或在 BluetoothLEDevice 上呼叫未快取的服務探索方法,或對裝置執行讀取/寫入作業。
- 如果 GattSession.MaintainConnection 設定為 True,則系統將無限期地等待連接,並在裝置可用時進行連接。 您的應用程式無需等待,因為 GattSession.MaintainConnection 是一個屬性。
- 對於 GATT 中的服務發現和讀取/寫入作業,系統會等待有限但可變的時間。 從瞬間到幾分鐘都有可能 因素包括堆疊上的流量,以及要求佇列的方式。 如果沒有其他掛起的要求,且遠端裝置無法訪問,則系統將在逾時之前等待七 (7) 秒。 如果還有其他待處理的要求,則佇列中的每個要求可能需要七 (7) 秒的時間來處理,因此您的要求距離佇列後面越遠,等待的時間就越長。
目前,您無法取消連線程序。
列舉支持的服務和特性
現在您已經有了 BluetoothLEDevice 物件,下一步是探索裝置公開的資料。 若要這樣做,第一個步驟是查詢服務:
GattDeviceServicesResult result = await bluetoothLeDevice.GetGattServicesAsync();
if (result.Status == GattCommunicationStatus.Success)
{
var services = result.Services;
// ...
}
一旦識別出感興趣的服務,下一個步驟就是查詢特性。
GattCharacteristicsResult result = await service.GetCharacteristicsAsync();
if (result.Status == GattCommunicationStatus.Success)
{
var characteristics = result.Characteristics;
// ...
}
作業系統會傳回 GattCharacteristic 物件的唯讀清單,然後您可以對其執行作業。
對特性執行讀取/寫入作業
特性是 GATT 型通訊的基本單位。 它包含表示裝置上不同資料的值。 例如,電池電量特性具有表示裝置的電池電量的值。
讀取特性屬性,以確定支援哪些作業:
GattCharacteristicProperties properties = characteristic.CharacteristicProperties
if(properties.HasFlag(GattCharacteristicProperties.Read))
{
// This characteristic supports reading from it.
}
if(properties.HasFlag(GattCharacteristicProperties.Write))
{
// This characteristic supports writing to it.
}
if(properties.HasFlag(GattCharacteristicProperties.Notify))
{
// This characteristic supports subscribing to notifications.
}
如果支援讀取,則可以讀取值:
GattReadResult result = await selectedCharacteristic.ReadValueAsync();
if (result.Status == GattCommunicationStatus.Success)
{
var reader = DataReader.FromBuffer(result.Value);
byte[] input = new byte[reader.UnconsumedBufferLength];
reader.ReadBytes(input);
// Utilize the data as needed
}
寫入特性會遵循類似的模式:
var writer = new DataWriter();
// WriteByte used for simplicity. Other common functions - WriteInt16 and WriteSingle
writer.WriteByte(0x01);
GattCommunicationStatus result = await selectedCharacteristic.WriteValueAsync(writer.DetachBuffer());
if (result == GattCommunicationStatus.Success)
{
// Successfully wrote to device
}
提示
使用您從許多藍牙 API 取得的原始緩衝區時,DataReader 和 DataWriter 是不可或缺的。
訂閱通知
確保特性支援指示或通知 (檢查特性屬性以確定)。
指示比較可靠,因為每個值變更的事件都會與來自用戶端裝置的通知結合。 通知比較普遍,因為大多數 GATT 交易寧願節省電量,也不願極度可靠。 無論如何,這些都是在控制器層處理的,因此應用程式不會參與其中。 我們將它們統稱為「通知」。
在收到通知之前需要注意兩件事:
- 寫入用戶端特性設定描述項 (CCCD)
- 處理 Characteristic.ValueChanged 事件
寫入 CCCD 會告訴伺服器裝置該用戶端想要知道每次特定特性值發生變化的情況。 若要這樣做:
GattCommunicationStatus status = await selectedCharacteristic.WriteClientCharacteristicConfigurationDescriptorAsync(
GattClientCharacteristicConfigurationDescriptorValue.Notify);
if(status == GattCommunicationStatus.Success)
{
// Server has been informed of clients interest.
}
現在,每當遠端裝置上的值發生變更時,都會呼叫 GattCharacteristic 的 ValueChanged 事件。 剩下的就是實作處理常式:
characteristic.ValueChanged += Characteristic_ValueChanged;
...
void Characteristic_ValueChanged(GattCharacteristic sender,
GattValueChangedEventArgs args)
{
// An Indicate or Notify reported that the value has changed.
var reader = DataReader.FromBuffer(args.CharacteristicValue)
// Parse the data however required.
}