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 <CoreBluetooth/CoreBluetooth.h>
 
static NSString *kServiceUUID = @"2A61A14B-D4A9-4F98-A260-CDF2A1E8BABA" ;
static NSString *kCharacteristicUUID = @"043A7DE8-BED5-4653-BA72-E33FF166D08D";

@interface KLCentralManager ()<CBCentralManagerDelegate,CBPeripheralDelegate>

@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<NSString *,id> *)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 ()<CBPeripheralManagerDelegate>

@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<CBATTRequest *> *)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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,445评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,889评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,047评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,760评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,745评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,638评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,011评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,669评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,923评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,655评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,740评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,406评论 4 320
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,995评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,961评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,023评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,483评论 2 342

推荐阅读更多精彩内容