美文网首页
02.BLE连接流程及外设搭建

02.BLE连接流程及外设搭建

作者: 越来越胖了 | 来源:发表于2020-05-07 16:02 被阅读0次

首先给demo的下载链接:DEMO 提取码:5tsm 如果链接失效请留言,看到后会及时更新.

蓝牙连接的大致流程:

  1. APP与硬件进行连接、扫描硬件,要把手机作为central来使用,首先创建中心对象完成初始化:
CBCentralManager * central =[[CBCentralManager alloc]initWithDelegate:self queue:queue];
  1. 初始化后会调用代理CBCentralManagerDelegate
    - (void)centralManagerDidUpdateState:(CBCentralManager *)central方法,在这个方法里CBCentralManagerState是个枚举,可以利用central.state来判断蓝牙开启、关闭、设备是否支持等等。
  2. 想要连接硬件,首先要扫描,就一句话扫描所有硬件(可以传入指定的Services缩小扫描的结果)
[central scanForPeripheralsWithServices:nil options:nil];
  1. 扫描完成后,一旦有peripheral被搜寻到,会调用如下方法
    -(void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI
    此方法可以获取扫描到的硬件里的所有数据,可以对数据进行分析,保存我们需要的设备;

  2. 连接自己想要连接的硬件

    • 停止扫描:[self.myCenter stopScan];
    • 连接:
      -(void)connectPeripheral:(CBPeripheral *)peripheral options:(nullable NSDictionary<NSString *, id> *)options
      到目前为止你就成功的连接到了想要连接的指定硬件了,接下来就是要进行对硬件的读与写了。
  3. 调用连接方法后,连接成功和失败都有回调:

      1. 如果连接成功会调用如下方法:
        - (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral
        在此方法里你就要停止扫描了(连接前调用了,这里就可以不调用):- (void)stopScan
        还要寻找连接设备里的服务:
        - (void)discoverServices:(nullable NSArray<CBUUID *> *)serviceUUIDs
        这里的参数:传入对应的serviceUUIDs 如果为nil,则all services will be discovered
      1. 如果连接失败会调用此方法:
        - (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error
        (前面如果没有停止扫描,这里要进行停止扫描stopScan)
  4. 外设连接之后,找到该设备上的指定服务时会调用CBPeripheralDelegate方法:
    (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error
    如果前面没有指定具体的服务,这里会得到所有的服务,我们可以根据service.UUID去判断需要对那些服务做处理;
    通过下面的方法发现服务中的characteristics(特征码):
    -(void)discoverCharacteristics:(nullable NSArray<CBUUID *> *)characteristicUUIDs forService:(CBService *)service

  5. 上面这个方法被调用,找到特征之后调用这个方法:
    - (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error
    在此方法里遍历service.characteristics用CBCharacteristic来接收;
    如果特征是 read 的要调用:
    - (void)readValueForCharacteristic:(CBCharacteristic *)characteristic
    如果特征是 notify 的要进行订阅,调用方法:
    - (void)setNotifyValue:(BOOL)enabled forCharacteristic:(CBCharacteristic *)characteristic
    如果特征是写,则我们需要保存这个writeCharact,因为在我们进行写操作时需要使用到它;

  6. 当setNotifyValue方法调用后会调用如下方法:
    -(void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
    进行判断characteristic是否为isNotifying,如果是 yes就调用:
    - (void)readValueForCharacteristic:(CBCharacteristic *)characteristic
    是的,这里没有搞错,notify的特征在订阅后要调用readValueForCharacteristic,则使得read和notify的消息回调都在一个方法中得到;

  7. 调用完readValueForCharacteristic:方法后会调用如下方法:
    -(void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
    此方法获取characteristic.value,这个 value 就是我们想要的notify(read)的值了。

  8. 如果连接上的设备突然断开,会自动回调下面的方法:
    -(void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error
    在此方法里就可以进行断线重连了:
    - (void)connectPeripheral:(CBPeripheral *)peripheral options:(nullable NSDictionary<NSString *, id> *)options

上面是 read 和notify特征的操作,那么特征为write又改如何操作呢,下面开始介绍 write 的操作;

  1. 往硬件里写数据要手动调用:
    - (void)writeValue:(NSData *)data forCharacteristic:(CBCharacteristic *)characteristic type:(CBCharacteristicWriteType)type
    CoreBluetooth框架还提供了检测是否写入成功的方法:
    -(void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
    写入成功时,可以再次调用readValueForCharacteristic:方法,接收写入后设备端的响应;
    以上步骤来自:传送门

具体的代码可以看上传的demo,下面是如何搭建一个外设(自己搭建一个外设,意味着我们可以使用两部手机,一个中心一个外设进行数据接收发送了,demo也在上面的下载中)

搭建外设

首先,我们要知道一点,一个外设可以有多个服务,一个服务可以有多个特征(1--->n--->n)图就不画了

导入 #import <CoreBluetooth/CoreBluetooth.h> ,遵从协议<CBPeripheralManagerDelegate>,创建外设管理器,会回调peripheralManagerDidUpdateState方法,必须实现此委托方法,以确保支持蓝牙低功耗并可在本地外围设备上使用

#import <CoreBluetooth/CoreBluetooth.h>
@interface ViewController ()<CBPeripheralManagerDelegate>
@property (weak, nonatomic) IBOutlet UITextField *textField;
@property (nonatomic, strong) CBPeripheralManager *peripheralManager;
@property (nonatomic, strong) CBMutableCharacteristic *characteristic;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.peripheralManager = [[CBPeripheralManager alloc] initWithDelegate:self queue:dispatch_get_main_queue()];
    
    NSLog(@"初始化外设管理器");
    
}   

设备的蓝牙状态
CBManagerStateUnknown = 0, 未知
CBManagerStateResetting, 重置中
CBManagerStateUnsupported, 不支持
CBManagerStateUnauthorized, 未验证
CBManagerStatePoweredOff, 未启动
CBManagerStatePoweredOn, 可用

回调方法,如果外设是可用的,可以进行服务和特征的创建了,这里就创建一个服务,服务中就包含一个特征,实际中我们的蓝牙都是包含多个服务多个特征的;

- (void)peripheralManagerDidUpdateState:(CBPeripheralManager *)peripheral {
    if (peripheral.state == CBManagerStatePoweredOn) {
        DLog(@"蓝牙状态为-->CBManagerStatePoweredOn");
        // 创建Service(服务)和Characteristics(特征)
        [self setupServiceAndCharacteristics];
    }else{
        DLog(@"😡😡当前蓝牙状态为=== %ld 无法作为外设",(long)peripheral.state);
    }
}

创建Service(服务)和Characteristics(特征)

#define SERVICE_UUID @"CDD1"  //服务的UUDI
#define CHARACTERISTIC_UUID @"CDD2" //特征的UUID
#pragma mark 创建服务和特征
- (void)setupServiceAndCharacteristics {
    // 创建服务
    CBUUID *serviceID = [CBUUID UUIDWithString:SERVICE_UUID];
    
    CBMutableService *service = [[CBMutableService alloc] initWithType:serviceID primary:YES];
    
    // 创建服务中的特征
    CBUUID *characteristicID = [CBUUID UUIDWithString:CHARACTERISTIC_UUID];
    
    CBMutableCharacteristic *characteristic = [
                                               [CBMutableCharacteristic alloc]
                                               initWithType:characteristicID
                                               properties:
                                               CBCharacteristicPropertyRead |
                                               CBCharacteristicPropertyWrite |
                                               CBCharacteristicPropertyNotify
                                               value:nil
                                               permissions:CBAttributePermissionsReadable |
                                               CBAttributePermissionsWriteable
                                               ];
   
    // 特征添加进服务
    service.characteristics = @[characteristic];
    // 服务加入外设管理
    [self.peripheralManager addService:service];  //这个方法被调用后,管理者会通知他的代理方法-peripheralManager:didAddService:error:  如果没有Error,你可以开始广播服务了
    
    // 为了手动给中心设备发送数据 ,自己持有这个特征
    self.characteristic = characteristic;
}

特征创建说明:

  • 每一个服务和特征都需要用一个UUID(unique identifier)去标识,UUID是一个16bit或者128bit的值。如果你要创建你的中央-周边App,你需要创建你自己的128bit的UUID;
  • UUID可用终端指令: uuidgen 生成,或者自己写一个16bit的,比如这里的 CDD1 CDD2;
  • 初始化特征方法的第三个参数赋值nil,是因为我告诉CoreBluetooth,我将稍后添加一个特征的值。当你想要创建一个动态的数据,一般都这么做。如果你的特征的值是静态的,你可以将它赋值在这.
  • 如果是动态的值,通过订阅Notify,可以在数据的回调函数得到实时数据;
    最后一个参数是属性的读、写、加密的权限;
  • 外设 1--->n 服务 1---->n 特征 (都是一对多的关系)

服务特征创建成功,调用如下方法进行广播:

-(void)peripheralManager:(CBPeripheralManager *)peripheral didAddService:(CBService *)service error:(NSError *)error{
    
    if (error == nil) {
        // 根据服务的UUID开始广播
        [self.peripheralManager startAdvertising:@{CBAdvertisementDataServiceUUIDsKey:@[[CBUUID UUIDWithString:SERVICE_UUID]]}];
    }else{
        DLog(@"🤣🤣服务添加到外设Manager失败,原因 ---> %@",error.localizedDescription);
    }
}

到这里,其实中央就可以扫描到我们这个外设了;

下面的代理方法是一些中央的数据回调和外设的数据发送等,具体看代码注释:

    /** 中心设备读取数据的时候回调 */
- (void)peripheralManager:(CBPeripheralManager *)peripheral didReceiveReadRequest:(CBATTRequest *)request {
    // 请求中的数据,这里把文本框中的数据发给中心设备
    request.value = [self.textField.text dataUsingEncoding:NSUTF8StringEncoding];
    // 成功响应请求
    [peripheral respondToRequest:request withResult:CBATTErrorSuccess];
}

/** 中心设备写入数据的时候回调 */
- (void)peripheralManager:(CBPeripheralManager *)peripheral didReceiveWriteRequests:(NSArray<CBATTRequest *> *)requests {
    // 写入数据的请求
    CBATTRequest *request = requests.lastObject;
    
    NSLog(@"接收到数据%@",request.value);
    //把写入的数据显示在文本框中
    self.textField.text = [[NSString alloc] initWithData:request.value encoding:NSUTF8StringEncoding];
}

/** 当连接的中心 订阅某个特征的值时,调用 */
-(void)peripheralManager:(CBPeripheralManager *)peripheral central:(CBCentral *)central didSubscribeToCharacteristic:(CBCharacteristic *)characteristic {
    NSLog(@"%s",__FUNCTION__);
    
}

/**
 取消订阅回调
 */
-(void)peripheralManager:(CBPeripheralManager *)peripheral central:(CBCentral *)central didUnsubscribeFromCharacteristic:(CBCharacteristic *)characteristic {
    NSLog(@"%s",__FUNCTION__);
}
- (IBAction)didClickPsot:(id)sender {
    
    BOOL sendSuccess = [self.peripheralManager updateValue:[self.textField.text dataUsingEncoding:NSUTF8StringEncoding] forCharacteristic:self.characteristic onSubscribedCentrals:nil];//发送数据---> 中央需要通过notify接收;
    if (sendSuccess) {
        NSLog(@"数据发送成功");
    }else {
        NSLog(@"数据发送失败");
    }
    
}

到这里,我们就可以通过外设的方法:

[self.peripheralManager updateValue:[self.textField.text dataUsingEncoding:NSUTF8StringEncoding] forCharacteristic:self.characteristic onSubscribedCentrals:nil];像中央发送数据了,这就是蓝牙想我们的手机传输数据的原理了.

因为手机(中央)订阅了我们的特征,所以我们发送的数据能够在中央的代理方法中接收到,这里就是讲了一个大致流程,具体的可以用两部手机跑下demo中的BEL_Center 和BEL_Peripheral感受下.

其实由于IOS的CoreBluetooth的封装,我们集成蓝牙是非常简便的,麻烦的是后面蓝牙发送的数据,前端进行解析时,有时候真的非常非常的麻烦,所以我在前一篇中对蓝牙的一些基本的Byte数组的解析进行了一次归纳,有兴趣的可以看看:01.BLE-数据处理,Byte数组,malloc,位操作

相关文章

网友评论

      本文标题:02.BLE连接流程及外设搭建

      本文链接:https://www.haomeiwen.com/subject/ttjaghtx.html