Desarrollando una aplicación de detección de iBeacons

4
20513

Desarrollando una aplicación de detección de iBeacons

Índice de contenidos

1. Introducción
2. Entorno
3. Desarrollando la aplicación
3.1. Creación de un nuevo proyecto con XCode
3.2. Código para monitorización de los iBeacons
3.3. Lanzando nuestra aplicación
4. Conclusiones
5. Referencias

1. Introducción

iBeacon es el nombre comercial que da Apple a un sistema basado en Bluetooth Low Energy (BLE, Bluetooth 4.0, Bluetooth LE, Bluetooth Smart) que extiende los servicios de localización de iOS (Core Location) y permite la detección de unos dispositivos conocidos como beacons cuando nos aproximamos a ellos.

beacons

Bajo este sistema los beacons se limitan a emitir unas señales sobre Bluetooth 4.0 de forma continua. Estas señales contienen información relativa al dispositivo, localización y nuestra proximidad. De este modo una aplicación perteneciente a un negocio podría ser lanzada cuando se detectara la proximidad a uno de sus establecimientos con un sistema de beacons establecido.

Aunque este sistema tiene un nombre inventado por Apple, no está limitado a dispositivos iOS u OS X. De hecho, siempre y cuando se disponga de una API para tratar la información de los beacons, los dispositivos compatibles con Bluetooth 4.0 y Android 4.3 o superior ya pueden utilizarlos.

iOS 5 ya tenía soporte para trabajar con Bluetooth 4.0 a través de Core Bluetooth pero no ha sido hasta iOS 7 que Apple ha puesto a disposición de los desarrolladores una API de alto nivel para trabajar directamente con el sistema iBeacon. El mecanismo consistió en extender la clase Core Location añadiendo una API nueva para manejar los iBeacons. En el siguiente apartado veremos qué métodos y clases deben utilizarse para la monitorización y acceso a la información desde una sencilla app.

2. Entorno

El tutorial está escrito usando el siguiente entorno:

  • Hardware: Portátil MacBook Pro 15 pulgadas (2.4 GHz Intel i7, 8GB 1333 Mhz DDR3, 500GB Flash Storage).

  • Sistema Operativo: Mac OS X Lion 10.9.1

  • XCode 5.1 (versión beta)

  • iBeacons Proveedor Kontakt.io

  • iPod Touch 5 generación (instalada versión 7.1 iOS)

3. Desarrollando la aplicación

En este apartado construiremos una pequeña app que detecte iBeacons y muestre un listado de ellos. Se podrá acceder al detalle de cada uno para ver la información desglosada.

3.1. Creación de un nuevo proyecto con XCode

Creamos un proyecto desde New -> Project -> Master-Detail Application. Hemos elegido este tipo de aplicación por comodidad ya que implementa por defecto un table view. Este table view servirá para mostrar nuestro listado de iBeacons.

crear-nuevo-proyecto

El nombre de nuestra aplicación será AppBeacons.

master-detail-app

La arquitectura de nuestro proyecto se compone de los siguientes ficheros:

  • Main.storyboard: Definiremos gráficamente nuestras pantallas y las asociaremos con los contorladores
  • AppDelegate: Delegado prinicipal de la aplicación.
  • MasterViewController: Controlador de la pantalla de inicio y que visualizará una tabla con los iBeacons detectados.
  • DetailViewController: Controlador de la pantalla de detalle al seleccionar una celda de la tabla. En el detalle se podrá acceder a información relevante del iBeacon.

El proyecto por defecto se compone de una vista principal que muestra una tabla editable y una segunda vista que muestra el detalle de una celda al seleccionarla. Para nuestro tutorial esta aplicación nos vale, pero necesitamos «limpiar» el controlador principal de nuestra aplicación.

Abrimos el fichero MasterViewController.m y eliminamos el código de edición de filas, quedando un esqueleto como el que sigue:


    @implementation MasterViewController


    - (void)insertNewObject:(id)sender
    {
        if (!self.detectedBeacons) {
            self.detectedBeacons = [[NSMutableArray alloc] init];
        }
        [self.detectedBeacons insertObject:[NSDate date] atIndex:0];
        NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:0];
        [self.tableView insertRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
    }

    #pragma mark - CLLocationManagerDelegate

    - (void)locationManager:(CLLocationManager *)manager
          didDetermineState:(CLRegionState)state
                  forRegion:(CLRegion *)region
    {
        if (state == CLRegionStateInside) {
            NSLog(@"inside region");
            [self.locationManager startRangingBeaconsInRegion:(CLBeaconRegion *)region];
        } else {
            NSLog(@"not in region");
            [self.locationManager stopRangingBeaconsInRegion:(CLBeaconRegion *)region];
        }
    }


    - (void)locationManager:(CLLocationManager *)manager
            didRangeBeacons:(NSArray *)beacons
                   inRegion:(CLBeaconRegion *)region
    {
        self.detectedBeacons = [NSMutableArray new];
        for (CLBeacon *beacon in beacons) {
            NSLog(@"%@",beacon);
            [self.detectedBeacons addObject:beacon];
        }
        [self.tableView reloadData];
    }


    #pragma mark - Table View and data source

    - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
    {
        return 1;
    }

    - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
    {
        return self.detectedBeacons.count;
    }

    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
    {
        UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell" forIndexPath:indexPath];

        CLBeacon *object = self.detectedBeacons[indexPath.row];
        
        
        NSLog(@"Beacon %@ monitorized with Major %ld and Minor: %ld",
              [object.proximityUUID UUIDString],
              (long)object.major.integerValue,
              (long)object.minor.integerValue);
        
        NSString *beaconLabel = [NSString stringWithFormat:
                                 @"Beacon monitorized with Major %ld and Minor: %ld ",
                                 (long)object.major.integerValue, (long)object.minor.integerValue];
        cell.textLabel.text = beaconLabel;
        return cell;
    }

    - (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath
    {
        // Return NO if you do not want the specified item to be editable.
        return YES;
    }

    - (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
    {
        if (editingStyle == UITableViewCellEditingStyleDelete) {
            [self.detectedBeacons removeObjectAtIndex:indexPath.row];
            [tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationFade];
        } else if (editingStyle == UITableViewCellEditingStyleInsert) {
            // Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view.
        }
    }


    #pragma mark - UIViewController

    - (void)viewDidLoad
    {
        [super viewDidLoad];
        
        
        NSUUID *proximityUUID = [[NSUUID alloc] initWithUUIDString:kIBeaconUUID];
        self.beaconRegion = [[CLBeaconRegion alloc] initWithProximityUUID:proximityUUID identifier:kRegionIdentifier];
        self.beaconRegion.notifyEntryStateOnDisplay = YES;
        
        self.locationManager = [CLLocationManager new];
        self.locationManager.delegate = self;
        
        if (![CLLocationManager isRangingAvailable]) {
            NSLog(@"Couldn't turn on ranging: Ranging is not available.");
            return;
        }
        
        if (self.locationManager.rangedRegions.count > 0) {
            NSLog(@"Didn't turn on ranging: Ranging already on.");
        }
        
        [self.locationManager startMonitoringForRegion:self.beaconRegion];

    }

    - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
    {
        if ([[segue identifier] isEqualToString:@"showDetail"]) {
            NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];
            NSDate *object = self.detectedBeacons[indexPath.row];
            [[segue destinationViewController] setDetailItem:object];
        }
    }


    @end

    

El delegado AppDelegate viene con unos métodos implementados por defecto que no necesitamos. Únicamente dejaremos implementado didFinishLaunchingWithOptions para comprobar que en background la aplicación se refresca:

    #import "AppDelegate.h"

    @implementation AppDelegate

    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
    {
        if ([[UIApplication sharedApplication] backgroundRefreshStatus] == UIBackgroundRefreshStatusAvailable) {
            NSLog(@"Background updates are available for the app.");
        }else if([[UIApplication sharedApplication] backgroundRefreshStatus] == UIBackgroundRefreshStatusDenied)
        {
            NSLog(@"The user explicitly disabled background behavior for this app or for the whole system.");
        }else if([[UIApplication sharedApplication] backgroundRefreshStatus] == UIBackgroundRefreshStatusRestricted)
        {
            NSLog(@"Background updates are unavailable and the user cannot enable them again. For example, this status can occur when parental controls are in effect for the current user.");
        }
        return YES;
    }

    @end
    

Ya tenemos nuestra aplicación preparada. Hasta ahora debería mostrar una tabla vacía:

salida-app

3.2. Código para monitorización de los iBeacons

Para la detección de iBeacons se hace uso de la librería CoreLocation y CoreBluetooth. Accedemos a la sección Build Phases de nuestro target.

build-phases

Añadimos los enlaces a las librerías.

enlaces-librerias

Necesitamos definir unas constantes y propiedades privadas en nuestro controlador. Estas propiedades contendrán toda la información de localización y array auxiliar donde almacenaremos los beacons detectados para asociarlo al table view.

    #import "MasterViewController.h"
    #import "DetailViewController.h"

    #pragma mark - Constants iBeacons info
    static NSString * const kIBeaconUUID = @"F7826DA6-4FA2-4E98-8024-BC5B71E0893E";
    static NSString * const kRegionIdentifier = @"Kontakt.io";


    @interface MasterViewController ()
        @property (nonatomic, strong) NSMutableArray *detectedBeacons;
        @property (nonatomic, strong) CLLocationManager *locationManager;
        @property (nonatomic, strong) CLBeaconRegion *beaconRegion;

    @end
    

Notar que para acceder al array detectedBeacons se accede previamente a la instancia a través de self. Esta notación es una buena práctica ya que aporta legibilidad.

    - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
    {
        return 1;
    }

    - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
    {
        return self.detectedBeacons.count;
    }
    

Ahora debemos indicar que nuestro controlador implementa el protocolo para la detección de beacons. Abrimos MasterViewController.h e incluimos lo siguiente:

    #import 
    #import 

    @interface MasterViewController : UITableViewController 

    @end
    

Para comenzar la monitorización de iBeacons hay que definir la región e inicializar el manager de localización. Actualizamos el evento del view controller viewDidLoad con el siguiente código:

    - (void)viewDidLoad
    {
        [super viewDidLoad];
        
        
        NSUUID *proximityUUID = [[NSUUID alloc] initWithUUIDString:kIBeaconUUID];
        self.beaconRegion = [[CLBeaconRegion alloc] initWithProximityUUID:proximityUUID identifier:kRegionIdentifier];
        self.beaconRegion.notifyEntryStateOnDisplay = YES;
        
        self.locationManager = [CLLocationManager new];
        self.locationManager.delegate = self;
        
        if (![CLLocationManager isRangingAvailable]) {
            NSLog(@"Couldn't turn on ranging: Ranging is not available.");
            return;
        }
        
        if (self.locationManager.rangedRegions.count > 0) {
            NSLog(@"Didn't turn on ranging: Ranging already on.");
        }
        
        [self.locationManager startMonitoringForRegion:self.beaconRegion];

    }
    

Hay que tener en cuenta que al definir la región de nuestros iBeacons es necesario indicar el ProximityUUID del proveedor y un identificador para la región. Para los beacons de kontakt el ProximityUUID es F7826DA6-4FA2-4E98-8024-BC5B71E0893E.

El siguiente paso es implementar los métodos del protocolo que detectan cuándo está el dispositivo dentro de una regi&oacite;n para monitorizar.

El método didDetermineState es el encargado de detectar si el dispositivo se encuentra dentro de una región definida y en ese caso comenzar a monitorizar los beacons:

    - (void)locationManager:(CLLocationManager *)manager
          didDetermineState:(CLRegionState)state
                  forRegion:(CLRegion *)region
    {
        if (state == CLRegionStateInside) {
            NSLog(@"inside region");
            [self.locationManager startRangingBeaconsInRegion:(CLBeaconRegion *)region];
        } else {
            NSLog(@"not in region");
            [self.locationManager stopRangingBeaconsInRegion:(CLBeaconRegion *)region];
        }
    }
    

El método didRangeBeacons es el que nos proporciona los beacons detectados en nuetra región. Aquí será donde refresquemos la tabla con los nuevos beacons detectados:

    - (void)locationManager:(CLLocationManager *)manager
            didRangeBeacons:(NSArray *)beacons
                   inRegion:(CLBeaconRegion *)region
    {
        self.detectedBeacons = [NSMutableArray new];
        for (CLBeacon *beacon in beacons) {
            NSLog(@"%@",beacon);
            [self.detectedBeacons addObject:beacon];
        }
        [self.tableView reloadData];
    }
    

Terminamos modificando el método del datasource que muestra el contenido en la tabla para que nos muestre el major y minor de los beacons detectados:

    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
    {
        UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell" forIndexPath:indexPath];

        CLBeacon *object = self.detectedBeacons[indexPath.row];
        
        
        NSLog(@"Beacon %@ monitorized with Major %ld and Minor: %ld",
              [object.proximityUUID UUIDString],
              (long)object.major.integerValue,
              (long)object.minor.integerValue);
        
        NSString *beaconLabel = [NSString stringWithFormat:
                                 @"Beacon monitorized with Major %ld and Minor: %ld ",
                                 (long)object.major.integerValue, (long)object.minor.integerValue];
        cell.textLabel.text = beaconLabel;
        return cell;
    }
    

Podemos mostrar más en detalle la información del iBeacon seleccionado. Para ello, adaptamos el controlador de detalle, DetailViewController.

DetailViewController.h

    #import 
    #import 

    @interface DetailViewController : UIViewController

    @property (strong, nonatomic) id detailItem;

    @property (weak, nonatomic) IBOutlet UILabel *detailProxUUIDLabel;
    @property (weak, nonatomic) IBOutlet UILabel *detailMajorLabel;
    @property (weak, nonatomic) IBOutlet UILabel *detailMinorLabel;
    @property (weak, nonatomic) IBOutlet UILabel *detailProximityLabel;
    @end
    

DetailViewController.m

    #import "DetailViewController.h"

    ...

    - (void)configureView
    {
        // Update the user interface for the detail item.

        if (self.detailItem) {
            CLBeacon *detailBeacon = (CLBeacon *)self.detailItem;
            NSString *beaconProxUUIDLabel = [NSString stringWithFormat:@"ProximityUUID: %@", [detailBeacon.proximityUUID UUIDString]];
            self.detailProxUUIDLabel.text = beaconProxUUIDLabel;
            NSString *beaconMajorLabel = [NSString stringWithFormat:@"Major: %ld", (long)detailBeacon.major.integerValue];
            self.detailMajorLabel.text = beaconMajorLabel;
            NSString *beaconMinorLabel = [NSString stringWithFormat:@"Minor: %ld", (long)detailBeacon.minor.integerValue];
            self.detailMinorLabel.text = beaconMinorLabel;
            NSString *beaconProximityLabel = nil;
            switch (detailBeacon.proximity) {
                case CLProximityImmediate:
                    beaconProximityLabel = @"Proximity: inmediate (0 - 20 cm)";
                    break;
                case CLProximityNear:
                    beaconProximityLabel = @"Proximity: near (20cm - 2m)";
                    break;
                case CLProximityFar:
                    beaconProximityLabel = @"Proximity: far (2m - 70m)";
                    break;
                case CLProximityUnknown:
                default:
                    beaconProximityLabel = @"Proximity: unknown";
                    break;
            }
            self.detailProximityLabel.text = beaconProximityLabel;
        }
    }

    ...

    

3.3. Lanzando nuestra aplicación

El resultado será una app que en su pantalla principal muestra los iBeacons detectados. Recordar que la aplicación necesita que el dispositivo tenga el bluetooth activado.

Listado de iBeacons detectados:

range-beacons

Detalle de iBeacon:

range-beacons

4. Conclusiones

iOS ofrece a través de Core Location una API sencilla para poder detectar estos dispositivos desde sencillas apps. Los iBeacons todaví necesitan ser explotados, pero son potencialmente mucho más precisos que detección de ubicaciones por GPS.

El dispositivo como tal no tiene mucha complejidad y se limita a enviar continuamente una información. La complejidad del desarrollo recae en la aplicación que tenga que explotar los datos enviados por el iBeacon. Por ejemplo, para saber la ubicación geográfica concreta, deberíamos desarrollar una app que a partir de los datos del iBeacon detectado recuperara esta información a través de la red. No obstante, todav´a hay mucha diferencia entre proveedores y encontrar la información necesaria y una API potente no es algo trivial.

5. Referencias

Este pequeño ejercicio lo he realizado apoyándome en varias fuentes:

  • http://maniacdev.com/2013/10/example-an-app-using-the-new-ios-7-ibeacon-api
  • http://www.punteroavoid.com/blog/2014/02/18/ibeacon-101/

Puedes descargarte el código del tutorial desde mi repositorio de github pinchando aquí.

Un saludo.

Sara

4 COMENTARIOS

  1. Ahora por suerte ya tenemos los Eddystone Beacons, nosotros ayudamos a realizar proyectos con Eddystone Beacons i IOT No solo somos una tienda On line, estamos especializados en el protocolo Eddystone y el Api de Google Nearby

DEJA UNA RESPUESTA

Por favor ingrese su comentario!

He leído y acepto la política de privacidad

Por favor ingrese su nombre aquí

Información básica acerca de la protección de datos

  • Responsable:
  • Finalidad:
  • Legitimación:
  • Destinatarios:
  • Derechos:
  • Más información: Puedes ampliar información acerca de la protección de datos en el siguiente enlace:política de privacidad