iOS 蓝牙开发笔记

iOS蓝牙框架介绍 (CoreBluetooth介绍)

CoreBluetooth中涉及以下对象类:

  • CBCentralManager:中心设备类
  • CBPeripheral:外围设备类
  • CBCharacteristic:设备特征类

关于蓝牙开发的一些重要的理论概念:

  1. CoreBluetooth框架的核心其实是两个东西,peripheral和central, 可以理解成外设和中心,就是你的苹果手机就是中心,外部蓝牙称为外设。
  2. 服务和特征(service and characteristic):简而言之,外部蓝牙中它有若干个服务service(服 务你可以理解为蓝牙所拥有的能力),而每个服务service下拥有若干个特征characteristic(特征你可以理解为解释这个服务的属性)。
  3. Descriptor(描述)用来描述characteristic变量的属性。例如,一个descriptor可以规定一个可读的描述,或者一个characteristic变量可接受的范围,或者一个characteristic变量特定的单位。
  4. UUID:蓝牙上的唯一标示符,为了区分不同设备、服务及特征,就用UUID来表示。

CBCentralMannager 中心模式

以手机(app)作为中心,连接其他外设的场景。详细流程如下:
1.建立一个Central Manager实例进行蓝牙管理

  1. 扫描外设(discover)
  2. 连接外设(connect)
  3. 获得外设中的服务和特征(discover)
    4.1 获取外设的services
    4.2 获取外设的Characteristics,获取Characteristics的值,
  4. 获取外设服务特征(Characteristics)的Descriptor和Descriptor的值
  5. 从外围设备读数据(直接读取和订阅两种方法)
  6. 与外设做数据交互(explore and interact)
  7. 断开连接(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 外设模式

  1. 建立外设角色
  2. 设置本地外设的服务和特征
  3. 发布外设和特征
  4. 广播服务
  5. 响应中心的读写请求
  6. 发送更新的特征值,订阅中心
//自定义 服务和外设数据
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

你可能感兴趣的:(iOS 蓝牙开发笔记)