Als ich vor ein paar Jahren zum ersten Mal in einem Arbeitsprojekt auf Bluetooth stieß, fand ich diesen Artikel, der sehr hilfreich war, um zu verstehen, wie es funktioniert, um einen „Ausgangspunkt“ zu finden. Hoffe, das ist praktisch für Anfänger.
Über den Autor: Yoav Schwartz ist ein führender iOS-Entwickler bei Donkey Republic, einem Biker-Sharing-System in Kopenhagen, das sich bemüht, die Einstellung zum Radfahren zu ändern. Als nächstes werden wir im Namen des Autors sprechen.
In diesem Artikel werde ich über praktische Techniken für die Arbeit mit CoreBluetooth sprechen. Zuerst über Bluetooth Low Energy (BLE), weil nicht jeder mit dieser Technologie vertraut ist, dann über CoreBluetooth, Apples Framework, mit dem wir mit BLE-Geräten interagieren können. Ich werde Ihnen auch einige der Entwicklungstricks erzählen, die ich selbst beim Debuggen, Weinen und Abreißen von Haaren gelernt habe.
Bluetooth energiesparend
Was ist BLE für den Anfang? Es ist wie Bluetooth, das wir alle in Lautsprechern, Headsets usw. verwenden, aber es gibt einen Unterschied - dieses Protokoll verbraucht sehr wenig Strom. Normalerweise kann eine einzelne Batterieladung für ein BLE-fähiges Gerät Monate oder sogar Jahre dauern (abhängig natürlich davon, wie das Gerät verwendet wird). Dies ermöglicht es uns, Dinge zu tun, die für „normales“ Bluetooth bisher nicht verfügbar waren. Dieser Standard heißt Bluetooth 4.0. Alles begann mit einer Technologie namens Smart Bluetooth, die sich später zu BLE entwickelte. Es gibt ein 200-seitiges
Handbuch , Sie können vor dem Schlafengehen lesen, spannende Lektüre.
BLE ist sehr sparsam im Energieverbrauch und das Protokoll selbst ist nicht sehr komplex. Warum also ble? Wie können wir es benutzen? Das allererste und häufigste Beispiel ist ein Herzfrequenzsensor. In der Regel misst und überträgt dieses Gerät Ihre Herzfrequenz über das Protokoll. Es gibt auch alle Arten von Sensoren, mit denen Sie über BLE eine Verbindung herstellen und die von ihnen gesammelten Daten lesen können. Schließlich gibt es iBeacons, die Ihnen die „Nähe“ zu einem Ort anzeigen können. In Anführungszeichen, da Apple auf iPhones die Erkennung von iBeacons als normale Bluetooth-Geräte blockiert. Daher müssen wir mit CoreLocation arbeiten. Im Allgemeinen ist dies das Internet der Dinge: Sie können eine Verbindung zu einem Fernseher oder einer Klimaanlage herstellen und mit diesem Protokoll kommunizieren.
Wie funktioniert es
Wir haben einen Perifer - dies sind die Geräte, die das Bluetooth-Protokoll verwenden. Jedes Peripheriegerät verfügt über Dienste, es kann eine beliebige Anzahl von Diensten geben, und jedes von ihnen weist Merkmale auf. Sie können den Perifer als Server betrachten. Mit all den folgenden Konsequenzen: Manchmal schaltet es sich aus, manchmal braucht es Zeit, um Daten zu übertragen, und manchmal kommen diese Daten überhaupt nicht.
Im Allgemeinen haben wir einen Dienst mit vielen Merkmalen, von denen jedes einen Wert, einen Typ usw. enthält. Um mit CoreBluetooth arbeiten zu können, müssen Sie nicht alles wissen. Das Wichtigste ist, die Daten zu lesen. Dies versuchen wir zu erhalten, zu modifizieren oder für unsere eigenen Zwecke zu verwenden. 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 technische Funktionen besser erklären als ich.
Core Bluetooth
Core Bluetooth wurde von Apple vor langer Zeit in iOS 5 eingeführt. Apple begann viel früher als Android mit der Einführung von BLE in seine Geräte und der wachsenden Popularität der Technologie. Viele Entwickler verwenden dieses Framework in ihren Anwendungen, im Großen und Ganzen ist es nur ein Wrapper, da die BLE-Protokolle selbst recht komplex sind. Nicht wirklich, aber glauben Sie mir, damit möchte ich nicht jeden Tag arbeiten. Wie viele andere Dinge hat Apple es in ein schönes und praktisches Paket verpackt, mit dem Sie Begriffe verwenden können, die wir alle, dumme Entwickler, verstehen können.
Jetzt ist es an Ihnen, Ihnen zu sagen, was Sie wirklich über die Klassen wissen müssen, die an der Kommunikation mit dem Framework beteiligt sind. Unser Hauptdarsteller ist CBCentralManager. Erstellen Sie es:
manager = CBCentralManager(delegate:self, queue:nil, options: nil)
Oben haben wir einen neuen Manager erstellt, der seinen Delegierten 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 in der Hauptwarteschlange erfolgt.
Sie müssen verstehen, was genau Sie tun werden - die Verwendung einer separaten Warteschlange erschwert die Anwendung, aber die Benutzer werden Sie natürlich mehr lieben. Wenn Sie mit nur einem Gerät kommunizieren möchten, können Sie die Hauptwarteschlange nicht stören und verwenden. Wenn Sie dennoch experimentieren möchten, erstellen Sie eine Warteschlange, geben Sie sie im Konstruktor an und vergessen Sie nicht, zur Hauptwarteschlange zurückzukehren, bevor Sie die Ergebnisse an einer anderen Stelle verwenden.
Optionen Hier gibt es nichts besonders Interessantes, vielleicht ist die Hauptsache - wenn Sie einen Manager erstellen und Bluetooth für den Benutzer deaktiviert ist - die Anwendung informiert ihn darüber, aber fast jeder klickt auf "OK" (was Bluetooth nicht wirklich enthält), weshalb ich diese Option auch verwende Ich benutze nicht.
Nach dem Erstellen des Managers ruft der Delegat zunächst die Methode auf:
func centralManagerDidUpdateState(_ central: CBCentralManager)
Wir erhalten also eine Antwort von der Hardware - unabhängig davon, ob der Benutzer Bluetooth aktiviert hat oder nicht.
Erster Tipp: Der Manager ist nutzlos, bis wir die Antwort erhalten, dass Bluetooth aktiviert ist. Sein Status ist. PoweredOn. Andere Zustände können nur verwendet werden, um den Benutzer aufzufordern, Bluetooth einzuschalten.
Gerätesuche
Jetzt, wo unser Manager richtig arbeitet, können wir beobachten,
Was ist um uns herum (nach Erhalt des .PoweredOn-Status rufen wir die Funktion scanForPeripheralsWithServices auf :)
manager.scanForPeripheralsWithServices([CBUUID], options: nil)
Bei Diensten handelt es sich um ein CBUUID-Array (eine Klasse, bei der es sich um eine universelle 128-Bit-eindeutige Kennung für die von Bluetooth Low Energy verwendeten Attribute handelt.), Die wir als Filter verwenden, um Geräte mit nur diesen UID-Sätzen zu finden. 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, ein Array der von uns benötigten Parameter anzugeben, aber wenn Sie sie nicht kennen, passiert nichts Schreckliches, wenn Sie Null übergeben, niemand stirbt.
Da wir die Suche nach Geräten gestartet haben, müssen wir sie stoppen. Andernfalls wird die Suche fortgesetzt und die Batterie des Benutzers gelandet, bis wir sie stoppen. Sobald wir das richtige Gerät gefunden haben oder die Notwendigkeit für die Suche verschwindet, hören wir auf:
manager.stopScan()
Jedes Mal, wenn ein neues Gerät entdeckt wird, ruft der Manager-Delegat die Funktion didDiscoverPeripheral in der Warteschlange auf, die wir bei der Initialisierung angegeben haben. Die Funktion überträgt uns das gefundene Gerät (Peripheriegerät), Informationen darüber (AdvertisingData - etwas, das die Chiphersteller jedes Mal anzeigen wollten) und den relativen RSSI-Signalpegel in Dezibel.
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber)
Zweiter Tipp: Halten Sie immer eine starke Verbindung zum erkannten Perifer. Wenn dies nicht erfolgt, entscheidet das System, dass wir das gefundene Gerät nicht benötigen, und verwirft es. Sie wird sich an ihn erinnern, aber wir werden keinen Zugang mehr zu ihm haben. Andernfalls können wir nicht mit dem Gerät arbeiten.
Geräteverbindung
Wir haben das Gerät gefunden, an dem wir interessiert sind - so kommt man zu einer Party und sieht ein hübsches Mädchen. Wir wollen uns verbinden, wir nennen die Funktion connectPeripheral - wir bieten "buy a drink" an. Daher versuchen wir, eine Verbindung zum gewünschten Gerät (Peripheriegerät) herzustellen, und es kann uns "Ja" oder "Nein" sagen, aber unser iPhone ist wirklich gut, sodass 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. Wir teilen ihm mit, mit welchem Gerät wir eine Verbindung herstellen, und geben den Optionen erneut nichts an (wenn Sie wirklich sehr daran interessiert sind, die Optionen kennenzulernen, lesen Sie die Dokumentation, aber Sie können normalerweise darauf verzichten). Wenn Sie mit dem Gerät fertig sind, können Sie am Morgen die Verbindung zum Gerät trennen - cancelPeripheralConnection:
//called to cancel and/or disconnect manager.cancelPeripheralConnection(peripheral)
Nachdem wir eine Verbindung hergestellt oder getrennt haben, teilt uns der Delegierte Folgendes mit:
//didConnect func centralManager(central: CBCentralManager!, didConnectPeripheral peripheral: CBPeripheral!) //didDisconnect func centralManager(central: CBCentralManager!, didDisconnectPeripheral peripheral: CBPeripheral!, error: NSError!)
Nun noch zwei wichtige Tipps. Das Bluetooth-Protokoll geht von einem Verbindungszeitlimit aus, Apple ist dies jedoch egal. iOS versucht immer wieder, eine Verbindung herzustellen, und stoppt erst, wenn Sie cancelPeripheralConnection aufrufen. Dieser Vorgang kann zu lange dauern, daher muss er zeitlich begrenzt werden. Wenn wir am Ende keine erfolgreiche Verbindungsnachricht (didConnectPeripheral) erhalten, müssen wir den Benutzer darüber informieren, dass ein Fehler aufgetreten ist.
Wenn Sie keine starke Verbindung zum Peripheriegerät haben, setzt iOS die Verbindung einfach zurück. Aus ihrer Sicht bedeutet dies, dass Sie es nicht benötigen, und die Wartung ist eine ziemlich energieintensive Aufgabe für den Akku, und wir wissen, wie Apple mit dem Energieverbrauch zusammenhängt.
Lassen Sie uns das Gerät nützlich machen
Und so haben wir uns mit dem Gerät verbunden, lassen Sie uns etwas damit machen. Ich habe bereits die Dienste und Funktionen 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 durch Aufrufen von peripher.discoverServices erhalten.
peripheral.discoverServices(nil) func peripheral(peripheral: CBPeripheral!, didDiscoverServices error: NSError!) peripheral.services
Jetzt klingt es etwas verwirrend, aber der Delegat wird in dem Thread aufgerufen, den wir beim Erstellen des Managers definiert haben, obwohl dies ein Delegat an der Peripherie ist. Das heißt, das System merkt sich, mit welchem Stream es arbeitet, und die gesamte Bluetooth-Kommunikation findet in diesem Stream statt. Es ist wichtig, nicht zu vergessen, zum Hauptbild zurückzukehren, wenn Sie es nicht verwendet haben.
Wir haben die Dienste bekommen, aber wir haben immer noch nichts zu arbeiten. Als Nächstes müssen Sie peripher.discoverCharacteristics aufrufen. Der Delegat gibt uns alle verfügbaren Merkmale für die empfangenen Services in didDiscoverCharacteristicsForService. Jetzt können wir die Werte lesen,
die dort enthalten sind (readValueForCharacteristic) oder um uns zu informieren, 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 Benachrichtigen. Das heißt, wir wissen nicht, was passiert - wir lesen etwas vom Gerät oder dieses Gerät sagt uns etwas.
Aufnahme auf ein Gerät
Wir haben ein Gerät, wir lesen Informationen daraus, verwalten es. So können wir in der Regel Informationen darüber aufzeichnen - gewöhnliche NSData. Sie müssen nur herausfinden, 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, über die klar ist, wie mit ihnen „kommuniziert“ werden soll. Sie können Daten aus den Merkmalen abrufen, um zumindest eine ungefähre Vorstellung davon zu erhalten, was das Gerät von uns erwartet.
Aus den Spezifikationen erfahren wir, in welchen Merkmalen welche Eigenschaften wir lesen und in welchen wir schreiben, ob wir über Änderungen informiert werden (isNotifying). Meistens finden wir hier alles, was zum Arbeiten 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 Aufzeichnungsprozesses teilt uns der Delegierte mit, dass alles gut gelaufen ist (didWriteValueForCharacteristics), dass der gewünschte 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 Abschnitt, der sich auf die Implementierung von Apple stützt. Daher gibt es eine Reihe von Problemen, mit denen wir uns befassen müssen. Zum Beispiel eine sehr starke Abhängigkeit von der Delegation, so geliebter Apple.
Vererbung von CBPeripheral? Wenn alles so einfach wäre
Es scheint, dass wir, da wir ein Gerät haben, damit beginnen können, es zu verwenden, aber tatsächlich sagt es uns nichts über sich selbst. Vielleicht möchten wir das Schloss, die Klimaanlage oder den Herzfrequenzsensor steuern. Sie müssen wissen, mit welchem Gerät wir kommunizieren.
Es sieht aus wie Vererbung: Wir haben einen Sonderfall von etwas gemeinsam. Aus meiner Erfahrung kann ich sagen, dass bei Verwendung der Vererbung etwas überhaupt nicht wie erwartet funktioniert, etwas überhaupt nicht funktioniert und Sie nicht wissen, warum. Im Allgemeinen möchte ich Sie vor der Idee warnen, CBPeripheral zu erben. Was tun?
Ich empfehle Ihnen, CBPeripheral zum Konstruktor des Objekts hinzuzufügen, das es verwalten wird. Dies kapselt es in dieser Klasse. Verwenden Sie es, um mit dem Gerät zu interagieren, und halten Sie eine starke Verbindung zu ihm, damit iOS die Verbindung nicht unterbricht. Das Wichtigste ist jedoch, dass diese Klasse als Delegat verwendet wird. Andernfalls ist es schwierig, alle Geräte an einem Ort zu verwalten. Dies bedroht eine Reihe von anderen.
Verbinden und Arbeiten mit CBPeripheralDelegate
Also verbinden wir uns mit dem Gerät und wollen CBPeripheralDelegate sein. Es gibt noch eine Nuance: Während Sie mit dem Gerät arbeiten, seine Dienste und Eigenschaften abfragen, lesen und 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, aber der Manager sollte wissen, was mit dem Gerät passiert. Und die Schwierigkeit besteht darin, eine Quelle der Wahrheit zu haben, um sicherzustellen, dass jeder rechtzeitig darüber informiert wird, was mit dem Gerät passiert. Zu diesem Zweck überwachen wir den Status des Peripheriegeräts - es kann sich von getrennt, verbunden und verbunden ändern. Es informiert Sie immer über die aktuelle Situation. Es bleibt eine Statusänderung in unserer Steuereinrichtung zu abonnieren, über die ich bereits gesprochen habe. Dadurch wird es möglich, von einem Ort aus mit dem Gerät zu kommunizieren.
Nähe
Ein sehr wichtiger Punkt, da es schwierig ist, eine normale Dokumentation zu diesem Thema zu finden. Bei Apple und ihren iBeacons ist alles einfach. Sie sagen uns, wie nah wir am Bluetooth-Gerät sind.
Leider ist es uns nicht so einfach, mit Geräten von Drittanbietern zu arbeiten. Und mehr als einmal musste das nächstgelegene Gerät ermittelt werden. Es ist auch schwer zu verstehen, ob sich das Gerät im verfügbaren Bereich befindet oder nicht. Wenn Sie nach Geräten suchen, werden Sie manchmal nur einmal über sich selbst informiert und verschwinden. Der Verbindungsversuch ist dann erfolglos.
Wir verwenden die folgende Methode: Speichern Sie einen Stapel mit Datums- und Signalstärkebezeichnungen (RSSI) für jede in DiscoveryPeripheral 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) ist, desto näher ist das Gerät normalerweise. Es ist schwieriger zu verstehen, ob sich ein Gerät in einem zugänglichen Bereich befindet oder nicht, auch weil dieses Konzept selbst recht flexibel ist. Dafür verwende ich den gewichteten Durchschnitt des Signals. Beachten Sie, dass die Signalstärke des angeschlossenen Geräts jedes Mal manuell angefordert werden muss, wenn Sie es wissen müssen.
Was weiter?
Leider macht Sie dieser Artikel nicht zum Experten, wenn Sie ihn Ihnen auch vorlesen.
es wurde interessant - beachten Sie
Apples CoreBluetooth-Programmierhandbuch , das nicht sehr umfangreich, aber sehr nützlich ist. Es gibt noch einige Sendungen von WWDC 2012 (
Basic und
Advanced ) und eine von
2013 , aber keine Sorge, seitdem hat sich wenig geändert.
Es gibt auch ein Video von
Altconf 2015 auf der Realm-Website, in dem die Erfahrungen von John Sher, einem großartigen Mann und Spezialisten, geteilt werden.