Bluetooth Low Energy Übersicht

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 ist die fehlende Unterstützung der Peripherie-Rolle unter Windows.

Was ist Bluetooth Low Energy?

Bluetooth Low Energy, auch bekannt als Bluetooth Smart, ist eine drahtlose Computernetzwerktechnologie, die 2011 offiziell eingeführt wurde. Sie arbeitet auf der gleichen 2,4-GHz-Frequenz wie das "klassische" Bluetooth. Der Hauptunterschied ist, wie der Name der Technologie schon sagt, der geringe Energieverbrauch. So 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:

  • Ultra-niedriger Spitzen-, Durchschnitts- und Leerlaufstromverbrauch
  • Fähigkeit, jahrelang mit Standard-Knopfzellenbatterien zu arbeiten
  • niedrige Kosten
  • Interoperabilität mit mehreren Anbietern
  • Erhöhte Reichweite

Bluetooth Low Energy verwendet eine Client-Server-Architektur. Der Server (auch als Peripheriegerät bezeichnet) bietet Dienste wie Temperatur oder Herzfrequenz an und macht sie bekannt. Der Client (auch als zentrales Gerät bezeichnet) stellt eine Verbindung zum Server her und liest die vom Server angekündigten Werte ab. Ein Beispiel wäre eine Wohnung mit Bluetooth Smart Ready Sensoren wie Thermostat, Luftfeuchtigkeits- oder Drucksensor. Diese Sensoren sind Peripheriegeräte, die die Umgebungswerte der Wohnung anzeigen. Gleichzeitig könnte ein Mobiltelefon oder ein Computer eine Verbindung zu diesen Sensoren herstellen, ihre Werte abrufen und sie dem Benutzer als Teil einer größeren Anwendung zur Umgebungskontrolle präsentieren.

Grundlegende Struktur des Dienstes

Bluetooth Low Energy basiert auf zwei Protokollen: ATT (Attribute Protocol) und GATT (Generic Attribute Profile). Sie spezifizieren die Kommunikationsschichten, die von jedem Bluetooth Smart Ready Gerät verwendet werden.

ATT-Protokoll

Der Grundbaustein von ATT ist ein Attribut. Jedes Attribut besteht aus drei Elementen:

  • einem Wert - die Nutzlast oder gewünschte Information
  • einer UUID - dem Typ des Attributs (wird von GATT verwendet)
  • ein 16-Bit-Handle - ein eindeutiger Bezeichner für das Attribut

Der Server speichert die Attribute und der Client verwendet das ATT-Protokoll, um Werte auf dem Server zu lesen und zu schreiben.

GATT-Profil

GATT definiert eine Gruppierung für eine Reihe von Attributen, indem vordefinierten UUIDs eine Bedeutung zugewiesen wird. Die nachstehende Tabelle zeigt einen Beispieldienst, der die Herzfrequenz an einem bestimmten Tag angibt. Die tatsächlichen Werte werden in den beiden Merkmalen gespeichert:

HandleUUIDWertBeschreibung
0x00010x2800UUID 0x180DBeginn des Herzfrequenzdienstes
0x00020x2803UUID 0x2A37, Wert-Handle: 0x0003Merkmal vom Typ Heart Rate Measurement (HRM)
0x00030x2A3765 bpmWert der Herzfrequenz
0x00040x2803UUID 0x2A08, Wert-Handle: 0x0005Merkmal vom Typ Datum Zeit
0x00050x2A0818/08/2014 11:00Datum und Uhrzeit der Messung
0x00060x2800UUID xxxxxxBeginn des nächsten Dienstes
............

GATT legt fest, dass die oben verwendete UUID 0x2800 den Beginn einer Dienstdefinition markiert. Jedes auf 0x2800 folgende Attribut ist Teil des Dienstes, bis die nächste 0x2800 oder das Ende erreicht ist. In ähnlicher Weise gibt die bekannte UUID 0x2803 an, dass ein Merkmal zu finden ist, und jedes der Merkmale hat einen Typ, der die Art des Wertes definiert. Im obigen Beispiel werden die UUIDs 0x2A08 (Datum Uhrzeit) und 0x2A37 (Herzfrequenzmessung) verwendet. Jede der oben genannten UUIDs wurde von der Bluetooth Special Interest Group definiert und ist in den GATT-Spezifikationen zu finden. Obwohl es ratsam ist, vordefinierte UUIDs zu verwenden, wo sie verfügbar sind, ist es durchaus möglich, neue und noch nicht verwendete UUIDs für Merkmale und Diensttypen zu verwenden.

Im Allgemeinen kann jeder Dienst aus einem oder mehreren Merkmalen bestehen. Ein Merkmal enthält Daten und kann durch Deskriptoren weiter beschrieben werden, die zusätzliche Informationen oder Mittel zur Manipulation des Merkmals liefern. Alle Dienste, Merkmale und Deskriptoren werden anhand ihrer 128-Bit-UUID erkannt. Schließlich ist es möglich, Dienste innerhalb von Diensten einzubinden (siehe Abbildung unten).

Verwendung von Qt Bluetooth Low Energy API

