블루투스 저에너지 심박수 서버
GATT 서비스를 설정하고 광고하는 방법을 보여주는 예제입니다. 이 예제는 주변기기(슬레이브) 기능과 관련된 Qt Bluetooth 주변기기(슬레이브) 기능과 관련된 저에너지 클래스의 사용법을 보여줍니다.
블루투스 저에너지 심박수 서버는 Qt Bluetooth API를 사용하여 Bluetooth GATT 서버를 개발하는 방법을 보여줍니다. 이 애플리케이션은 서비스 설정, 서비스 광고, 그리고 특성 값 변경 사항을 클라이언트에 알리는 과정을 다룹니다.
이 예제에서는 다음 Qt 클래스를 사용합니다:
- QLowEnergyAdvertisingData
- QLowEnergyAdvertisingParameters
- QLowEnergyServiceData
- QLowEnergyCharacteristicData
- QLowEnergyDescriptorData
- QLowEnergyController
- QLowEnergyService
이 예제는 서버 애플리케이션을 구현하므로 그래픽 사용자 인터페이스(GUI)가 없습니다. 애플리케이션의 동작을 시각화하려면, 기본적으로 이 애플리케이션의 클라이언트 측 대응 버전인 ‘심박수 게임(Heart Rate Game )’ 예제를 사용할 수 있습니다.
블루투스 권한 확인
애플리케이션이 서비스를 생성하고 광고를 시작하기 전에, 애플리케이션에 블루투스 사용 권한이 있는지 확인해야 합니다.
auto permissionStatus = app.checkPermission(QBluetoothPermission{});블루투스 권한 요청
블루투스 권한 상태가 미확정인 경우, 블루투스 사용 권한을 요청해야 합니다.
if (permissionStatus== Qt::PermissionStatus::Undetermined) {
qInfo("Requesting Bluetooth permission ...");
app.requestPermission(QBluetoothPermission{}, [&permissionStatus](const QPermission&permission){
qApp->exit();
permissionStatus = permission.status();
});
// 이제 권한 요청이 처리될 때까지 기다립니다.
app.exec();
}광고 데이터 및 매개변수 설정
광고 프로세스를 구성하는 데는 다음 두 개의 클래스가 사용됩니다:
- QLowEnergyAdvertisingData 어떤 정보를 브로드캐스팅할지 지정합니다
- QLowEnergyAdvertisingParameters 광고 간격 설정이나 연결을 허용할 기기 제어와 같은 구체적인 측면을 처리하는 데 사용됩니다.
이 예제에서는 단순히 기본 매개변수를 사용합니다.
QLowEnergyAdvertisingData 에 포함된 정보는 현재 스캔 중인 다른 기기들이 볼 수 있습니다. 다른 기기들은 이 정보를 바탕으로 연결을 설정할지 여부를 결정할 수 있습니다. 이 예제에서는 제공하는 서비스 유형, 사람이 이해하기 쉬운 장치 이름, 그리고 장치의 송신 전력 레벨을 포함합니다. 후자는 잠재적인 클라이언트에게 유용한 경우가 많습니다. 수신 신호 강도를 광고된 신호 강도와 비교하여 장치가 얼마나 멀리 있는지 파악할 수 있기 때문입니다.
참고: 광고 데이터에 할당된공간은 매우 제한적입니다(총 31바이트에 불과함). 따라서 기기 이름과 같은 가변 길이 데이터는 가능한 한 짧게 유지해야 합니다.
QLowEnergyAdvertisingData advertisingData;
advertisingData.setDiscoverability(QLowEnergyAdvertisingData::DiscoverabilityGeneral);
advertisingData.setIncludePowerLevel(true);
advertisingData.setLocalName("HeartRateServer");
advertisingData.setServices(QList<QBluetoothUuid>() << QBluetoothUuid::ServiceClassUuid::HeartRate);서비스 데이터 설정
다음으로 제공하고자 하는 서비스의 종류를 구성합니다. 블루투스 사양에 정의된 ‘ Heart Rate ’ 서비스를 최소한의 형태로, 즉 ‘ Heart Rate Measurement ’ 특성만으로 구성하여 사용합니다. 이 특성은 ‘ Notify ’ 속성(그 외 다른 속성은 없음)을 지원해야 하며, 클라이언트가 특성 값 변경에 대한 알림을 받기 위해 등록할 수 있도록 하는 ‘ Client Characteristic Configuration ’ 디스크립터를 가져야 합니다. 초기 심박수 값은 어차피 읽을 수 없기 때문에(클라이언트가 이 값을 얻을 수 있는 유일한 방법은 알림을 받는 것뿐입니다) 0으로 설정합니다.
QLowEnergyCharacteristicData charData;
charData.setUuid(QBluetoothUuid::CharacteristicType::HeartRateMeasurement);
charData.setValue(QByteArray(2, 0));
charData.setProperties(QLowEnergyCharacteristic::Notify);
const QLowEnergyDescriptorData clientConfig(QBluetoothUuid::DescriptorType::ClientCharacteristicConfiguration,
QByteArray(2, 0));
charData.addDescriptor(clientConfig);
QLowEnergyServiceData serviceData;
serviceData.setType(QLowEnergyServiceData::ServiceTypePrimary);
serviceData.setUuid(QBluetoothUuid::ServiceClassUuid::HeartRate);
serviceData.addCharacteristic(charData);광고 및 수신 연결 수신 대기
이제 모든 데이터 설정이 완료되었으므로 광고를 시작할 수 있습니다. 먼저 ` peripheral role ` 내에서 ` QLowEnergyController ` 객체를 생성하고, 이를 사용하여 (정적) ` QLowEnergyServiceData`에서 (동적) ` QLowEnergyService ` 객체를 생성합니다. 그런 다음 ` QLowEnergyController::startAdvertising()`를 호출합니다. QLowEnergyAdvertisingData 를 두 번 전달한다는 점에 유의하십시오. 첫 번째 인자는 실제 광고 데이터로, 두 번째 인자는 스캔 응답 데이터로 사용됩니다. 이 두 인자는 서로 다른 정보를 전달할 수도 있지만, 여기서는 그럴 필요가 없습니다. 또한 QLowEnergyAdvertisingParameters 의 기본 생성된 인스턴스를 전달하는데, 기본 광고 매개변수로도 충분하기 때문입니다. 클라이언트가 광고된 서비스에 관심이 있다면 이제 우리 기기와 연결을 설정할 수 있습니다. 연결이 수립되면 기기는 광고를 중단하고 QLowEnergyController::connected() 신호를 발산합니다.
참고: 클라이언트가 연결을끊어도 광고는 자동으로 재개되지 않습니다. 광고를 자동으로 재개하려면 QLowEnergyController::disconnected() 신호에 연결하고 해당 슬롯에서 QLowEnergyController::startAdvertising()를 호출해야 합니다.
bool errorOccurred = false;
const std::unique_ptr<QLowEnergyController> leController(QLowEnergyController::createPeripheral());
auto errorHandler = [&leController, &errorOccurred](QLowEnergyController::Error errorCode) {
qWarning().noquote().nospace() << errorCode << " occurred: "
<< leController->errorString();
if (errorCode != QLowEnergyController::RemoteHostClosedError) {
qWarning("Heartrate-server quitting due to the error.");
errorOccurred = true;
QCoreApplication::quit();
}
};
QObject::connect(leController.get(), &QLowEnergyController::errorOccurred, errorHandler);
std::unique_ptr<QLowEnergyService> service(leController->addService(serviceData));
leController->startAdvertising(QLowEnergyAdvertisingParameters(), advertisingData,
advertisingData);
if (errorOccurred)
return-1;심박수 제공
지금까지는 순조롭습니다. 하지만 클라이언트는 실제로 심박수를 어떻게 얻을 수 있을까요? 이는 위 코드 스니펫에서 QLowEnergyController 로부터 수신한 QLowEnergyService 객체 내의 해당 특성 값을 정기적으로 업데이트함으로써 이루어집니다. 심박수의 출처는 일반적으로 어떤 종류의 센서일 것이지만, 이 예제에서는 60과 100 사이에서 변동하도록 임의의 값을 생성합니다. 다음 코드 스니펫에서 가장 중요한 부분은 ` QLowEnergyService::writeCharacteristic` 호출입니다. 클라이언트가 현재 연결되어 있고 앞서 언급한 ` Client Characteristic Configuration`에 값을 기록하여 알림을 활성화한 경우, 새로운 값에 대한 알림을 받게 됩니다.
QTimer heartbeatTimer;
quint8 currentHeartRate = 60;
enum ValueChange { ValueUp, ValueDown } valueChange = ValueUp;
const auto heartbeatProvider = [&service, ¤tHeartRate, &valueChange]() {
QByteArray value;
value.append(char(0)); // Flags that specify the format of the value.
value.append(char(currentHeartRate)); // Actual value.
QLowEnergyCharacteristic characteristic
= service->characteristic(QBluetoothUuid::CharacteristicType::HeartRateMeasurement);
Q_ASSERT(characteristic.isValid());
service->writeCharacteristic(characteristic, value); // Potentially causes notification.
if (currentHeartRate == 60)
valueChange = ValueUp;
else if (currentHeartRate == 100)
valueChange = ValueDown;
if (valueChange == ValueUp)
++currentHeartRate;
else
--currentHeartRate;
};
QObject::connect(&heartbeatTimer, &QTimer::timeout, heartbeatProvider);
heartbeatTimer.start(1000);© 2026 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.