App iOS para conectar con periférico bluetooth 4.0

1
11486

Conectar iOS con emisor de bluetooth 4.0.

0. Índice de
contenidos.

1. Entorno

Este tutorial está escrito usando el siguiente entorno:

  • Hardware: Portátil Mac Book Pro 17″ (2,93 Ghz Intel Core 2 Duo, 8 GB DDR3)
  • Sistema Operativo: Mac OS X Mavericks 10.9
  • Xcode, Version 5.0.2
  • Polar H6, como emisor de bluetooth.
  • iPod touch 5, como receptor de bluetooth.

2. Introducción.

Viendo que se empiezan a salir hasta debajo de las piedras dispositivos con Bluetooth 4.0 LE (Low Energy), que pueden emitir durante años en algunos casos, y la amplia baraja de opciones que se podrían hacer con esta tecnología, vamos a ver cómo crear una app para iOS y recoger los datos de este tipo de dispositivos.

La aplicación mostrará por pantalla en el iPod los datos que reciba del dispositivo de monitorización por bluetooth, en adelante PolarH6 (por el modelo del dispositivo).

Primero vamos a ver cómo funcionan este tipo de dispositivos, y lo más básico es explicar los roles que tienen. En este caso, el iPod hará el rol de Central, que será el que pida datos al periférico (PolarH6). La estructura es como un servicio cliente servidor, en la que el iPod es el cliente (pide datos) y PolarH6 es el servidor (envía datos).

Bien, ahora que sabemos los roles, tenemos que ver cómo se transmiten los datos. Primero se mandan lo que se conoce como paquetes publicitarios, que se usan para encontrar el dispositivo (u otros usos como los iBeacons). Éstos paquetes contienen poca información, por lo que para nuestra aplicación nos hará falta conectarnos con el dispositivo.

Para conectarnos, PolarH6 tiene una serie de servicios, y cada servicio tiene una o varias características. Por ejemplo, el servicio de monitorización tiene dos características, los latidos por minuto (bpm) y la altura del dispositivo en el cuerpo.

Adentrándonos más en la materia, es pasar lo que hemos explicado a código, y para eso, el framework CoreBluetooth nos proporciona tres clases que encajan con el periférico (CBPeripheral), con los servicios (CBService) y con las características (CBCharacteristic). Estos objetos serán necesarios en nuestra app, ya explicaremos cómo los incluimos.

3. Al lío.

Bueno vamos a empezar por crear un proyecto nuevo en Xcode. Para ello vamos a File –> New –> Project… y elejimos Single View Aplication.

Damos a siguiente e introducimos los datos que nos faltan, por ejemplo así:

Bien, ahora vamos a configurar ViewController.h, agregamos la librería CoreBluetooth y los protocolos que vamos a necesitar.


	@import CoreBluetooth;

	@interface ViewController : UIViewController<CBCentralManagerDelegate, CBPeripheralDelegate>

Lo siguiente es crearnos unas constantes con la identificación de los servicios que vamos a monitorizar, en este caso serán el de la frecuencia cardíaca y el nivel de batería.


	#define POLARH6_HEART_RATE_SERVICE @"180D"       
	#define POLARH6_BATTERY_SERVICE @"180F"

Ahora vamos a crear las constantes para las características que nos interesan, que tendrán el UUID por convención, sacados de esta página.


	#define POLARH6_HEART_RATE_CHARACTERISTIC @"2A37"
	#define POLARH6_BATTERY_LEVEL_CHARACTERISTIC @"2A19"

Ahora agregamos las propiedades del periférico y la central que usaremos luego.


	@property (nonatomic, strong) CBCentralManager *centralManager;
	@property (nonatomic, strong) CBPeripheral     *polarH6;

Ahora vamos a poner métodos que nos harán falta para la interfaz y el manejo de los objetos, como sus callbacks. Se explica en el código su propósito.


	// Propiedades para la vista
	@property (nonatomic, strong) IBOutlet UILabel *heartRateBPM;
    
	// Aquí expondremos los datos recibidos por polarH6
	@property (nonatomic, strong) IBOutlet UITextView  *dataTextView;
    
	// Estado de la conexión
	@property (nonatomic, strong) NSString   *connected;
    
	// Estado del nivel de la batería
	@property (nonatomic, strong) NSString   *batteryLevel;
    
	// Ratio cardíaco
	@property (assign) uint16_t heartRate;
    
	// Método para coger los datos de las características de la frecuencia cardíaca.
	- (void) getHeartBPMData:(CBCharacteristic *)characteristic error:(NSError *)error;
    
	// Método para coger los datos de las características del nivel de batería.
	- (void) getBatteryData:(CBCharacteristic *)characteristic ;

