iOS 7 SDK Core Bluetooth - Leçon pratique

La structure Core Bluetooth (CB) fournit les ressources dont vos applications iOS ont besoin pour communiquer avec des périphériques équipés de la technologie Bluetooth Low Energy (BTLE). Ce tutoriel vous guidera à travers l'évolution de CB d'iOS 5 à iOS 7. De plus, vous apprendrez à configurer un central et un périphérique Bluetooth central, à communiquer entre eux et aux meilleures pratiques de programmation inhérentes à l'utilisation de CB..


introduction

Les didacticiels Core Bluetooth sont divisés en deux parties. La première traite de l’aspect théorique de Core Bluetooth, tandis que ce tutoriel est une leçon pratique complète. Vous trouverez le code source complet en pièce jointe à ce message..


1. Téléchargez l'exemple de code source

L'objectif de ce tutoriel est de vous apprendre à utiliser le framework Bluetooth Core. Nous avons préparé un exemple de code source qui vous facilitera la vie et contournera la création du projet et la configuration des vues. Vous devriez télécharger l'exemple de code au début de cette page.

Nous supposons que vous connaissez les bases de Xcode et iOS car nous nous concentrerons uniquement sur les données Bluetooth de base. L'exemple de code contient les éléments suivants:

  • Une application qui utilise le contrôleur de navigation, trois vues et les contrôleurs inhérents.
  • Le contrôleur de vue initial ViewController avec deux boutons
  • UNE CBCentralManagerViewController qui crée un iBeacon personnalisé
  • UNE CBPeripheralViewController qui reçoit l'iBeacon et les informations inhérentes
  • UNE PRESTATIONS DE SERVICE fichier d'en-tête avec certaines variables à utiliser dans l'application.

Toutes les vues sont déjà en place et correctement définies. Vous devez simplement ajouter le code pour le processus Core Bluetooth. Ouvrez le projet, lancez-le et jouez avec les objets pour vous familiariser avec le code.

le SERVICES.h Le fichier contient deux UUID uniques. Celles-ci ont été générées à l'aide de la commande terminal Uuidgen. Vous devriez les générer sur votre application, ou vous pouvez les utiliser.

Notez que cette leçon nécessite deux périphériques iOS pour fonctionner correctement. Courir le projet et vous verrez une interface semblable à celle-ci:

Illustration de l'interface CB

2. Programmer un rôle central

Dans ce tutoriel, vous allez centrer le CBCentralManagerViewController classe. La première étape consiste à ajouter les deux protocoles prenant en charge le CBCentralManager et CBPériphérique. La déclaration de ces protocoles définit des méthodes (nous en parlerons plus tard). Votre interface devrait être comme ça:

@interface CBCentralManagerViewController: UIViewController < CBCentralManagerDelegate, CBPeripheralDelegate>

Maintenant, vous devez définir trois propriétés: CBCentralManager, CBPériphérique, et NSMutableData. Les deux premiers sont évidents et le dernier sert à stocker des informations partagées entre des périphériques..

@property (fort, non atomique) CBCentralManager * centralManager; @propriété (forte, non atomique) CBPériphérique * découvertPériphérique; @property (strong, nonatomic) NSMutableData * data;

