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,数据被组织到服务及特点中:
服务是数据和描述特定功能相关联行为的特定集合。一个设备可以有一个以上的服务。心脏监测仪展示心脏速率传感器获得的心脏速率数据,这个行为可以描述为一个服务。


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

例如,刚刚描述可以包含心脏速率传感器的预期身体位置和透射心脏速率的测量数据的另一个特征特性心脏速率的服务。
一个服务可以有多个特点。
下面进一步对图描述服务和特性之间的关系:


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

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

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

下图说明了一个周边服务和特征对象层次的基本结构:


创建的每个服务和特性,必须通过一个唯一的标识符,或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



利用蓝牙框架
打开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:开始搜索具有心脏速率或设备信息服务的蓝牙设备。当发现这些设备时,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];
    }
}

当一个指定服务的外围被发现后,委托方法调用与周边对象广播数据,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:

由于您调用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:

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

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


让你的心跳快一点!


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

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


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值