Bluetooth GATT 서버
이 항목은 UWP(Universal Windows Platform) 앱에 GATT(Bluetooth Generic Attribute) 서버 API를 사용하는 방법을 보여 줍니다.
중요
Package.appxmanifest에서 "bluetooth" 기능을 선언해야 합니다.
<Capabilities> <DeviceCapability Name="bluetooth" /> </Capabilities>
중요 API
개요
일반적으로 Windows는 클라이언트 역할로 작동합니다. 그러나 많은 경우 Windows에서 Bluetooth LE GATT 서버로서도 작동해야 합니다. 대부분의 IoT 디바이스 시나리오에서 대부분의 플랫폼 간 BLE 통신과 함께 GATT 서버로 Windows가 필요합니다. 또한 근처 착용식 컴퓨터에 알림을 보내는 데 이 기술을 사용해야 하는 시나리오가 흔하게 사용되는 경우입니다.
서버 운영은 서비스 공급자 및 GattLocalCharacteristic을 중심으로 이루어집니다. 이러한 두 가지 클래스는 원격 디바이스에 데이터의 계층을 선언, 구현 및 노출하는 데 필요한 기능을 제공합니다.
지원되는 서비스 정의하기
앱은 Windows에 의해 게시될 하나 이상의 서비스를 선언할 수 있습니다. 각 서비스는 UUID로 고유하게 식별됩니다.
특성 및 UUID
각 서비스, 특성 및 설명자는 128비트 UUID로 정의됩니다.
모든 Windows API는 GUID 용어를 사용하지만 Bluetooth 표준은 이를 UUID로 정의합니다. 목적을 위해 이 두 용어는 상호 변경 가능하므로 계속해서 UUID라는 용어를 사용합니다.
특성이 표준이며 Bluetooth SIG 정의에 정의된 경우 해당하는 약식 16비트 ID 또한 가질 수 있습니다(예: 배터리 잔량 UUID는 00002A19-0000-1000-8000-00805F9B34FB이며 약식 ID는 0x2A19임). 이러한 표준 UUID는 GattServiceUuids 및 GattCharacteristicUuids에서 볼 수 있습니다.
앱에서 고유사용자 지정 서비스를 구현하는 경우 사용자 지정 UUID를 생성해야 합니다. 이는 Visual Studio에서 Tools > CreateGuid("xxxxxxxx-xxxx-...xxxx" 형식에서 이를 얻으려면 옵션 5를 사용)를 사용하여 쉽게 완료할 수 있습니다. 이제 이 UUID를 사용하여 새 로컬 서비스, 특성 또는 설명자를 선언할 수 있습니다.
제한된 서비스
다음의 서비스는 시스템에 의해 예약되며 현재로서 게시될 수 없습니다.
- 디바이스 정보 서비스(DIS)
- 일반 특성 프로필 서비스(GATT)
- 일반 액세스 프로필 서비스(GAP)
- 검사 매개 변수 서비스(SCP)
차단된 서비스를 만들려고 시도할 때 CreateAsync에 대한 호출에서 BluetoothError.DisabledByPolicy가 반환됩니다.
생성된 특성
다음의 설명자는 특성을 생성하는 동안 제공된 GattLocalCharacteristicParameters에 따라 시스템에 의해 자동으로 생성됩니다.
- 클라이언트 특성 구성(특징이 표시 가능 또는 알림 가능으로 표시된 경우).
- 특성 사용자 설명(UserDescription 속성이 설정된 경우). 자세한 정보는 GattLocalCharacteristicParameters.UserDescription 속성을 참조하세요.
- 특성 형식(지정된 각 프레젠테이션 형식에 대한 하나의 설명자). 자세한 정보는 GattLocalCharacteristicParameters.PresentationFormats 속성을 참조하세요.
- 특성 집계 형식(한 개 이상의 프레젠테이션 형식이 지정된 경우). GattLocalCharacteristicParameters. 자세한 정보는 PresentationFormats 속성을 참조하세요.
- 특성 확장 속성(특성이 확장된 속성 비트로 표시된 경우).
확장 속성 설명자 값은 ReliableWrites 및 WritableAuxiliaries 특성 속성을 통해 결정됩니다.
예약된 설명자를 만들려고 시도하면 예외가 발생합니다.
브로드캐스트는 현재 지원되지 않습니다. 브로드캐스트 GattCharacteristicProperty를 지정하면 예외가 발생합니다.
서비스 및 특징 계층 구축하기
GattServiceProvider는 루트 기본 서비스 정의를 만들고 보급하는 데 사용됩니다. 각 서비스에는 GUID에서 사용되는 고유한 ServiceProvider 개체가 필요합니다.
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;
}
서비스 게시하기
서비스가 완전히 정의되면 다음 단계는 서비스에 대한 지원 게시입니다. 이는 원격 디바이스에서 서비스 검색을 수행할 때 서비스가 반환되어야 함을 OS에 알립니다. IsDiscoverable 및 IsConnectable의 두 개의 속성을 설정해야 합니다.
GattServiceProviderAdvertisingParameters advParameters = new GattServiceProviderAdvertisingParameters
{
IsDiscoverable = true,
IsConnectable = true
};
serviceProvider.StartAdvertising(advParameters);
- IsDiscoverable: 알림의 원격 디바이스에 식별 이름을 알려 디바이스를 검색할 수 있도록 합니다.
- IsConnectable: 주변 역할의 사용에 대한 연결 가능한 알림을 알립니다.
서비스가 검색 가능하며 연결 가능할 때 시스템은 알림 패킷에 서비스 UUID를 추가합니다. 알림 패킷의 용량은 31바이트뿐이며 128비트 UUID가 이 중 16바이트를 차지합니다!
전면에 서비스가 게시되는 경우 응용 프로그램이 일시 중지될 때 응용 프로그램은 StopAdvertising을 호출해야 합니다.
읽기 및 쓰기 요청에 응답하기
위에서 설명한 것처럼 필요한 특성을 선언할 때 GattLocalCharacteristics는 ReadRequested, WriteRequested 및 SubscribedClientsChanged의 3가지 유형의 이벤트를 갖게 됩니다.
읽기
원격 디바이스에서 특성 값을 읽으려 할 때(상수 값이 아닌 경우), 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 이벤트를 처리할 때 아래에 설명된 프로세스를 사용하여 현재 구독된 클라이언트 디바이스에 대한 전체 정보를 확인할 수 있습니다.
- SubscribedClientsChanged 이벤트의 args는 GattLocalCharacteristic 개체입니다.
- GattSubscribedClient 개체의 컬렉션인 해당 개체의 GattLocalCharacteristic.SubscribedClients 속성에 액세스합니다.
- 해당 컬렉션을 반복합니다. 각 요소에 대해 다음을 수행합니다.
- GattSession 개체인 GattSubscribedClient.Session 속성에 액세스합니다.
- BluetoothDeviceId 개체인 GattSession.DeviceId 속성에 액세스합니다.
- 디바이스 ID 문자열인 BluetoothDeviceId.Id 속성에 액세스합니다.
- 디바이스 ID 문자열을 BluetoothLEDevice.FromIdAsync에 전달하여 BluetoothLEDevice 개체를 검색합니다. 디바이스에 대한 전체 정보를 해당 개체에서 가져올 수 있습니다.