Bluetooth GATT 클라이언트

이 문서는 UWP(Universal Windows Platform) 앱에 GATT(Bluetooth Generic Attribute) 클라이언트 API를 사용하는 방법을 보여 줍니다.

중요

Package.appxmanifest에서 "블루투스" 기능을 선언해야 합니다.

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

중요 API

개요

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

  • 서비스
  • 특성
  • 설명자

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

Bluetooth LE GATT API는 원시 전송에 대한 액세스보다는 개체와 함수를 표시합니다. GATT API를 통해 개발자는 다음의 작업을 수행할 수 있는 Bluetooth LE 디바이스도 사용할 수 있습니다.

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

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

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

예시

전체 샘플은 Bluetooth Low Energy 샘플을 참조하세요.

주변 디바이스에 대한 쿼리

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

두 번째 방법은 광고 문서에서 자세히 다루므로 여기에서는 자세히 다루지 않지만, 기본 개념은 특정 광고 필터를 충족하는 주변 디바이스의 Bluetooth 주소를 찾는 것입니다. 주소를 가져온 뒤, BluetoothLEDevice.FromBluetoothAddressAsync를 호출하여 디바이스에 참조를 가져올 수 있습니다.

이제 DeviceWatcher 메서드로 돌아갑니다. Bluetooth 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를 사용하여 의심스러운 디바이스에 대한 Bluetooth 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();

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

참고

이 메서드만 호출하여 BluetoothLEDevice 개체를 만든다고 해서 반드시 연결을 시작하는 것은 아닙니다. 연결을 시작하려면 GattSession.MaintainConnectiontrue(으)로 설정하거나, 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;
    // ...
}

그러면 OS에서 작업을 수행할 수 있는 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
}

DataReaderDataWriter는 많은 Bluetooth API에서 가져온 원시 버퍼로 작업할 때 없어서는 안 될 필수 요소입니다.

알림 구독

특성이 표시 또는 알림을 지원하는지 확인합니다(확실하게 하기 위해 특성 속성 확인).

Indicate는 각 값 변경 이벤트가 클라이언트 장치의 승인과 결합되기 때문에 더 신뢰할 수 있는 것으로 간주됩니다. 대부분의 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.
}