iOS蓝牙之Introduction to Core Bluetooth: Building a Heart Rate Monitor(翻译)

原文地址:http://www.raywenderlich.com/52080/introduction-core-bluetooth-building-heart-rate-monitor


核心蓝牙框架可以让你的iOS和Mac应用程序蓝牙低功耗设备蓝牙LE的简称)进行通信。蓝牙LE设备包括心脏速率监视器、数字恒温器等等。

核心蓝牙框架是一组抽象蓝牙4.0规范定义了一组易于使用的协议与蓝牙LE的设备进行通信。
在本教程中将了解蓝牙核心架构关键概念,以及如何利用该框架来发现,连接和检索兼容设备的数据。您将通过构建一个心脏率监测应用程序,蓝牙心脏监测器通信使用这些技能

我们本教程中使用的心脏速率监视器Polar H7 Bluetooth Smart Heart Rate Sensor如果你没有这些设备之一你仍然可以跟着教程但你需要调整,你需要使用的任何蓝牙设备的代码。


好吧这是蓝牙LE的时间!


1、了解中央和外围设备蓝牙

参加蓝牙通信的两个主要角色:中心设备和周边设备。

中心设备:主要功能是获取周边设备提供的服务、特征及相关数据值。

周边设备:主要功能是将设备中所存储的设备广播到中心设备。


这种情况下,在iOS设备中央)与心率监视器外设)进行通信,扫描、连接、检索心脏速率的信息,以友好的方式在设备上显示

中心写周边设备如何沟通

广告是主要的方式:除了广播自己的存在,广播的数据包可以包含一些其它数据如对外围设备名称。可包括周边设备的一组集合数据例如一个心脏监测仪该数据包还提供了每分钟心跳(BPM)的数据

中心任务是扫描这些广播的数据包找出任何相关的外围设备并连接到单个设备的详细信息。

周围设备数据结构
就像我说的,广播的数据包都非常小,并且不能包含了大量的信息所以,要获得更多的,中央需要连接到一个外设,以获得所有的可用数据

一旦中央连接到外围设备,它需要选择它感兴趣数据在BLE数据被组织到服务及特点中:
服务是数据和描述特定功能相关联行为的特定集合。一个设备可以有一个以上的服务心脏监测仪展示心脏速率传感器获得的心脏速率数据,这个行为可以描述为一个服务。


一个特征提供有关周边服务的更多细节。

例如刚刚描述可以包含心脏速率传感器的预期身体位置透射心脏速率的测量数据的另一个特征特性心脏速率的服务。
一个服务可以有多个特点。
下面进一步图描述服务和特性之间的关系
iOS蓝牙之Introduction to Core Bluetooth: Building a Heart Rate Monitor(翻译)_第1张图片

一旦中央已经建立了一个周围设备的连接就自动发现周边服务和特征的可用

周围、服务、特征
CoreBluetooth框架,外围是由CBPeripheral对象表示,而与特定外围设备服务是由CBService对象表示。
一个周边服务的特征是由被定义为含有一个单一的逻辑的属性类型CBCharacteristic对象表示。

中心CBCentralManager对象表示,并且被用于管理发现或连接的外围设备。

下图说明了一个周边服务和特征对象层次的基本结构
iOS蓝牙之Introduction to Core Bluetooth: Building a Heart Rate Monitor(翻译)_第2张图片

创建的每个服务特性,必须通过一个唯一的标识符或UUID来识别。 UUID可16位或128位的但如果你建立你的客户端 - 服务器中央外设)的应用程序那么你就需要创建自己128位的UUID您还需要确保的UUID的唯一

入门
首先下载项目启动本教程starter project。
接下来,您需要导入CoreBluetooth框架到您的项目
接下来添加#defines 来定义the Polar H7 heart rate monitor服务的UUID。在这里services section有一些服务接口。

#define POLARH7_HRM_DEVICE_INFO_SERVICE_UUID @"180A"       
#define POLARH7_HRM_HEART_RATE_SERVICE_UUID @"180D"

在这里有两个有兴趣的服务一个是设备信息以及一个用于心脏速率的服务。

同样,对于你感兴趣的特点增加了一些#define定义,这些来自蓝牙规范的特征部分(characteristics section)

#define POLARH7_HRM_MEASUREMENT_CHARACTERISTIC_UUID @"2A37"
#define POLARH7_HRM_BODY_LOCATION_CHARACTERISTIC_UUID @"2A38"
#define POLARH7_HRM_MANUFACTURER_NAME_CHARACTERISTIC_UUID @"2A29"

在这里,列出从您所感兴趣的心脏速率服务三大特征

需要注意的是,如果你使用的是不同类型的设备,可以在这里您的设备添加相应的服务/特征

配置代理
打开HRMViewController.h和更新接口声明如下:

@interface HRMViewController : UIViewController <CBCentralManagerDelegate, CBPeripheralDelegate>

添加中心和同边设备

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

接下来,让我们实现委托方法切换到HRMViewController.m添加以下代码:

#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 
{
}

现在添加CBPeripheralDelegate协议的代理方法