Dieser Abschnitt beschreibt die Verwendung der Bluetooth Low Energy API, die von Qt bereitgestellt wird. Auf der Client-Seite ermöglicht die API das Herstellen von Verbindungen zu Peripheriegeräten, das Auffinden ihrer Dienste sowie das Lesen und Schreiben von auf dem Gerät gespeicherten Daten. Auf der Serverseite ermöglicht sie das Einrichten von Diensten, das Anzeigen von Diensten und das Erhalten einer Benachrichtigung, 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 verbunden werden. Dazu muss das Peripheriegerät sein Vorhandensein und seine Dienste bekannt geben. Wir starten die Geräteerkennung mit Hilfe der Klasse QBluetoothDeviceDiscoveryAgent. Wir verbinden uns mit seinem QBluetoothDeviceDiscoveryAgent::deviceDiscovered()-Signal 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 im Empfangsbereich. Der Gerätetyp kann mit Hilfe des QBluetoothDeviceInfo::coreConfigurations()-Flags ermittelt werden. Das Signal deviceDiscovered() kann mehrfach für dasselbe Gerät ausgegeben werden, wenn weitere Details entdeckt werden. Hier passen wir diese Gerätefunde an, damit 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 nimmt die Adresse des entfernten Geräts QBluetoothAddress entgegen. Schließlich richten wir die üblichen Slots ein und stellen mit 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();

Das obige Codeschnipsel zeigt, wie die Anwendung die Dienstsuche einleitet, sobald die Verbindung hergestellt ist.

Der serviceDiscovered() Slot unten wird als Ergebnis des QLowEnergyController::serviceDiscovered() Signals ausgelöst und liefert einen intermittierenden Fortschrittsbericht. Da es sich um die Heart-Listener-App handelt, die HeartRate-Geräte in der Umgebung überwacht, ignorieren wir jeden Dienst, der nicht vom Typ QBluetoothUuid::ServiceClassUuid::HeartRate ist.

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. Wenn ein HeartRate-Dienst gefunden wurde, wird eine Instanz von QLowEnergyService erstellt, um den Dienst zu repräsentieren. Das zurückgegebene Dienstobjekt liefert die erforderlichen Signale für Aktualisierungsbenachrichtigungen, und die Ermittlung von Dienstdetails wird mit 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 geht state() des Dienstes von RemoteService zu RemoteServiceDiscovering über 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 Codebeispiel ist das gewünschte Merkmal vom Typ HeartRateMeasurement. Da die Anwendung die Herzfrequenzänderungen misst, muss sie Änderungsmeldungen für das Merkmal aktivieren. Beachten Sie, dass nicht alle Merkmale Änderungsbenachrichtigungen bereitstellen. Da das Merkmal HeartRate standardisiert wurde, kann man davon ausgehen, dass Benachrichtigungen empfangen werden können. Letztendlich muss bei QLowEnergyCharacteristic::properties() das Kennzeichen QLowEnergyCharacteristic::Notify gesetzt sein und ein Deskriptor des Typs QBluetoothUuid::DescriptorType::ClientCharacteristicConfiguration muss vorhanden sein, um die Verfügbarkeit einer entsprechenden Meldung zu bestätigen.

Schließlich verarbeiten wir den Wert des HeartRate-Merkmals 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 Wertstruktur ab. Eine große Anzahl wurde von der Bluetooth SIG standardisiert, während andere einem benutzerdefinierten Protokoll folgen können. Das obige Codeschnipsel zeigt, wie der standardisierte HeartRate-Wert gelesen werden kann.

Werbedienste

Wenn wir eine GATT-Server-Anwendung auf einem Peripheriegerät implementieren, müssen wir die Dienste definieren, die wir zentralen Geräten anbieten wollen, und sie bekannt machen:

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;

Jetzt können potenzielle Kunden eine Verbindung zu unserem Gerät herstellen, den bereitgestellten Dienst entdecken und sich registrieren, um über Änderungen des Merkmalswerts benachrichtigt zu werden. Dieser Teil der API wurde bereits in den obigen Abschnitten behandelt.

Implementieren eines Dienstes auf dem Peripheriegerät

Der erste Schritt besteht darin, den Dienst, seine Merkmale und Deskriptoren zu definieren. Dies geschieht mit Hilfe 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 unten stehende Codeausschnitt definiert einen einfachen HeartRate-Dienst, der die gemessenen Schläge pro Minute veröffentlicht. Ein Beispiel für die Verwendung 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 daraus resultierende serviceData Objekt kann wie im obigen Abschnitt über Werbedienste beschrieben veröffentlicht werden. Trotz der teilweisen Informationsüberschneidung zwischen den von QLowEnergyServiceData und QLowEnergyAdvertisingData verpackten Informationen dienen die beiden Klassen zwei sehr unterschiedlichen Aufgaben. Die Werbedaten werden für Geräte in der Nähe veröffentlicht und sind aufgrund ihrer 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 verbindenden Client erst sichtbar, wenn eine Verbindung mit einer aktiven Dienstsuche hergestellt wurde.

Im nächsten Abschnitt wird gezeigt, wie der Dienst den Herzfrequenzwert aktualisieren kann. Je nach Art des Dienstes muss er der offiziellen Dienstdefinition entsprechen, die auf 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, &currentHeartRate, &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 die gleichen Methoden verwendet wie für die Verbindung von Bluetooth Low Energy Geräten.

Hinweis: Zur Verwendung von Qt Bluetooth (sowohl in der zentralen als auch in der peripheren Rolle) unter iOS zu verwenden, müssen Sie eine Info.plist-Datei bereitstellen, die die Verwendungsbeschreibung enthält. Gemäß der Dokumentation von CoreBluetooth: Ihre App wird abstürzen, wenn ihre Info.plist keine Verwendungsbeschreibungsschlüssel für die Datentypen enthält, auf die sie zugreifen muss. Um auf CoreBluetooth-APIs in Apps zuzugreifen, die mit iOS 13 oder später verknüpft wurden, fügen Sie den Schlüssel NSBluetoothAlwaysUsageDescription ein. In iOS 12 und früher müssen Sie NSBluetoothPeripheralUsageDescription einschließen, um auf Bluetooth-Peripheriedaten zuzugreifen.

© 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.