原文地址:http://www.raywenderlich.com/52080/introduction-core-bluetooth-building-heart-rate-monitor
核心蓝牙框架可以让你的iOS和Mac应用程序与蓝牙低功耗设备(蓝牙LE的简称)进行通信。蓝牙LE设备包括心脏速率监视器、数字恒温器等等。
我们在本教程中使用的心脏速率监视器Polar H7 Bluetooth Smart Heart Rate Sensor。如果你没有这些设备之一,你仍然可以跟着教程,但你需要调整的,你需要使用的任何蓝牙设备的代码。
好吧,这是蓝牙LE的时间!
1、了解中央和外围设备的蓝牙
参加蓝牙通信的两个主要角色:中心设备和周边设备。
中心设备:主要功能是获取周边设备提供的服务、特征及相关数据值。
周边设备:主要功能是将设备中所存储的设备广播到中心设备。
在这种情况下,在iOS设备(中央)与心率监视器(外设)进行通信,扫描、连接、检索心脏速率的信息,以友好的方式在设备上显示。
中心写周边设备如何沟通
广告是主要的方式:除了广播自己的存在,广播的数据包可以包含一些其它数据,如对外围设备的名称。它也可包括周边设备的一组集合数据。例如,一个心脏监测仪,该数据包还提供了每分钟心跳(BPM)的数据。
中心的任务是扫描这些广播的数据包,找出任何相关的外围设备,并连接到单个设备的详细信息。
#define POLARH7_HRM_DEVICE_INFO_SERVICE_UUID @"180A" #define POLARH7_HRM_HEART_RATE_SERVICE_UUID @"180D"
#define POLARH7_HRM_MEASUREMENT_CHARACTERISTIC_UUID @"2A37" #define POLARH7_HRM_BODY_LOCATION_CHARACTERISTIC_UUID @"2A38" #define POLARH7_HRM_MANUFACTURER_NAME_CHARACTERISTIC_UUID @"2A29"
@interface HRMViewController : UIViewController <CBCentralManagerDelegate, CBPeripheralDelegate>
@property (nonatomic, strong) CBCentralManager *centralManager; @property (nonatomic, strong) CBPeripheral *polarH7HRMPeripheral;
#pragma mark - CBCentralManagerDelegate // method called whenever you have successfully connected to the BLE peripheral - (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral { } // CBCentralManagerDelegate - This is called with the CBPeripheral class as its main input parameter. This contains most of the information there is to know about a BLE peripheral. - (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI { } // method called whenever the device state changes. - (void)centralManagerDidUpdateState:(CBCentralManager *)central { }
#pragma mark - CBPeripheralDelegate // CBPeripheralDelegate - Invoked when you discover the peripheral's available services. - (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error { } // Invoked when you discover the characteristics of a specified service. - (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error { } // Invoked when you retrieve a specified characteristic's value, or when the peripheral device notifies your app that the characteristic's value has changed. - (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error { }
#pragma mark - CBCharacteristic helpers // Instance method to get the heart rate BPM information - (void) getHeartBPMData:(CBCharacteristic *)characteristic error:(NSError *)error { } // Instance method to get the manufacturer name of the device - (void) getManufacturerName:(CBCharacteristic *)characteristic { } // Instance method to get the body location of the device - (void) getBodyLocation:(CBCharacteristic *)characteristic { } // Helper method to perform a heartbeat animation - (void)doHeartBeat { }
// Properties for your Object controls @property (nonatomic, strong) IBOutlet UIImageView *heartImage; @property (nonatomic, strong) IBOutlet UITextView *deviceInfo; // Properties to hold data characteristics for the peripheral device @property (nonatomic, strong) NSString *connected; @property (nonatomic, strong) NSString *bodyData; @property (nonatomic, strong) NSString *manufacturer; @property (nonatomic, strong) NSString *polarH7DeviceData; @property (assign) uint16_t heartRate; // Properties to handle storing the BPM and heart beat @property (nonatomic, strong) UILabel *heartRateBPM; @property (nonatomic, retain) NSTimer *pulseTimer; // Instance method to get the heart rate BPM information - (void) getHeartBPMData:(CBCharacteristic *)characteristic error:(NSError *)error; // Instance methods to grab device Manufacturer Name, Body Location - (void) getManufacturerName:(CBCharacteristic *)characteristic; - (void) getBodyLocation:(CBCharacteristic *)characteristic; // Instance method to perform heart beat animations - (void) doHeartBeat;
- (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib. self.polarH7DeviceData = nil; [self.view setBackgroundColor:[UIColor groupTableViewBackgroundColor]]; [self.heartImage setImage:[UIImage imageNamed:@"HeartImage"]]; // Clear out textView [self.deviceInfo setText:@""]; [self.deviceInfo setTextColor:[UIColor blueColor]]; [self.deviceInfo setBackgroundColor:[UIColor groupTableViewBackgroundColor]]; [self.deviceInfo setFont:[UIFont fontWithName:@"Futura-CondensedMedium" size:25]]; [self.deviceInfo setUserInteractionEnabled:NO]; // Create your Heart Rate BPM Label 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]]; [self.heartImage addSubview:self.heartRateBPM]; // Scan for all available CoreBluetooth LE devices NSArray *services = @[[CBUUID UUIDWithString:POLARH7_HRM_HEART_RATE_SERVICE_UUID], [CBUUID UUIDWithString:POLARH7_HRM_DEVICE_INFO_SERVICE_UUID]]; CBCentralManager *centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:nil]; [centralManager scanForPeripheralsWithServices:services options:nil]; self.centralManager = centralManager; }
- (void)centralManagerDidUpdateState:(CBCentralManager *)central { // Determine the state of the peripheral 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"); } }
让我们尝试一下。构建并运行代码 在一个实际的设备,而不是模拟器上。你应该在控制台中看到如下输出:
CoreBluetooth[WARNING] <CBCentralManager: 0x14e3a8c0> is not powered on CoreBluetooth BLE hardware is powered on and ready
请记住,在viewDidLoad中,你调用scanForPeripheralWithServices:开始搜索具有心脏速率或设备信息服务的蓝牙设备。当发现这些设备时,didDiscoverPeripheral:peripheral:委托方法会被调用。
- (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.polarH7HRMPeripheral = peripheral; peripheral.delegate = self; [self.centralManager connectPeripheral:peripheral options:nil]; } }
在这里,您检查,以确保该设备具有一个非空的本地名称,如果是这样,你注销的名称和存储CBPeripheral供日后参考。您也停止扫描设备和中央管理的方法来建立与peripheral对象的连接。
编译运行:
Found the heart rate monitor: Polar H7 252D9F
你的下一个步骤是确定,如果你已经建立了与peripheral的连接。
打开HRMViewController.m和替换centralManager:central:peripheral:
- (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); }
centralManager:didConnectPeripheral:
在实现上述方法,你先设置你的peripheral对象的代理方法为当前控制器,以便它可以使用通知回调视图控制器。如果没有出现错误,你下次访问peripheral发现与该设备相关的服务。最后,你决定了外设的当前状态看,如果你已经建立了一个连接。
然而,如果该连接尝试失败,则中央管理器对象的调用centralManager:didFailToConnectPeripheral:error:
再次运行在设备上的代码(仍然穿着你的心脏速率监视器),并在几秒钟后,你应该看到这个控制台上:
Found the heart rate monitor: Polar H7 252D9F Connected: YES
一旦peripheral的服务发现,peripheral:didDiscoverServices:
将被调用
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error { for (CBService *service in peripheral.services) { NSLog(@"Discovered service: %@", service.UUID); [peripheral discoverCharacteristics:nil forService:service]; } }
构建并运行,这时候你应该看到类似的控制台执行以下操作:
Discovered service: Unknown (<180d>) Discovered service: Device Information Discovered service: Battery Discovered service: Unknown (<6217ff49 ac7b547e eecf016a 06970ba9>)
#define POLARH7_HRM_HEART_RATE_SERVICE_UUID @"180D"
添加peripheral:didDiscoverCharacteristicsForService:
由于您调用discoverCharacteristics:forService:一旦特征被发现peripheral:didDiscoverCharacteristicsForService:将被调用。因此,替换为以下内容:
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error { if ([service.UUID isEqual:[CBUUID UUIDWithString:POLARH7_HRM_HEART_RATE_SERVICE_UUID]]) { // 1 for (CBCharacteristic *aChar in service.characteristics) { // Request heart rate notifications if ([aChar.UUID isEqual:[CBUUID UUIDWithString:POLARH7_HRM_MEASUREMENT_CHARACTERISTIC_UUID]]) { // 2 [self.polarH7HRMPeripheral setNotifyValue:YES forCharacteristic:aChar]; NSLog(@"Found heart rate measurement characteristic"); } // Request body sensor location else if ([aChar.UUID isEqual:[CBUUID UUIDWithString:POLARH7_HRM_BODY_LOCATION_CHARACTERISTIC_UUID]]) { // 3 [self.polarH7HRMPeripheral readValueForCharacteristic:aChar]; NSLog(@"Found body sensor location characteristic"); } } } // Retrieve Device Information Services for the Manufacturer Name if ([service.UUID isEqual:[CBUUID UUIDWithString:POLARH7_HRM_DEVICE_INFO_SERVICE_UUID]]) { // 4 for (CBCharacteristic *aChar in service.characteristics) { if ([aChar.UUID isEqual:[CBUUID UUIDWithString:POLARH7_HRM_MANUFACTURER_NAME_CHARACTERISTIC_UUID]]) { [self.polarH7HRMPeripheral readValueForCharacteristic:aChar]; NSLog(@"Found a device manufacturer name characteristic"); } } } }
Found heart rate measurement characteristic Found body sensor location characteristic Found a device manufacturer name characteristic
peripheral:didUpdateValueForCharacteristic:
当CBPeripheral读取的值(或定期更新的值)将被调用。你需要实现这个方法来检查,看看哪些特征的价值已经被更新。
实现下面这个方法
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error { // Updated value for heart rate measurement received if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:POLARH7_HRM_MEASUREMENT_CHARACTERISTIC_UUID]]) { // 1 // Get the Heart Rate Monitor BPM [self getHeartBPMData:characteristic error:error]; } // Retrieve the characteristic value for manufacturer name received if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:POLARH7_HRM_MANUFACTURER_NAME_CHARACTERISTIC_UUID]]) { // 2 [self getManufacturerName:characteristic]; } // Retrieve the characteristic value for the body sensor location received else if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:POLARH7_HRM_BODY_LOCATION_CHARACTERISTIC_UUID]]) { // 3 [self getBodyLocation:characteristic]; } // Add your constructed device information to your UITextView self.deviceInfo.text = [NSString stringWithFormat:@"%@\n%@\n%@\n", self.connected, self.bodyData, self.manufacturer]; // 4 }
要了解如何从一个特征解释这些数据,你必须检查的蓝牙规范。heart rate measurement.
你会看到一个心脏速率测量由许多标志,其次是心脏速率测量本身,一些能源信息和其他数据。你需要写一个方法来读取该
在in HRMViewController.m文件中实现getHeartRateBPM:(CBCharacteristic *)characteristic error:(NSError *)error
- (void) getHeartBPMData:(CBCharacteristic *)characteristic error:(NSError *)error { // Get the Heart Rate Monitor BPM NSData *data = [characteristic value]; // 1 const uint8_t *reportData = [data bytes]; uint16_t bpm = 0; if ((reportData[0] & 0x01) == 0) { // 2 // Retrieve the BPM value for the Heart Rate Monitor bpm = reportData[1]; } else { bpm = CFSwapInt16LittleToHost(*(uint16_t *)(&reportData[1])); // 3 } // Display the heart rate value to the UI if no error occurred if( (characteristic.value) || !error ) { // 4 self.heartRate = bpm; self.heartRateBPM.text = [NSString stringWithFormat:@"%i bpm", bpm]; self.heartRateBPM.font = [UIFont fontWithName:@"Futura-CondensedMedium" size:28]; [self doHeartBeat]; self.pulseTimer = [NSTimer scheduledTimerWithTimeInterval:(60. / self.heartRate) target:self selector:@selector(doHeartBeat) userInfo:nil repeats:NO]; } return; }
添加getManufacturerName:(CBCharacteristic *)characteristic
在HRMViewController.m文件中实现getManufacturerName:(CBCharacteristic *)characteristic
方法,在这里找到对应的特征值manufacturer name characteristic.
// Instance method to get the manufacturer name of the device - (void) getManufacturerName:(CBCharacteristic *)characteristic { NSString *manufacturerName = [[NSString alloc] initWithData:characteristic.value encoding:NSUTF8StringEncoding]; // 1 self.manufacturer = [NSString stringWithFormat:@"Manufacturer: %@", manufacturerName]; // 2 return; }
添加getBodyLocation:(CBCharacteristic *)characteristic
在HRMViewController.m文件中添加:getBodyLocation:(CBCharacteristic *)characteristic
方法,在这里找到body sensor location设备信息
- (void) getBodyLocation:(CBCharacteristic *)characteristic { NSData *sensorData = [characteristic value]; // 1 uint8_t *bodyData = (uint8_t *)[sensorData bytes]; if (bodyData ) { uint8_t bodyLocation = bodyData[0]; // 2 self.bodyData = [NSString stringWithFormat:@"Body Location: %@", bodyLocation == 1 ? @"Chest" : @"Undefined"]; // 3 } else { // 4 self.bodyData = [NSString stringWithFormat:@"Body Location: N/A"]; } return; }
让你的心跳快一点!
恭喜你,你现在有一个可以工作的心脏监测仪,以及更重要的是有蓝牙核心如何工作的一个很好的理解。可以应用这些相同的技术到各种蓝牙的设备上。
在你离开之前,我们有一个小的奖金给你。为了好玩,让我们在时间上的心脏跳动像从心脏监护器的BPM的数据。
打开HRMViewController.m文件,替代doHeartBeat
方法
- (void) doHeartBeat { CALayer *layer = [self heartImage].layer; CABasicAnimation *pulseAnimation = [CABasicAnimation animationWithKeyPath:@"transform.scale"]; pulseAnimation.toValue = [NSNumber numberWithFloat:1.1]; pulseAnimation.fromValue = [NSNumber numberWithFloat:1.0]; pulseAnimation.duration = 60. / self.heartRate / 2.; pulseAnimation.repeatCount = 1; pulseAnimation.autoreverses = YES; pulseAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn]; [layer addAnimation:pulseAnimation forKey:@"scale"]; self.pulseTimer = [NSTimer scheduledTimerWithTimeInterval:(60. / self.heartRate) target:self selector:@selector(doHeartBeat) userInfo:nil repeats:NO]; }