Bluetooth 저에너지 개요
Qt Bluetooth 저에너지 API는 주변 장치/서버 및 중앙/클라이언트 역할을 지원합니다. 모든 주요 Qt 플랫폼에서 지원됩니다. 유일한 예외는 Windows에서 주변 장치 역할 지원이 누락되었다는 점입니다.
블루투스 저에너지란?
Bluetooth 스마트라고도 하는 Bluetooth 저에너지는 2011년에 공식적으로 도입된 무선 컴퓨터 네트워크 기술입니다. "클래식" Bluetooth와 동일한 2.4GHz 주파수에서 작동합니다. 주요 차이점은 기술 이름에서 알 수 있듯이 에너지 소비가 적다는 것입니다. 이 기술을 통해 저전력 Bluetooth 장치는 코인 셀 배터리로 몇 달, 심지어 몇 년 동안 작동할 수 있습니다. 이 기술은 Bluetooth v4.0에서 도입되었습니다. 이 기술을 지원하는 디바이스를 Bluetooth 스마트 지원 디바이스라고 합니다. 이 기술의 주요 특징은 다음과 같습니다:
- 매우 낮은 피크, 평균 및 유휴 모드 전력 소비량
- 표준 코인셀 배터리로 수년 동안 작동 가능
- 저렴한 비용
- 여러 공급업체와의 상호 운용성
- 향상된 범위
Bluetooth 저에너지는 클라이언트-서버 아키텍처를 사용합니다. 서버(주변 장치라고도 함)는 체온이나 심박수 등의 서비스를 제공하고 이를 광고합니다. 클라이언트(중앙 장치라고도 함)는 서버에 연결하여 서버가 광고하는 값을 읽습니다. 예를 들어 온도 조절기, 습도 또는 압력 센서와 같은 Bluetooth Smart Ready 센서가 있는 아파트를 들 수 있습니다. 이러한 센서는 아파트의 환경 값을 광고하는 주변 장치입니다. 동시에 휴대폰이나 컴퓨터가 이러한 센서에 연결하여 해당 값을 검색하고 이를 더 큰 환경 제어 애플리케이션의 일부로 사용자에게 표시할 수 있습니다.
기본 서비스 구조
Bluetooth 저에너지는 두 가지 프로토콜을 기반으로 합니다: ATT(속성 프로토콜)와 GATT(일반 속성 프로필)입니다. 이 프로토콜은 모든 Bluetooth 스마트 레디 장치에서 사용하는 통신 계층을 지정합니다.
ATT 프로토콜
ATT의 기본 구성 요소는 속성입니다. 각 속성은 세 가지 요소로 구성됩니다:
- 값 - 페이로드 또는 원하는 정보
- UUID - 속성 유형(GATT에서 사용)
- 16비트 핸들 - 속성의 고유 식별자
서버는 속성을 저장하고 클라이언트는 ATT 프로토콜을 사용하여 서버에서 값을 읽고 씁니다.
GATT 프로필
GATT는 미리 정의된 UUID에 의미를 적용하여 속성 집합에 대한 그룹화를 정의합니다. 아래 표는 특정 날짜의 심박수를 노출하는 서비스의 예시를 보여줍니다. 실제 값은 두 특성 안에 저장됩니다:
핸들 | UUID | 값 | 설명 |
---|---|---|---|
0x0001 | 0x2800 | UUID 0x180D | 심박수 서비스 시작 |
0x0002 | 0x2803 | UUID 0x2A37, 값 핸들: 0x0003 | 심박수 측정(HRM) 유형의 특성 |
0x0003 | 0x2A37 | 65 bpm | 심박수 값 |
0x0004 | 0x2803 | UUID 0x2A08, 값 핸들: 0x0005 | 날짜 시간 유형의 특성 |
0x0005 | 0x2A08 | 18/08/2014 11:00 | 측정 날짜 및 시간 |
0x0006 | 0x2800 | UUID xxxxxx | 다음 서비스 시작 |
... | ... | ... | ... |
GATT는 위에 사용된 UUID 0x2800
가 서비스 정의의 시작을 표시하도록 지정합니다. 0x2800
다음에 나오는 모든 특성은 다음 0x2800
또는 끝이 나타날 때까지 서비스의 일부입니다. 비슷한 방식으로 잘 알려진 UUID 0x2803
는 특성을 찾아야 하며 각 특성에는 값의 성격을 정의하는 유형이 있음을 나타냅니다. 위의 예에서는 0x2A08
(날짜 시간) 및 0x2A37
(심박수 측정)이라는 UUID를 사용합니다. 위의 각 UUID는 블루투스 특별 관심 그룹에서 정의한 것으로 GATT 사양에서 찾을 수 있습니다. 가능한 경우 사전 정의된 UUID를 사용하는 것이 좋지만, 특성 및 서비스 유형에 대해 아직 사용되지 않은 새로운 UUID를 사용하는 것도 가능합니다.
일반적으로 각 서비스는 하나 이상의 특성으로 구성될 수 있습니다. 특성은 데이터를 포함하며 추가 정보나 특성 조작 수단을 제공하는 설명자로 추가로 설명할 수 있습니다. 모든 서비스, 특성 및 설명자는 128비트 UUID로 인식됩니다. 마지막으로 서비스 내부에 서비스를 포함할 수 있습니다(아래 그림 참조).
Qt Bluetooth 저에너지 API 사용
이 섹션에서는 Qt에서 제공하는 Bluetooth 저에너지 API를 사용하는 방법을 설명합니다. 클라이언트 측에서 API를 사용하면 주변 장치에 대한 연결을 생성하고, 해당 서비스를 검색하고, 장치에 저장된 데이터를 읽고 쓸 수 있습니다. 서버 측에서는 서비스를 설정하고, 서비스를 광고하고, 클라이언트가 특성을 쓸 때 알림을 받을 수 있습니다. 아래 예제 코드는 심박수 게임과 심박수 서버 예제에서 가져온 것입니다.
연결 설정하기
블루투스 저에너지 주변 장치의 특성을 읽고 쓰려면 해당 장치를 찾아 연결해야 합니다. 이를 위해서는 주변 장치가 자신의 존재와 서비스를 알려야 합니다. QBluetoothDeviceDiscoveryAgent 클래스의 도움으로 장치 검색을 시작합니다. QBluetoothDeviceDiscoveryAgent::deviceDiscovered () 신호에 연결하고 start()로 검색을 시작합니다:
m_deviceDiscoveryAgent = new QBluetoothDeviceDiscoveryAgent(this); m_deviceDiscoveryAgent->setLowEnergyDiscoveryTimeout(15000); connect(m_deviceDiscoveryAgent, &QBluetoothDeviceDiscoveryAgent::deviceDiscovered, this, &DeviceFinder::addDevice); connect(m_deviceDiscoveryAgent, &QBluetoothDeviceDiscoveryAgent::errorOccurred, this, &DeviceFinder::scanError); connect(m_deviceDiscoveryAgent, &QBluetoothDeviceDiscoveryAgent::finished, this, &DeviceFinder::scanFinished); connect(m_deviceDiscoveryAgent, &QBluetoothDeviceDiscoveryAgent::canceled, this, &DeviceFinder::scanFinished); m_deviceDiscoveryAgent->start(QBluetoothDeviceDiscoveryAgent::LowEnergyMethod);
저에너지 디바이스에만 관심이 있으므로 수신 슬롯 내에서 디바이스 유형을 필터링합니다. 장치 유형은 QBluetoothDeviceInfo::coreConfigurations() 플래그를 사용하여 확인할 수 있습니다. deviceDiscovered () 신호는 더 많은 세부 정보가 발견되면 동일한 디바이스에 대해 여러 번 전송될 수 있습니다. 여기서는 이러한 디바이스 검색을 일치시켜 사용자에게 개별 디바이스만 표시되도록 합니다:
void DeviceFinder::addDevice(const QBluetoothDeviceInfo &device) { // If device is LowEnergy-device, add it to the list if (device.coreConfigurations() & QBluetoothDeviceInfo::LowEnergyCoreConfiguration) { auto devInfo = new DeviceInfo(device); auto it = std::find_if(m_devices.begin(), m_devices.end(), [devInfo](DeviceInfo *dev) { return devInfo->getAddress() == dev->getAddress(); }); if (it == m_devices.end()) { m_devices.append(devInfo); } else { auto oldDev = *it; *it = devInfo; delete oldDev; } setInfo(tr("Low Energy device found. Scanning more...")); setIcon(IconProgress); } //... }
주변 장치의 주소가 알려지면 QLowEnergyController 클래스를 사용합니다. 이 클래스는 모든 Bluetooth 저에너지 개발의 시작점입니다. 이 클래스의 생성자는 원격 장치의 QBluetoothAddress 을 받아들입니다. 마지막으로 사용자 지정 슬롯을 설정하고 connectToDevice()를 사용하여 장치에 직접 연결합니다:
m_control = QLowEnergyController::createCentral(m_currentDevice->getDevice(), this); connect(m_control, &QLowEnergyController::serviceDiscovered, this, &DeviceHandler::serviceDiscovered); connect(m_control, &QLowEnergyController::discoveryFinished, this, &DeviceHandler::serviceScanDone); connect(m_control, &QLowEnergyController::errorOccurred, this, [this](QLowEnergyController::Error error) { Q_UNUSED(error); setError("Cannot connect to remote device."); setIcon(IconError); }); connect(m_control, &QLowEnergyController::connected, this, [this]() { setInfo("Controller connected. Search services..."); setIcon(IconProgress); m_control->discoverServices(); }); connect(m_control, &QLowEnergyController::disconnected, this, [this]() { setError("LowEnergy controller disconnected"); setIcon(IconError); }); // Connect m_control->connectToDevice();
서비스 검색
위의 코드 스니펫은 연결이 설정된 후 애플리케이션이 서비스 검색을 시작하는 방법을 보여줍니다.
아래의 serviceDiscovered()
슬롯은 QLowEnergyController::serviceDiscovered() 신호의 결과로 트리거되며 간헐적인 진행률 보고서를 제공합니다. 여기서는 주변의 심박수 장치를 모니터링하는 심박수 청취기 앱에 대해 이야기하고 있으므로 QBluetoothUuid::ServiceClassUuid::HeartRate 유형이 아닌 모든 서비스는 무시합니다.
void DeviceHandler::serviceDiscovered(const QBluetoothUuid &gatt) { if (gatt == QBluetoothUuid(QBluetoothUuid::ServiceClassUuid::HeartRate)) { setInfo("Heart Rate service discovered. Waiting for service scan to be done..."); setIcon(IconProgress); m_foundHeartRateService = true; } }
결국 QLowEnergyController::discoveryFinished() 신호가 전송되어 서비스 검색이 성공적으로 완료되었음을 알립니다. HeartRate 서비스가 발견되면 해당 서비스를 나타내는 QLowEnergyService 인스턴스가 생성됩니다. 반환된 서비스 개체는 업데이트 알림에 필요한 신호를 제공하며 QLowEnergyService::discoverDetails()를 사용하여 서비스 세부 정보 검색이 트리거됩니다:
// If heartRateService found, create new service if (m_foundHeartRateService) m_service = m_control->createServiceObject(QBluetoothUuid(QBluetoothUuid::ServiceClassUuid::HeartRate), this); if (m_service) { connect(m_service, &QLowEnergyService::stateChanged, this, &DeviceHandler::serviceStateChanged); connect(m_service, &QLowEnergyService::characteristicChanged, this, &DeviceHandler::updateHeartRateValue); connect(m_service, &QLowEnergyService::descriptorWritten, this, &DeviceHandler::confirmedDescriptorWrite); m_service->discoverDetails(); } else { setError("Heart Rate Service not found."); setIcon(IconError); }
세부 정보 검색 중에 서비스의 state()는 RemoteService 에서 RemoteServiceDiscovering 로 전환되고 결국 RemoteServiceDiscovered 로 끝납니다:
void DeviceHandler::serviceStateChanged(QLowEnergyService::ServiceState s) { switch (s) { case QLowEnergyService::RemoteServiceDiscovering: setInfo(tr("Discovering services...")); setIcon(IconProgress); break; case QLowEnergyService::RemoteServiceDiscovered: { setInfo(tr("Service discovered.")); setIcon(IconBluetooth); const QLowEnergyCharacteristic hrChar = m_service->characteristic(QBluetoothUuid(QBluetoothUuid::CharacteristicType::HeartRateMeasurement)); if (!hrChar.isValid()) { setError("HR Data not found."); setIcon(IconError); break; } m_notificationDesc = hrChar.descriptor(QBluetoothUuid::DescriptorType::ClientCharacteristicConfiguration); if (m_notificationDesc.isValid()) m_service->writeDescriptor(m_notificationDesc, QByteArray::fromHex("0100")); break; } default: //nothing for now break; } emit aliveChanged(); }
주변 장치와의 상호 작용
위의 코드 예제에서 원하는 특성은 HeartRateMeasurement 유형입니다. 애플리케이션은 심박수 변화를 측정하므로 특성에 대한 변경 알림을 활성화해야 합니다. 모든 특성이 변경 알림을 제공하는 것은 아닙니다. 심박수 특성은 표준화되었으므로 알림을 받을 수 있다고 가정할 수 있습니다. 궁극적으로 QLowEnergyCharacteristic::properties()에는 QLowEnergyCharacteristic::Notify 플래그가 설정되어 있어야 하며 적절한 알림이 있는지 확인하려면 QBluetoothUuid::DescriptorType::ClientCharacteristicConfiguration 유형의 설명자가 존재해야 합니다.
마지막으로 Bluetooth 저에너지 표준에 따라 심박수 특성 값을 처리합니다:
void DeviceHandler::updateHeartRateValue(const QLowEnergyCharacteristic &c, const QByteArray &value) { // ignore any other characteristic change -> shouldn't really happen though if (c.uuid() != QBluetoothUuid(QBluetoothUuid::CharacteristicType::HeartRateMeasurement)) return; auto data = reinterpret_cast<const quint8 *>(value.constData()); quint8 flags = *data; //Heart Rate int hrvalue = 0; if (flags & 0x1) // HR 16 bit? otherwise 8 bit hrvalue = static_cast<int>(qFromLittleEndian<quint16>(data[1])); else hrvalue = static_cast<int>(data[1]); addMeasurement(hrvalue); }
일반적으로 특성 값은 일련의 바이트입니다. 이러한 바이트의 정확한 해석은 특성 유형 및 값 구조에 따라 다릅니다. 상당수는 Bluetooth SIG에 의해 표준화되었지만 다른 일부는 사용자 지정 프로토콜을 따를 수 있습니다. 위의 코드 스니펫은 표준화된 심박수 값을 읽는 방법을 보여줍니다.
광고 서비스
주변 장치에서 GATT 서버 애플리케이션을 구현하는 경우 중앙 장치에 제공하려는 서비스를 정의하고 이를 광고해야 합니다:
QLowEnergyAdvertisingData advertisingData; advertisingData.setDiscoverability(QLowEnergyAdvertisingData::DiscoverabilityGeneral); advertisingData.setIncludePowerLevel(true); advertisingData.setLocalName("HeartRateServer"); advertisingData.setServices(QList<QBluetoothUuid>()<< QBluetoothUuid::ServiceClassUuid::HeartRate);bool errorOccurred = false;const std::unique_ptr<QLowEnergyController> leController(QLowEnergyController::createPeripheral());auto errorHandler = [&leController, &errorOccurred](QLowEnergyController::오류 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;
이제 잠재 고객이 디바이스에 연결하여 제공되는 서비스를 검색하고 등록하여 특성 값 변경에 대한 알림을 받을 수 있습니다. API의 이 부분은 위의 섹션에서 이미 다루었습니다.
주변 장치에서 서비스 구현하기
첫 번째 단계는 서비스, 특성 및 설명자를 정의하는 것입니다. 이는 QLowEnergyServiceData, QLowEnergyCharacteristicData 및 QLowEnergyDescriptorData 클래스를 사용하여 수행됩니다. 이러한 클래스는 정의할 Bluetooth 저에너지 서비스를 구성하는 필수 정보를 위한 컨테이너 또는 빌딩 블록 역할을 합니다. 아래 코드 조각은 측정된 분당 비트를 게시하는 간단한 HeartRate 서비스를 정의합니다. 이러한 서비스를 사용할 수 있는 예로는 손목 시계를 들 수 있습니다.
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);
결과물인 serviceData
객체는 위의 광고 서비스 섹션에 설명된 대로 게시할 수 있습니다. QLowEnergyServiceData 과 QLowEnergyAdvertisingData 에 의해 래핑된 정보 사이에 부분적으로 정보가 겹치지만 두 클래스는 매우 다른 두 가지 작업을 수행합니다. 광고 데이터는 주변 디바이스에 게시되며 29바이트라는 크기 제한으로 인해 범위가 제한되는 경우가 많습니다. 따라서 항상 100% 완전하지는 않습니다. 이에 비해 QLowEnergyServiceData 에 포함된 서비스 데이터는 완전한 서비스 데이터 세트를 제공하며 활성 서비스 검색을 통한 연결이 수행된 경우에만 연결 클라이언트에게 표시됩니다.
다음 섹션에서는 서비스가 심박수 값을 업데이트하는 방법을 보여줍니다. 서비스의 성격에 따라 https://www.bluetooth.org 에 정의된 공식 서비스 정의를 준수해야 할 수도 있습니다. 다른 서비스는 완전히 사용자 정의될 수도 있습니다. 심박수 서비스는 채택되었으며 그 사양은 https://www.bluetooth.com/specifications/adopted-specifications 에서 확인할 수 있습니다.
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);
일반적으로 주변 장치의 특성 및 설명자 값 업데이트는 Bluetooth 저에너지 장치 연결과 동일한 방법을 사용합니다.
참고: 사용 방법 Qt Bluetooth (중앙 및 주변 역할 모두에서)를 사용하려면 사용 설명이 포함된 Info.plist 파일을 제공해야 합니다. 코어블루투스 설명서를 참조하세요: Info.plist에 액세스해야 하는 데이터 유형에 대한 사용 설명 키가 포함되지 않은 경우 앱이 충돌합니다. iOS 13 이상에 연결된 앱에서 코어 블루투스 API에 액세스하려면 NSBluetoothAlwaysUsageDescription 키를 포함하세요. iOS 12 및 이전 버전에서는 NSBluetoothPeripheralUsageDescription을 포함시켜 Bluetooth 주변 장치 데이터에 액세스합니다.
© 2025 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.