• TECH • Intégrer du BLE (Bluetooth Low Energy) dans une app iOS et trouver les devices à proximité

Suite au précédent article technique, cette semaine nous allons vous expliquer, pas à pas, comment développer et intégrer le BLE (Bluetooth Low Energy) dans une application iOS.

BLE & iOS

Apple a intégré le BLE avec la version iOS 5.0 SDK dans le framework CoreBluetooth. Cette bibliothèque permet de faire communiquer deux smartphones ou un smartphone avec un widget en utilisant la technologie BLE.

Le CoreBluetooth est intégré dans tous les smartphones iOS depuis l’iPhone 4S et seulement dans les simulateurs sur les machines MAC qui ont déjà le Bluetooth 4.0.

 

Mon application « Hello BLE »

Dans ce chapitre, nous supposons que vous avez les bases de développement sur iOS et XCode. Nous allons donc nous concentrer sur ce framework CoreBluetooth en expliquant en détail son implémentation.

L’enjeu de ce projet est d’arriver à implémenter les fonctionnalités qui vous permettront de scanner et afficher les informations des devices BLE autour de vous.

Cette application utilisera un NavigationController et contiendra trois classes héritant de UIViewCotroller et de deux classes objets :

  • HomeViewController est la page d’accueil de l’application. Elle contient un bouton « Start Scan » et une « TableView » pour afficher la liste des périphériques BLE à proximité ;
  • DevicesDetailsViewController permet d’afficher les informations du périphérique sélectionné ;
  • DevicesItemCell représente le type de la cellule de la « TableView » ;
  • CustomPeripheral représente la classe objet d’un périphérique BLE ;
  • BleDiscoveryService est le service permettant d’activer/arrêter la recherche de device.

 

 

Notre Service BLE : BleDiscoveryService

C’est une classe singleton qui implémente toutes les fonctions nécessaires permettant la gestion du BLE.

R A P P E L
  • Une classe singleton est une classe qui ne peut avoir qu’une seule instance.

Nous y avons ajouté un protocole personnalisé BleDiscoveryDelegate, contenant les fonctions permettant d’envoyer l’état du CBCentralManager et la fonction « discoveryPeripheral » qui retourne le périphérique BLE trouvé au Delegate.

R A P P E L
  • Un Delegate est une classe qui implémente un ou plusieurs protocoles pour recevoir des messages grâce au protocole.
  • Le CBCentralManager permet de gérer la recherche, la connexion et la déconnexion avec des périphériques externes.
@protocol BLeDiscoveryDelegate <NSObject>
@optional
- (void) discoveryDidRefresh; // permet de savoir que CBCentralManager  est en train de redémarrer
- (void) discoveryStatePoweredOn; // permet de savoir que le CBCentralManager est actif
- (void) discoveryStatePoweredOff;
- (void) discoveryStateStateUnsupported; // permet de savoir que le CBCentralManager ne prend pas en charge le BLE
- (void) discoveryStateStateUnknown; // il y a un problème inconnu avec le CBCentralManager 
- (void) discoveryStateStateUnauthorized; // permet de savoir que l’application n’est pas autorisé à utiliser le BLE
- (void) discoveryPeripheral:(CBPeripheral *)peripheral Rssi:(NSNumber *)RSSI andIsNewDevice:(BOOL)isNew; // permet de recevoir le périphérique trouvé
@end

Après nous devons ajouter deux protocoles supportés par CBCentralManager et CBPeripheral : « CBCentralManagerDelegate » et « CBPeripheralDelegate ».

 

L’intégration de ces protocoles permet d’implémenter les fonctions essentielles à la gestion du BLE. Donc, notre interface doit ressembler à ça :

 

@interface BLeDiscoveryService () <CBCentralManagerDelegate, CBPeripheralDelegate>

Nous rajoutons en second lieu, trois paramètres :

@property (nonatomic, assign) id<BLeDiscoveryDelegate>   discoveryDelegate;
@property (retain, nonatomic) NSMutableArray    *foundPeripherals;
@property (strong,nonatomic) CBCentralManager    *centralManager;

Le premier paramètre représente la classe implémentant le protocole BleDiscoveryDelegate qui sera notifié de la modification. Le deuxième permet de récolter la liste des périphériques BLE et le dernier représente le gestionnaire du Bluetooth.

Ils seront initialisés dans le constructeur de notre classe Singleton. 

+(void) initInstance
{
    if( leDiscoveryInstance == nil )
    {
        leDiscoveryInstance = [[self alloc] init ];
    } 
    leDiscoveryInstance.centralManager   = [[CBCentralManager alloc] initWithDelegate:leDiscoveryInstance queue:nil];    
    leDiscoveryInstance.foundPeripherals = [[NSMutableArray alloc] init];
}

 

