iOS 蓝牙开发

最近项目应用到蓝牙与Glass端传输数据,所以写下收货与开发中注意的事项。

我没有使用原生的Bluetooth.framework 而是使用 BabyBluetooth https://github.com/coolnameismy/BabyBluetooth 它将各种事件封装成block和链式语法,很方便使用。

蓝牙开发原理看BabyBluetooth作者博客,非常详细。

数据交互

使用蓝牙通知接受数据。使用自带的写数据。每帧20字节,每帧间隔50毫秒
使用自定义的协议,分发送头,发送体。
发送头 是Byte数组,第0位有个边标志0,代表头。1-4位代表此次发送的总字节。
发送体 byte数组,第1个字节1,代表体。其余19个字节为内容,多次发送。

以下核心代码

/**
 *  根据data,组织蓝牙发送数据
 *
 *  @param data data
 *
 *  @return 组织后的dic.
 *  headData:  头部数据
 *  bodyArray: 内容数据
 *  sendCount: 发送数量,头部数据
 *  蓝牙数据约定:头部数据第一位value = 1 内容数据第一个字节 value = 2做标志
 */
- (NSMutableDictionary *)organizationSendData:(NSData *)data
{
    NSMutableDictionary *dic = [NSMutableDictionary dictionary];
    NSMutableArray *bodyArray = [NSMutableArray array];
    dic[@"bodyArray"] = bodyArray;
    
    //---------- 发送头部数据 -----------
    long value = data.length;
    Byte headBytes[5] = {0};
    headBytes[4] = (Byte)((value>>24) & 0xFF);
    headBytes[3] = (Byte)((value>>16) & 0xFF);
    headBytes[2] = (Byte)((value>>8) & 0xFF);
    headBytes[1] = (Byte)(value & 0xFF);
    headBytes[0] = (Byte)(kDataHeadFlag & 0xFF); // 标示符:head数据
    NSMutableData *headData = [NSMutableData dataWithBytes:headBytes length:5];
    dic[@"headData"] = headData;
    NSLog(@"头数据长度:%ld %ld flag:%ld", (unsigned long)data.length, [self bytesToLong:headBytes], [self bytesFirstBitToLong:headBytes]);
    
    //---------- 发送内容数据 -----------
    // 一帧发送的最大字节
    //    NSInteger byteCount = 19;
    // 计算数据最后余多少字节
    NSInteger yuCount = data.length % byteCount == 0 ? byteCount : data.length % byteCount;
    // 余字节大于0,多发送一帧
    NSInteger yuCountTransformSendCount = (yuCount > 0 && yuCount < byteCount) ? 1 : 0;
    // 总发送帧数
    NSInteger sendCount = floor(data.length / byteCount) + yuCountTransformSendCount;
    dic[@"sendCount"] = @(sendCount);
    
    for (int i = 0; i < sendCount; i++) {
        // 截取位置
        CGFloat subDataLocation = i * byteCount;
        // 截取长度
        NSInteger subDataLength = byteCount;
        // 最后一次,截取长度为取余长度
        if (i == sendCount - 1) {
            subDataLength = yuCount;
        }
        // 截取发送数据
        NSData *subData = [data subdataWithRange:NSMakeRange(subDataLocation, subDataLength)];
        
        Byte bodyFirstFrameBytes[1] = {(kDataBodyFlag & 0xFF)}; // 标示符:body数据
        NSMutableData *bodyFirstFrameData = [NSMutableData dataWithBytes:bodyFirstFrameBytes length:1];
        [bodyFirstFrameData appendData:subData];
        [bodyArray addObject:bodyFirstFrameData];
    }
    return dic;
}

/**
 *  发送数据
 *
 *  @param data data
 */
- (BOOL)sendData:(NSData *)data
{
    /* // 1秒等于1000毫秒,等于1000000微秒 50000 = 1000000 / 20
     usleep(50000);// 单位微妙。50毫秒发送一次
     */
    if (data.length == 0) {
        return NO;
    }
    
    // 获取特征
    CBCharacteristic *currentCharacteristic = self.viewModel.writeCharacteristic;
    CBPeripheral *currPeripheral = self.viewModel.currPeripheral;
    
    if (!currentCharacteristic && !currPeripheral) {
        DLog(@"未连接外设或未找到特征");
        return NO;
    }
    
    NSMutableDictionary *sendDic = [self organizationSendData:data];
    // 发送头部数据
    [currPeripheral writeValue:sendDic[@"headData"] forCharacteristic:currentCharacteristic type:CBCharacteristicWriteWithResponse];
    usleep(20000);// 单位微妙。50毫秒发送一次
    NSLog(@"发送头部数据:%@", sendDic[@"headData"]);
    // 发送body数据
    NSArray *bodyArray = sendDic[@"bodyArray"];
    for (int i = 0; i < bodyArray.count; i++) {
        NSLog(@"发送body数据 index: %d:%@", i, bodyArray[i]);
        [currPeripheral writeValue:bodyArray[i] forCharacteristic:currentCharacteristic type:CBCharacteristicWriteWithResponse];
        usleep(20000);// 单位微妙。50毫秒发送一次
    }
    return YES;
}

/**
 *  注册通知,接收数据
 */
- (void)addNotificationCharacteristic
{
    self.viewModel = kApp.deviceViewModel;
    CBCharacteristic *characteristic = self.viewModel.notificationCharacteristic;
    // 订阅
    if (!(characteristic.properties & CBCharacteristicPropertyNotify ||  characteristic.properties & CBCharacteristicPropertyIndicate)) {
        [FWProgressHUD showText:@"这个characteristic没有nofity的权限"];
        return;
    }
    
    if(characteristic.isNotifying) {
        [self.viewModel.babyBluetooth cancelNotify:self.viewModel.currPeripheral characteristic:characteristic];
    } else {
        __weak typeof(self) _self = self;
        __block NSMutableData *data = [[NSMutableData alloc] init];
        __block long allLength = 0;
        
        [self.viewModel.babyBluetooth notify:self.viewModel.currPeripheral characteristic:characteristic block:^(CBPeripheral *peripheral, CBCharacteristic *characteristics, NSError *error) {
            __strong typeof(_self) self = _self;
            if (characteristics.value.bytes == NULL) {
                DLog(@"error:第一针头部数据为NULL或解析值为-1");
                return;
            }
            
            NSData *dataValue = [NSMutableData dataWithData:characteristic.value];
            Byte *dataValueBytes = (Byte *)dataValue.bytes;
            
            long flag = [self bytesFirstBitToLong:dataValueBytes];
            if (flag == kDataHeadFlag) {
                allLength = [self bytesToLong:dataValueBytes];
                data = NSMutableData.new;
            } else {
                NSData *subData = [dataValue subdataWithRange:NSMakeRange(1, dataValue.length - 1)];
                [data appendData:subData];
//                DLog(@"接收到数据:%ld/%ld",allLength, data.length);
                if (data.length == allLength) {
                    DLog(@"接收到数据:%@",[data JSONString]);
                    // 处理数据逻辑
                    id dataObj = [data JSONObject];
                    [self selectDataTypeData:dataObj sendState:NO];
                    allLength = 0;
                    data = NSMutableData.new;
                }
            }
        }];
    }
}

断开蓝开问题

cancelPeripheralConnection: 调用后不会立即断开。查了资料这是正常的。不过确实影响到我了。测试的时候App已经断开,但物理层没断开。

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