iOS 蓝牙开发(框架集成和数据交互)

最近几个月都在做蓝牙的项目,趁现在有空,就把在蓝牙开发过程中的心得和踩过的坑给记录下来,分享给大家,避免大家在蓝牙开发过程中能避免踩相同的坑。

网上关于蓝牙开发的原理知识都有了,这里就不在重复累赘,只简单说一下实现过程逻辑:

  1. 首先需要在info.plist添加请求蓝牙功能的权限“NSBluetoothPeripheralUsageDescription”
  2. 创建全局蓝牙管理类
  3. 用蓝牙管理类进行扫描
  4. 从扫描的结果中找到你的蓝牙外设,进行手动或自动连接
  5. 连接成功收扫描外设的服务和特征,每个服务都包含一个特征的数组,这些不同的特征就是用来跟蓝牙外设进行数据通信的对象,特征一般分两种,一种是可读characteristic_notify,一种是可读写characteristic_readWrite。这两个特征蓝牙硬件工程师会告诉你,你只需要找到这两个特征用私有变量保存起来即可,然后把可读特征characteristic_notify设置为notify,你就能收到蓝牙外设发送过来的实时数据。characteristic_readWrite特征就是用来发送数据了。

接下来看一下代码实现:
一. 定义全局蓝牙管理类

  1. 这个类负责蓝牙的扫描,停止扫描,连接,断开连接
    它有一个代理CBCentralManagerDelegate,必须实现它的centralManagerDidUpdateState方法,用来返回蓝牙功能打开和关闭的状态
self.centralManage = [[CBCentralManager alloc] initWithDelegate:self queue:dispatch_get_main_queue()];

这句代码实现后会自动判断你的设备有没有打开蓝牙功能,如果没有回自动弹框让你去设置里打开开关。

  1. 然后我们在centralManagerDidUpdateState代理中实现蓝牙扫描:
#pragma mark - CBCentralManagerDelegate

- (void)centralManagerDidUpdateState:(CBCentralManager *)central{
    if (central.state == CBCentralManagerStatePoweredOn) {
        [self.centralManage scanForPeripheralsWithServices:nil options:nil];
    }else{
        [MBProgressHUD wj_showPlainText:@"蓝牙已关闭" view:nil];
    }
    
}
  1. 扫描结果会在didDiscoverPeripheral返回
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI{
    NSData *data = advertisementData[@"kCBAdvDataManufacturerData"];
    DLog(@"广播数据---------------%@",data);
   if ([peripheral.name isEqualToString:xxx]) {
        // 这里找到你匹配的外设名字
       [self.centralManage connectPeripheral:peripheral options:nil];
    }
    
    NSString *dataStr = [FGDataChangeTool hexadecimalString:data];
    if ([dataStr isEqualToString:xxx]) {
        // 这里找到外设给你发的广播数据匹配
         [self.centralManage connectPeripheral:peripheral options:nil];
    }
}

注意:上面实例代码中的两个if判断条件为非必须,这个需要根据你的业务需求,比如可以根据你的外设名称进行匹配(不靠谱,因为名字可变)、或者让你的蓝牙硬件工程师发一个标识码用来区分。
随便说一句 *由于iOS蓝牙开发不能直接获取蓝牙的Mac地址,如果需要用到Mac地址作为外设的唯一标识码,只能让硬件工程师把Mac地址用广播发送过来,字段还是用“ kCBAdvDataManufacturerData” *
4.1. 连接后,会走didConnectPeripheral代理方法,在这个方法里面需要把peripheral蓝牙外设对象设置一下代理CBPeripheralDelegate,并且实现对服务的扫描:

- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral{

    // 4.对外设进行扫描
    peripheral.delegate = self;
    [peripheral discoverServices:nil];

}

4.2. 断开连接,用户主动断开或者代码执行断开连接都会执行这个方法:

- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(nullable NSError *)error{
    [MBProgressHUD hideAllHUDsForView:kKeyWindow animated:YES];
    [MBProgressHUD wj_showError:@"蓝牙已断开连接" toView:nil];
}

4.3 连接失败:

- (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(nullable NSError *)error{
    [MBProgressHUD hideAllHUDsForView:kKeyWindow animated:YES];
    [MBProgressHUD wj_showError:@"连接失败,请重试" toView:nil];
}

二. 前面4.1我们进行对服务扫描后,如果成功没什么问题就会走CBPeripheralDelegate代理方法didDiscoverServices发现服务,返回的服务是多个,每个服务又包含多个特征,这里只需要把所有的服务遍历然后扫描所有的特征即可。

#pragma mark - CBPeripheralDelegate
// 外设发现服务回调
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(nullable NSError *)error{
        // 保存私有变量peripheral
        self.peripheral = peripheral;
        for (CBService *service in peripheral.services) {
        //4. 1对外设扫描到的服务进行特征扫描
        [self.peripheral discoverCharacteristics:nil forService:service];
        
    }
    
    [self beginRefresh];
   //停止扫描
    [self.centralManager stopScan];
}

扫描特征后会在代理方法返回所有的特征:

// 外设发现特征回调
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error {
    
    
    for (CBCharacteristic *characteristic in service.characteristics) {
        DLog(@"特征---------%@",characteristic);
        if ([characteristic.UUID.UUIDString isEqualToString:@"你的读写特征值"]) {
            self.characteristic_write = characteristic;
            
        }
        else if ([characteristic.UUID.UUIDString isEqualToString:@"你的只读特征值"]){
            self.characteristic_notify = characteristic;
            [self.peripheral setNotifyValue:YES forCharacteristic:self.characteristic_notify];
        }

        //电池特征值,注意打印的时候是显示Battery Level而不是2A19,但是代码中需要用2A19进行匹配。
        else if ([characteristic.UUID.UUIDString isEqualToString:@"2A19"]){
            self.characteristic_Battery = characteristic;
            // 设置readValueForCharacteristic才能获得电量值
            [peripheral readValueForCharacteristic:characteristic];
           // 设置setNotifyValue才能收到电量改变的通知
            [self.peripheral setNotifyValue:YES forCharacteristic:characteristic];
       }
    }

}

三. 蓝牙发送的所有数据都会在这个代理方法didUpdateValueForCharacteristic返回一个NSData对象:

// 当特征的值发生变化时回调
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error {

    for (CBService *sever in peripheral.services) {
        for (CBCharacteristic *character in sever.characteristics) {
            if ([character.UUID.UUIDString isEqualToString:@"2A19"]) {
                DLog(@"电量信息----------%@",character);
            }
        }
    }

        // 其他数据处理
        [self dealWithData];
    
}

四. 上面的代码基本已经完成大部分的需求了,包括蓝牙的连接,断开,扫描设备,保存特征值,如果需要进行下一步的蓝牙数据通讯,只需要用writeValue方法进行发送即可。发送后的同样在上面的didUpdateValueForCharacteristic代理方法接收数据。

Byte *bytes = (Byte *)malloc(6);
    bytes[0] = 0xaa;
    bytes[1] = data1;
    bytes[2] = data2;
    bytes[3] = (Byte)(((bytes[1] & 0xff) + (bytes[2] & 0xff)) / 3);
    bytes[4] = (Byte)(((bytes[1] & 0xff) + (bytes[2] & 0xff)) % 3);
    bytes[5] = 0xdc;
NSData *sendData = [NSData dataWithBytes:sendByte length:6];
[self.peripheral writeValue:sendData forCharacteristic:self.characteristic type:0];

五. 在蓝牙手法数据过程中需要对数据进行解析,数据都是16进制的NSData对象,这些数据可不是像HTTP通讯那样简单传个Json对象给你解析就完事了,需要按照硬件工程师给你的协议进行一一解析,解码成看得懂的数据。这里发一份我在工作中常用到的一些解析工具类,包括一些进制间的转化。喜欢的点个star,这是给我最好的鼓励
iOSBlueTool

最后:我在第二篇介绍蓝牙固件升级的相关操作

iOS 蓝牙开发(固件升级&空中升级)

你可能感兴趣的:(iOS 蓝牙开发(框架集成和数据交互))