Après cette initialisation, nous devons implémenter les fonctions suivantes des protocoles CBCentralManagerDelegate et CBPeripheralDelegate pour ne plus avoir d’erreurs.

 

centralManagerDidUpdateState, cette fonction permet de suivre et écouter l’état du fonctionnement du gestionnaire Bluetooth (CBCentralManager), et pour chaque état nous l’envoyons à notre Delegate.

 

- (void) centralManagerDidUpdateState:(CBCentralManager *) central
{
    static CBCentralManagerState previousState = -1;    
 switch ( [self.centralManager state] )
    {
        // Le Bluetooth est activé et prêt à l'emploi
        case CBCentralManagerStatePoweredOn:
        {
            [discoveryDelegate discoveryStatePoweredOn];
            break;
        }
        // Le Bluetooth est couramment désactivé
 case CBCentralManagerStatePoweredOff:
 {
            if (previousState != -1)
            {
                [discoveryDelegate discoveryStatePoweredOff];
            }
 break;
 }
        //L'application n'est pas autorisée à utiliser à utiliser le mode Central/Client du Bluetooth Low Energy
 case CBCentralManagerStateUnauthorized:
 {
            [discoveryDelegate discoveryStateStateUnauthorized];
 break;
 }
        // Etat inconnu
 case CBCentralManagerStateUnknown:
 {
 // Etat inconnu
            [discoveryDelegate discoveryStateStateUnknown];
 break;
 }
        // La connexion avec le système de service est momentanément perdue
 case CBCentralManagerStateResetting:
 {
            // Redemarrage du central manager
            [discoveryDelegate discoveryDidRefresh];
            
 break;
 }
        // La plateforme ne supporte pas le mode Central/Client du Bluetooth Low Energy
        case CBCentralManagerStateUnsupported:
        {
            [discoveryDelegate discoveryStateStateUnsupported];
            break;
        }
 }
    previousState = [self.centralManager state];
}

Et la deuxième fonction est didDiscoverPeripheral permet de collecter les périphériques BLE détectés autour moi, de l’ajouter à la liste « foundPeripherals » et notifier le delegate.

- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI
{   
    if (![foundPeripherals containsObject:peripheral] )
    {
         [foundPeripherals addObject:peripheral];        
        [discoveryDelegate discoveryPeripheral:peripheral Rssi:RSSI andIsNewDevice:true ];
    }   else  {
        [discoveryDelegate discoveryPeripheral:peripheral Rssi:RSSI andIsNewDevice:false ];
    }
}

Et finalement, nous implémentons les fonctions « startScanningDevices » et « stopScanning » permettant de lancer et arrêter la recherche des périphériques BLE.

-(void) startScanningDevices:(NSArray*) uuidArrayToSearch
{
    if( self.centralManager != nil && ![self.centralManager isScanning] )
    {
        NSDictionary *options = [NSDictionary dictionaryWithObject:[NSNumber numberWithBool:YES] forKey:CBCentralManagerScanOptionAllowDuplicatesKey];
        
        // Au cas ou on fait la recherche de services spécifiques
        if( uuidArrayToSearch != nil && [uuidArrayToSearch count] > 0 )
        {
            [self.centralManager scanForPeripheralsWithServices:uuidArrayToSearch options:options ];
        }
        else
        {
            // Scan de tous les devices BLE autour
            [self.centralManager scanForPeripheralsWithServices:nil options:options ];
        }
    }
}
- (void) stopScanning
{
    if( self.centralManager != nil && [self.centralManager isScanning ] )
    {
        [self.centralManager stopScan];
    }
}

Maintenant, notre service est terminé et nous passons à son implémentation dans la vue HomeViewController qui doit ajouter notre protocole BleDiscoveryDelegate pour pouvoir communiquer avec le service, il ne faut pas oublier d’instancier la classe BleDiscoveryService dans le viewDidLoad.

if( ![BLeDiscoveryService isInited] )
    {
        [BLeDiscoveryService initInstance];
    }    
    bleDiscovery = [BLeDiscoveryService getInstance];
    bleDiscovery.discoveryDelegate = self;

Finalement, vous devez terminer de :

  • Activer le « startScann » et le « stopScann » dans l’évènement du bouton ;
  • Afficher la liste des devices trouvés dans la TableView ;
  • Afficher les informations du périphérique BLE sélectionné dans la vue DevicesDetailsViewController.

Conclusion

À la fin de ce tutoriel, vous avez pu remarquer la facilité d’intégration et d’utilisation du framework CoreBluetooth. Si vous êtes arrivé au bout de cet exercice, vous pouvez maintenant implémenter et utiliser plus en détails les autres fonctions du CBCentralManager permettant par exemple d’échanger des données entre deux périphériques BLE. Peut-être dans un prochain billet 😉

Vous pouvez télécharger le code source du projet en cliquant ici.

You may also like