#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 
{
}

最后,创建用于检索心率制造商名称身体位置CBCharacteristic信息

#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 {
}

配置UI界面
打开 HRMViewController.h文件,添加下面代码

// 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;

接下来打开storyboadr,添加一个label、一个imageview、一个uitextview
iOS蓝牙之Introduction to Core Bluetooth: Building a Heart Rate Monitor(翻译)_第3张图片


利用蓝牙框架
打开HRMViewController.m和替换viewDidLoad中用下面的代码

- (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;
 
}

在这里,您初始化和设置您的用户界面控件,并从Xcode的资产库中加载你的心脏图像接下来创建CBCentralManager对象;第一个参数为delegate,在此处设置为当前的视图控制器第二个参数为线程队列,如果设置nil因为外围设备管理器将运行在主线程中

然后调用scanForPeripheralsWithServices:;通知中央管理器来搜索范围内所有兼容的服务最后,您可以指定一个搜索所有兼容的心脏速率监控设备,并检索与该设备相关的设备信息

添加代理方法:
一旦外围设备管理器进行初始化需要立即检查其状态这就告诉你,如果你应用程序在其上运行设备是否符合BLE标准
打开HRMViewController.m和替换centralManagerDidUpdateState中央用下面的代码

- (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");
    }
}

上述方法保证了设备蓝牙低功耗标准,它可以用来作为CBCentralManager中央装置对象如果中央管理器的状态上电后,您会收到CBCentralManagerStatePoweredOn的状态如果状态更改为CBCentralManagerStatePoweredOff那么已经从中央管理器获得的所有外围对象变为无效,必须重新发现

让我们尝试一下构建并运行代码一个实际的设备,而不是模拟器应该在控制台中看到如下输出

CoreBluetooth[WARNING] <CBCentralManager: 0x14e3a8c0> is not powered on
CoreBluetooth BLE hardware is powered on and ready

添加didDiscoverPeripheral:peripheral:代理方法

请记住,在viewDidLoad中调用scanForPeripheralWithServices开始搜索具有心脏速率或设备信息服务的蓝牙设备当发现这些设备时,didDiscoverPeripheralperipheral委托方法会被调用


- (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];
    }
}

一个指定服务外围被发现后,委托方法调用与周边对象广播数据RSSI

在这里,您检查,以确保该设备具有一个非空的本地名称,如果是这样,你注销的名称和存储CBPeripheral供日后参考。您也停止扫描设备和中央管理的方法来建立与peripheral对象的连接。


编译运行:

Found the heart rate monitor: Polar H7 252D9F

添加:centralManager:central:peripheral:

你的下一个步骤是确定,如果你已经建立了与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);
}

当你建立一个peripheral的本地连接中央管理器对象调用 centralManager:didConnectPeripheral:

实现上述方法,你先设置你的peripheral对象的代理方法为当前控制器,以便它可以使用通知回调视图控制器如果没有出现错误你下次访问peripheral发现与该设备相关的服务最后决定了外设的当前状态,如果你已经建立了一个连接。

然而,如果该连接尝试失败,则中央管理器对象的调用centralManager:didFailToConnectPeripheral:error:

再次运行在设备上的代码仍然穿着你的心脏速率监视器)并在几秒钟后,你应该看到这个控制台上

Found the heart rate monitor: Polar H7 252D9F
Connected: YES

添加peripheral:didDiscoverServices:

一旦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];
    }
}

在这里,您只需遍历每个发现的服务注销UUID并调用一个方法来发现该服务的特点

构建并运行,这时候你应该看到类似控制台执行以下操作:


Discovered service: Unknown (<180d>)
Discovered service: Device Information
Discovered service: Battery
Discovered service: Unknown (<6217ff49 ac7b547e eecf016a 06970ba9>)

这个未知<180D>值,看看熟悉吗?它应该;它是HREF=https://developer.bluetooth.org/gatt/services/Pages/ServicesHome.aspx>您先前定义的蓝牙规范的服务部分的心脏速率监视器服务ID
#define POLARH7_HRM_HEART_RATE_SERVICE_UUID @"180D"

添加peripheral:didDiscoverCharacteristicsForService:

由于您调用discoverCharacteristicsforService一旦特征被发现peripheraldidDiscoverCharacteristicsForService将被调用因此,替换为以下内容:

- (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:

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
}

添加getHeartRateBPM:(CBCharacteristic *)characteristic error:(NSError *)error

要了解如何从一个特征解释这些数据你必须检查蓝牙规范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;
}

运行,你将看到下面的结果

iOS蓝牙之Introduction to Core Bluetooth: Building a Heart Rate Monitor(翻译)_第4张图片


让你的心跳快一点


恭喜你,你现在有一个可以工作的心脏监测仪以及更重要的是蓝牙核心如何工作的一个很好的理解。可以应用这些相同的技术到各种蓝牙设备上。

在你离开之前我们有一个小的奖金给你。为了好玩,让我们在时间上的心脏跳动心脏监护器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];
}


你可能感兴趣的:(ios,蓝牙)