Ya tenemos todo lo que necesitamos en nuestro .h, ahora vamos a implementar el .m (ViewController.m).

Bien, abrimos el fichero y lo primero en implementar son los métodos relativos al bluetooth, por lo que ponemos lo siguiente (explicado en el propio código):



	//Para determinar si hemos establecido la conexión
	- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral
	{
		[peripheral setDelegate:self];
		[peripheral discoverServices:nil];
		self.connected = [NSString stringWithFormat:@"Connected: %@", peripheral.state == CBPeripheralStateConnected ? @"YES" : @"NO"];
		NSLog(@"%@", self.connected);
	}
    
	//Es el método del delegate que será llamado cuando se encuentre un peripheral, pasándole éste
	- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI
	{
		NSString *localName = [advertisementData objectForKey:CBAdvertisementDataLocalNameKey];
		if ([localName length] > 0) {
			NSLog(@"Found the heart rate monitor: %@", localName);
			[self.centralManager stopScan];
			self.polarH6 = peripheral;
			peripheral.delegate = self;
			[self.centralManager connectPeripheral:peripheral options:nil];
		}
	}
    
	//Determina el estado del peripheral y lo imprime en el log
	- (void)centralManagerDidUpdateState:(CBCentralManager *)central
	{
		if ([central state] == CBCentralManagerStatePoweredOff) {
			NSLog(@"CoreBluetooth BLE hardware is powered off");
		}
		else if ([central state] == CBCentralManagerStatePoweredOn) {
			NSLog(@"CoreBluetooth BLE hardware is powered on and ready");
		}
		else if ([central state] == CBCentralManagerStateUnauthorized) {
			NSLog(@"CoreBluetooth BLE state is unauthorized");
		}
		else if ([central state] == CBCentralManagerStateUnknown) {
			NSLog(@"CoreBluetooth BLE state is unknown");
		}
		else if ([central state] == CBCentralManagerStateUnsupported) {
			NSLog(@"CoreBluetooth BLE hardware is unsupported on this platform");
		}
	}


Ahora vamos a implementar los métodos callback del protocolo CBPeripheralDelegate


	//Sirve para imprimir en el log los servicios que ha encontrado por cada dispositivo bluetooth que encuentra.
	- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error
	{
		for (CBService *service in peripheral.services) {
			NSLog(@"Discovered service: %@", service.UUID);
			[peripheral discoverCharacteristics:nil forService:service];
		}
	}

	- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error
	{
		// Servicio de monitorización
		if ([service.UUID isEqual:[CBUUID UUIDWithString:POLARH6_HEART_RATE_SERVICE]])  {
			// Iteramos por cadacaractrística encontrada (pueden tener varias y sólo nos interesa heart rate)
			for (CBCharacteristic *aChar in service.characteristics)
			{
				if ([aChar.UUID isEqual:[CBUUID UUIDWithString:POLARH6_HEART_RATE_CHARACTERISTIC]]) {
					[self.polarH6 setNotifyValue:YES forCharacteristic:aChar];
					NSLog(@"Found heart rate measurement characteristic");
				}

			}
		}
		// Servicio de batería
		if ([service.UUID isEqual:[CBUUID UUIDWithString:POLARH6_BATTERY_SERVICE]])  {
			// Iteramos por cadacaractrística encontrada (pueden tener varias y sólo nos interesa battery level)
			for (CBCharacteristic *aChar in service.characteristics)
			{
				if ([aChar.UUID isEqual:[CBUUID UUIDWithString:POLARH6_BATTERY_LEVEL_CHARACTERISTIC]]) {
					[self.polarH6 readValueForCharacteristic:aChar];
					NSLog(@"Found a battery level characteristic");
				}
			}
		}
	}

	// Método que será llamado cuando se actualice las características de los servicios.
	- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
	{
		// Actualización del monitor de frecuencia
		if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:POLARH6_HEART_RATE_CHARACTERISTIC]]) {
			[self getHeartBPMData:characteristic error:error];
		}
		// Actualización del estado de la batería
		if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:POLARH6_BATTERY_LEVEL_CHARACTERISTIC]]) {
			[self getBatteryData:characteristic];
		}

		// Añadiendo texto a mostrar
		self.dataTextView.text = [NSString stringWithFormat:@"%@n\nBattery Level:%hhu", self.connected,self.batteryLevel];
	}

