CoreBluetooth in der Praxis

Ursprünglicher Autor: Yoav Schwartz
  • Übersetzung

Übersetzung des Artikels Praktisches CoreBluetooth für Peripheriegeräte


Als ich vor ein paar Jahren zum ersten Mal auf Bluetooth mit einem Arbeitsentwurf gestoßen bin, habe ich diesen Artikel gefunden, der sehr hilfreich war, um zu verstehen, wie er funktioniert, um den „Ausgangspunkt“ zu finden. Ich hoffe, dass es für Anfänger nützlich ist.

Über den Autor: Yoav Schwartz ist ein führender iOS-Entwickler in Donkey Republic, dem Radfahrersystem von Kopenhagen, um die Einstellung zum Fahrradtransport zu ändern. Als nächstes wird es im Namen des Autors sein.

In diesem Artikel werde ich über die praktischen Tricks der Arbeit mit CoreBluetooth sprechen. Zunächst zu Bluetooth Low Energy (BLE), da nicht jeder mit dieser Technologie vertraut ist, dann zu CoreBluetooth, dem Apple-Framework, mit dem wir mit BLE-Geräten interagieren können. Ich werde Ihnen auch einige Techniken in der Entwicklung erzählen, die ich selbst beim Debuggen, Weinen und Zerreißen der Haare auf meinem Kopf herausgefunden habe.

Bluetooth Low Energy


Was ist BLE? Es ist ein bisschen wie Bluetooth, das wir alle in Lautsprechern, Headsets usw. verwenden, aber es gibt einen Unterschied - dieses Protokoll verbraucht sehr wenig Energie. Normalerweise kann eine einzelne Batterieladung für Monate oder sogar Jahre für ein Gerät ausreichen, das mit BLE arbeitet (natürlich abhängig von der Verwendung dieses Geräts). Dies ermöglicht uns, Dinge zu tun, die zuvor für „normales“ Bluetooth nicht verfügbar waren. Dieser Standard wird Bluetooth 4.0 genannt. Begonnen hat alles mit einer Technologie namens Smart Bluetooth, die später zu BLE entwickelt wurde. Es gibt ein 200-seitiges Handbuch , das Sie vor dem Zubettgehen lesen können, eine aufregende Lektüre.

BLE ist sehr sparsam im Energieverbrauch und das Protokoll selbst ist nicht sehr kompliziert. Warum also blasen? Wie können wir es benutzen? Das erste und häufigste Beispiel ist der Herzfrequenzsensor. Normalerweise misst und überträgt dieses Gerät Ihre Herzfrequenz an das Protokoll. Es gibt eine Vielzahl von Sensoren, an die Sie sich über BLE anschließen und die gesammelten Daten lesen können. Schließlich gibt es iBeacons, die Ihnen die Nähe zu einem Ort anzeigen können. In Anführungszeichen, weil das iPhone von Apple die Erkennung von iBeacons als gewöhnliche Bluetooth-Geräte blockiert, so müssen wir mit CoreLocation arbeiten. Im Allgemeinen ist dies das Internet der Dinge: Sie können eine Verbindung zu einem Fernsehgerät oder einer Klimaanlage herstellen und mit diesem Protokoll kommunizieren.

Wie funktioniert das?


Wir haben ein Peripheriegerät, sogenannte Geräte, die das Bluetooth-Protokoll verwenden. Jedes periferal hat Dienste, es kann so viele davon geben, und jedes hat Merkmale. Sie können die periferal als Server betrachten. Mit allen Konsequenzen: Manchmal schaltet es sich aus, manchmal dauert es länger, Daten zu übertragen, und manchmal kommen diese Daten überhaupt nicht.

Im Allgemeinen haben wir einen Dienst mit einer Vielzahl von Merkmalen, von denen jede einen Wert, einen Typ usw. enthält. Um mit CoreBluetooth arbeiten zu können, müssen Sie nicht alles wissen. Das Wichtigste ist, Daten zu lesen. Dies versuchen wir, für unsere eigenen Zwecke zu erhalten, zu verändern oder zu nutzen. Wir brauchen diese Daten und das Wissen, was wir damit machen können.

Hier ist eine kurze Einführung in BLE, da es Tausende von Ressourcen gibt, die die technischen Merkmale besser erklären werden als ich.

