培训
学习路径
Use advance techniques in canvas apps to perform custom updates and optimization - Training
Use advance techniques in canvas apps to perform custom updates and optimization
本主题演示如何使用适用于 通用 Windows 平台 (UWP) 应用的蓝牙泛型属性 (GATT) 服务器 API。
重要
必须在 Package.appxmanifest 中声明“蓝牙”功能。
<Capabilities> <DeviceCapability Name="bluetooth" /> </Capabilities>
重要的 API
Windows 通常以客户端角色运行。 然而,许多方案也要求 Windows 充当蓝牙 LE GATT 服务器。 几乎所有 IoT 设备的方案,以及大多数跨平台 BLE 通信都需要 Windows 成为 GATT 服务器。 此外,向附近的可穿戴设备发送通知已成为一种需要这项技术的热门方案。
服务器操作将围绕服务提供商和 GattLocalCharacteristic。 这两个类会提供声明、实现数据层次结构并向远程设备公开所需的功能。
你的应用可以声明一个或多个将由 Windows 发布的服务。 每个服务都由 UUID 唯一标识。
每个服务、特征和描述符都由它自己的唯一 128 位 UUID 定义。
Windows API 都使用术语 GUID,但蓝牙标准将这些 API 定义为 UUID。 出于我们的目的,这两个术语是可互换的,因此我们将继续使用术语 UUID。
如果属性是标准的并按照蓝牙 SIG 的定义进行定义,则它还会具有对应的 16 位短 ID(例如,电池电量 UUID 是 00002A19-0000-1000-8000-00805F9B34FB,而短 ID 是 0x2A19)。 可以在 GattServiceUuids 和 GattCharacteristicUuids 中看到这些标准 UUID。
如果你的应用正在实现它自己的自定义服务,则必须生成自定义 UUID。 这可以在 Visual Studio 中通过“工具”->“创建 Guid”轻松实现(使用选项 5 可采用“xxxxxxxx-xxxx-...xxxx”格式获取它)。 此 uuid 现在可用于声明新的本地服务、特征或描述符。
以下服务由系统保留,目前无法发布:
尝试创建阻止的服务将导致从对 CreateAsync 的调用返回 BluetoothError.DisabledByPolicy。
系统根据创建特征期间提供的 GattLocalCharacteristicParameters 自动生成以下描述符:
扩展属性描述符的值通过 ReliableWrites 和 WritableAuxiliaries 特征属性确定。
尝试创建保留描述符将导致异常。
请注意,目前不支持广播。 指定 Broadcast GattCharacteristicProperty 将导致异常。
GattServiceProvider 用于创建和播发根主服务定义。 每个服务都需要它自己的 ServiceProvider 对象,该对象采用 GUID:
GattServiceProviderResult result = await GattServiceProvider.CreateAsync(uuid);
if (result.Error == BluetoothError.Success)
{
serviceProvider = result.ServiceProvider;
//
}
主要服务是 GATT 树的顶层。 主要服务包含特征和其他服务(称为“包含”或辅助服务)。
现在,使用所需的特征和描述符填充服务:
GattLocalCharacteristicResult characteristicResult = await serviceProvider.Service.CreateCharacteristicAsync(uuid1, ReadParameters);
if (characteristicResult.Error != BluetoothError.Success)
{
// An error occurred.
return;
}
_readCharacteristic = characteristicResult.Characteristic;
_readCharacteristic.ReadRequested += ReadCharacteristic_ReadRequested;
characteristicResult = await serviceProvider.Service.CreateCharacteristicAsync(uuid2, WriteParameters);
if (characteristicResult.Error != BluetoothError.Success)
{
// An error occurred.
return;
}
_writeCharacteristic = characteristicResult.Characteristic;
_writeCharacteristic.WriteRequested += WriteCharacteristic_WriteRequested;
characteristicResult = await serviceProvider.Service.CreateCharacteristicAsync(uuid3, NotifyParameters);
if (characteristicResult.Error != BluetoothError.Success)
{
// An error occurred.
return;
}
_notifyCharacteristic = characteristicResult.Characteristic;
_notifyCharacteristic.SubscribedClientsChanged += SubscribedClientsChanged;
如上所示,这也是声明每个特征支持的操作的事件处理程序的好位置。 若要正确响应请求,应用必须定义并设置属性支持的每个请求类型的事件处理程序。 注册处理程序失败将导致请求立即由系统完成,且 不太可能的Error 。
有时,某些特征值在应用的生存期内不会更改。 在这种情况下,建议声明一个常量特征,以防止不必要的应用激活:
byte[] value = new byte[] {0x21};
var constantParameters = new GattLocalCharacteristicParameters
{
CharacteristicProperties = (GattCharacteristicProperties.Read),
StaticValue = value.AsBuffer(),
ReadProtectionLevel = GattProtectionLevel.Plain,
};
var characteristicResult = await serviceProvider.Service.CreateCharacteristicAsync(uuid4, constantParameters);
if (characteristicResult.Error != BluetoothError.Success)
{
// An error occurred.
return;
}
完全定义服务后,下一步是发布对服务的支持。 这会通知 OS 在远程设备执行服务发现时应返回该服务。 必须设置两个属性 - IsDiscoverable 和 IsConnectable:
GattServiceProviderAdvertisingParameters advParameters = new GattServiceProviderAdvertisingParameters
{
IsDiscoverable = true,
IsConnectable = true
};
serviceProvider.StartAdvertising(advParameters);
当服务既可发现又可连接时,系统将向播发数据包添加服务 Uuid。 播发数据包中只有 31 个字节,128 位 UUID 占用其中 16 个字节!
请注意,当服务在前台发布时,应用程序必须在应用程序挂起时调用 StopAdvertising。
正如我们在声明所需特征时看到的,GattLocalCharacteristics 有 3 种类型的事件 - ReadRequested、WriteRequested 和 SubscribedClientsChanged。
当远程设备尝试从特征(而不是常量值)读取值时,将调用 ReadRequested 事件。 调用读取的特征以及参数(包含有关远程设备的信息)将传递给委托:
characteristic.ReadRequested += Characteristic_ReadRequested;
// ...
async void ReadCharacteristic_ReadRequested(GattLocalCharacteristic sender, GattReadRequestedEventArgs args)
{
var deferral = args.GetDeferral();
// Our familiar friend - DataWriter.
var writer = new DataWriter();
// populate writer w/ some data.
// ...
var request = await args.GetRequestAsync();
request.RespondWithValue(writer.DetachBuffer());
deferral.Complete();
}
当远程设备尝试将值写入特征时,将调用 WriteRequested 事件,其中包含有关远程设备的详细信息,该特征是写入到哪些特征以及值本身:
characteristic.ReadRequested += Characteristic_ReadRequested;
// ...
async void WriteCharacteristic_WriteRequested(GattLocalCharacteristic sender, GattWriteRequestedEventArgs args)
{
var deferral = args.GetDeferral();
var request = await args.GetRequestAsync();
var reader = DataReader.FromBuffer(request.Value);
// Parse data as necessary.
if (request.Option == GattWriteOption.WriteWithResponse)
{
request.Respond();
}
deferral.Complete();
}
有 2 种类型的写入 - 具有无响应。 使用 GattWriteOption(GattWriteRequest 对象上的属性)确定远程设备正在执行的写入类型。
GATT 服务器操作的最常见情况是通知执行将数据推送到远程设备的关键功能。 有时,需要通知所有订阅的客户端,但有时可能需要选择要将新值发送到的设备:
async void NotifyValue()
{
var writer = new DataWriter();
// Populate writer with data
// ...
await notifyCharacteristic.NotifyValueAsync(writer.DetachBuffer());
}
当新设备订阅通知时,将调用 SubscribedClientsChanged 事件:
characteristic.SubscribedClientsChanged += SubscribedClientsChanged;
// ...
void _notifyCharacteristic_SubscribedClientsChanged(GattLocalCharacteristic sender, object args)
{
List<GattSubscribedClient> clients = sender.SubscribedClients;
// Diff the new list of clients from a previously saved one
// to get which device has subscribed for notifications.
// You can also just validate that the list of clients is expected for this app.
}
备注
应用程序可以使用 MaxNotificationSize 属性获取特定客户端的最大通知大小。 大于最大大小的任何数据都将被系统截断。
处理 GattLocalCharacteristic.SubscribedClientsChanged 事件时,可以使用下面所述的过程来确定有关当前订阅的客户端设备的完整信息:
培训
学习路径
Use advance techniques in canvas apps to perform custom updates and optimization - Training
Use advance techniques in canvas apps to perform custom updates and optimization