最近很闲,没什么事可做,想来不能闲着,所以就想着研究下蓝牙相关的技术,所以就尝试了下,外设和中心设备都是手机,一个手机做当做虚拟外设,一个作为中心设备,尝试连接和通信一切都还顺利。当然也知道在实际开发中会遇到意想不到的问题。过来之人可以一起探讨。
文中所提有不足之处 还望指正。先谢过[抱拳]。

IOS蓝牙技术—CoreBluetooth


  • 一、iOS蓝牙简述
  • 二、CoreBluetooth简介
  • 1、基本概念
  • 2、CoreBluetooth 框架介绍
  • 三、开发模式
  • 1、中心模式
  • 2、外设模式


一、iOS蓝牙简述

iOS开发中关于蓝牙技术的框架有四种:

  • GameKit.framework 多用于游戏开发,iOS设备之间的连接。
  • MultipeerConnectivity.framework iOS设备之间传递文件,点对点的连接。
  • ExternalAccessory.framework 主要是用于和蓝颜2.0(需要苹果认证)设备连接通信的框架。
  • CoreBluetooth.framework 主要用户和蓝牙4.0的设备连接和通讯。蓝牙4.0也叫 BLE。

在这里我们就主要针对蓝牙4.0的开发,也就是iOS的BLE开发。

二、CoreBluetooth简介

1、基本概念
  • 中心设备(central)。主动发起连接的设备。
  • 外设(peripheral)。外部设备,比如耳机,音箱,智能手环。(当然手机也可以是外部设备,中兴设备也可能成为外部设备)。
  • 服务。每个外设都有若干个服务,可以添加和删除服务。外设可以广播服务供中心设备区扫描。每个服务都有一个UUID,用来标识每个服务。
  • 特征。每个服务都有若干个特征,设备之间是通过特征值来进行数据交互的。这些特征值有很多属性(比如:读,写,通知)。只有这个特征设置了相应的属性才能进行相应的操作。如果没有读的属性,中心设备数无法读取到外设的数据的。
  • 特征的属性。每个特征都有它的属性值。不同的属性限制了外设对外提供的服务。读的属性可以有对外提供数据的能力,写属性可以对外提供数据写入特征的能力。
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(10_9, 6_0) = 0x100,
 CBCharacteristicPropertyIndicateEncryptionRequired NS_ENUM_AVAILABLE(10_9, 6_0) = 0x200
};
2、CoreBluetooth 框架介绍
  • CBCentralManager 中心设备管理,检测设备状态,扫描外部设备,链接外部设备等。
  • CBPeripheralManager 外部设备管理,检测外部设备状态,添加/移除服务,广播服务,更新特征值,响应中心设备的读写等。
  • CBPeripheral 外设对象 向外设写/读数据,订阅通知,查找服务,查找服务特征等。
  • CBCentral 中心设备
  • CBMutableService 特征服务
  • CBMutableCharacteristic 特征
  • CBUUID 表示id
  • CBATTError 状态码, 比如响应请求返回状态码。
typedef NS_ENUM(NSInteger, CBATTError) {
 CBATTErrorSuccess NS_ENUM_AVAILABLE(10_9, 6_0) = 0x00,
 CBATTErrorInvalidHandle = 0x01,
 CBATTErrorReadNotPermitted = 0x02,
 CBATTErrorWriteNotPermitted = 0x03,
 CBATTErrorInvalidPdu = 0x04,
 CBATTErrorInsufficientAuthentication = 0x05,
 CBATTErrorRequestNotSupported = 0x06,
 CBATTErrorInvalidOffset = 0x07,
 CBATTErrorInsufficientAuthorization = 0x08,
 CBATTErrorPrepareQueueFull = 0x09,
 CBATTErrorAttributeNotFound = 0x0A,
 CBATTErrorAttributeNotLong = 0x0B,
 CBATTErrorInsufficientEncryptionKeySize = 0x0C,
 CBATTErrorInvalidAttributeValueLength = 0x0D,
 CBATTErrorUnlikelyError = 0x0E,
 CBATTErrorInsufficientEncryption = 0x0F,
 CBATTErrorUnsupportedGroupType = 0x10,
 CBATTErrorInsufficientResources = 0x11
};

三、开发模式

1、中心模式

开发步骤:

  • 创建中心设备管理(CBCentralManager)。
  • 扫描外设。
  • 链接外部设备。
  • 找到外设服务。
  • 找到外设服务特征。
  • 通过特征做数据交互。
  • 交互完毕断开链接。

交互图如下:

ios vlc 蓝牙 ios的蓝牙_写数据

首先创建中心设备管理

NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
                             //蓝牙power没打开时alert提示框
                             [NSNumber numberWithBool:YES],CBCentralManagerOptionShowPowerAlertKey,
                             //重设centralManager恢复的IdentifierKey
                          @"IdentifierKey",CBCentralManagerOptionRestoreIdentifierKey,
                             nil];
self.centralManageer = [[CBCentralManager alloc] initWithDelegate:self queue:nil options:options];

这个时候代理会回调检测设备状态

- (void)centralManagerDidUpdateState:(CBCentralManager *)central{
  if(central.state == CBManagerStateUnknown){
        NSLog(@"设备未知");
        [central stopScan];
    }else if(central.state == CBManagerStateResetting){

    }else if(central.state == CBManagerStateUnsupported){
        NSLog(@"设备不支持");
        [central stopScan];
    }else if(central.state == CBManagerStateUnauthorized){
        NSLog(@"设备未认证");
        [central stopScan];
    }else if(central.state == CBManagerStatePoweredOff){
        NSLog(@"蓝牙不可用");
        [central stopScan];
    }else if(central.state == CBManagerStatePoweredOn){
        NSLog(@"可用");
        // 开始扫面外设
     NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber  numberWithBool:YES], CBCentralManagerScanOptionAllowDuplicatesKey, nil];
    [self.centralManageer scanForPeripheralsWithServices:nil options:options];
    }else {
        NSLog(@"error");

    }
}

扫描到外设的回调

- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary<NSString *, id> *)advertisementData RSSI:(NSNumber *)RSSI{
 //   peripheral 外设对象 (如果扫描到的统一外设,对象也是同一个不是新建的对象)
// advertisementData 外设服务的广播数据 
  // RSSI 信号强度 (可以计算设备的距离)

}

链接外部设备

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

链接回调

- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral{
    NSLog(@"链接成功");
  // 停止扫描
    [self.centralManageer stopScan];
  // 设置代理
    self.perpheral.delegate = self;
}
- (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error{
    NSLog(@"连接失败");
}

查找外设服务

[peripheral discoverServices:nil];

找到外设服务的回调

- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error{
    for (CBService *service in peripheral.services) {
      // 外设服务
    }

}

通过找的外设服务再查找外设服务的特征

[peripheral discoverCharacteristics:nil forService:service];

查找到服务特征的回调

- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error{
   for(CBCharacteristic *character in service.characteristics){
       //character 所有的特征
   }
}

读取特征服务的数据

// 只有特征的属性包含读 才能读取到数据
[peripheral readValueForCharacteristic:character];

特征读取回调(外设服务特征值被修改也会回调到这里)

//    【订阅之后】才会收到特征数据更新的通知
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(nonnull CBCharacteristic *)characteristic error:(nullable NSError *)error{
    NSLog(@"----------characteristic--------:%@",characteristic);
    NSString *sting = [[NSString alloc] initWithData:characteristic.value encoding:NSUTF8StringEncoding]; // 假设是个字符串这样解析,也有可能是其他的数据格式 和一些特殊的信息表示
    NSLog(@"----value:%@",sting);
}

给特征写数据

// data 每一包传输的数据大小有限制,经过测试最大可以传输512个字节,超过就会写失败。
[self.perpheral writeValue:data forCharacteristic:characteristicl type:CBCharacteristicWriteWithResponse];
//  CBCharacteristicWriteWithResponse 需要在写完数据之后得到外部设备的响应 ,如果没有响应处理这里就会写失败

写数据回调

- (void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{
    if(!error){
        NSLog(@"写数据");
    }else{
//         可能写入数据过大或者是写入需要外设响应但是外设没有设置响应,这个时候也会出错
        NSLog(@"写数据失败");
    }
}

订阅特征

// 开启和关闭订阅
//只有订阅开启了之后 外设特征跟新才会通知到中心设备 (中心设备的回调才能接收到)
[self.perpheral setNotifyValue:BOOL forCharacteristic:characteristicl];
// 订阅状态的回调
- (void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{
    if(!error){
    NSLog(@"订阅成功");
    }else{
        NSLog(@"订阅失败");
    }
}
2、外设模式

开发步骤

  • 创建外设管理。
  • 创建外设服务并添加服务。
  • 创建外设特征并给服务添加特征。
  • 广播服务。
  • 处理订阅和读写请求。

交互图如下:

ios vlc 蓝牙 ios的蓝牙_写数据_02

创建外设管理

self.peripheralManage = [[CBPeripheralManager alloc] initWithDelegate:self queue:nil]

// 外设状态回调
  - (void)peripheralManagerDidUpdateState:(CBPeripheralManager *)peripheral{
    switch (peripheral.state) {
        case CBManagerStateUnknown:
            break;
        case CBManagerStateResetting:
            break;
        case CBManagerStateUnsupported:
            break;
        case CBManagerStateUnauthorized:
            break;
        case CBManagerStatePoweredOff:
            break;
        case CBManagerStatePoweredOn:
//              可以添加外设服务
            break;
        default:
            break;
    }
}

创建外设服务,创建特征,服务添加特征

//1、创建服务
CBMutableService *service = [[CBMutableService alloc] initWithType:[CBUUID UUIDWithString:@"你的服务id"] primary:YES];
// 创建特征
 CBMutableCharacteristic *readCharactertistic = [[CBMutableCharacteristic alloc] initWithType:[CBUUID UUIDWithString:@"特征ID"] properties:CBCharacteristicPropertyRead value:nil permissions:CBAttributePermissionsReadable];  // CBCharacteristicPropertyRead 这是一个只可以读取的特征 CBAttributePermissionsReadable 给中心设备读的权限。
// 还可以创建一个特征描述
    CBMutableDescriptor *descriptor = [[CBMutableDescriptor alloc] initWithType:[CBUUID UUIDWithString:CBUUIDCharacteristicUserDescriptionString] value:@"读写特征"];
    [readCharactertistic setDescriptors:@[descriptor]];
// 给服务添加特征
 [service setCharacteristics:@[readCharactertistic]];

给外设添加服务

[self.peripheralManage addService:service];

开始广播特征服务

NSDictionary *dic = @{CBAdvertisementDataLocalNameKey:@"virtual"}; // 这个数据可以在中心设备扫描到外设的时候接收到这个数据
[self.peripheralManage startAdvertising:dic];

// 广播成功的回调
- (void)peripheralManagerDidStartAdvertising:(CBPeripheralManager *)peripheral error:(NSError *)error{
    if(!error){
    NSLog(@"广播成功");
      // 这个时候中心设备就能扫描到外设的服务
    }else{
        NSLog(@"广播失败");
    }
}

中心设备请求处理

- (void)peripheralManager:(CBPeripheralManager *)peripheral central:(CBCentral *)central didSubscribeToCharacteristic:(CBCharacteristic *)characteristic{
//     取消订阅之后才可以再次订阅
    NSLog(@"收到订阅");
// 这个时候我们修改特征值中心设备就会收到特征值更新的通知回调
// 这里的data 6p测试最大是155字节
   [peripheral updateValue:data forCharacteristic:characteristic onSubscribedCentrals:nil];
}
- (void)peripheralManager:(CBPeripheralManager *)peripheral central:(CBCentral *)central didUnsubscribeFromCharacteristic:(nonnull CBCharacteristic *)characteristic{
    NSLog(@"----取消订阅");
  // 做一些需要处理的逻辑
}
- (void)peripheralManager:(CBPeripheralManager *)peripheral didReceiveReadRequest:(CBATTRequest *)request{
    NSLog(@"didReceiveReadRequest");
  // 收到读数据的请求
  // 当中心设备的
    [peripheral respondToRequest:request withResult:CBATTErrorSuccess];
}
- (void)peripheralManager:(CBPeripheralManager *)peripheral didReceiveWriteRequests:(NSArray<CBATTRequest *> *)requests{
  // 收到写数据的请求 如果写数据的时候中心设备需要外设响应的是没有响应中心设备写数据就会失败
    NSLog(@"didReceiveWriteRequests");
    CBATTRequest *request = requests[0];// 这里只处理了第一个....
    NSString *sting = [[NSString alloc] initWithData:request.value encoding:NSUTF8StringEncoding];
    NSLog(@"----value:%@",sting)
    [peripheral respondToRequest:request withResult:CBATTErrorSuccess];
}