iOS蓝牙的使用

一、结构图

Paste_Image.png

二、服务和特征

每个设备都会有一些服务,每个服务里面都会有一些特征,特征就是具体键值对,提供数据的地方。每个特征属性分为这么几种:读,写,通知这么几种方式。

外设、服务、特征间的关系

Paste_Image.png

蓝牙中心模式流程

  1. 建立中心角色
  2. 扫描外设(discover)
  3. 连接外设(connect)
  4. 扫描外设中的服务和特征(discover)
    • 4.1 获取外设的services
    • 4.2 获取外设的Characteristics,获取Characteristics的值,获取Characteristics的Descriptor和Descriptor的值
  5. 与外设做数据交互(explore and interact)
  6. 订阅Characteristic的通知
  7. 断开连接(disconnect)

蓝牙外设模式流程

  1. 启动一个Peripheral管理对象
  2. 本地Peripheral设置服务,特性,描述,权限等等
  3. Peripheral发送广告
  4. 设置处理订阅、取消订阅、读characteristic、写characteristic的委托方法

三、创建Peripheral

3.1 导入头文件、设置代理、设置宏
//这里的uuid是通过mac终端生成的
#define kServiceUUID        @"51BE80A3-9A24-4777-8180-3DBA54A0786A" //服务的UUID
#define kCharacteristicUUID @"ABA43AD0-6DA8-44C5-ABA5-9301092B8B79" //特征的UUID

@interface ViewController ()<CBPeripheralManagerDelegate>
@property (nonatomic, strong) CBPeripheralManager *peripheralManager;
@property (nonatomic, strong) CBMutableCharacteristic *customCharacteristic;
@property (nonatomic, strong) CBMutableService *customService;
@property (strong,nonatomic) NSMutableArray *centralM;
@property (weak, nonatomic) IBOutlet UITextView *lof;
@end

- (void)viewDidLoad {
    [super viewDidLoad];
    // ① 创建Peripheral Manager
    self.peripheralManager = [[CBPeripheralManager alloc] initWithDelegate:self queue:nil];
}

3.2 调用代理方法

//判断当前设备蓝牙是否可用,如果可用,调用setupService方法
-(void)peripheralManagerDidUpdateState:(CBPeripheralManager *)peripheral{
    NSLog(@"%ld",(long)peripheral.state);
    switch (peripheral.state) {
        case CBPeripheralManagerStatePoweredOn:
            [self setupService];
            break;
        case CBPeripheralManagerStateResetting:
            NSLog(@"CBPeripheralManagerStateResetting");
            break;
        case CBPeripheralManagerStateUnsupported:
            NSLog(@"CBPeripheralManagerStateUnsupported");
            break;
        default:
            NSLog(@"Peripheral Manager did change state");
            break;
        }
}

//创建Serivice和Characteristic 并添加到Peripheral
- (void)setupService{
    NSLog(@"setupService!!");
    [self writeToLog:@"init setupService..."];
    //特征值
    CBUUID *characteristicUUID = [CBUUID UUIDWithString:kCharacteristicUUID];
    // 这里 properties 和 permissions 代表着不同的权限 按照业务需要添加 比如 读写操作等
    self.customCharacteristic = [[CBMutableCharacteristic alloc] initWithType: characteristicUUID properties:CBCharacteristicPropertyWrite | CBCharacteristicPropertyRead | CBCharacteristicPropertyNotify value:nil permissions:CBAttributePermissionsWriteable | CBAttributePermissionsReadable];
    
    CBUUID *serviceUUID = [CBUUID UUIDWithString:kServiceUUID];
    self.customService = [[CBMutableService alloc] initWithType:serviceUUID primary:YES];
    
    [self.customService setCharacteristics:@[self.customCharacteristic]];
    
    [self.peripheralManager addService:self.customService];
}

当调用addService方法的时候,会调用代理方法 didAddService

- (void)peripheralManager:(CBPeripheralManager *)peripheral didAddService:(CBService *)service error:(NSError *)error {
    [self writeToLog:@"init didAddService..."];
    NSLog(@"init didAddService");
    if (error == nil) {
        // 外围设备开始广播服务 这里指定ServiceUUID用于中心设备找到他
    [self.peripheralManager startAdvertising:
        @{ CBAdvertisementDataLocalNameKey : kPeripheralName, CBAdvertisementDataServiceUUIDsKey :@[[CBUUID UUIDWithString:kServiceUUID]] }];
    }
}

开启广播后,会调用另外一个代理方法 peripheralManagerDidStartAdvertising

-(void)peripheralManagerDidStartAdvertising:(CBPeripheralManager *)peripheral error:(NSError *)error{
    
    if(error){
        NSLog(@"error====%@",error);
    }else{
        [self writeToLog:@"init peripheralManagerDidStartAdvertising..."];
        NSLog(@"init peripheralManagerDidStartAdvertising");
    }
}

此时,就等待central设备连接,如果连接成功,会调用代理方法 didSubscribeToCharacteristic

