Поделиться через


Сервер Bluetooth GATT

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

Внимание

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

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

Важные API

Обзор

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

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

Определение поддерживаемых служб

Ваше приложение может объявить одну или несколько служб, которые будут опубликованы Windows. Каждая служба однозначно определяется идентификатором UUID.

Атрибуты и идентификаторы UUID

Каждая служба, характеристика и дескриптор определяются собственным уникальным 128-разрядным UUID.

API Windows используют термин GUID, но стандарт Bluetooth определяет их как UUID. В наших целях эти два термина взаимозаменяемы, поэтому мы будем продолжать использовать термин UUID.

Если атрибут является стандартным и определен с помощью Bluetooth SIG, он также будет иметь соответствующий 16-разрядный короткий идентификатор (например, идентификатор UUID уровня батареи равен 00002A19-000-1000-8000-00805F9B34FB и короткий идентификатор равен 0x2A19). Эти стандартные идентификаторы UUID можно увидеть в GattServiceUuids и GattCharacteristicUuids.

Если приложение реализует собственную пользовательскую службу, необходимо создать пользовательский идентификатор UUID. Это легко сделать в Visual Studio с помощью инструментов> — CreateGuid (используйте вариант 5, чтобы получить его в файле xxxx-xxxx-... формат xxxx". Теперь этот uuid можно использовать для объявления новых локальных служб, характеристик или дескрипторов.

Ограниченные службы

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

  1. Служба сведений об устройстве (DIS)
  2. Служба профилей универсальных атрибутов (GATT)
  3. Универсальная служба профилей доступа (GAP)
  4. Служба параметров сканирования (SCP)

Попытка создания заблокированной службы приведет к возврату BluetoothError.DisabledByPolicy из вызова CreateAsync.

Созданные атрибуты

Следующие дескрипторы автоматически создаются системой на основе GattLocalCharacteristicParameters, предоставляемых во время создания характеристик:

  1. Конфигурация характеристик клиента (если характеристика помечена как указываемая или неизменяемая).
  2. Описание пользователя (если свойство UserDescription задано). Дополнительные сведения см. в свойстве GattLocalCharacteristicParameters.UserDescription.
  3. Формат характеристик (один дескриптор для каждого указанного формата презентации). Дополнительные сведения см. в свойстве GattLocalCharacteristicParameters.PresentationFormats.
  4. Формат агрегата характеристик (если задано несколько форматов презентации). GattLocalCharacteristicParameters.See PresentationFormats property for more info.
  5. Свойства расширенных характеристик (если характеристика помечена битом расширенных свойств).

Значение дескриптора расширенных свойств определяется с помощью свойств свойств ReliableWrites и WritableAuxiliaries.

Попытка создать зарезервированный дескриптор приведет к исключению.

Обратите внимание, что широковещательная трансляция в настоящее время не поддерживается. Указание вещания 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;

Как показано выше, это также хорошее место для объявления обработчиков событий для операций, поддерживаемых каждой характеристикой. Чтобы правильно реагировать на запросы, приложение должно определить и задать обработчик событий для каждого типа запроса, который поддерживает атрибут. Сбой регистрации обработчика приведет к тому, что запрос завершается немедленно с помощью UnlikelyError системой.

Константные характеристики

Иногда существуют характерные значения, которые не изменятся в течение времени существования приложения. В этом случае рекомендуется объявить константную характеристику, чтобы предотвратить ненужную активацию приложения:

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

Публикация службы

После полного определения службы следующим шагом является публикация поддержки службы. Это сообщает ОС, что служба должна быть возвращена при выполнении обнаружения служб удаленными устройствами. Вам потребуется задать два свойства — IsDiscoverable и IsConnectable:

GattServiceProviderAdvertisingParameters advParameters = new GattServiceProviderAdvertisingParameters
{
    IsDiscoverable = true,
    IsConnectable = true
};
serviceProvider.StartAdvertising(advParameters);
  • IsDiscoverable: объявляет понятное имя удаленным устройствам в объявлении, что делает устройство обнаруживаемым.
  • IsConnectable: объявляет подключаемую рекламу для использования в периферийной роли.

Если служба доступен для обнаружения и подключения, система добавит service Uuid в пакет рекламы. В пакете рекламы есть только 31 байт, а 128-разрядная UUID занимает 16 из них!

Обратите внимание, что при публикации службы на переднем плане приложение должно вызывать StopAdvertising при приостановке приложения.

Реагирование на запросы на чтение и запись

Как мы видели выше при объявлении необходимых характеристик, GattLocalCharacteristics имеет 3 типа событий — ReadRequested, WriteRequested и SubscribedClientsChanged.

Читать

Когда удаленное устройство пытается считывать значение из характеристик (и это не константное значение), вызывается событие ReadRequested. Признак, на который был вызван чтение, а также а также args (содержащие сведения об удаленном устройстве), передается делегату:

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();
}

Write

Когда удаленное устройство пытается записать значение в характеристику, событие 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 Server, уведомления выполняют важную функцию отправки данных на удаленные устройства. Иногда вы хотите уведомить всех подписанных клиентов, но в других случаях может потребоваться выбрать устройства для отправки нового значения:

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 можно использовать описанный ниже процесс, чтобы определить полные сведения о клиентских устройствах, подписанных на данный момент: