iOS蓝牙框架介绍 (CoreBluetooth介绍)
CoreBluetooth中涉及以下对象类:
- CBCentralManager:中心设备类
- CBPeripheral:外围设备类
- CBCharacteristic:设备特征类
关于蓝牙开发的一些重要的理论概念:
- CoreBluetooth框架的核心其实是两个东西,peripheral和central, 可以理解成外设和中心,就是你的苹果手机就是中心,外部蓝牙称为外设。
- 服务和特征(service and characteristic):简而言之,外部蓝牙中它有若干个服务service(服 务你可以理解为蓝牙所拥有的能力),而每个服务service下拥有若干个特征characteristic(特征你可以理解为解释这个服务的属性)。
- Descriptor(描述)用来描述characteristic变量的属性。例如,一个descriptor可以规定一个可读的描述,或者一个characteristic变量可接受的范围,或者一个characteristic变量特定的单位。
- UUID:蓝牙上的唯一标示符,为了区分不同设备、服务及特征,就用UUID来表示。
CBCentralMannager 中心模式
以手机(app)作为中心,连接其他外设的场景。详细流程如下:
1.建立一个Central Manager实例进行蓝牙管理
- 扫描外设(discover)
- 连接外设(connect)
- 获得外设中的服务和特征(discover)
4.1 获取外设的services
4.2 获取外设的Characteristics,获取Characteristics的值, - 获取外设服务特征(Characteristics)的Descriptor和Descriptor的值
- 从外围设备读数据(直接读取和订阅两种方法)
- 与外设做数据交互(explore and interact)
- 断开连接(disconnect)
#import
static NSString *kServiceUUID = @"2A61A14B-D4A9-4F98-A260-CDF2A1E8BABA" ;
static NSString *kCharacteristicUUID = @"043A7DE8-BED5-4653-BA72-E33FF166D08D";
@interface KLCentralManager ()
@property (strong,nonatomic) CBCentralManager *rCentralManager;//中心设备管理器
@property (strong,nonatomic) CBPeripheral *rPeripherals;//连接的外围设备
@property(nonatomic,strong)CBCharacteristic *rCharacteristic; //特征
@end
@implementation KLCentralManager
- (instancetype)init
{
self = [super init];
if (self) {
self.rCentralManager = [[CBCentralManager alloc]initWithDelegate:self queue:nil];
}
return self;
}
-(void)startScan {
if (self.rCentralManager.isScanning) {
[self.rCentralManager stopScan ] ;
}
NSLog(@"开始扫描") ;
// Services 不是服务的UUID 也不是特征的UUID 而是设备广播的UUID ,如果不知道设备广播的UUID 就设置为nil
[self.rCentralManager scanForPeripheralsWithServices:nil options:@{CBCentralManagerScanOptionAllowDuplicatesKey:@YES,CBCentralManagerOptionShowPowerAlertKey:@YES}];
//CBCentralManagerScanOptionAllowDuplicatesKey设置为NO表示不重复扫瞄已发现设备,为YES就是允许。CBCentralManagerOptionShowPowerAlertKey设置为YES就是在蓝牙未打开的时候显示弹框
//services 设置为nil,就会扫描所有
// [self.rCentralManager scanForPeripheralsWithServices:@[[CBUUID UUIDWithString:@"2A61A14B-D4A9-4F98-A260-CDF2A1E8BABA"],[CBUUID UUIDWithString:@"1807"],[CBUUID UUIDWithString:@"FE86"],[CBUUID UUIDWithString:@"FE02"],[CBUUID UUIDWithString:@"180D"],[CBUUID UUIDWithString:@"7A483BEC-5F71-FBBB-252E-EDC43FEFE6AC"],[CBUUID UUIDWithString:@"00004A02-0000-1000-8000-00805F9B34FB"],[CBUUID UUIDWithString:@"00003802-0000-1000-8000-00805F9B34FB"]] options:@{
// CBCentralManagerScanOptionAllowDuplicatesKey:@(NO)
// }];
}
//发送数据到外围设备
-(void)sndMessage:(NSString*)message{
NSString *valueStr=[NSString stringWithFormat:@"%@ --%@",@"中心",[NSDate date]];
if ([message stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet ]].length) {
valueStr = message ;
}
NSData *data=[valueStr dataUsingEncoding:NSUTF8StringEncoding];
[self.rPeripherals writeValue:data forCharacteristic:self.rCharacteristic type:CBCharacteristicWriteWithResponse];
}
#pragma mark -CBCentralManagerDelegate
- (void)centralManagerDidUpdateState:(nonnull CBCentralManager *)central {
CBCentralManagerState state = (CBCentralManagerState)central.state ;
switch (state) {
case CBCentralManagerStatePoweredOn:
NSLog(@"蓝牙开启");
break;
case CBCentralManagerStatePoweredOff:
NSLog(@"蓝牙关闭");
break;
case CBCentralManagerStateResetting:
NSLog(@"蓝牙重置中");
break;
case CBCentralManagerStateUnsupported:
NSLog(@"蓝牙不支持");
break;
case CBCentralManagerStateUnauthorized:
NSLog(@"蓝牙未授权");
break;
case CBCentralManagerStateUnknown:
NSLog(@"蓝牙未知状态");
break;
default:
break;
}
}
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI{
NSLog(@"发现外围设备 %@ 信号:%@",peripheral,advertisementData) ;
//停止扫描
[self.rCentralManager stopScan];
if ( peripheral) {
if (self.rPeripherals) {
[self.rCentralManager cancelPeripheralConnection:self.rPeripherals];
}
self.rPeripherals = peripheral ;
//开始连接外围设备
[self.rCentralManager connectPeripheral:peripheral options:nil];
}
}
//连接外围设备成功回调
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral{
NSLog(@"连接外设成功: %@",peripheral) ;
[self startScanServicePeripheral:peripheral] ;
}
-(void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error{
NSLog(@"连接设备失败: %@",peripheral);
}
#pragma mark- peripheral
-(void)startScanServicePeripheral:(CBPeripheral *)peripheral{
//设置外围设备的代理为当前视图控制器
peripheral.delegate = self;
//外围设备开始寻找服务
[peripheral discoverServices:@[[CBUUID UUIDWithString:@"180D"],[CBUUID UUIDWithString:kServiceUUID]]];
}
#pragma mark- CBPeripheralDelegate
//扫描到服务
-(void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error{
NSLog(@"发现服务 %@", peripheral);
if(error){
NSLog(@"外围设备寻找服务过程中发生错误,错误信息:%@",error.localizedDescription);
return;
}
//遍历查找到的服务
CBUUID *serviceUUID=[CBUUID UUIDWithString:kServiceUUID];
CBUUID *characteristicUUID=[CBUUID UUIDWithString:kCharacteristicUUID];
for (CBService *service in peripheral.services) {
if([service.UUID isEqual:serviceUUID]){
//外围设备查找指定服务中的特征
[peripheral discoverCharacteristics:@[characteristicUUID] forService:service];
}
}
}
//扫描到特征
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error{
NSLog(@"已发现可用特征通道 %@", service.characteristics);
if (error) {
NSLog(@"外围设备寻找特征过程中发生错误,错误信息:%@",error.localizedDescription);
return;
}
//遍历服务中的特征
CBUUID *serviceUUID=[CBUUID UUIDWithString:kServiceUUID];
CBUUID *characteristicUUID=[CBUUID UUIDWithString:kCharacteristicUUID];
if ([service.UUID isEqual:serviceUUID]) {
for (CBCharacteristic *characteristic in service.characteristics) {
if ([characteristic.UUID isEqual:characteristicUUID]) {
//需要说明的是characteristic.UUID是硬件定义好给你,如果硬件也是个新手,那你可以先打印出所有的UUID, 找出有用的
// 这里只获取一个特征,写入数据的时候需要用到这个特征
self.rCharacteristic = characteristic ;
//情景一:通知
/*找到特征后设置外围设备为已通知状态(订阅特征):
*1.调用此方法会触发代理方法:-(void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
*2.调用此方法会触发外围设备的订阅代理方法,否则无法收到外设发过来的数据
*/
[peripheral setNotifyValue:YES forCharacteristic:characteristic];
//情景二:读取
[peripheral readValueForCharacteristic:characteristic];
if(characteristic.value){
NSString *value=[[NSString alloc]initWithData:characteristic.value encoding:NSUTF8StringEncoding];
NSLog(@"读取到特征值:%@",value);
}
}
}
}
// for (CBCharacteristic *charater in service.characteristics) {
// if ([charater.UUID.UUIDString isEqual: @"2A37"]) {
// [peripheral setNotifyValue:YES forCharacteristic:charater];
// break;
// }
// }
}
//特征值被更新后
- (void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{
NSLog(@"收到特征更新状态改变: %@",characteristic.isNotifying?@"开启":@"关闭");
if (error) {
NSLog(@"更新通知状态时发生错误,错误信息:%@",error.localizedDescription);
return;
}
//给特征值设置新的值
CBUUID *characteristicUUID=[CBUUID UUIDWithString:kCharacteristicUUID];
if ([characteristic.UUID isEqual:characteristicUUID]) {
if (characteristic.isNotifying) {
if (characteristic.properties==CBCharacteristicPropertyNotify) {
NSLog(@"已订阅特征通知.");
return;
}else if (characteristic.properties ==CBCharacteristicPropertyRead) {
//从外围设备读取新值,调用此方法会触发代理方法:-(void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
[peripheral readValueForCharacteristic:characteristic];
}
}else{
NSLog(@"停止已停止.");
//取消连接
[self.rCentralManager cancelPeripheralConnection:peripheral];
}
}
}
//更新特征值后(调用readValueForCharacteristic:方法或者外围设备在订阅后更新特征值都会调用此代理方法)
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{
if (error) {
NSLog(@"更新特征值时发生错误,错误信息:%@",error.localizedDescription);
return;
}
// 拿到外设发送过来的数据
if (characteristic.value) {
NSString *value=[[NSString alloc]initWithData:characteristic.value encoding:NSUTF8StringEncoding];
NSLog(@"读取到特征值:%@",value);
if (self.rCenterdateValue) {
self.rCenterdateValue(NO, value) ;
}
}else{
NSLog(@"未发现特征值.");
}
}
- (void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{
if (error) {
NSLog(@"写入数据失败 %@",error.localizedDescription) ;
return;
}
NSLog(@"写入数据成功") ;
}
@end
CBPeripheralManager 外设模式
- 建立外设角色
- 设置本地外设的服务和特征
- 发布外设和特征
- 广播服务
- 响应中心的读写请求
- 发送更新的特征值,订阅中心
//自定义 服务和外设数据
static NSString * const kAppService = @"2A61A14B-D4A9-4F98-A260-CDF2A1E8BABA";
static NSString *kAppMeasurement = @"043A7DE8-BED5-4653-BA72-E33FF166D08D";
@interface KLPeripheralManager ()
@property(nonatomic,strong)CBPeripheralManager *rPheralManager;
@property(nonatomic,strong) CBMutableCharacteristic *rCharacter;
@property(nonatomic,strong) CBMutableCharacteristic *rReadWriteCharacter;
@property(nonatomic,strong) NSData *rReadWriteData;
@property(nonatomic,strong) NSMutableArray *rCenterArray;
@end
@implementation KLPeripheralManager
- (instancetype)init
{
self = [super init];
if (self) {
self.rPheralManager = [[CBPeripheralManager alloc]initWithDelegate:self queue:nil] ;
}
return self;
}
// 添加服务后开始广播
-(void)startAdvservice{
NSLog(@"开始广播") ;
[self.rPheralManager startAdvertising:@{
CBAdvertisementDataLocalNameKey:kPeripheralName, //广播名称
CBAdvertisementDataServiceUUIDsKey:@[[CBUUID UUIDWithString:kAppService]
]
}];
}
-(void)stopAdvservice{
NSLog(@"停止广播") ;
[self.rPheralManager stopAdvertising];
}
-(BOOL)rIsAdv{
return self.rPheralManager.isAdvertising;
}
//创建心跳的服务
-(void)setupServices{
// CBUUID *serviceId = [CBUUID UUIDWithString:CBUUIDCharacteristicUserDescriptionString];
CBUUID *serviceId = [CBUUID UUIDWithString:@"180D"];
CBMutableService *service = [[CBMutableService alloc]initWithType:serviceId primary:YES];
//创建服务器中的特征
CBUUID *serid = [CBUUID UUIDWithString:@"2A37"];
CBMutableCharacteristic *character = [[CBMutableCharacteristic alloc]initWithType:serid properties:CBCharacteristicPropertyNotify value:nil permissions:(CBAttributePermissionsReadable | CBAttributePermissionsWriteable)] ;
service.characteristics = @[character] ;
self.rCharacter = character ;
//将服务加入设备
[self.rPheralManager addService:service];
}
//创建自定义读写服务和特征
-(void)setupWirteReadCharater {
CBUUID *serviceId = [CBUUID UUIDWithString:kAppService] ;
CBMutableService *service = [[CBMutableService alloc]initWithType:serviceId primary:YES];
CBUUID *characterId =[CBUUID UUIDWithString:kAppMeasurement ] ;
CBMutableCharacteristic *character = [[CBMutableCharacteristic alloc]initWithType:characterId properties:CBCharacteristicPropertyRead | CBCharacteristicPropertyWrite | CBCharacteristicPropertyNotify value:nil permissions:(CBAttributePermissionsReadable | CBAttributePermissionsWriteable)] ;
service.characteristics = @[character] ;
self.rReadWriteCharacter = character;
[self.rPheralManager addService:service];
}
#pragma mark- CBPeripheralManagerDelegate
- (void)peripheralManagerDidUpdateState:(nonnull CBPeripheralManager *)peripheral {
CBPeripheralState state = (CBPeripheralState)peripheral.state ;
switch (state) {
case CBPeripheralManagerStatePoweredOff:
NSLog(@"蓝牙关闭") ;
break;
case CBPeripheralManagerStatePoweredOn:{
NSLog(@"蓝牙开启");
// [self setupServices];
[self setupWirteReadCharater] ;
}
break;
case CBPeripheralManagerAuthorizationStatusAuthorized:
NSLog(@"蓝牙未授权") ;
break;
case CBPeripheralManagerStateUnsupported:
NSLog(@"蓝牙不支持");
break;
case CBPeripheralManagerStateResetting:
NSLog(@"蓝牙重置中");
break;
case CBPeripheralManagerStateUnknown:
NSLog(@"未知");
break;
default:
break;
}
}
//外围设备添加服务后调用
- (void)peripheralManager:(CBPeripheralManager *)peripheral didAddService:(CBService *)service error:(NSError *)error{
if (error) {
NSLog(@"向外围设备添加服务失败,错误详情:%@",error.localizedDescription);
return;
}
}
- (void)peripheralManagerDidStartAdvertising:(CBPeripheralManager *)peripheral error:(NSError *)error{
if (error) {
NSLog(@"启动广播过程中发生错误,错误信息:%@",error.localizedDescription);
}else {
NSLog(@"启动广播...");
}
}
////订阅特征
- (void)peripheralManager:(CBPeripheralManager *)peripheral central:(CBCentral *)central didSubscribeToCharacteristic:(CBCharacteristic *)characteristic{
NSLog(@"中心设备:%@ 已订阅特征:%@.",central,characteristic);
//发现中心设备并存储
if (![self.rCenterArray containsObject:central]) {
[self.rCenterArray addObject:central];
}
//更新特征值
[self updateCharacteristicValue:@""];
/*中心设备订阅成功后外围设备可以更新特征值发送到中心设备,一旦更新特征值将会触发中心设备的代理方法:
-(void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
*/
}
////取消订阅特征
- (void)peripheralManager:(CBPeripheralManager *)peripheral central:(CBCentral *)central didUnsubscribeFromCharacteristic:(CBCharacteristic *)characteristic{
NSLog(@"取消订阅") ;
}
- (void)peripheralManager:(CBPeripheralManager *)peripheral didReceiveReadRequest:(CBATTRequest *)request{
//读取数值请求
NSLog(@"读取数值请求") ;
if ([request.characteristic.UUID isEqual:self.rReadWriteCharacter.UUID]){
request.value = self.rReadWriteData;
if (self.rPerheruodateValue) {
self.rPerheruodateValue(NO, self.rReadWriteData) ;
}
//反馈请求成功
[peripheral respondToRequest:request withResult:CBATTErrorSuccess];
}
}
- (void)peripheralManager:(CBPeripheralManager *)peripheral didReceiveWriteRequests:(NSArray *)requests{
// 写数值请求
NSLog(@"写数值请求") ;
CBATTRequest *requ = requests.firstObject ;
if( requ && [requ.characteristic.UUID isEqual:self.rReadWriteCharacter.UUID] ){
self.rReadWriteData = requ.value ;
if (self.rPerheruodateValue) {
self.rPerheruodateValue(YES, self.rReadWriteData) ;
}
[peripheral respondToRequest:requ withResult:CBATTErrorSuccess];
}
}
//更新特征值
-(void)updateCharacteristicValue:(NSString*) notStr{
//特征值
NSString *valueStr=[NSString stringWithFormat:@"%@ --%@",kPeripheralName,[NSDate date]];
if ([notStr stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet ]].length) {
valueStr = notStr ;
}
NSData *value=[valueStr dataUsingEncoding:NSUTF8StringEncoding];
//更新特征值
[self.rPheralManager updateValue:value forCharacteristic:self.rReadWriteCharacter onSubscribedCentrals:nil];
}
@end
链接:https://www.jianshu.com/p/94d9fbfc536d