Creamos los métodos que extraen la información que nos interesa.


	//Recoge los datos de la característica del monitor de frecuencia
	- (void) getHeartBPMData:(CBCharacteristic *)characteristic error:(NSError *)error
	{
		// Recibe el ratio de BPM
		NSData *data = [characteristic value];
		const uint8_t *reportData = [data bytes];
		uint16_t bpm = 0;

		if ((reportData[0] & 0x01) == 0) {
			// Recoge los BPM del dispositivo
			bpm = reportData[1];
		} else {
			bpm = CFSwapInt16LittleToHost(*(uint16_t *)(&reportData[1]));
		}
		// Muestra por pantalla los BPM si no hay error
		if( (characteristic.value)  || !error ) {
			self.heartRate = bpm;
			self.heartRateBPM.text = [NSString stringWithFormat:@"%i bpm", bpm];
			self.heartRateBPM.font = [UIFont fontWithName:@"Futura-CondensedMedium" size:28];
		}
		return;
	}

	- (void) getBatteryData:(CBCharacteristic *)characteristic
	{

		NSData *data = [characteristic value];
		const uint8_t *reportData = [data bytes];
		self.batteryLevel = reportData[0] ;
		return;

	}

Y ahora nos queda el último paso de código que es modificar el método viewDidLoad


	- (void)viewDidLoad
	{
		[super viewDidLoad];
		// Do any additional setup after loading the view, typically from a nib.

		[self.view setBackgroundColor:[UIColor groupTableViewBackgroundColor]];

		// Limpiamos el textView
		[self.dataTextView setText:@""];
		[self.dataTextView setTextColor:[UIColor blueColor]];
		[self.dataTextView setBackgroundColor:[UIColor groupTableViewBackgroundColor]];
		[self.dataTextView setFont:[UIFont fontWithName:@"Futura-CondensedMedium" size:25]];
		[self.dataTextView setUserInteractionEnabled:NO];

		// Creamos la label para mostrar los BPM
		self.heartRateBPM = [[UILabel alloc] initWithFrame:CGRectMake(55, 30, 75, 50)];
		[self.heartRateBPM setTextColor:[UIColor whiteColor]];
		[self.heartRateBPM setText:[NSString stringWithFormat:@"%i", 0]];
		[self.heartRateBPM setFont:[UIFont fontWithName:@"Futura-CondensedMedium" size:28]];

		// Escaneamos en busca de los servicios
		NSArray *services = @[[CBUUID UUIDWithString:POLARH6_HEART_RATE_SERVICE], [CBUUID UUIDWithString:POLARH6_BATTERY_SERVICE]];
		CBCentralManager *centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:nil];
		[centralManager scanForPeripheralsWithServices:services options:nil];
		self.centralManager = centralManager;
	}

Bien, ahora sólo nos queda arreglar la vista, para ello nos ayudaremos del storyboard. Abrimos el fichero Main.storyboard y tenemos que crear un UILabel donde se imprimirá la frecuencia cardíaca y un UITextView donde pondremos la batería que le queda.

Después de crearlos tenemos que conectarlos con las propiedades que hemos definido en ViewController.h (heartRateBPM y dataTextView).

Bien, ahora ya tenemos todo preparado para compilar y ejecutar. Si todo va bien nos debe de salir en la pantalla las pulsaciones y el nivel de la batería en los labels que hemos creado.

Referencias.

Éste tutorial está sacado gran parte de esta página en la que hay muchos tutoriales sobre iOS e investigación a partir del tutorial.

Además de repasar principios básicos y referencias de bluetooth de esta página.

Para descargar el proyecto pulsar aquí.

Conclusiones.

Hemos visto cómo es muy fácil crear una aplicación para iOS en pocos pasos y con un poco más de investigación se pueden llegar a crear grandes proyectos.

Para cualquier duda o aclaración, en los comentarios.

Un saludo.

1 COMENTARIO

  1. Estimado, he comprado un smartwacht A9, lamentablement no lo puedo conectar con mi iphone 4.
    Me podrias ayudar

    Hay un app mediatekdevice, pero no reconoce reloj
    Gracias

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