Core Bluetooth


Core Bluetooth wurde vor langer Zeit von Apple in iOS 5 eingeführt. Apple begann mit der Einführung von BLE in seine Geräte viel früher als Android und der wachsenden Beliebtheit der Technologie. Viele Entwickler verwenden dieses Framework im Großen und Ganzen in ihren Anwendungen - dies ist nur ein Wrapper, da die BLE-Protokolle selbst ziemlich komplex sind. Nicht so viel, aber glauben Sie mir, damit möchte ich nicht jeden Tag arbeiten. Genau wie viele andere Dinge hat Apple es in ein schönes und praktisches Paket verpackt, so dass wir Begriffe verwenden können, die jeder von uns dummen Entwicklern verstehen kann.

Nun ist es an der Reihe zu sagen, was Sie wirklich wissen müssen - über die Klassen, die an der Kommunikation mit dem Framework beteiligt sind. Unser Hauptdarsteller CBCentralManager wird es schaffen:

manager = CBCentralManager(delegate:self, queue:nil, options: nil)

Oben haben wir einen neuen Manager erstellt, der seinen Stellvertreter angibt, andernfalls können wir ihn nicht verwenden. Wir geben auch die Warteschlange an, in unserem Fall Null, was bedeutet, dass die gesamte Kommunikation mit dem Manager auf der Hauptleitung erfolgt.

Sie müssen genau wissen, was Sie tun werden - die Verwendung einer separaten Warteschlange macht die Anwendung komplizierter, aber die Benutzer werden Sie natürlich mehr lieben. Wenn Sie nur mit einem Gerät kommunizieren möchten, können Sie die Hauptwarteschlange nicht verwenden. Wenn Sie noch experimentieren möchten, erstellen Sie eine Warteschlange, geben Sie diese im Konstruktor an und vergessen Sie nicht, zur Hauptwarteschlange zurückzukehren, bevor Sie die an anderer Stelle erhaltenen Ergebnisse verwenden.

Optionen Es ist nichts besonders interessantes hier, vielleicht ist die Hauptsache, wenn Sie einen Manager erstellen und der Benutzer Bluetooth deaktiviert hat - die Anwendung wird es ihm mitteilen, aber fast jeder klickt auf „OK“ (was eigentlich kein Bluetooth enthält), weshalb Ich benutze nicht

Nach dem Erstellen eines Managers müssen Sie zunächst seinen Delegierten anrufen:

func centralManagerDidUpdateState(_ central: CBCentralManager)

Wir erhalten also eine Antwort von der Hardware - unabhängig davon, ob der Benutzer Bluetoooth aktiviert hat oder nicht.
Erster Hinweis: Der Manager ist unbrauchbar, bis wir eine Antwort erhalten, dass Bluet®oth eingeschaltet ist. Sein Zustand ist .PoweredOn. Die verbleibenden Zustände können verwendet werden, mit der Ausnahme, dass der Benutzer aufgefordert wird, Bluetooth zu aktivieren.

Gerätesuche


Nun, da unser Manager ordnungsgemäß arbeitet, können wir uns ansehen,
was um uns herum ist (nachdem wir den Zustand .PoweredOn erhalten haben, rufen wir die Funktion scanForPeripheralsWithServices auf :)

manager.scanForPeripheralsWithServices([CBUUID], options: nil)

Bei den Services handelt es sich um ein CBUUID-Array (eine Klasse, die universelle eindeutige 128-Bit-Bezeichner von Attributen darstellt, die von Bluetooth Low Energy ca. Lane verwendet werden), die wir als Filter zum Suchen von Geräten nur mit dieser UID verwenden. Dies ist in CoreBluetooth üblich .

Wenn Sie als Argument nil übergeben, können wir alle Geräte in der Umgebung sehen. Für die Leistung ist es natürlich besser, eine Reihe von Parametern anzugeben, die wir benötigen. Wenn Sie sie jedoch nicht kennen, wird nichts Schreckliches passieren. Wenn Sie null überschreiten, stirbt niemand.

