Il framework Core Bluetooth (CB) fornisce le risorse necessarie alle tue app iOS per comunicare con dispositivi dotati della tecnologia Bluetooth a bassa energia (BTLE). Questo tutorial ti guiderà attraverso l'evoluzione di CB da iOS 5 a iOS 7. Inoltre, imparerai come configurare un core Bluetooth centrale e periferico, come comunicare tra di loro e le migliori pratiche di programmazione inerenti quando lavori con CB.
I tutorial Core Bluetooth sono divisi in due parti. Il primo copre l'aspetto teorico di Core Bluetooth, mentre questo tutorial è una lezione pratica completa. Troverete il codice sorgente completo allegato a questo post.
L'obiettivo di questo tutorial è quello di insegnarti come usare il framework Core Bluetooth. Abbiamo preparato un codice sorgente di esempio che renderà la tua vita più semplice e aggirerà la configurazione del progetto e la configurazione delle visualizzazioni. Dovresti scaricare il codice di esempio all'inizio di questa pagina.
Presumiamo che tu conosca le basi di Xcode e iOS poiché ci concentreremo solo sui dati del Core Bluetooth. Il codice di esempio contiene quanto segue:
ViewController
con due pulsantiCBCentralManagerViewController
che crea un iBeacon personalizzatoCBPeripheralViewController
che riceve iBeacon e informazioni inerentiSERVIZI
file di intestazione con alcune variabili da utilizzare attraverso l'app.Tutte le viste sono già posizionate e correttamente definite. Hai solo bisogno di aggiungere il codice per il processo Core Bluetooth. Apri il progetto, eseguilo e gioca con gli oggetti per familiarizzare con il codice.
Il SERVICES.h
il file contiene due UUID univoci. Quelli sono stati generati usando il comando del terminale uuidgen
. Dovresti generarli nella tua app o puoi usarli.
Nota che questa lezione richiede due dispositivi iOS per funzionare correttamente. Correre
il progetto e vedrai un'interfaccia simile a questa:
In questo tutorial, centrerai il CBCentralManagerViewController
classe. Il primo passo è aggiungere i due protocolli che supportano il CBCentralManager
e CBPeripheral
. La dichiarazione di questi protocolli definisce i metodi (ne parleremo più avanti). Il tuo interfaccia
dovrebbe essere così:
@interface CBCentralManagerViewController: UIViewController < CBCentralManagerDelegate, CBPeripheralDelegate>
Ora, è necessario definire tre proprietà: CBCentralManager
, CBPeripheral
, e NSMutableData
. I primi due sono ovvi, e l'ultimo è usato per memorizzare le informazioni che sono condivise tra i dispositivi.
@property (strong, nonatomic) CBCentralManager * centralManager; @property (strong, nonatomic) CBPeripheral * discoverPeripheral; @property (strong, nonatomic) NSMutableData * data;
A questo punto, puoi passare al file di implementazione. Vedrai un avvertimento, ma prima di risolverlo, dovresti iniziare il centralManger
e il dati
oggetti. Dovresti iniziare il centralManager
con un auto delegato e senza alcuna coda. Dovresti usare il viewDidLoad
metodo e il risultato dovrebbe essere simile a questo:
_centralManager = [[CBCentralManager alloc] initWithDelegate: self queue: nil]; _data = [[NSMutableData alloc] init];
Per risolvere il problema di avviso è necessario aggiungere il - (void) centralManagerDidUpdateState: (CBCentralManager *) centrale
metodo.
È un metodo di protocollo richiesto. Controlla lo stato del dispositivo e agisce di conseguenza. Ci sono diversi stati possibili e nella tua applicazione dovresti sempre controllarli. Gli stati sono:
Ad esempio, se si esegue questa applicazione in un dispositivo non Bluetooth 4.0 si otterrà il CBCentralManagerStateUnsupported
codice. Qui andrai per il CBCentralManagerStatePoweredOn
e quando si verifica inizierai la scansione dei dispositivi. Per quello, usa il scanForPeripheralsWithServices
metodo. Se si passa nil come primo argomento, il CBCentralManager
inizia a cercare qualsiasi servizio. Qui userai l'UUID memorizzato nel SERVICES.h
.
Il metodo completo è:
- (void) centralManagerDidUpdateState: (CBCentralManager *) central // Dovresti testare tutti gli scenari if (central.state! = CBCentralManagerStatePoweredOn) return; if (central.state == CBCentralManagerStatePoweredOn) // Ricerca dispositivi [_centralManager scanForPeripheralsWithServices: @ [[CBUUID UUIDWithString: TRANSFER_SERVICE_UUID]] opzioni: @ CBCentralManagerScanOptionAllowDuplicatesKey: @YES]; NSLog (@ "Scansione avviata");
In questo momento, la tua app cercherà altri dispositivi. Ma nonostante il fatto che nessuno o nessuno sia disponibile, non otterrai alcuna informazione. Noi possiamo aggiustarlo. Dovresti aggiungere il - (void) centralManager: (CBCentralManager *) centrale didDiscoverPeripheral: (CBPeripheral *) perifericaData: (NSDictionary *) advertisementData RSSI: (NSNumber *) RSSI
metodo. Sarà chiamato ogni volta che viene scoperto un dispositivo. Tuttavia, lo programmerai per reagire solo alle periferiche che pubblicizzano il TRANSFER_SERVICE_UUID
.
Inoltre, utilizzeremo il nuovo sistema di cache e memorizzeremo il dispositivo per futuri riferimenti e comunicazioni più rapide. Il codice sorgente completo è il seguente:
- (void) centralManager: (CBCentralManager *) centrale didDiscoverPeripheral: (CBPeripheral *) perifericaData: (NSDictionary *) advertisementData RSSI: (NSNumber *) RSSI NSLog (@ "Rilevato% @ at% @", peripheral.name, RSSI) ; if (_discoveredPeripheral! = peripheral) // Salva una copia locale della periferica, quindi CoreBluetooth non si sbarazza di esso _discoveredPeripheral = peripheral; // Collega NSLog (@ "Connessione alla periferica% @", periferica); [_centralManager connectPeripheral: opzioni periferiche: nil];
La connessione a quel dispositivo può fallire. Abbiamo bisogno di affrontare lo scenario usando un metodo specifico chiamato: - (void) centralManager: (CBCentralManager *) centrale didFailToConnectPeripheral: (CBPeripheral *) errore di periferica: errore (NSError *)
. Aggiungilo e informa l'utente su quell'errore.
- (void) centralManager: (CBCentralManager *) centrale didFailToConnectPeripheral: errore periferico (CBPeripheral *): errore (NSError *) NSLog (@ "Impossibile connettersi"); [auto pulizia];
Noterai un avvertimento, dal momento che pulire
il metodo non è ancora stato dichiarato. Diciamolo! A questo punto, potresti trovare complicato il codice sorgente del metodo. Tuttavia, lo spiegheremo in seguito. Dovresti tornare ad esso alla fine del tutorial per una comprensione completa.
Questo metodo annulla tutti gli abbonamenti a un dispositivo remoto (se ce ne sono) o disconnessi diretti se non lo sono. Passa lungo i servizi, quindi le caratteristiche e rimuove i vincoli. Il metodo completo è:
- (void) cleanup // Guarda se siamo abbonati a una caratteristica sulla periferica if (_discoveredPeripheral.services! = nil) for (servizio CBService * in _discoveredPeripheral.services) if (service.characteristics! = nil) for (Caratteristica caratteristica CBC in service.characteristics) if ([character.UUID isEqual: [CBUUID UUIDWithString: TRANSFER_CHARACTERISTIC_UUID]]) if (characteristic.isNotifying) [_scopertoPeripheral setNotifyValue: NO forCharacteristic: characteristic]; ritorno; [_centralManager cancelPeripheralConnection: _discoveredPeripheral];
Considerando che siamo riusciti a connetterci al dispositivo, ora dobbiamo scoprire i servizi e le caratteristiche di esso. Devi dichiarare il - (void) centralManager: (CBCentralManager *) centrale didConnectPeripheral: (CBPeripheral *) periferica
. Dopo aver stabilito la connessione, interrompere il processo di scansione. Quindi cancellare i dati che potremmo aver ricevuto. Quindi assicurati di ottenere la funzione di richiamata alla scoperta e infine cerca i servizi che corrispondono al tuo UUID (TRANSFER_SERVICE_UUID
). Ecco il codice:
- (void) centralManager: (CBCentralManager *) centrale didConnectPeripheral: (CBPeripheral *) periferica NSLog (@ "Connesso"); [_centralManager stopScan]; NSLog (@ "Scansione terminata"); [_data setLength: 0]; peripheral.delegate = self; [peripheral discoverServices: @ [[CBUUID UUIDWithString: TRANSFER_SERVICE_UUID]]];
A questo punto, la periferica inizia a notificare il proprio delegato con diverse richiamate. Uno di questi callback è il - (vuoto) periferica: (CBPeripheral *) periferica didDiscoverServices: errore (NSError *)
. È usato per scoprire le caratteristiche di un determinato servizio. Non è necessario controllare sempre se quel metodo restituisce un errore. Se non viene trovato alcun errore, dovresti trovare le caratteristiche che ti servono, in questo caso il TRANSFER_CHARACTERISTIC_UUID
. Ecco il metodo completo:
- (void) peripheral: (CBPeripheral *) periferica didDiscoverServices: (NSError *) error if (error) [self cleanup]; ritorno; for (servizio CBService * in peripheral.services) [peripheral discoverCharacteristics: @ [[CBUUID UUIDWithString: TRANSFER_CHARACTERISTIC_UUID]] forService: service]; // Scopri altre caratteristiche
A questo punto se tutto è corretto, la caratteristica di trasferimento è stata scoperta. Ancora una volta, viene chiamato un metodo delegato: - (vuoto) periferica: (CBPeripheral *) periferica didDiscoverCharacteristicsForService: (CBService *) errore di servizio: errore (NSError *)
. Una volta che questo è stato trovato, vuoi iscriverti ad esso, che consente al tuo CBCentralManager
ricevere i dati di quella periferica.
Ancora una volta, dovresti occuparti degli errori (se ce ne sono). Puoi fare un atto di fede e iscriverti direttamente alla caratteristica. Tuttavia, è necessario scorrere l'array delle caratteristiche e verificare se la caratteristica è quella corretta. Se lo è, quindi iscriversi ad esso. Una volta che è completo, devi solo aspettare che i dati entrino (un altro metodo). Il metodo completo è di seguito.
- (void) periferica: (CBPeripheral *) periferica didDiscoverCharacteristicsForService: (CBService *) errore di servizio: (NSError *) error if (error) [self cleanup]; ritorno; for (Caratteristica caratteristica CBC in service.characteristics) if ([characteristic.UUID isEqual: [CBUUID UUIDWithString: TRANSFER_CHARACTERISTIC_UUID]]) [periferal setNotifyValue: YES forCharacteristic: characteristic];
Ogni volta che la periferica invia nuovi dati, il delegato periferico utilizza il - (vuoto) periferica: (CBPeripheral *) periferica didUpdateValueForCharacteristic: errore caratteristico (CBCharacteristic *): errore (NSError *)
metodo. Il secondo argomento contiene la caratteristica che puoi leggere.
Inizialmente, creerai un NSString
per memorizzare il valore caratteristico. Quindi, verificherà se i dati ricevuti sono completi o se ne verranno consegnati altri. Contemporaneamente, aggiornerai il tuo TextView
non appena vengono ricevuti nuovi dati. Una volta completati tutti i dati, è possibile disconnettersi dalla caratteristica e disconnettersi dal dispositivo (sebbene sia possibile rimanere connessi).
Si noti che, dopo i dati in entrata, è possibile disconnettere o attendere altri dati. Questa richiamata ci consente di sapere se sono arrivati altri dati tramite la notifica sulla caratteristica. La fonte completa è qui sotto:
- (vuoto) periferica: (CBPeripheral *) periferica didUpdateValueForCharacteristic: errore caratteristico (CBCharacteristic *): errore (NSError *) if (errore) NSLog (@ "Errore"); ritorno; NSString * stringFromData = [[NSString alloc] initWithData: codifica character.value: NSUTF8StringEncoding]; // Abbiamo tutto ciò che ci serve? if ([stringFromData isEqualToString: @ "EOM"]) [_viewview setText: [[NSString alloc] initWithData: codifica self.data: NSUTF8StringEncoding]]; [set di perifericheNotifyValue: NO forCharacteristic: characteristic]; [_centralManager cancelPeripheralConnection: peripheral]; [_data appendData: characteristic.value];
Inoltre, esiste un metodo che garantisce che il CBCentral
sa quando cambia uno stato di notifica per una determinata caratteristica. È molto importante rintracciarlo per capire quando uno stato caratteristico cambia (aggiorna i valori dell'app). Il metodo è: - (vuoto) periferica: (CBPeripheral *) periferica didUpdateNotificationStateForCharacteristic: errore caratteristico (CBCharacteristic *): errore (NSError *)
. Dovresti controllare se la notifica caratteristica è stata interrotta. Se lo è, dovresti disconnetterti da esso:
- (vuoto) periferica: (CBPeripheral *) periferica didUpdateNotificationStateForCharacteristic: (CBCharacteristic *) errore caratteristico: (NSError *) error if (! [caratteristica.UUID isEqual: [CBUUID UUIDWithString: TRANSFER_CHARACTERISTIC_UUID]]) return; if (characteristic.isNotifying) NSLog (@ "Notifica iniziata su% @", caratteristica); else // La notifica è stata arrestata [_centralManager cancelPeripheralConnection: peripheral];
Se si verifica la disconnessione tra i dispositivi, è necessario pulire la copia locale della periferica. Per quello usi il - (void) centralManager: (CBCentralManager *) centrale didDisconnectPeripheral: errore periferico (CBPeripheral *): errore (NSError *)
metodo. Questo metodo è semplice e imposta la periferica a zero. Inoltre, puoi riavviare la scansione del dispositivo o lasciare l'app (o un'altra). In questo esempio, si riavvierà il processo di scansione.
- (void) centralManager: (CBCentralManager *) centrale didDisconnectPeripheral: errore periferico (CBPeripheral *): errore (NSError *) _iscopededPeripheral = nil; [_centralManager scanForPeripheralsWithServices: @ [[CBUUID UUIDWithString: TRANSFER_SERVICE_UUID]] opzioni: @ CBCentralManagerScanOptionAllowDuplicatesKey: @YES];
Infine, è richiesto un ulteriore passaggio. Ogni volta che la vista scompare, è necessario interrompere il processo di scansione. Nel viewWillDisappear: (BOOL) animate
metodo che dovresti aggiungere:
[_centralManager stopScan];
Puoi Correre
l'app, tuttavia è necessaria l'app periferica per ricevere alcuni dati. La prossima immagine presenta l'interfaccia finale del CBCentralManager
.
In questo tutorial, centrerai il CBPeripheralViewController
classe. Il primo passaggio consiste nell'aggiungere due protocolli: CBPeripheralManagerDelegate e UITextViewDelegate. Il tuo interfaccia
dovrebbe ora apparire come:
@interface CBPeripheralViewController: UIViewController < CBPeripheralManagerDelegate, UITextViewDelegate>
Ora devi definire quattro proprietà: CBPeripheralManager
, CBMutableCharacteristic
, NSData
, e NSInterger
. I primi due rappresentano il gestore periferico e le sue caratteristiche, mentre il terzo è il dato che verrà inviato. L'ultimo rappresenta l'indice dei dati.
@property (strong, nonatomic) CBPeripheralManager * peripheralManager; @property (strong, nonatomic) CBMutableCharacteristic * transferCharacteristic; @property (strong, nonatomic) NSData * dataToSend; @property (nonatomic, readwrite) NSInteger sendDataIndex;
Passare ora al file di implementazione. Il nostro primo passo è iniziare il _peripheralManager
e configurarlo per iniziare la pubblicità. L'annuncio di servizio dovrebbe iniziare con il suddetto UUID di servizio. Il tuo viewDidLoad
dovrebbe assomigliare a questo:
- (void) viewDidLoad [super viewDidLoad]; _peripheralManager = [[Assegnazione CBPeripheralManager] initWithDelegate: self queue: nil]; [_peripheralManager startAdvertising: @ CBAdvertisementDataServiceUUIDsKey: @ [[CBUUID UUIDWithString: TRANSFER_SERVICE_UUID]]];
Dovresti vedere un avvertimento. Per risolvere il problema, dichiarare il - (void) peripheralManagerDidUpdateState: (CBPeripheralManager *) periferica
metodo di protocollo. Simile a CBCentralManager
dovresti controllare e testare tutti gli stati delle app. Se lo stato è CBPeripheralManagerStatePoweredOn
dovresti costruire e definire il tuo servizio e le caratteristiche (una delle vere caratteristiche di iOS 7).
Ogni servizio e caratteristica devono essere identificati da un UUID univoco. Nota che il terzo argomento del metodo init è a zero. Così facendo dichiara che i dati da scambiare verranno definiti in seguito. Questo di solito viene fatto quando si desidera creare i dati in modo dinamico. Se vuoi avere un valore statico da trasmettere, puoi dichiararlo qui.
Le proprietà determinano come è possibile utilizzare il valore caratteristico e esistono diversi valori possibili:
Per una comprensione completa di tali proprietà, è necessario controllare il Riferimento di classe a caratteri CBC.
L'ultimo argomento di init è le autorizzazioni di lettura, scrittura e crittografia per un attributo. Ancora una volta, ci sono diversi valori possibili:
Dopo che la caratteristica lo ha definito, è giunto il momento di definire il servizio utilizzando il CBMutableService
. Si noti che il servizio deve essere definito con TRANSFER_CHARACTERISTIC_UUID
. Aggiungere la caratteristica al servizio e quindi aggiungerla al gestore periferiche. Il metodo completo è di seguito:
- (void) peripheralManagerDidUpdateState: (CBPeripheralManager *) periferica if (peripheral.state! = CBPeripheralManagerStatePoweredOn) return; if (peripheral.state == CBPeripheralManagerStatePoweredOn) self.transferCharacteristic = [[CBMutableCharacteristic alloc] initWithType: [CBUUID UUIDWithString: TRANSFER_CHARACTERISTIC_UUID] properties: CBCharacteristicPropertyNotify valore: nil permessi: CBAttributePermissionsReadable]; CBMutableService * transferService = [[CBMutableService alloc] initWithType: [CBUUID UUIDWithString: TRANSFER_SERVICE_UUID] primary: YES]; transferService.characteristics = @ [_ transferCharacteristic]; [_peripheralManager addService: transferService];
Ora che abbiamo il servizio e le sue caratteristiche (uno in questo caso), è giunto il momento di rilevare quando un dispositivo si connette a questo e reagisce di conseguenza. Il - (void) peripheralManager: (CBPeripheralManager *) centrale periferica: (CBCentral *) centrale didSubscribeToCharacteristic: caratteristica (CBCharacteristic *)
il metodo si cattura quando qualcuno si iscrive alla nostra caratteristica, quindi inizia a inviare loro i dati.
L'app invia i dati disponibili al TextView
. Se l'utente lo modifica, l'app lo invia in tempo reale al centro sottoscritto. Il metodo chiama un metodo personalizzato chiamato invia i dati
.
- (void) peripheralManager: (CBPeripheralManager *) centrale periferica: (CBCentral *) centrale didSubscribeToCharacteristic: (caratteristica CBCharacteristic *) _dataToSend = [_textView.text dataUsingEncoding: NSUTF8StringEncoding]; _sendDataIndex = 0; [self sendData];
Il invia i dati
è il metodo che si occupa di tutte le logiche riguardanti il trasferimento dei dati. Può fare diverse azioni come:
Il codice sorgente completo è presentato di seguito. Diversi commenti sono stati lasciati espressamente per facilitarne la comprensione.
- (void) sendData static BOOL sendingEOM = NO; // fine del messaggio? if (sendingEOM) BOOL didSend = [self.peripheralManager updateValue: [@ "EOM" dataUsingEncoding: NSUTF8StringEncoding] forCharacteristic: self.transferCharacteristic suSubscribedCentrals: nil]; if (didSend) // Lo ha fatto, quindi contrassegnalo come inviato sendingEOM = NO; // non ha inviato, quindi usciremo e aspetteremo che peripheralManagerIsReadyToUpdateSubscribers invochi nuovamente sendData; // Stiamo inviando dati // C'è ancora qualcosa da inviare? if (self.sendDataIndex> = self.dataToSend.length) // Nessun dato disponibile. Non fare nulla di ritorno; // Sono rimasti i dati, quindi inviare fino a quando la callback fallisce, o il gioco è fatto. BOOL didSend = YES; while (didSend) // Calcola quanto deve essere grande NSInteger amountToSend = self.dataToSend.length - self.sendDataIndex; // Non può essere più lungo di 20 byte se (amountToSend> NOTIFY_MTU) amountToSend = NOTIFY_MTU; // Copia i dati che vogliamo NSData * chunk = [NSData dataWithBytes: self.dataToSend.bytes + self.sendDataIndex length: amountToSend]; didSend = [self.peripheralManager updateValue: chunk forCharacteristic: self.transferCharacteristic suSubscribedCentrals: nil]; // Se non ha funzionato, abbandonare e attendere la callback se (! DidSend) return; NSString * stringFromData = [[NSString alloc] initWithData: codifica chunk: NSUTF8StringEncoding]; NSLog (@ "Inviato:% @", stringFromData); // Ha mandato, quindi aggiorna il nostro indice self.sendDataIndex + = amountToSend; // Era l'ultimo? if (self.sendDataIndex> = self.dataToSend.length) // Impostalo in modo che se l'invio fallisce, lo invieremo la prossima volta sendEOM = YES; BOOL eomSent = [self.peripheralManager updateValue: [@ "EOM" dataUsingEncoding: NSUTF8StringEncoding] forCharacteristic: self.transferCharacteristic suSubscribedCentrals: nil]; se (eomSent) // inviato, abbiamo finito inviando EOM = NO; NSLog (@ "Inviato: EOM"); ritorno;
Infine, è necessario definire un callback chiamato quando il PeripheralManager
è pronto per inviare il prossimo pezzo di dati. Ciò garantisce che i pacchetti arrivino nell'ordine in cui vengono inviati. Il metodo è il - (void) peripheralManagerIsReadyToUpdateSubscribers: (CBPeripheralManager *) periferica
e chiama solo il invia i dati
metodo. La versione completa è qui sotto:
- (void) peripheralManagerIsReadyToUpdateSubscribers: (CBPeripheralManager *) periferica [self sendData];
Ora puoi finalmente Correre
l'app e testare la comunicazione Bluetooth. La prossima immagine mostra l'interfaccia del CBCentralManager
.
Alla fine di questo tutorial, è necessario comprendere le specifiche della struttura del core Bluetooth. Dovresti anche essere in grado di definire e configurare un ruolo CBCentralManager e un ruolo CBPeripheral e comprendere e applicare alcune best practice durante lo sviluppo con Core Bluetooth.
Se avete domande o commenti, per favore lasciateli qui sotto!
Se lavori spesso con l'SDK di iOS, dai un'occhiata a Envato Market per trovare centinaia di modelli di app per iOS utili e che fanno risparmiare tempo.