Bluetooth GATT 클라이언트

이 문서에서는 Windows 앱에 대해 GATT(Bluetooth 일반 특성) 클라이언트 API를 사용하는 방법을 보여 줍니다.

Important

Package.appxmanifest에서 "bluetooth" 기능을 선언해야 합니다.

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

개요

Windows.Devices.Bluetooth.GenericAttributeProfile 네임스페이스의 API를 사용하여 Bluetooth LE 디바이스에 액세스할 수 있습니다. Bluetooth LE 디바이스는 다음 컬렉션을 통해 해당 기능을 노출합니다.

  • Services
  • 특성
  • 서술자

서비스는 LE 디바이스의 기능 계약을 정의하고 서비스를 정의하는 특성 컬렉션을 포함합니다. 이러한 특성에는 특성을 설명하는 설명자가 포함됩니다. 이러한 3개 용어는 일반적으로 디바이스의 특성이라고 합니다.

Bluetooth LE GATT API는 원시 전송에 액세스하는 대신 개체와 함수를 노출합니다. GATT API를 사용하면 다음 작업을 수행할 수 있는 Bluetooth LE 디바이스를 사용할 수도 있습니다.

  • 특성 검색 수행
  • 특성 값 읽기 및 쓰기
  • Characteristic ValueChanged 이벤트에 대한 콜백 등록

유용한 구현을 만들려면 애플리케이션이 사용하려는 GATT 서비스 및 특성에 대한 사전 지식이 있어야 하며, API에서 제공하는 이진 데이터가 사용자에게 표시되기 전에 유용한 데이터로 변환되도록 특정 특성 값을 처리해야 합니다. Bluetooth GATT API는 Bluetooth LE 디바이스와 통신하는 데 필요한 기본 기본 형식만 노출합니다. 데이터를 해석하려면 Bluetooth SIG 표준 프로필 또는 디바이스 공급업체에서 구현한 사용자 지정 프로필을 통해 애플리케이션 프로필을 정의해야 합니다. 프로필은 교환된 데이터가 나타내는 내용과 해석 방법에 대해 애플리케이션과 디바이스 간에 바인딩 계약을 만듭니다.

편의를 위해 Bluetooth SIG는 사용 가능한 공개 프로필 목록을 유지 관리합니다 .

주변 디바이스에 대한 쿼리

주변 디바이스를 쿼리하는 두 가지 주요 방법이 있습니다.

두 번째 방법은 광고 설명서에서 길게 설명되므로 여기에서 많이 논의되지는 않지만 기본 아이디어는 특정 광고 필터를 충족하는 주변 장치의 Bluetooth 주소를 찾는 것입니다. 주소가 있으면 BluetoothLEDevice.FromBluetoothAddressAsync 를 호출하여 디바이스에 대한 참조를 가져올 수 있습니다.

이제 DeviceWatcher 메서드로 돌아갑니다. Bluetooth LE 디바이스는 Windows 다른 장치와 마찬가지로 Enumeration 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을 받게 되며, 각 디바이스는 해당 디바이스에 대한 Added 이벤트에 대한 처리기의 쿼리를 충족합니다. DeviceWatcher에 대한 자세한 내용은 전체 샘플 on 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();

앱이 디바이스에 다시 액세스해야 하는 경우 디바이스 개체를 다시 만들고 특성(다음 섹션에서 설명)에 액세스하면 필요할 때 OS가 다시 연결되도록 트리거됩니다. 디바이스가 근처에 있는 경우 디바이스에 액세스할 수 있습니다. 그렇지 않으면 DeviceUnreachable 오류와 함께 반환됩니다.

Note

이 메서드만 호출하여 BluetoothLEDevice 개체를 만드는 것은 반드시 연결을 시작하는 것은 아닙니다. 연결을 시작하려면 GattSession.MaintainConnectiontrueBluetoothLEDevice에서 캐시되지 않은 서비스 검색 메서드로 설정하거나 디바이스에 대해 읽기/쓰기 작업을 수행합니다.

  • 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;
    // ...
}

OS는 작업을 수행할 수 있는 GattCharacteristic 개체의 ReadOnly 목록을 반환합니다.

특성에 대한 읽기/쓰기 작업 수행

특징은 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

DataReaderDataWriter 는 많은 Bluetooth API에서 가져오는 원시 버퍼로 작업할 때 없어서는 안 됩니다.

알림 구독

특성이 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.
}

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

Examples

전체 샘플은 Bluetooth 저에너지 샘플 참조하세요.