Da wir mit der Suche nach Geräten begonnen haben, sollten wir sie stoppen. Andernfalls wird die Suche fortgesetzt und der Akku des Benutzers wird geladen, bis wir ihn stoppen. Sobald wir das richtige Gerät gefunden haben oder die Notwendigkeit der Suche verschwindet, hören wir auf:

manager.stopScan()

Jedes Mal, wenn ein neues Gerät erkannt wird, wird die Funktion didDiscoverPeripheral in der Warteschlange aufgerufen, die wir während der Initialisierung angegeben haben. Die Funktion sendet uns das gefundene Gerät (Peripheriegerät), Informationen dazu (AdvertisementData ist etwas, was die Chiphersteller jedes Mal anzeigen wollten) und den relativen Pegel des RSSI-Signals in Dezibel.

func centralManager(_ central: CBCentralManager, didDiscover peripheral:
CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber)

Zweiter Tipp: Behalten Sie immer einen starken Link zur gewünschten periferalen Verbindung. Wenn dies nicht der Fall ist, entscheidet das System, dass das Gerät nicht gefunden werden muss, und verwirft es. Sie wird sich an ihn erinnern, aber wir haben keinen Zugang mehr zu ihm. Andernfalls können wir nicht mit dem Gerät arbeiten.

Verbindung zum Gerät herstellen


Wir haben das Gerät gefunden, an dem wir interessiert sind - es ist, als würden wir zu einer Party kommen und ein hübsches Mädchen sehen. Wir möchten eine Verbindung herstellen, die connectPeripheral-Funktion aufrufen - wir bieten "ein Getränk kaufen". Wir versuchen also, eine Verbindung zum richtigen Gerät (Peripheriegerät) herzustellen, und es kann uns "Ja" oder "Nein" sagen, aber unser iPhone ist wirklich gut, daher werden wir eine positive Antwort hören.

manager.connectPeripheral(peripheral, options: nil)

Hier haben wir uns an den Manager gewandt, der für die Verbindungen verantwortlich ist, ihm sagen, mit welchem ​​Gerät wir uns verbinden, und wir geben wieder keine Optionen an (wenn Sie wirklich an den Möglichkeiten interessiert sind, lesen Sie die Dokumentation, aber Sie können normalerweise auf sie verzichten). Wenn Sie die Arbeit mit dem Gerät beendet haben, können Sie die Verbindung trennen.

//called to cancel and/or disconnect
manager.cancelPeripheralConnection(peripheral)

Nachdem wir die Verbindung hergestellt oder getrennt haben, teilt uns der Delegierte mit:

//didConnect
func centralManager(central: CBCentralManager!, didConnectPeripheral
peripheral: CBPeripheral!)
//didDisconnect
func centralManager(central: CBCentralManager!, didDisconnectPeripheral
peripheral: CBPeripheral!, error: NSError!)

Nun noch zwei weitere wichtige Tipps. Das Bluetooth-Protokoll impliziert eine Verbindungszeitüberschreitung, aber Apple kümmert sich nicht darum. iOS versucht immer wieder, eine Verbindung herzustellen und wird erst beendet, wenn Sie cancelPeripheralConnection aufrufen. Dieser Vorgang kann zu lange dauern, daher ist eine zeitliche Begrenzung erforderlich. Wenn wir am Ende keine Nachrichten über eine erfolgreiche Verbindung (didConnectPeripheral) erhalten, müssen Sie den Benutzer darüber informieren, dass ein Problem aufgetreten ist.

Wenn Sie keine starke Verbindung zum Peripheriegerät aufrechterhalten, bricht iOS die Verbindung einfach ab. Aus ihrer Sicht bedeutet dies, dass Sie es nicht brauchen, und die Unterstützung ist eine ziemlich energieintensive Aufgabe für eine Batterie. Wir wissen, wie Apple den Energieverbrauch in Beziehung setzt.

Das Gerät nützlich machen


Und so haben wir uns mit dem Gerät verbunden, lass uns damit etwas anfangen. Zuvor habe ich Services und Features erwähnt, die Werte, die sie enthalten, das ist es, was wir brauchen. Jetzt haben wir ein Gerät, es ist verbunden und wir können seine Dienste erhalten, indem Sie peripherie.discoverServices aufrufen.

peripheral.discoverServices(nil)
func peripheral(peripheral: CBPeripheral!, didDiscoverServices error:
NSError!)
peripheral.services

