Bluetooth Low Energy – Überblick
Die „ Qt Bluetooth “ Low Energy-API unterstützt die Rollen „Peripherie/Server“ und „Zentrale/Client“. Sie wird auf allen wichtigen Qt-Plattformen unterstützt. Die einzige Ausnahme bildet die fehlende Unterstützung der Peripherie-Rolle unter Windows.
Was ist Bluetooth Low Energy?
Bluetooth Low Energy, auch bekannt als Bluetooth Smart, ist eine Technologie für drahtlose Computernetzwerke, die 2011 offiziell eingeführt wurde. Sie arbeitet auf derselben 2,4-GHz-Frequenz wie das „klassische“ Bluetooth. Der Hauptunterschied liegt, wie der Name der Technologie schon sagt, im geringen Energieverbrauch. Dadurch können Bluetooth-Low-Energy-Geräte monatelang, ja sogar jahrelang mit Knopfzellenbatterien betrieben werden. Die Technologie wurde mit Bluetooth v4.0 eingeführt. Geräte, die diese Technologie unterstützen, werden als „Bluetooth Smart Ready Devices“ bezeichnet. Die wichtigsten Merkmale der Technologie sind:
- Extrem geringer Stromverbrauch im Spitzen-, Durchschnitts- und Ruhemodus
- Die Fähigkeit, jahrelang mit handelsüblichen Knopfzellen zu laufen
- Niedrige Kosten
- Herstellerübergreifende Interoperabilität
- Erweiterte Reichweite
Bluetooth Low Energy nutzt eine Client-Server-Architektur. Der Server (auch als Peripheriegerät bezeichnet) bietet Dienste wie Temperatur- oder Herzfrequenzmessung an und sendet diese Werte aus. Der Client (als zentrales Gerät bezeichnet) verbindet sich mit dem Server und liest die vom Server gesendeten Werte aus. Ein Beispiel hierfür wäre eine Wohnung mit Bluetooth Smart Ready-Sensoren wie einem Thermostat, einem Feuchtigkeits- oder einem Drucksensor. Diese Sensoren sind Peripheriegeräte, die die Umgebungswerte der Wohnung übertragen. Gleichzeitig kann sich ein Mobiltelefon oder ein Computer mit diesen Sensoren verbinden, deren Werte abrufen und sie dem Nutzer als Teil einer umfassenderen Anwendung zur Raumklimasteuerung anzeigen.
Grundlegende Dienststruktur
Bluetooth Low Energy basiert auf zwei Protokollen: ATT (Attribute Protocol) und GATT (Generic Attribute Profile). Sie legen die Kommunikationsschichten fest, die von jedem Bluetooth Smart Ready-Gerät verwendet werden.
ATT-Protokoll
Der grundlegende Baustein von ATT ist ein Attribut. Jedes Attribut besteht aus drei Elementen:
- ein Wert – die Nutzlast oder die gewünschte Information
- eine UUID – der Attributtyp (wird von GATT verwendet)
- ein 16-Bit-Handle – eine eindeutige Kennung für das Attribut
Der Server speichert die Attribute, und der Client nutzt das ATT-Protokoll, um Werte auf dem Server zu lesen und zu schreiben.
GATT-Profil
GATT definiert die Gruppierung einer Reihe von Attributen, indem es vordefinierten UUIDs eine Bedeutung zuweist. Die folgende Tabelle zeigt ein Beispiel für einen Dienst, der die Herzfrequenz an einem bestimmten Tag bereitstellt. Die tatsächlichen Werte werden in den beiden Merkmalen gespeichert:
| Handle | UUID | Wert | Beschreibung |
|---|---|---|---|
| 0x0001 | 0x2800 | UUID 0x180D | Herzfrequenz-Dienst starten |
| 0x0002 | 0x2803 | UUID 0x2A37, Wert-Handle: 0x0003 | Merkmal vom Typ Herzfrequenzmessung (HRM) |
| 0x0003 | 0x2A37 | 65 Schläge pro Minute | Herzfrequenzwert |
| 0x0004 | 0x2803 | UUID 0x2A08, Wert-Handle: 0x0005 | Merkmal vom Typ Datum/Uhrzeit |
| 0x0005 | 0x2A08 | 18.08.2014 11:00 | Datum und Uhrzeit der Messung |
| 0x0006 | 0x2800 | UUID xxxxxx | Nächster Service beginnt |
| ... | ... | ... | ... |
GATT legt fest, dass die oben verwendete UUID „ 0x2800 “ den Beginn einer Dienstdefinition markiert. Jedes Attribut, das auf „ 0x2800 “ folgt, ist Teil des Dienstes, bis das nächste „ 0x2800 “ oder das Ende erreicht wird. In ähnlicher Weise gibt die bekannte UUID 0x2803 an, dass ein Merkmal vorhanden ist, und jedes dieser Merkmale verfügt über einen Typ, der die Art des Werts definiert. Das obige Beispiel verwendet die UUIDs 0x2A08 (Datum und Uhrzeit) und 0x2A37 (Herzfrequenzmessung). Jede der oben genannten UUIDs wird von der Bluetooth Special Interest Group definiert und ist in den GATT-Spezifikationen zu finden. Es ist zwar ratsam, vordefinierte UUIDs zu verwenden, sofern diese verfügbar sind, doch ist es durchaus möglich, neue und noch nicht verwendete UUIDs für Merkmals- und Diensttypen zu nutzen.
Im Allgemeinen kann jeder Dienst aus einer oder mehreren Eigenschaften bestehen. Eine Eigenschaft enthält Daten und kann durch Deskriptoren näher beschrieben werden, die zusätzliche Informationen oder Möglichkeiten zur Bearbeitung der Eigenschaft bereitstellen. Alle Dienste, Merkmale und Deskriptoren werden anhand ihrer 128-Bit-UUID identifiziert. Schließlich ist es möglich, Dienste in Dienste einzubetten (siehe Abbildung unten).

