Swift51.com
麦子学院 头像
麦子学院  2016-10-24 14:27

iOS中蓝牙连接和数据读写详解

回复:0  查看:4651  

当下蓝牙开发可谓是越来越火,不论是智能穿戴的兴起还是蓝牙家具,车联网蓝牙等等,很多同学也会接触到蓝牙的项目。本文也将围绕iOS蓝牙开发,和大家一起聊聊iOS系统中有关蓝牙开发的知识,一起来看看吧。

  蓝牙开发分为中心者模式和管理者模式:1.常用的(其实99.99%)就是使用中心者模式作为开发,就是我们手机作为主机,连接蓝牙外设;2.管理者模式,这个基本用到的比较少,我们手机自己作为外设,自己创建服务和特征,然后有其他的设备连接我们的手机。

  在做蓝牙开发之前,最好先了解一些概念:

  服务(services):蓝牙外设对外广播的必定会有一个服务,可能也有多个,服务下面包含着一些特征,服务可以理解成一个模块的窗口;

  特征(characteristic):存在于服务下面的,一个服务下面也可以存在多个特征,特征可以理解成具体实现功能的窗口,一般特征都会有value,也就是特征值,特征是与外界交互的最小单位;

  UUID:可以理解成蓝牙上的唯一标识符(硬件上肯定不是这个意思,但是这样理解便于我们开发),为了区分不同的服务和特征,或者给服务和特征取名字,我们就用UUID来代表服务和特征。

  蓝牙连接可以大致分为以下几个步骤

  1.建立一个Central Manager实例进行蓝牙管理

  2.搜索外围设备

  3.连接外围设备

  4.获得外围设备的服务

  5.获得服务的特征

  6.从外围设备读数据

  7.给外围设备发送数据

  其他:提醒

  首先我们先导入系统的BLE的框架

  #import

  必须遵守2个协议

  / * 中心管理者 /

  @property (nonatomic, strong) CBCentralManager *cMgr;

  / * 连接到的外设 /

  @property (nonatomic, strong) CBPeripheral *peripheral;

  1.建立一个Central Manager实例进行蓝牙管理

  -(CBCentralManager *)cmgr

  {

  if (!_cmgr) {

  _cMgr = [[CBCentralManager alloc] initWithDelegate:self queue:nil];

  }

  return _cMgr;

  }

  //只要中心管理者初始化 就会触发此代理方法 判断手机蓝牙状态

  - (void)centralManagerDidUpdateState:(CBCentralManager *)central

  {

  switch (central.state) {

  case 0:

  NSLog(@"CBCentralManagerStateUnknown");

  break;

  case 1:

  NSLog(@"CBCentralManagerStateResetting");

  break;

  case 2:

  NSLog(@"CBCentralManagerStateUnsupported");//不支持蓝牙

  break;

  case 3:

  NSLog(@"CBCentralManagerStateUnauthorized");

  break;

  case 4:

  {

  NSLog(@"CBCentralManagerStatePoweredOff");//蓝牙未开启

  }

  break;

  case 5:

  {

  NSLog(@"CBCentralManagerStatePoweredOn");//蓝牙已开启

  // 在中心管理者成功开启后再进行一些操作

  // 搜索外设

  [self.cMgr scanForPeripheralsWithServices:nil // 通过某些服务筛选外设

  options:nil]; // dict,条件

  // 搜索成功之后,会调用我们找到外设的代理方法

  // - (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI; //找到外设

  }

  break;

  default:

  break;

  }

  }

  2.搜索外围设备 (我这里为了举例,采用了自己身边的一个手环)

  // 发现外设后调用的方法

  - (void)centralManager:(CBCentralManager *)central // 中心管理者

  didDiscoverPeripheral:(CBPeripheral *)peripheral // 外设

  advertisementData:(NSDictionary *)advertisementData // 外设携带的数据

  RSSI:(NSNumber *)RSSI // 外设发出的蓝牙信号强度

  {

  //NSLog(@"%s, line = %d, cetral = %@,peripheral = %@, advertisementData = %@, RSSI = %@", __FUNCTION__, __LINE__, central, peripheral, advertisementData, RSSI);

  /*

  peripheral =

  , advertisementData = {

  kCBAdvDataChannel = 38;

  kCBAdvDataIsConnectable = 1;

  kCBAdvDataLocalName = OBand;

  kCBAdvDataManufacturerData =<4c69616e 75="" 0e060678="" a5043853="">;

  kCBAdvDataServiceUUIDs = (

  FEE7

  );

  kCBAdvDataTxPowerLevel = 0;

  }, RSSI = -55

  根据打印结果,我们可以得到运动手环它的名字叫 OBand-75

  */

  // 需要对连接到的外设进行过滤

  // 1.信号强度(40以上才连接, 80以上连接)

  // 2.通过设备名(设备字符串前缀是 OBand)

  // 在此时我们的过滤规则是:OBand前缀并且信号强度大于35

  // 通过打印,我们知道RSSI一般是带-

  if ([peripheral.name hasPrefix:@"OBand"]) {

  // 在此处对我们的 advertisementData(外设携带的广播数据进行一些处理

  // 通常通过过滤,我们会得到一些外设,然后将外设储存到我们的可变数组中,

  // 这里由于附近只有1个运动手环所以我们先按1个外设进行处理

  // 标记我们的外设,让他的生命周期 = vc

  self.peripheral = peripheral;

  // 发现完之后就是进行连接

  [self.cMgr connectPeripheral:self.peripheral options:nil];

  NSLog(@"%s, line = %d", __FUNCTION__, __LINE__);

  }

  }

  3.连接外围设备

  // 中心管理者连接外设成功

  - (void)centralManager:(CBCentralManager *)central // 中心管理者

  didConnectPeripheral:(CBPeripheral *)peripheral // 外设

  {

  NSLog(@"%s, line = %d, %@=连接成功", __FUNCTION__, __LINE__, peripheral.name);

  // 连接成功之后,可以进行服务和特征的发现

  // 设置外设的代理

  self.peripheral.delegate = self;

  // 外设发现服务,nil代表不过滤

  // 这里会触发外设的代理方法 - (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error

  [self.peripheral discoverServices:nil];

  }// 外设连接失败

  - (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error

  {

  NSLog(@"%s, line = %d, %@=连接失败", __FUNCTION__, __LINE__, peripheral.name);

  }

  // 丢失连接

  - (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error

  {

  NSLog(@"%s, line = %d, %@=断开连接", __FUNCTION__, __LINE__, peripheral.name);

  }

  4.获得外围设备的服务 & 5.获得服务的特征

  // 发现外设服务里的特征的时候调用的代理方法(这个是比较重要的方法,你在这里可以通过事先知道UUID找到你需要的特征,订阅特征,或者这里写入数据给特征也可以)

  - (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error{

  NSLog(@"%s, line = %d", __FUNCTION__, __LINE__);

  for (CBCharacteristic *cha in service.characteristics) {

  //NSLog(@"%s, line = %d, char = %@", __FUNCTION__, __LINE__, cha);

  }

  }

  6.从外围设备读数据

  // 更新特征的value的时候会调用 (凡是从蓝牙传过来的数据都要经过这个回调,简单的说这个方法就是你拿数据的唯一方法) 你可以判断是否- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error

  {

  NSLog(@"%s, line = %d", __FUNCTION__, __LINE__);

  if (characteristic == @"你要的特征的UUID或者是你已经找到的特征") {

  //characteristic.value就是你要的数据

  }

  }

  7.给外围设备发送数据(也就是写入数据到蓝牙)

  这个方法你可以放在button的响应里面,也可以在找到特征的时候就写入,具体看你业务需求怎么用啦

  [self.peripherale writeValue:_batteryData forCharacteristic:self.characteristic type:CBCharacteristicWriteWithResponse];//第一个参数是已连接的蓝牙设备 ;第二个参数是要写入到哪个特征; 第三个参数是通过此响应记录是否成功写入

  // 需要注意的是特征的属性是否支持写数据

  - (void)yf_peripheral:(CBPeripheral *)peripheral didWriteData:(NSData *)data forCharacteristic:(nonnull CBCharacteristic *)characteristic

  {

  /*

  typedef NS_OPTIONS(NSUInteger, CBCharacteristicProperties) {

  CBCharacteristicPropertyBroadcast = 0x01,

  CBCharacteristicPropertyRead = 0x02,

  CBCharacteristicPropertyWriteWithoutResponse = 0x04,

  CBCharacteristicPropertyWrite = 0x08,

  CBCharacteristicPropertyNotify = 0x10,

  CBCharacteristicPropertyIndicate = 0x20,

  CBCharacteristicPropertyAuthenticatedSignedWrites = 0x40,

  CBCharacteristicPropertyExtendedProperties = 0x80,

  CBCharacteristicPropertyNotifyEncryptionRequired NS_ENUM_AVAILABLE(NA, 6_0) = 0x100,

  CBCharacteristicPropertyIndicateEncryptionRequired NS_ENUM_AVAILABLE(NA, 6_0) = 0x200

  };

  打印出特征的权限(characteristic.properties),可以看到有很多种,这是一个NS_OPTIONS的枚举,可以是多个值

  常见的又read,write,noitfy,indicate.知道这几个基本够用了,前俩是读写权限,后俩都是通知,俩不同的通知方式

  */

  // NSLog(@"%s, line = %d, char.pro = %d", __FUNCTION__, __LINE__, characteristic.properties);

  // 此时由于枚举属性是NS_OPTIONS,所以一个枚举可能对应多个类型,所以判断不能用 = ,而应该用包含&

  }

  其他:提醒

  有那么多的特征,我们怎么知道哪些特征是用来读数据的,哪些是用来写入的,哪些是需要订阅之后再读的呢?

  如果你们硬件工程师事先告诉你了,或者有完成的开发文档,那么就可以直接知道了,否则你就需要自己去查看特征的属性,推介可以使用下第三方的app——LightBlue,让你更能清楚的看到你蓝牙里面的服务,特征,特征的属性。

 

文章来源:CocoaChina