À ce stade, vous pouvez permuter le fichier d'implémentation. Vous verrez un avertissement, mais avant de résoudre ce problème, vous devez lancer la centralManger et le Les données objets. Vous devriez commencer le centralManager avec un délégué et sans file d'attente. Vous devriez utiliser le viewDidLoad méthode et le résultat devrait être semblable à ceci:

 _centralManager = [[CBCentralManager alloc] initWithDelegate: file d'attente auto: nil]; _data = [[NSMutableData alloc] init];

Pour résoudre le problème de l’avertissement, vous devez ajouter le - (void) centralManagerDidUpdateState: (CBCentralManager *) central méthode.

C'est une méthode de protocole requise. Il vérifie l'état de l'appareil et agit en conséquence. Il existe plusieurs états possibles et dans votre application, vous devez toujours les vérifier. Les états sont:

  • CBCentralManagerStateUnknown
  • CBCentralManagerStateResetting
  • CBCentralManagerStateUnsupported
  • CBCentralManagerStateUnauthorized
  • CBCentralManagerStatePoweredOff
  • CBCentralManagerStatePoweredOn

Par exemple, si vous exécutez cette application sur un périphérique non Bluetooth 4.0, vous obtiendrez le CBCentralManagerStateUnsupported code. Ici, vous irez pour le CBCentralManagerStatePoweredOn et quand cela se produit, vous commencerez à rechercher des périphériques. Pour cela, utilisez le scanForPeripheralsWithServices méthode. Si vous passez nil comme premier argument, le CBCentralManager commence à chercher un service. Ici, vous utiliserez l’UUID stocké dans le répertoire SERVICES.h.

La méthode complète est:

- (void) centralManagerDidUpdateState: (CBCentralManager *) central // Vous devez tester tous les scénarios si (central.state! = CBCentralManagerStatePoweredOn) return;  if (central.state == CBCentralManagerStatePoweredOn) // Rechercher des périphériques [_centralManager scanForPeripheralsWithServices: @ [[CBUUID UUIDWithString: TRANSFER_SERVICE_UUID]] options: @ CBCentralManagerSourceOllowSuivanteSante) NSLog (@ "La numérisation a commencé"); 

À ce moment, votre application cherchera d'autres appareils. Mais malgré le fait qu’il y en ait ou qu’aucun ne soit disponible, vous n’obtiendrez aucune information. Nous pouvons arranger cela. Vous devriez ajouter le - (void) centralManager: (CBCentralManager *) centrale didDiscoverPeripheral: (CBPeripheral *) advertisementData: (NSDictionary *) advertisementData RSSI: (NSNumber *) RSSI méthode. Il sera appelé chaque fois qu'un périphérique est découvert. Cependant, vous allez le programmer pour qu’il ne réagisse qu’aux périphériques TRANSFER_SERVICE_UUID.

De plus, nous allons utiliser le nouveau système de cache et stocker le périphérique pour une référence future et une communication plus rapide. Le code source complet est le suivant:

- (void) centralManager: (CBCentralManager *) central didDiscoverPeripheral: (CBPeripheral *) périphérique advertisementData: (NSDictionary *) advertisementData RSSI: (NSNumber *) RSSI NSLog (@ "Découvert% @ at% @", périphérique.nom, RSSI) ; if (_discoveredPeripheral! = périphérique) // Enregistrez une copie locale du périphérique pour que CoreBluetooth ne s'en débarrasse pas _discoveredPeripheral = périphérique; // Et connectez NSLog (@ "Connexion au périphérique% @", périphérique); [_centralManager connectPeripheral: options de périphérique: nil]; 

La connexion à ce périphérique peut échouer. Nous devons traiter ce scénario en utilisant une méthode spécifique appelée: - (void) centralManager: (CBCentralManager *) central didFailToConnectPeripheral: (CBPeripheral *) erreur périphérique: erreur (NSError *). Ajoutez-le et informez l'utilisateur de cette erreur.

- (void) centralManager: (CBCentralManager *) central didFailToConnectPeripheral: (CBPeripheral *) erreur de périphérique: (NSError *) error NSLog (@ "Echec de la connexion"); [auto nettoyage]; 

Vous remarquerez un avertissement, puisque le nettoyer La méthode n'est pas encore déclarée. Disons le! À ce stade, vous pouvez trouver le code source de la méthode compliqué. Cependant, nous l'expliquerons plus tard. Vous devriez y revenir à la fin du tutoriel pour une compréhension complète.

Cette méthode annule tous les abonnements à un périphérique distant (s'il en existe) ou, dans le cas contraire, une déconnexion directe. Il boucle les services, puis les caractéristiques, et supprime les liaisons avec eux. La méthode complète est:

- (void) cleanup // Voyons si nous sommes abonnés à une caractéristique du périphérique if (_discoveredPeripheral.services! = nil) pour (service CBService * dans _discoveredPeripheral.services) if (service.characteristics! = nil) for (CBCharacteristic * caractéristique dans service.characteristics) if ([character.UUID isEqual: [CBUUID UUIDWithString: TRANSFER_CHARACTERISTIC_UUID]])) if (character.isNotifying) [_discoveredPeripheral setNotifyValue: NO. Pour la caractéristique:) revenir;  [_centralManager cancelPeripheralConnection: _discoveredPeripheral]; 

Étant donné que nous avons réussi à connecter le périphérique, nous devons maintenant en découvrir les services et les caractéristiques. Vous devez déclarer le - (void) centralManager: (CBCentralManager *) central didConnectPeripheral: (CBPeripheral *) périphérique. Une fois la connexion établie, arrêtez le processus de numérisation. Puis effacez les données que nous avons pu recevoir. Assurez-vous ensuite d’obtenir les rappels de découverte et recherchez enfin les services correspondant à votre UUID (TRANSFER_SERVICE_UUID). Voici le code:

- (void) centralManager: (CBCentralManager *) centrale didConnectPeripheral: (CBPeripheral *) périphérique NSLog (@ "Connected"); [_centralManager stopScan]; NSLog (@ "Numérisation arrêtée"); [_data setLength: 0]; périphérique.delegate = self; [périphérique discoverServices: @ [[CBUUID UUIDWithString: TRANSFER_SERVICE_UUID]]]; 

À ce stade, le périphérique commence à notifier son délégué avec plusieurs rappels. Un de ces rappels est le - (void) périphérique: erreur (CBPeripheral *) périphérique didDiscoverServices: (NSError *). Il est utilisé pour découvrir les caractéristiques d'un service donné. Cela ne veut pas dire que vous devriez toujours vérifier si cette méthode renvoie une erreur. Si aucune erreur n’est détectée, vous devez rechercher les caractéristiques dont vous avez besoin. Dans ce cas, TRANSFER_CHARACTERISTIC_UUID. Voici la méthode complète:

- (void) périphérique: (CBPeripheral *) périphérique didDiscoverServices: (NSError *) error if (erreur) [nettoyage automatique]; revenir;  for (service CBService * dans périphériques.services) [découverte du périphériqueCaractéristiques: @ [[CBUUID UUIDWithString: TRANSFER_CHARACTERISTIC_UUID]] pourService: service];  // Découvrir d'autres caractéristiques

À ce stade, si tout est correct, la caractéristique de transfert a été découverte. Une fois encore, une méthode déléguée est appelée: - (void) périphérique: (CBPeripheral *) périphérique didDiscoverCharacteristicsForService: (CBService *) erreur de service: (NSError *) error. Une fois que cela a été trouvé, vous voulez vous y abonner, ce qui permet à votre CBCentralManager recevoir les données de ce périphérique.

Encore une fois, vous devez gérer les erreurs (le cas échéant). Vous pouvez faire un acte de foi et souscrire directement à la caractéristique. Cependant, vous devez parcourir le tableau de caractéristiques et vérifier si la caractéristique est la bonne. Si c'est le cas, alors abonnez-vous. Une fois cette opération terminée, il vous suffit d'attendre que les données soient entrées (une autre méthode). La méthode complète est ci-dessous.

- (void) périphérique: (CBPeripheral *) périphérique didDiscoverCharacteristicsForService: (CBService *) erreur de service: (NSError *) error if (erreur) [nettoyage automatique]; revenir;  pour (caractéristique CBCharacteristic * dans service.characteristics) if ([caractéristique.UUID estEqual: [CBUUID UUIDWithString: TRANSFER_CHARACTERISTIC_UUID]]) [périphérique setNotifyValue: YES pourCharacteristic: caractéristique]; 

Chaque fois que le périphérique envoie de nouvelles données, le délégué de périphérique utilise le - (void) périphérique: périphérique (CBPeripheral *) didUpdateValueForCharacteristic: (CBCharacteristic *) erreur caractéristique: (NSError *) error méthode. Le deuxième argument contient la caractéristique que vous pouvez lire.

Au départ, vous allez créer un NSString pour stocker la valeur caractéristique. Ensuite, vous vérifierez si les données reçues sont complètes ou si d’autres seront livrées. Simultanément, vous mettrez à jour votre affichage dès que de nouvelles données sont reçues. Une fois toutes les données complétées, vous pouvez vous déconnecter de la caractéristique et de l'appareil (bien que vous puissiez rester connecté)..

Notez qu’après les données entrantes, vous pouvez soit vous déconnecter, soit attendre d’autres données. Ce rappel nous permet de savoir si davantage de données sont arrivées via la notification de la caractéristique. La source complète est ci-dessous:

- (void) périphérique: (CBPeripheral *) périphérique didUpdateValueForCharacteristic: (CBCharacteristic *) erreur caractéristique: (NSError *) error if (erreur) NSLog (@ "Erreur"); revenir;  NSString * stringFromData = [[NSString alloc]] initWithData: caractéristique.encodage à la valeur: NSUTF8StringEncoding]; // Avons-nous tout ce dont nous avons besoin? if ([stringFromData isEqualToString: @ "EOM"]) [_textview setText: [[NSString alloc]] initWithData: codage self.data: NSUTF8StringEncoding]]; [périphérique setNotifyValue: NO pourCaractéristique: caractéristique]; [_centralManager cancelPeripheralConnection: périphérique];  [_data appendData: caractéristique.value]; 

En outre, il existe une méthode qui garantit que le CBCentral sait quand un état de notification pour une caractéristique donnée change. Il est très important de le suivre afin de comprendre le moment où un état caractéristique change (mise à jour des valeurs d'application). La méthode est la suivante: - (void) périphérique: périphérique (CBPeripheral *) didUpdateNotificationStateForCharacteristic: (CBCharacteristic *) erreur caractéristique: erreur (NSError *). Vous devez vérifier si la notification de caractéristique s'est arrêtée. Si tel est le cas, vous devez vous déconnecter:

- (void) périphérique: (CBPeripheral *) périphérique didUpdateNotificationStateForCharacteristic: (CBCharacteristic *) erreur caractéristique: (NSError *) erreur if (! [caractéristique.UUID isEqual: [CBUUID UUIDWithString: erreur [if (!  if (feature.isNotifying) NSLog (@ "La notification a commencé le% @", caractéristique);  else // La notification a été arrêtée [_centralManager cancelPeripheralConnection: périphérique]; 

Si la déconnexion entre les périphériques se produit, vous devez nettoyer votre copie locale du périphérique. Pour cela utiliser le - (void) centralManager: (CBCentralManager *) central didDisconnectPeripheral: (CBPeripheral *) erreur de périphérique: (NSError *) error méthode. Cette méthode est simple et met le périphérique à zéro. De plus, vous pouvez redémarrer l'analyse du périphérique ou quitter l'application (ou une autre). Dans cet exemple, vous allez redémarrer le processus de numérisation..

- (void) centralManager: (CBCentralManager *) diddisconnectPeripheral central: (CBPeripheral *) erreur de périphérique: (NSError *) error _discoveredPeripheral = nil; [_centralManager scanForPeripheralsWithServices: @ [[CBUUID UUIDWithString: TRANSFER_SERVICE_UUID]] options: @ CBCentralManagerScanOptionAllowDuplicatesKey: @YES]; 

Enfin, une étape supplémentaire est requise. Chaque fois que la vue disparaît, vous devez arrêter le processus de numérisation. dans le viewWillDisappear: (BOOL) animé méthode, vous devez ajouter:

 [_centralManager stopScan];

Vous pouvez Courir l'application, mais vous avez besoin de l'application périphérique pour recevoir des données. L'image suivante présente l'interface finale du CBCentralManager.

Illustration de CBCentralManager.

3. Programmer un rôle périphérique

Dans ce tutoriel, vous allez centrer le CBPeripheralViewController classe. La première étape consiste à ajouter deux protocoles: CBPeripheralManagerDelegate et UITextViewDelegate. Votre interface devrait maintenant ressembler à:

@interface CBPeripheralViewController: UIViewController < CBPeripheralManagerDelegate, UITextViewDelegate>

Vous devez maintenant définir quatre propriétés: CBPeripheralManager, CBMutableCharacteristic, NSData, et NSInterger. Les deux premiers représentent le gestionnaire de périphériques et ses caractéristiques, tandis que le troisième représente les données qui seront envoyées. Le dernier représente l'index de données.

@property (strong, nonatomic) CBPeripheralManager * périphériqueManager; @property (strong, nonatomic) CBMutableCharacteristic * transferCharacteristic; @property (strong, nonatomic) NSData * dataToSend; @property (nonatomic, readwrite) NSInteger sendDataIndex;

Passez maintenant au fichier d'implémentation. Notre première étape consiste à initier le _peripheralManager et le configurer pour commencer à faire de la publicité. L'annonce de service doit commencer avec l'UUID de service susmentionné. Votre viewDidLoad devrait ressembler à ceci:

- (void) viewDidLoad [super viewDidLoad]; _peripheralManager = [[CBPeripheralManager alloc] initWithDelegate: file d'attente auto: nil]; [_peripheralManager startAdvertising: @ CBAdvertisementDataServiceUUIDsKey: @ [[CBUUID UUIDWithString: TRANSFER_SERVICE_UUID]]; 

Vous devriez voir un avertissement. Pour le réparer, déclarez le - (void) deviceParameterDidUpdateState: (CBPeripheralManager *) méthode du protocole. Semblable à CBCentralManager vous devez contrôler et tester tous les états des applications. Si l'état est CBPeripheralManagerStatePoweredOn vous devez créer et définir vos services et caractéristiques (l'une des véritables fonctionnalités d'iOS 7).

Chaque service et caractéristique doit être identifié par un UUID unique. Notez que le troisième argument de la méthode init est à rien. Cela signifie que les données à échanger seront définies ultérieurement. Cela se fait généralement lorsque vous souhaitez créer les données de manière dynamique. Si vous voulez avoir une valeur statique à transmettre, vous pouvez la déclarer ici.

Les propriétés déterminent comment la valeur de caractéristique peut être utilisée, et il existe plusieurs valeurs possibles:

  • CBCharacteristicPropertyBroadcast
  • CBCharacteristicPropertyLead
  • CBCharacteristicPropertyWriteWithoutResponse
  • CBCharacteristicPropertyWrite
  • CBCharacteristicPropertyWrite
  • CBCharacteristicPropertyNotify
  • CBCharacteristicPropertyIndicate
  • CBCharacteristicPropertyAuthenticatedSignedWrites
  • CBCharacteristicPropertyExtendedProperties
  • CBCharacteristicPropertyNotifyEncryptionRequired
  • CBCharacteristicPropertyIndicateEncryptionRequired

Pour une compréhension complète de ces propriétés, vous devez vérifier la référence de classe CBCharacteristic.

Le dernier argument de init est les autorisations de lecture, d'écriture et de chiffrement pour un attribut. Encore une fois, il y a plusieurs valeurs possibles:

  • CBAttributePermissionsReadable
  • CBAttributePermissionsWriteable
  • CBAttributePermissionsReadEncryptionRequired
  • CBAttributePermissionsWriteEncryptionRequired

Une fois la caractéristique définie, il est maintenant temps de définir le service à l'aide de la CBMutableService. Notez que le service doit être défini avec le TRANSFER_CHARACTERISTIC_UUID. Ajoutez la caractéristique au service, puis ajoutez-la au gestionnaire de périphériques. La méthode complète est ci-dessous:

- (void) deviceParameterDidUpdateState: (CBPeripheralManager *) périphérique if (périphérique.state! = CBPeripheralManagerStatePoweredOn) return;  if (périphérique.etat == CBPeripheralManagerStatePoweredOn) self.transferCharacteristic = [[CBMutableCharacteristic alloc] initWithType: [CBUUID UUIDWithString: TRANSFER_CHARACTERISTIC_UUID] propriétés: CBCalacteristeEnregistrement de l'en-tête: CBMutableService * transferService = [[CBMutableService alloc] initWithType: [CBUUID UUIDWithString: TRANSFER_SERVICE_UUID] primaire: YES]; transferService.characteristics = @ [_ transferCharacteristic]; [_peripheralManager addService: transferService]; 

Maintenant que nous avons le service et ses caractéristiques (une dans ce cas), il est maintenant temps de détecter le moment où un appareil se connecte à celui-ci et réagit en conséquence. le - (void) périphériquesManager: (CBPeripheralManager *) périphérique central: (CBCentral *) central didSubscribeToCharacteristic: caractéristique (CBCharacteristic *) méthode attrape quand quelqu'un souscrit à notre caractéristique, puis commence à leur envoyer des données.

L'application envoie les données disponibles au affichage. Si l'utilisateur le modifie, l'application l'envoie en temps réel à la centrale abonnée. La méthode appelle une méthode personnalisée appelée envoyer des données.

- (void) périphériqueManager: (CBPeripheralManager *) périphérique central: (CBCentral *) central didSubscribeToCharacteristic: (CBCharacteristic *) caractéristique _dataToSend = [_textView.text dataUsingEncoding: NSUTF8StringEncoding]; _sendDataIndex = 0; [self sendData]; 

le envoyer des données est la méthode qui traite de toute la logique concernant le transfert de données. Il peut faire plusieurs actions telles que:

  • Envoyer des données
  • Envoie le drapeau de fin de communication
  • Tester si l'application a envoyé les données
  • Vérifier si toutes les données ont été envoyées
  • Réagir à tous les sujets précédents

Le code source complet est présenté ci-dessous. Plusieurs commentaires ont été laissés à dessein afin de faciliter sa compréhension.

- (void) sendData static BOOL savingEOM = NO; // fin du message? if (savingEOM) BOOL didSend = [self.peripheralManager updateValue: [@ "EOM" dataUsingEncoding: NSUTF8StringEncoding] pourCaractéristique: auto.transferCaractéristique surSubscribeCentrals: nil]; if (didSend) // Si, alors marquez-le comme envoyé sendEOM = NO;  // n'a pas envoyé. Nous allons donc quitter et attendre que périphériquePhotoManagerIsReadyToUpdateSubscribers appelle à nouveau sendData return;  // Nous envoyons des données // Reste-t-il des données à envoyer? if (self.sendDataIndex> = self.dataToSend.length) // Il ne reste aucune donnée. Ne rien faire revenir;  // Il reste des données, donc envoyez jusqu'à ce que le rappel échoue ou que nous ayons terminé. BOOL didSend = YES; while (didSend) // Déterminez sa taille. NSInteger amountToSend = self.dataToSend.length - self.sendDataIndex; // Ne peut pas dépasser 20 octets si (amountToSend> NOTIFY_MTU) amountToSend = NOTIFY_MTU; // Copie les données souhaitées. NSData * chunk = [NSData dataWithBytes: self.dataToSend.bytes + self.sendDataIndex length: amountToSend]; didSend = [self.peripheralManager updateValue: chunk forCharacteristic: self.transferCharacteristic onSubscribCentrals: nil]; // Si cela ne fonctionne pas, abandonne le processus et attend le rappel if (! DidSend) return;  NSString * stringFromData = [[NSString alloc]] initWithData: codage de bloc: NSUTF8StringEncoding]; NSLog (@ "Sent:% @", stringFromData); // Il a été envoyé, alors mettez à jour notre index self.sendDataIndex + = amountToSend; // Était-ce le dernier? if (self.sendDataIndex> = self.dataToSend.length) // Définissez cette option pour que, si l'envoi échoue, nous l'enverrons la prochaine fois, sendEOM = YES; BOOL eomSent = [self.peripheralManager updateValue: [@ "EOM" dataUsingEncoding: NSUTF8StringEncoding] pourCaractéristique: auto.transfertCaractéristique onSouscritCentrales: nil]; if (eomSent) // Il a été envoyé, nous avons tous terminé sendEOM = NO; NSLog (@ "Envoyé: EOM");  revenir; 

Enfin, vous devez définir un rappel appelé lorsque le Gestionnaire de périphériques est prêt à envoyer le prochain bloc de données. Cela garantit que les paquets arrivent dans l'ordre dans lequel ils sont envoyés. La méthode est la - (void) périphériquesManagerIsReadyToUpdateSubscribers: (CBPeripheralManager *) périphérique et il appelle seulement le envoyer des données méthode. La version complète est ci-dessous:

- (void) périphériquesManagerIsReadyToUpdateSubscribers: (CBPeripheralManager *) périphérique [self sendData]; 

Vous pouvez maintenant enfin Courir l'application et tester la communication Bluetooth. L'image suivante montre l'interface du CBCentralManager.

Illustration de l'interface périphérique.

Conclusion

À la fin de ce didacticiel, vous devez comprendre les spécifications de l’infrastructure Core Bluetooth. Vous devez également pouvoir définir et configurer un rôle CBCentralManager et un rôle CBPeripheral, ainsi que comprendre et appliquer certaines des meilleures pratiques lors du développement avec Core Bluetooth..

Si vous avez des questions ou des commentaires, veuillez les laisser ci-dessous!

Si vous utilisez souvent le SDK iOS, consultez Envato Market pour trouver des centaines de modèles d'applications iOS utiles et permettant de gagner du temps..