Jetzt wird es etwas verwirrend klingen, aber der Delegat wird für den Thread aufgerufen, den wir beim Erstellen des Managers definiert haben, obwohl dies ein Delegierter der Periferalen ist. Das System merkt sich also, mit welchem ​​Stream es arbeitet, und die gesamte Bluetooth-Kommunikation findet in diesem Stream statt. Es ist wichtig, nicht zu vergessen, zum Hauptmenü zurückzukehren, wenn Sie es nicht verwendet haben.

Wir haben Dienstleistungen erhalten, aber wir haben immer noch nichts zu tun. Als Nächstes müssen Sie peripher.discoverCharacteristics aufrufen. Der Delegat gibt uns alle verfügbaren Merkmale für die empfangenen Services in didDiscoverCharacteristicsForService an. Jetzt können wir die dort enthaltenen Werte lesen
(readValueForCharacteristic) oder bitten, uns Bescheid zu geben, sobald sich dort etwas ändert - setNotifyValue.

peripheral.discoverCharacteristics(nil, forService: (service as CBService))
func peripheral(peripheral: CBPeripheral!,
didDiscoverCharacteristicsForService service: CBService!, error: NSError!)
peripheral.readValueForCharacteristic(characteristic)
peripheral.setNotifyValue(true, forCharacteristic: characteristic)
func peripheral(peripheral: CBPeripheral!, didUpdateValueForCharacteristic
characteristic: CBCharacteristic!, error: NSError!)

Im Gegensatz zu Android unterscheidet Apple nicht zwischen Lesen und Benachrichtigung. Das heißt, wir wissen nicht, was los ist - wir lesen etwas vom Gerät oder dieses Gerät sagt uns etwas.

Schreiben Sie in das Gerät


Wir haben ein Gerät, wir lesen Informationen daraus, wir verwalten es. Wir können also in der Regel Informationen dazu schreiben - die üblichen NSData. Es ist nur notwendig herauszufinden, was dieses Gerät von uns erwartet und was von ihm akzeptiert wird.

Die meisten BLE-Geräte verfügen über eine Spezifikation, eine Art API, aus der klar hervorgeht, wie mit ihnen "kommuniziert" wird. Sie können die Daten aus den Merkmalen ziehen, um zumindest eine ungefähre Vorstellung davon zu erhalten, was das Gerät von uns erwartet.

Aus den Vorgaben erfahren wir, in welchen Merkmalen welche Eigenschaften wir lesen und in welche wir schreiben, ob wir über Änderungen informiert werden (isNotifying). Meistens finden wir hier alles, was für die Arbeit benötigt wird.

peripheral.writeValue(data: NSData!, forCharacteristic: CBCharacteristic!,
type: CBCharacteristicWriteType)
characteristic.properties - OptionSet type
characteristic.isNotifying
func peripheral(peripheral: CBPeripheral!, didWriteValueForCharacteristic
characteristic: CBCharacteristic!, error: NSError!)

Während des Schreibvorgangs teilt uns der Delegierte mit, dass alles gut gelaufen ist (didWriteValueForCharacteristics), dass der erforderliche Wert aktualisiert wurde, und wir können den Benutzer darüber informieren oder diese Informationen anders verwenden.

Wir betrachten das Thema in einem sehr engen Schnitt und verlassen uns auf die Implementierung von Apple. Es gibt also eine Reihe von Problemen, mit denen wir uns befassen müssen. Zum Beispiel eine sehr starke Abhängigkeit von der Delegation, so beliebt bei Apple.

Vererbung von CBPeripheral? Wenn alles so einfach wäre


Es scheint, als würden wir, da wir ein Gerät haben, damit anfangen, es zu benutzen, aber es sagt uns nichts über uns aus. Vielleicht möchten wir das Schloss, die Klimaanlage oder den Impulssensor steuern. Sie müssen wissen, mit welchem ​​Gerät wir kommunizieren.

Es sieht aus wie Vererbung: Wir haben einen speziellen Fall von etwas gemeinsam. Aus meiner Erfahrung kann ich sagen, dass bei der Verwendung von Vererbung etwas nicht wie erwartet funktioniert, etwas überhaupt nicht funktioniert und Sie nicht wissen, warum. Generell möchte ich Sie vor der Idee, CBPeripheral zu erben, warnen. Was zu tun

