Cadrul de bază Bluetooth (CB) furnizează resursele pe care aplicațiile dvs. iOS trebuie să le comunice cu dispozitive care sunt echipate cu tehnologia Bluetooth low energy (BTLE). Acest tutorial vă va îndruma prin evoluția CB de la iOS 5 la iOS 7. În plus, veți învăța cum să configurați o centrală centrală și periferică Bluetooth, cum să comunicați între ei și practicile de programare inerente cele mai bune atunci când lucrați cu CB.
Core tutorialele Bluetooth sunt împărțite în două părți. Primul acoperă aspectul teoretic al Core Bluetooth, în timp ce acest tutorial este o lecție practică completă. Veți găsi codul sursă complet atașat la acest post.
Obiectivul acestui tutorial este să vă învețe cum să utilizați cadrul de bază Bluetooth. Am pregătit un exemplu de cod sursă care vă va ușura viața și va ocoli configurația de creare și vizualizare a proiectului. Ar trebui să descărcați codul de probă la începutul acestei pagini.
Presupunem că știți elementele de bază ale Xcode și iOS, deoarece ne vom concentra doar pe datele de bază Bluetooth. Codul eșantionului conține următoarele:
ViewController
cu două butoaneCBCentralManagerViewController
care creează un iBeacon personalizatCBPeripheralViewController
care primește iBeacon și informații inerenteSERVICII
antet fișier cu unele variabile de utilizat în întreaga aplicație.Toate vizualizările sunt deja stabilite și definite. Trebuie doar să adăugați codul pentru procesul Core Bluetooth. Deschideți proiectul, rulați-l și jucați cu obiectele pentru a vă familiariza cu codul.
SERVICES.h
fișierul conține două UUID-uri unice. Acestea au fost generate folosind comanda terminal uuidgen
. Ar trebui să le generați aplicației sau să le puteți utiliza.
Rețineți că această lecție necesită funcționarea corectă a două dispozitive iOS. Alerga
proiectul și veți vedea o interfață similară cu aceasta:
În acest tutorial, veți centraliza CBCentralManagerViewController
clasă. Primul pas este să adăugați cele două protocoale care acceptă CBCentralManager
și CBPeripheral
. Declarația acestor protocoale definește metode (mai târziu, mai târziu). Ta interfață
ar trebui să fie așa:
@interface CBCentralManagerViewController: UIViewController < CBCentralManagerDelegate, CBPeripheralDelegate>
Acum, trebuie să definiți trei proprietăți: CBCentralManager
, CBPeripheral
, și NSMutableData
. Primele două sunt evidente, iar ultima este utilizată pentru a stoca informații care sunt distribuite între dispozitive.
@property (puternic, nonatomic) CBCentralManager * centralManager; @property (puternic, nonatomic) CBPerifer * descoperitPerifer; @property (date puternice, nonatomice) NSMutableData *;
În acest moment, puteți schimba fișierul de implementare. Veți vedea un avertisment, dar înainte de a rezolva acest lucru, ar trebui să inițiați centralManger
si date
obiecte. Ar trebui să porniți centralManager
cu un delegat de sine și fără nici o coadă. Ar trebui să utilizați viewDidLoad
metoda și rezultatul ar trebui să fie similar cu acesta:
_centralManager = [[CBCentralManager alloc] initWithDelegate: coada de sine: nil]; _data = [[NSMutableData alocare] init];
Pentru a rezolva problema de avertizare, trebuie să adăugați - (void) centralManagerDidUpdateState: (CBCentralManager *) central
metodă.
Este o metodă necesară protocolului. Verifică starea dispozitivului și acționează corespunzător. Există mai multe stări posibile și în aplicația dvs. trebuie să verificați întotdeauna pentru ele. Statele sunt:
De exemplu, dacă rulați această aplicație într-un dispozitiv non Bluetooth 4.0, veți obține CBCentralManagerStateUnsupported
cod. Aici veți merge pentru CBCentralManagerStatePoweredOn
iar când apare, veți începe scanarea pentru dispozitive. Pentru aceasta, utilizați scanForPeripheralsWithServices
metodă. Dacă treceți ca fiind un prim argument, atunci CBCentralManager
începe să caute orice serviciu. Aici veți folosi UUID stocat în SERVICES.h
.
Metoda completă este:
- (void) centralManagerDidUpdateState: (CBCentralManager *) central // ar trebui să testați toate scenariile dacă (central.state! = CBCentralManagerStatePoweredOn) return; if (central.state == CBCentralManagerStatePoweredOn) // Scanarea dispozitivelor [_centralManager scanForPeripheralsWithServices: @ [[CBUUID UUIDWithString: TRANSFER_SERVICE_UUID]] opțiuni: @ CBCentralManagerScanOptionAllowDuplicatesKey: @YES]; NSLog (@ "Scanarea a început");
În acest moment, aplicația dvs. va căuta alte dispozitive. Dar, în ciuda faptului că nu există sau nu sunt disponibile, nu veți primi nicio informație. Putem rezolva asta. Ar trebui să adăugați - (void) centralManager: (CBCentralManager *) central didDiscoverPeripheral: (CBPeripheral *) publicitate perifericăData: (NSDictionary *) publicitateData RSSI: (NSNumber *) RSSI
metodă. Se va numi ori de câte ori este descoperit un dispozitiv. Cu toate acestea, îl veți programa să reacționeze numai la perifericele care fac publicitate TRANSFER_SERVICE_UUID
.
În plus, vom folosi noul sistem de memorie cache și vom stoca dispozitivul pentru referințe viitoare și pentru o comunicare mai rapidă. Codul sursă complet este după cum urmează:
- (void) centralManager: (CBCentralManager *) central didDiscoverPeripheral: (CBPeripheral *) publicitate perifericăData: (NSDictionary *) publicitateData RSSI: (NSNumber *) RSSI NSLog (@ descoperit% @ la% @, periferic.name, RSSI) ; dacă (_discoveredPeripheral! = periferic) // Salvați o copie locală a perifericului, deci CoreBluetooth nu scapă de el _discoveredPeripheral = periferic; // Și conectați NSLog (@ "Conectarea la periferic% @", periferic); [_centralManager connectPeripheral: opțiuni periferice: zero];
Conexiunea cu dispozitivul respectiv poate eșua. Trebuie să abordăm acest scenariu folosind o metodă specifică numită: - (void) centralManager: (CBCentralManager *) central didFailToConnectPeripheral: (CBPeripheral *) eroare periferică: (NSError *) eroare
. Adăugați-o și informați utilizatorul despre această eroare.
- (void) centralManager: (CBCentralManager *) central didFailToConnectPeripheral: (CBPeripheral *) eroare periferică: (NSError *) eroare NSLog (@ "Failed to connect"); [auto-curățare];
Veți observa un avertisment, deoarece a curăța
metoda nu este încă declarată. Să spunem! În acest moment, este posibil ca codul sursă al metodei să fie complicat. Cu toate acestea, vom explica mai târziu. Ar trebui să vă întoarceți la sfârșitul tutorialului pentru o înțelegere completă.
Această metodă anulează orice abonament la un dispozitiv la distanță (dacă există) sau deconectează direct dacă nu. Aceasta buclele de-a lungul serviciilor, apoi caracteristicile, și elimină legăturile cu ele. Metoda completă este:
- (void) cleanup // A se vedea dacă suntem abonați la o caracteristică pe periferic dacă (_discoveredPeripheral.services! = nil) pentru (serviciul CBService * în _discoveredPeripheral.services) if (service.characteristics! = nil) pentru (Caracteristică CBC caracteristică caracteristică în caracteristică de serviciu) if ([caracteristicăUUID este egal: [CBUUID UUIDWithString: TRANSFER_CHARACTERISTIC_UUID]]) dacă (caracteristică.isNotificare) [descoperitPeripheral setNotifyValue: NU pentruCharacteristică: caracteristică]; întoarcere; [_centralManager cancelPeripheralConnection: _descoperitPeriferal];
Având în vedere faptul că am reușit să conectăm dispozitivul, trebuie să descoperim acum serviciile și caracteristicile acestuia. Trebuie să declarați - (void) centralManager: (CBCentralManager *) central didConnectPeripheral: (CBPeripheral *) periferic
. După stabilirea conexiunii, opriți procesul de scanare. Apoi, ștergeți datele pe care le-am primit. Apoi asigurați-vă că primiți apelurile de descoperire și, în cele din urmă, căutați servicii care se potrivesc cu UUID-ul dvs. (TRANSFER_SERVICE_UUID
). Iată codul:
- (void) centralManager: (CBCentralManager *) central didConnectPeripheral: (CBPeripheral *) periferic NSLog (@ "Connected"); [_centralManager stopScan]; NSLog (@ "Scanarea a fost oprită"); [setare datăLungime: 0]; peripheral.delegate = auto; [servicii periferice de descoperire: @ [[CBUUID UUIDWithString: TRANSFER_SERVICE_UUID]]];
În acest moment, perifericul începe să-și anunțe delegatul cu mai multe apeluri de apel. Unul din aceste apeluri este - (gol) periferic: (CBPeripheral *) periferic didDiscoverServices: (NSError *) eroare
. Este folosit pentru a descoperi caracteristicile unui serviciu dat. Nu că trebuie să verificați întotdeauna dacă această metodă returnează o eroare. Dacă nu se găsește nicio eroare, ar trebui să găsiți caracteristicile de care aveți nevoie, în acest caz TRANSFER_CHARACTERISTIC_UUID
. Iată metoda completă:
- (void) periferic: (CBPeripheral *) periferic didDiscoverServices: (NSError *) eroare if (eroare) [auto-curățare]; întoarcere; pentru (serviciul CBService * în servicii periferice) [descoperire perifericăCaracteristici: @ [[CBUUID UUIDWithString: TRANSFER_CHARACTERISTIC_UUID]] pentruService: serviciu]; // Descoperă alte caracteristici
În acest moment, dacă totul este corect, a fost descoperită caracteristica de transfer. Încă o dată, se numește o metodă delegată: - (gol) periferic: (CBPeripheral *) periferic didDiscoverCharacteristicsForService: (CBService *) eroare de serviciu: (NSError *) eroare
. Odată ce acest lucru a fost găsit, doriți să vă abonați la acesta, ceea ce vă permite CBCentralManager
să primească datele acelui periferic.
Încă o dată, ar trebui să rezolvați erorile (dacă există). Puteți să faceți un salt de credință și să vă abonați direct la caracteristică. Cu toate acestea, ar trebui să vă conectați prin matricea caracteristică și să verificați dacă caracteristica este cea potrivită. Dacă este, abonați-vă la ea. Odată ce acest lucru este complet, trebuie doar să așteptați ca datele să vină (o altă metodă). Metoda completă este de mai jos.
- (void) periferic: (CBPeripheral *) periferic didDiscoverCharacteristicsForService: (CBService *) eroare de serviciu: (NSError *) eroare if (error) [self cleanup]; întoarcere; pentru (caracteristică CBCharacteristică * caracteristică în serviciu.characteristici) dacă ([caracteristicăUUID este egal: [CBUUID UUIDWithString: TRANSFER_CHARACTERISTIC_UUID]]) [setNotifyValue periferic: DA pentruCaracteristică: caracteristică];
De fiecare dată când perifericul trimite date noi, delegatul periferic utilizează - (gol) periferic: (CBPeripheral *) periferic didUpdateValueForCharacteristic: (CBCharacteristic *) eroare caracteristică: (NSError *) eroare
metodă. Al doilea argument conține caracteristica pe care o puteți citi.
Inițial, veți crea un NSString
pentru a stoca valoarea caracteristică. Apoi, veți verifica dacă datele primite sunt complete sau dacă vor fi livrate mai multe. În același timp, vă veți actualiza TextView
de îndată ce se primesc date noi. După terminarea tuturor datelor, puteți să vă deconectați de la caracteristică și să vă deconectați de la dispozitiv (deși puteți rămâne conectat).
Rețineți că, după datele primite, puteți să deconectați sau să așteptați alte date. Acest apel telefonic ne permite să știm dacă s-au înregistrat mai multe date prin notificarea privind caracteristica. Sursa completă este mai jos:
- (void) periferic: (CBPeripheral *) periferic didUpdateValueForCharacteristic: (CBCharacteristic *) eroare caracteristică: (NSError *) eroare if (eroare) NSLog (@ "Error"); întoarcere; NSString * stringFromData = [[NSString alocare] initWithData: caracteristică.value codificare: NSUTF8StringEncoding]; // Avem tot ce ne trebuie? if ([stringFromData isEqualToString: @ "MOA"]) [_textview setText: [[NSString alloc] initWithData: Codificare self.data: NSUTF8StringEncoding]]; [setNotifyValue periferic: NU pentruCaracteristică: caracteristică]; [_centralManager cancelPeripheralConnection: periferic]; [data adăugatăData: caracteristică.value];
În plus, există o metodă care asigură faptul că CBCentral
știe când se modifică o stare de notificare pentru o anumită caracteristică dată. Este foarte important să o urmăriți pentru a înțelege momentul în care se schimbă o stare caracteristică (actualizați valorile aplicației). Metoda este: - (gol) periferic: (CBPeripheral *) periferic didUpdateNotificationStateForCharacteristic: (CBCharacteristic *) eroare caracteristică: (NSError *) eroare
. Ar trebui să verificați dacă notificarea caracteristică sa oprit. Dacă este cazul, trebuie să vă deconectați de la ea:
- (Void) periferice: (CBPeripheral *) didUpdateNotificationStateForCharacteristic periferice: (CBCharacteristic *) Eroare caracteristică: (NSError *) Eroare if ([characteristic.UUID isEqual: [CBUUID UUIDWithString: TRANSFER_CHARACTERISTIC_UUID]]!) Return; dacă (caracteristica este de notificare) NSLog (@ "Notificarea a început pe% @", caracteristică); altceva // Notificarea s-a oprit [_centralManager cancelPeripheralConnection: periferic];
Dacă se produce deconectarea între dispozitive, trebuie să vă curățați copia locală a dispozitivului periferic. Pentru acea utilizare - (void) centralManager: (CBCentralManager *) central didDisconnectPeripheral: (CBPeripheral *) eroare periferică: (NSError *) eroare
metodă. Această metodă este simplă și stabilește periferia la zero. În plus, puteți relua scanarea dispozitivului sau puteți părăsi aplicația (sau o altă aplicație). În acest exemplu, veți relua procesul de scanare.
- (void) centralManager: (CBCentralManager *) centralizatDisconnectPeripheral: (CBPeripheral *) eroare periferică: (NSError *) eroare _discoveredPeripheral = nil; [ScanForPeripheralsWithServices _centralManager: @ [[CBUUID UUIDWithString: TRANSFER_SERVICE_UUID]] opțiuni: @ CBCentralManagerScanOptionAllowDuplicatesKey: @YES];
În cele din urmă, este necesar un pas suplimentar. De fiecare dată când vizualizarea dispare, ar trebui să opriți procesul de scanare. În viewWillDisappear: (bool) animat
metoda pe care ar trebui să o adăugați:
[_centralManager stopScan];
Poti Alerga
app, cu toate acestea aveți nevoie de aplicația periferică pentru a primi anumite date. Următoarea imagine prezintă interfața finală a CBCentralManager
.
În acest tutorial, veți centraliza CBPeripheralViewController
clasă. Primul pas este să adăugați două protocoale: CBPeripheralManagerDelegate și UITextViewDelegate. Ta interfață
Ar trebui să arate acum:
@interface CBPeripheralViewController: UIViewController < CBPeripheralManagerDelegate, UITextViewDelegate>
Acum trebuie să definiți patru proprietăți: CBPeripheralManager
, CBMutableCharacteristic
, NSData
, și NSInterger
. Primele două reprezintă managerul periferic și caracteristicile sale, în timp ce al treilea reprezintă datele care vor fi trimise. Ultimul reprezintă indicele de date.
@property (puternic, nonatomic) CBPeripheralManager * peripheralManager; @property (puternic, nonatomic) CBMutableCharacteristic * transferCharacteristic; @property (puternic, nonatomic) NSData * dataToSend; @property (nonatomic, citiți) NSInteger sendDataIndex;
Acum treceți la fișierul de implementare. Primul nostru pas este să inițiem _peripheralManager
și configurați-o pentru a începe publicitatea. Anunțul de serviciu ar trebui să inițieze cu UUID-ul de serviciu menționat mai sus. Ta viewDidLoad
ar trebui să arate astfel:
- (vid) viewDidLoad [super viewDidLoad]; _peripheralManager = [[CBPeripheralManager alloc] initWithDelegate: coada de sine: nil]; [_peripheralManager startPublicitate: @ CBAdvertisementDataServiceUUIDsKey: @ [[CBUUID UUIDWithString: TRANSFER_SERVICE_UUID]]];
Ar trebui să vedeți un avertisment. Pentru ao rezolva, declarați - (void) peripheralManagerDidUpdateState: (CBPeripheralManager *) periferic
metoda protocolului. Similar cu CBCentralManager
trebuie să controlați și să testați toate stările aplicației. Dacă statul este CBPeripheralManagerStatePoweredOn
trebuie să construiți și să definiți serviciile și caracteristicile dvs. (una dintre caracteristicile reale ale iOS 7).
Fiecare serviciu și caracteristică trebuie identificate de un UUID unic. Rețineți că al treilea argument al metodei init nu este nimic. Acest lucru declară că datele care urmează a fi schimbate vor fi definite mai târziu. Acest lucru se face, de obicei, atunci când doriți să creați datele dinamic. Dacă doriți să aveți o valoare statică pentru transmitere, atunci o puteți declara aici.
Proprietățile determină modul în care se poate utiliza valoarea caracteristică și există câteva valori posibile:
Pentru o înțelegere completă a acestor proprietăți, ar trebui să verificați referința Clasa CBCharacteristică.
Ultimul argument al init este permisiunile de citire, scriere și criptare pentru un atribut. Din nou, există câteva valori posibile:
După definirea caracteristicii, este acum timpul să definim serviciul folosind CBMutableService
. Rețineți că serviciul trebuie definit cu TRANSFER_CHARACTERISTIC_UUID
. Adăugați caracteristica serviciului și apoi adăugați-o la managerul periferic. Metoda completă este de mai jos:
- (void) perifericManagerDidUpdateState: (CBPeripheralManager *) periferic if (peripheral.state! = CBPeripheralManagerStatePoweredOn) retur; if (peripheral.state == CBPeripheralManagerStatePoweredOn) self.transferCharacteristic = [[CBMutableCharacteristic alloc] initWithType: [CBUUID UUIDWithString: TRANSFER_CHARACTERISTIC_UUID] proprietăți: CBCharacteristicPropertyNotify valoare: permisiuni zero: CBAttributePermissionsReadable]; CBMutableService * transferService = [[CBMutableService alloc] initWithType: [CBUUID UUIDWithString: TRANSFER_SERVICE_UUID] primar: DA]; transferService.characteristici = @ [[transferCharacteristic]; [_peripheralManager addService: transferService];
Acum, că avem serviciul și caracteristicile acestuia (unul în acest caz), este momentul să detectăm când un dispozitiv se conectează la acesta și reacționează în consecință. - (void) peripheralManager: (CBPeripheralManager *) central periferic: (CBCentral *) central didSubscribeToCharacteristic: (CBCharacteristic *) caracteristic
metoda capturiază atunci când cineva subscrie la caracteristica noastră, apoi începe să le trimită datele.
Aplicația trimite datele disponibile la TextView
. Dacă utilizatorul îl modifică, aplicația îl trimite în timp real către abonatul central. Metoda numește o metodă personalizată sendData
.
- (Void) peripheralManager: (CBPeripheralManager *) periferice centrale: (CBCentral *) didSubscribeToCharacteristic centrale: (CBCharacteristic *) caracteristic _dataToSend = [_textView.text dataUsingEncoding: NSUTF8StringEncoding]; _sendDataIndex = 0; [self sendData];
sendData
este metoda care se ocupă de toate logica cu privire la transferul de date. Poate face mai multe acțiuni, cum ar fi:
Codul sursă complet este prezentat mai jos. Mai multe comentarii au fost lăsate în mod intenționat pentru a facilita înțelegerea acesteia.
- (void) sendData static BOOL sendingEOM = NO; // sfârșitul mesajului? if (sendingEOM) bool didSend = [self.peripheralManager updateValue: [@ "MOA" dataUsingEncoding: NSUTF8StringEncoding] forCharacteristic: self.transferCharacteristic onSubscribedCentrals: nil]; dacă (didSend) // A făcut-o, deci marcați-o ca trimis sendingEOM = NO; // nu a trimis, deci vom iesi si asteptam pentru perifericManagerIsReadyToUpdateSubscribers pentru a apela sendData reveni din nou; // Transmitem date // Există stânga de trimis? dacă (self.sendDataIndex> = self.dataToSend.length) // Nu mai există date. Nu face nimic returnat; // S-au lăsat date, trimiteți astfel până când apelul nu reușește, sau am terminat. BOOL a răspuns = DA; în timp ce (didSend) // Elaborați cât de mare ar trebui să fie NSInteger amountToSend = self.dataToSend.length - self.sendDataIndex; // nu poate fi mai lungă de 20 de octeți dacă (amountToSend> NOTIFY_MTU) amountToSend = NOTIFY_MTU; // Copiați datele dorite NSData * chunk = [NSData dataWithBytes: self.dataToSend.bytes + self.sendDataIndex length: quantityToSend]; didSend = [auto.peripheralManager updateValue: bucată pentruCharacteristică: self.transferCaracteristică peClanuri abonate: nil]; // Dacă nu a funcționat, renunțați și așteptați apelul invers dacă (! DidSend) return; NSString * stringFromData = [[NSString alocare] initWithData: codarea bucla: NSUTF8StringEncoding]; NSLog (@ "Trimise:% @", stringFromData); // A trimis-o, actualizați indexul auto.sendDataIndex + = amountToSend; // A fost ultima? dacă (self.sendDataIndex> = self.dataToSend.length) // Setați acest lucru dacă trimiterea eșuează, o vom trimite data viitoare sendingEOM = YES; BOOL eomSent = [self.peripheralManager updateValue: [@ "MOA" dataUsingEncoding: NSUTF8StringEncoding] forCharacteristic: self.transferCharacteristic onSubscribedCentrals: nil]; dacă (eomSent) // A trimis, suntem toți terminate sendEOM = NO; NSLog (@ "trimis: MOM"); întoarcere;
În cele din urmă, trebuie să definiți un apel invers care se numește atunci când PeripheralManager
este gata să trimită următoarea bucată de date. Acest lucru asigură că pachetele sosesc în ordinea în care sunt trimise. Metoda este - (void) peripheralManagerIsReadyToUpdateSubscribers: (CBPeripheralManager *) periferic
și doar sună sendData
metodă. Versiunea completă este de mai jos:
- (void) perifericManagerIsReadyToUpdateSubscribers: (CBPeripheralManager *) periferic [auto sendData];
Acum puteți în sfârșit Alerga
aplicația și testați comunicarea Bluetooth. Următoarea imagine prezintă interfața cu CBCentralManager
.
La sfarsitul acestui tutorial, trebuie sa intelegeti specificatiile cadru de baza Bluetooth. De asemenea, ar trebui să puteți defini și configura un CBCentralManager și un rol CBPeripheral și să înțelegeți și să aplicați câteva bune practici atunci când vă dezvoltați cu Core Bluetooth.
Dacă aveți întrebări sau comentarii, lăsați-le mai jos!
Dacă lucrați adesea cu SDK-ul iOS, aruncați o privire la Envato Market pentru a găsi sute de șabloane de aplicații iOS utile și care economisesc timp.