-(void)peripheralManager:(CBPeripheralManager *)peripheral central:(CBCentral *)central didSubscribeToCharacteristic:(CBCharacteristic *)characteristic{
    
    [self updateCharacteristicValue];
    if (![self.centralM containsObject:central]) {
        [self.centralM addObject:central];
    }
#     [self writeToLog:@"init didSubscribeToCharacteristic"];
    NSLog(@"init didSubscribeToCharacteristic");
}

如果central想要对我们的characteristic进行读写操作,会调用下面的代理方法,这里会有权限设置,需要在前面创建的时候指定

-(void)peripheralManager:(CBPeripheralManager *)peripheral didReceiveWriteRequests:(NSArray<CBATTRequest *> *)requests{
    NSLog(@"didReceiveWriteRequests");
    CBATTRequest *request = requests[0];
    //判断是否有写数据的权限
    
    if (request.characteristic.properties & CBCharacteristicPropertyWrite) {
        //需要转换成CBMutableCharacteristic对象才能进行写值
        CBMutableCharacteristic *c =(CBMutableCharacteristic *)request.characteristic;
        c.value = request.value;
        
        NSString *dataStr = [[NSString alloc]initWithData:request.value encoding:NSUTF8StringEncoding];
        
        [self writeToLog:[NSString stringWithFormat:@"收到central发来的数据%@",dataStr]];
        
        [self.peripheralManager respondToRequest:request withResult:CBATTErrorSuccess];
    }else{
        [self.peripheralManager respondToRequest:request withResult:CBATTErrorWriteNotPermitted];
    }
    
}

-(void)peripheralManager:(CBPeripheralManager *)peripheral didReceiveReadRequest:(CBATTRequest *)request{
    NSLog(@"didReceiveReadRequest");
    //判断是否有读数据的权限
    if (request.characteristic.properties & CBCharacteristicPropertyRead) {
        NSData *data = request.characteristic.value;
        [request setValue:data];
        //对请求作出成功响应
        [self.peripheralManager respondToRequest:request withResult:CBATTErrorSuccess];
    }else{
        [self.peripheralManager respondToRequest:request withResult:CBATTErrorWriteNotPermitted];
    }
}

四、创建central

4.1导入头文件,创建对象,设置代理
#import "ClientViewController.h"
#import <CoreBluetooth/CoreBluetooth.h>

@interface ClientViewController () <CBCentralManagerDelegate, CBPeripheralDelegate>
@property (nonatomic, strong) CBCentralManager *manager;
@end
- (void)viewDidLoad {  
    [super viewDidLoad];  
    self.manager = [[CBCentralManager alloc] initWithDelegate:self queue:nil];
}
4.2调用代理方法 CBCentralManagerDelegate(主要用于连接)
//初始化的时候回调用此代理方法检查蓝牙状态
- (void)centralManagerDidUpdateState: (CBCentralManager *)central {
    switch (central.state) {
        case CBCentralManagerStatePoweredOn:
            // Scans for  peripheral with uuid
            NSLog(@"自动搜索!");
            [self writeToLog:@"正在搜索..."];
            [self.manager scanForPeripheralsWithServices:@[[CBUUID UUIDWithString:kServiceUUID]] options:@{CBCentralManagerScanOptionAllowDuplicatesKey : @YES }];
            break;
        case CBCentralManagerStatePoweredOff:
            NSLog(@"蓝牙没有打开,请先打开蓝牙");
            break;
        case CBCentralManagerStateUnsupported:
            NSLog(@"当前设备不支持蓝牙");
            break;
        default:
            NSLog(@"Central Manager did change state");
        break;
    }
}

如果蓝牙可用,就开始扫描设备 通过下面方法,这里的kServiceUUID是在Peripheral端定义好的,用于找我们需要的service

[self.manager scanForPeripheralsWithServices:@[[CBUUID UUIDWithString:kServiceUUID]] options:@{CBCentralManagerScanOptionAllowDuplicatesKey : @YES }];

如果找到了对于的服务,就会调用代理方法didDiscoverPeripheral

    -(void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary<NSString *,id> *)advertisementData RSSI:(NSNumber *)RSSI{
    NSLog(@"init didDiscoverPeripheral");
    [self writeToLog:@"发现peripheral..."];
    
    if (self.peripheral != peripheral) {
        self.peripheral = peripheral;
        NSLog(@"Connecting to peripheral %@", peripheral); 
        // 这里尝试连接我们发现的 peripheral
        [self.manager connectPeripheral:peripheral options:nil];
    }
}

连接成功和连接失败都会调用对应的代理方法

-(void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error{
    NSLog(@"init didFailToConnectPeripheral");
    [self writeToLog:@"连接peripheral失败..."];
}

-(void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral{
    NSLog(@"init didConnectPeripheral");
    // Stops scanning for peripheral
    [self.manager stopScan];
    [self writeToLog:@"连接peripheral成功..."];
    
    // Clears the data that we may already have
    [self.data setLength:0]; 
    
    // Sets the peripheral delegate
    [self.peripheral setDelegate:self];
    
    // Asks the peripheral to discover the service
    [self.peripheral discoverServices:@[ [CBUUID UUIDWithString:kServiceUUID] ]];
}

连接成功后,根据对于的UUID去寻找对于的服务,这里的UUID在Peripheral端定义好的,如果发现服务后会调用didDiscoverServices方法

4.3调用代理方法 CBPeripheralDelegate(主要用于和peripheral进行交互)
- (void)peripheral:(CBPeripheral *)aPeripheral didDiscoverServices:(NSError *)error {
  if (error) {
      NSLog(@"Error discovering service: %@", [error localizedDescription]);
//        [self cleanup];
      return;
  }
  for (CBService *service in aPeripheral.services) {
      NSLog(@"Service found with UUID: %@",service.UUID);
      // Discovers the characteristics for a given service
      // 这里可以对特定service 找到特定characteristics 
      if ([service.UUID isEqual:[CBUUID UUIDWithString:kServiceUUID]]) {
          [self writeToLog:[NSString stringWithFormat:@"发现service,service.UUID:%@",service.UUID]];
          [self.peripheral discoverCharacteristics:@[[CBUUID UUIDWithString: kCharacteristicUUID]] forService:service];
      }
  }
}

如果找到对于的charcateristic的话,会调用对于代理方法,可以在这里面进行处理

- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService: (CBService *)service error:(NSError *)error {
  if (error) {
      NSLog(@"Error discovering characteristic:%@", [error localizedDescription]);
//        [self cleanup];
            return;
      }
  if ([service.UUID isEqual:[CBUUID UUIDWithString: kServiceUUID]]) {
      for (CBCharacteristic *characteristic in service.characteristics) {
          NSLog(@"遍历服务!");
          [self writeToLog:@"遍历characteristics..."];
              if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:kCharacteristicUUID]]) {
                  self.characteristic = characteristic;
                  //1.调用此方法会调用didUpdateNotificationStateForCharacteristic 这个代理方法
                  [peripheral setNotifyValue:YES forCharacteristic:characteristic];
                  //2.或者不调用代理方法 直接读取
//                    [peripheral readValueForCharacteristic:characteristic];
//                    if (characteristic.value) {
//                        NSString *value = [[NSString alloc]initWithData:characteristic.value encoding:NSUTF8StringEncoding];
//                    }
          }
      }
  }
}

如果我们用方式1 对我们的characteristic 进行处理的话,会调用对应代理方法didUpdateNotificationStateForCharacteristic

- (void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic: (CBCharacteristic *)characteristic error:(NSError *)error {
  if (error) {
      NSLog(@"Error changing notification state:%@", error.localizedDescription);
  }
   // Exits if it's not the transfer characteristic
   if (![characteristic.UUID isEqual:[CBUUID UUIDWithString:kCharacteristicUUID]]) {
       return;
  }
   // Notification has started
  if (characteristic.isNotifying) {
      [self writeToLog:@"监听characteristic的变化..."];
          NSLog(@"Notification began on %@", characteristic);
          // 这里我们进行readValueForCharacteristic 操作,会调用对于的代理方法didUpdateValueForCharacteristic
          [peripheral readValueForCharacteristic:characteristic];
     } else { // Notification has stopped
          // so disconnect from the peripheral
          NSLog(@"Notification stopped on %@. Disconnecting", characteristic);
         [self.manager cancelPeripheralConnection:self.peripheral];
    }
}

// 在这个方法中我们能拿到由peripheral传来的特定characteristic
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{
  NSString *dataStr = [[NSString alloc]initWithData:characteristic.value encoding:NSUTF8StringEncoding];
  [self writeToLog:[NSString stringWithFormat:@"读取characteristic.value = %@",dataStr]];
  NSLog(@"characteristic===%@",characteristic);
}
4.4传递数据给peripheral
- (IBAction)writeData:(id)sender {
  NSString *valueStr =[NSString stringWithFormat:@"UgenCentral--%@",[NSDate date]];
  NSData *value = [valueStr dataUsingEncoding:NSUTF8StringEncoding];
  NSLog(@"self.characteristic===%@",self.characteristic);
  //通过下面这个方法可以给peripheral更新特性 如果type 选择withResponse的话,还会调用下面的代理方法
  [self.peripheral writeValue:value forCharacteristic:self.characteristic type:CBCharacteristicWriteWithResponse];
  [self writeToLog:[NSString stringWithFormat:@"central端更新特性:%@",valueStr]];
}

//会告诉你是否成功 
//我们刚刚穿的数据可以在 第三节的 didReceiveWriteRequests代理方法中找到
-(void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{
  if (error) {
      NSLog(@"error wtrting characteristic value: %@",[error localizedDescription]);
  }
  [self writeToLog:@"成功给外围设备写数据"];
}

参考代码 https://git.oschina.net/jxjxwujie/IOS_bluetooth.git

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

推荐阅读更多精彩内容