Ich empfehle Ihnen, CBPeripheral zum Konstruktor des Objekts hinzuzufügen, das es verwaltet. Es kapselt es in dieser Klasse. Verwenden Sie es, um mit dem Gerät zu interagieren, halten Sie einen starken Link, damit iOS die Verbindung nicht unterbricht. Das Wichtigste ist jedoch, dass diese Klasse als Delegierter verwendet wird. Andernfalls wird es schwierig sein, alle Geräte an einem Ort zu verwalten. Dies droht mit einer Reihe von Wenn-Andern.

Verbinden Sie sich und arbeiten Sie mit CBPeripheralDelegate


Und hier verbinden wir uns mit dem Gerät und möchten CBPeripheralDelegate sein. Es gibt noch eine weitere Nuance: Während Sie mit dem Gerät arbeiten, seine Dienste und Eigenschaften „abfragen“, lesen und darauf schreiben, findet fast die gesamte Kommunikation mit dem Peripheriegerät statt. Alles außer der Verbindung.

Natürlich möchten wir die gesamte Kommunikation an einem Ort konzentrieren, der Manager sollte jedoch wissen, was mit dem Gerät passiert. Die Schwierigkeit besteht darin, eine einzige Quelle der Wahrheit zu haben, um sicherzustellen, dass jeder rechtzeitig über das Geschehen mit dem Gerät informiert ist. Zu diesem Zweck überwachen wir den Status des Peripheriegeräts - es kann von getrennt (getrennt), verbunden (verbinden) und verbunden (verbunden) wechseln. Es wird Ihnen immer über die aktuelle Situation berichten. Die Statusänderung in unserer Management-Einrichtung, die ich bereits erwähnt habe, muss weiterhin abonniert werden. Dadurch haben Sie die Möglichkeit, von einem Ort aus mit dem Gerät zu kommunizieren.

Bestimmung der Nähe


Ein sehr wichtiger Punkt, da es schwierig ist, eine normale Dokumentation zu diesem Thema zu finden. Im Fall von Apple und ihren iBeacons ist alles einfach. Sie sagen uns, wie nahe wir dem Bluetooth-Gerät sind.
Leider haben wir keine so einfache Möglichkeit, mit Geräten von Drittanbietern zu arbeiten. Und mehr als einmal passierte es, dass das nächste Gerät ermittelt werden musste. Es ist auch schwer zu verstehen, ob sich das Gerät im verfügbaren Bereich befindet oder nicht. Bei der Suche nach Geräten kann es manchmal vorkommen, dass der Verbindungsversuch nicht erfolgreich ist.

Wir verwenden die folgende Methode: Speichern Sie den Stapel mit Datums- und Signalpegelbezeichnungen (RSSI) für jede in discoverPeripheral empfangene Nachricht. Wenn jemand auf CoreLocation gestoßen ist, ähnelt unsere Methode der Speicherung von Zeitstempeln und entsprechenden Koordinaten. Je höher das Signal (RSSI), desto näher das Gerät. Zu verstehen, ob sich das Gerät im verfügbaren Bereich befindet oder nicht, ist schwieriger, zum Teil weil dieses Konzept selbst recht flexibel ist. Dafür verwende ich den gewichteten Mittelwert des Signals. Beachten Sie, dass der Signalpegel eines angeschlossenen Geräts jedes Mal manuell angefordert werden muss, wenn Sie es wissen müssen.

Was weiter?


Leider wird dieser Artikel Sie nicht zum Experten machen, wenn Sie ihn lesen und
interessant für Sie werden - achten Sie auf das CoreBluetooth-Programmierhandbuch von Apple. Das Handbuch ist nicht sehr umfangreich, aber sehr nützlich. Es gibt immer noch ein paar Sendungen aus der WWDC 2012 ( Basic und Advanced ) und eine seit 2013 , aber keine Sorge, seitdem hat sich nicht viel geändert.

Es gibt auch ein Video von Altconf 2015 auf der Realm-Website, wo John Shier, ein hervorragender Typ und Spezialist, seine Erfahrungen teilt.

Jetzt auch beliebt: