Клиент Bluetooth GATT

В этой статье показано, как использовать API клиента Bluetooth Generic Attribute (GATT) для приложений Windows.

Important

Необходимо объявить функцию Bluetooth в Package.appxmanifest.

<Capabilities> <DeviceCapability Name="bluetooth" /> </Capabilities>

Общие сведения

Вы можете использовать API в пространстве имен Windows.Devices.Bluetooth.GenericAttributeProfile для доступа к устройствам Bluetooth LE. Устройства Bluetooth LE предоставляют свои функциональные возможности через коллекцию:

  • Услуги
  • Характеристики
  • Дескрипторы

Службы определяют функциональный контракт устройства LE и содержат коллекцию характеристик, определяющих службу. Эти характеристики, в свою очередь, содержат дескрипторы, описывающие характеристики. Эти 3 термины называются атрибутами устройства.

API Bluetooth LE GATT предоставляют объекты и функции, а не доступ к необработанному транспорту. API GATT также позволяют работать с устройствами Bluetooth LE с возможностью выполнения следующих задач:

  • Выполнить обнаружение атрибутов
  • Чтение и запись значений атрибутов
  • Зарегистрируйте обратный вызов для события изменения значения характеристики

Чтобы создать полезную реализацию, необходимо иметь предварительное знание служб GATT и характеристик, которые приложение намерено использовать и обрабатывать определенные значения характеристик, таким образом, чтобы двоичные данные, предоставляемые API, преобразовывались в полезные данные перед отправкой пользователю. API Bluetooth GATT предоставляют только основные примитивы, необходимые для взаимодействия с устройством Bluetooth LE. Чтобы интерпретировать данные, необходимо определить профиль приложения либо стандартным профилем Bluetooth SIG, либо пользовательским профилем, реализованным поставщиком устройств. Профиль создает обязательное соглашение между приложением и устройством о том, что представляют собой передаваемые данные и как их интерпретировать.

Для удобства Bluetooth SIG поддерживает список общедоступных профилей .

Запрос на близлежащие устройства

Существует два основных метода запроса для близлежащих устройств:

2-й метод подробно рассматривается в документации по объявлению , поэтому здесь не будет обсуждаться много, но основная идея заключается в поиске Bluetooth-адреса близлежащих устройств, удовлетворяющих конкретному фильтру рекламы. После получения адреса можно вызвать BluetoothLEDevice.FromBluetoothAddressAsync , чтобы получить ссылку на устройство.

Теперь вернитесь к методу DeviceWatcher. Устройство Bluetooth LE аналогично любому другому устройству в Windows и может запрашиваться с помощью API Enumeration. Используйте класс 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 для каждого устройства, удовлетворяющего запросу, в обработчике события Added для соответствующих устройств. Более подробную информацию о DeviceWatcher см. в полном примере на GitHub.

Подключение к устройству

После обнаружения требуемого устройства используйте DeviceInformation.Id , чтобы получить объект Устройства Bluetooth LE для этого устройства:

private async Task 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.

Note

Создание объекта 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
}

Tip

DataReader и DataWriter являются обязательными при работе с необработанными буферами, которые вы получаете от многих API Bluetooth.

Подписка на уведомления

Убедитесь, что характеристика поддерживает либо Indicate, либо Notify (проверьте свойства характеристики, чтобы убедиться в этом).

Indicate считается более надежным, так как каждое событие изменения значения связано с подтверждением с клиентского устройства. Notify более распространён, поскольку большинство транзакций GATT предпочитают экономить энергию, а не обеспечивать максимально высокую надёжность. В любом случае все это обрабатывается на уровне контроллера, поэтому приложение не участвует. Мы будем совместно ссылаться на них как на "уведомления".

Перед получением уведомлений необходимо позаботиться о двух вещах:

  • Запись в дескриптор конфигурации характеристик клиента (CCCD)
  • Обработайте событие Characteristic.ValueChanged

Запись в CCCD сообщает серверному устройству, что этот клиент хочет знать о каждом изменении значения конкретной характеристики. Для этого выполните следующие действия.

GattCommunicationStatus status = await selectedCharacteristic.WriteClientCharacteristicConfigurationDescriptorAsync(
                        GattClientCharacteristicConfigurationDescriptorValue.Notify);
if(status == GattCommunicationStatus.Success)
{
    // Server has been informed of clients interest.
}

Теперь событие ValueChanged объекта GattCharacteristic будет возникать каждый раз, когда значение изменяется на удалённом устройстве. Осталось только реализовать обработчик:

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.
}

Examples

Полный пример см. в разделе Bluetooth Low Energy sample.