Verwendung der „ Qt Bluetooth -Low-Energy“-API
In diesem Abschnitt wird beschrieben, wie die von Qt bereitgestellte Bluetooth Low Energy-API verwendet wird. Auf der Client-Seite ermöglicht die API das Herstellen von Verbindungen zu Peripheriegeräten, das Erkennen ihrer Dienste sowie das Lesen und Schreiben von auf dem Gerät gespeicherten Daten. Auf der Server-Seite ermöglicht sie das Einrichten von Diensten, deren Bekanntgabe und das Erhalten von Benachrichtigungen, wenn der Client Merkmale schreibt. Der folgende Beispielcode stammt aus den Beispielen „Heart Rate Game “ und „Heart Rate Server “.
Herstellen einer Verbindung
Um die Merkmale des Bluetooth-Low-Energy-Peripheriegeräts lesen und schreiben zu können, muss das Gerät gefunden und eine Verbindung zu ihm hergestellt werden. Dazu muss das Peripheriegerät seine Anwesenheit und seine Dienste bekanntgeben. Wir starten die Geräteerkennung mithilfe der Klasse „ QBluetoothDeviceDiscoveryAgent “. Wir verbinden uns mit ihrem Signal „ QBluetoothDeviceDiscoveryAgent::deviceDiscovered()“ und starten die Suche mit „ 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);Da wir nur an Low-Energy-Geräten interessiert sind, filtern wir den Gerätetyp innerhalb des Empfangsslots heraus. Der Gerätetyp lässt sich mithilfe des Flags „ QBluetoothDeviceInfo::coreConfigurations()“ ermitteln. Das Signal „ deviceDiscovered()“ kann für dasselbe Gerät mehrfach ausgesendet werden, sobald weitere Details erkannt werden. Hier ordnen wir diese Geräteerkennungen einander zu, sodass der Benutzer nur die einzelnen Geräte sieht:
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);
}
//...
}Sobald die Adresse des Peripheriegeräts bekannt ist, verwenden wir die Klasse ` QLowEnergyController `. Diese Klasse ist der Einstiegspunkt für die gesamte Bluetooth-Low-Energy-Entwicklung. Der Konstruktor der Klasse akzeptiert die ` QBluetoothAddress` des Remote-Geräts. Schließlich richten wir die üblichen Slots ein und stellen über ` connectToDevice()` eine direkte Verbindung zum Gerät her:
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();Dienstsuche
Der obige Codeausschnitt zeigt, wie die Anwendung die Dienstermittlung initiiert, sobald die Verbindung hergestellt wurde.
Der unten stehende Slot „ serviceDiscovered() “ wird als Reaktion auf das Signal „ QLowEnergyController::serviceDiscovered()“ ausgelöst und liefert einen zeitweiligen Fortschrittsbericht. Da es sich um die „Heart Listener“-App handelt, die HeartRate-Geräte in der Umgebung überwacht, ignorieren wir alle Dienste, die nicht vom Typ „ QBluetoothUuid::ServiceClassUuid::HeartRate “ sind.
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;
}
}Schließlich wird das Signal „ QLowEnergyController::discoveryFinished()“ ausgesendet, um den erfolgreichen Abschluss der Dienstsuche anzuzeigen. Sofern ein HeartRate-Dienst gefunden wurde, wird eine „ QLowEnergyService “-Instanz erstellt, um den Dienst darzustellen. Das zurückgegebene Dienstobjekt stellt die erforderlichen Signale für Aktualisierungsbenachrichtigungen bereit, und die Ermittlung der Dienstdetails wird über „ QLowEnergyService::discoverDetails()“ ausgelöst:
// 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);
}Während der Detailsuche wechselt der Status „ state “ () des Dienstes von „ RemoteService “ zu „ RemoteServiceDiscovering “ und endet schließlich mit „ 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();
}Interaktion mit dem Peripheriegerät
Im obigen Code-Beispiel ist die gewünschte Eigenschaft vom Typ „ HeartRateMeasurement “. Da die Anwendung die Herzfrequenzänderungen misst, muss sie Änderungsbenachrichtigungen für diese Eigenschaft aktivieren. Beachten Sie, dass nicht alle Merkmale Änderungsbenachrichtigungen bereitstellen. Da das Merkmal „HeartRate“ standardisiert wurde, kann davon ausgegangen werden, dass Benachrichtigungen empfangen werden können. Letztendlich muss bei „ QLowEnergyCharacteristic::properties()“ das Flag „ QLowEnergyCharacteristic::Notify “ gesetzt sein und ein Deskriptor vom Typ „ QBluetoothUuid::DescriptorType::ClientCharacteristicConfiguration “ vorhanden sein, um die Verfügbarkeit einer entsprechenden Benachrichtigung zu bestätigen.
Abschließend verarbeiten wir den Wert des Merkmals „HeartRate“ gemäß dem Bluetooth-Low-Energy-Standard:
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);
}Im Allgemeinen besteht ein Merkmalswert aus einer Reihe von Bytes. Die genaue Interpretation dieser Bytes hängt vom Merkmalstyp und der Wertestruktur ab. Ein Großteil davon wurde von der Bluetooth SIG standardisiert, während andere möglicherweise einem benutzerdefinierten Protokoll folgen. Der obige Codeausschnitt veranschaulicht, wie der standardisierte „HeartRate“-Wert gelesen wird.
Advertising-Dienste
Wenn wir eine GATT-Serveranwendung auf einem Peripheriegerät implementieren, müssen wir die Dienste definieren, die wir zentralen Geräten anbieten möchten, und diese bekanntgeben:
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::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;Nun können potenzielle Clients eine Verbindung zu unserem Gerät herstellen, den bereitgestellten Dienst erkennen und sich registrieren, um über Änderungen des Merkmalswerts benachrichtigt zu werden. Dieser Teil der API wurde bereits in den vorangegangenen Abschnitten behandelt.
Implementierung eines Dienstes auf dem Peripheriegerät
Der erste Schritt besteht darin, den Dienst, seine Merkmale und Deskriptoren zu definieren. Dies geschieht mithilfe der Klassen ` QLowEnergyServiceData`, ` QLowEnergyCharacteristicData ` und ` QLowEnergyDescriptorData `. Diese Klassen dienen als Container oder Bausteine für die wesentlichen Informationen, aus denen der zu definierende Bluetooth Low Energy-Dienst besteht. Der folgende Codeausschnitt definiert einen einfachen HeartRate-Dienst, der die gemessenen Schläge pro Minute veröffentlicht. Ein Beispiel für den Einsatz eines solchen Dienstes ist eine Armbanduhr.
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);Das resultierende „ serviceData “-Objekt kann wie im obigen Abschnitt „Advertising Services“ beschrieben veröffentlicht werden. Trotz der teilweisen Überschneidung der Informationen, die von „ QLowEnergyServiceData “ und „ QLowEnergyAdvertisingData “ umschlossen werden, erfüllen die beiden Klassen zwei sehr unterschiedliche Aufgaben. Die Advertising-Daten werden an Geräte in der Nähe gesendet und sind aufgrund der Größenbeschränkung von 29 Byte oft in ihrem Umfang begrenzt. Daher sind sie nicht immer zu 100 % vollständig. Im Vergleich dazu liefern die in „ QLowEnergyServiceData “ enthaltenen Dienstdaten den vollständigen Satz an Dienstdaten und werden für den sich verbindenden Client erst sichtbar, wenn eine Verbindung mit aktiver Diensterkennung hergestellt wurde.
Der nächste Abschnitt zeigt, wie der Dienst den Herzfrequenzwert aktualisieren kann. Je nach Art des Dienstes muss dieser möglicherweise der offiziellen Dienstdefinition entsprechen, wie sie unter https://www.bluetooth.org definiert ist. Andere Dienste können vollständig benutzerdefiniert sein. Der Herzfrequenzdienst wurde übernommen, und seine Spezifikation ist unter https://www.bluetooth.com/specifications/adopted-specifications zu finden.
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);Im Allgemeinen werden für die Aktualisierung von Merkmalen und Deskriptorwerten auf dem Peripheriegerät dieselben Methoden verwendet wie beim Verbinden von Bluetooth-Low-Energy-Geräten.
Hinweis: Um Qt Bluetooth (sowohl in der zentralen als auch in der peripheren Rolle) unter iOS nutzen zu können, müssen Sie eine Info.plist-Datei bereitstellen, die die Nutzungsbeschreibung enthält. Laut der CoreBluetooth-Dokumentation stürzt Ihre App ab, wenn ihre Info.plist keine Nutzungsbeschreibungsschlüssel für die Datentypen enthält, auf die sie zugreifen muss. Um auf Core-Bluetooth-APIs in Apps zuzugreifen, die unter iOS 13 oder später verknüpft wurden, fügen Sie den Schlüssel „NSBluetoothAlwaysUsageDescription“ ein. In iOS 12 und früheren Versionen fügen Sie „NSBluetoothPeripheralUsageDescription“ ein, um auf Daten von Bluetooth-Peripheriegeräten zuzugreifen.
© 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.