iOS蓝牙开发蓝牙学习蓝牙

iOS 蓝牙开发技术分享

2023-07-10  本文已影响0人  Alexander
一、技术背景

本文主要是从蓝牙的扫描、连接、收发数据、打印等方向快速熟悉蓝牙开发,记录了在开发过程中遇到的的问题及解决方法。在分享之前,我们需要清楚几个BLE相关的概念。

二、基本概念
三、申请权限

1、需要在info.plist文件中添加相对应的键值对 Privacy - Bluetooth Always Usage Description,否则会闪退。

四、核心重点:蓝牙数据接收的一般流程
五、中心设备-相关函数
- (instancetype)init;

- (instancetype)initWithDelegate:(nullable id<CBCentralManagerDelegate>)delegate
                           queue:(nullable dispatch_queue_t)queue;

- (instancetype)initWithDelegate:(nullable id<CBCentralManagerDelegate>)delegate
                           queue:(nullable dispatch_queue_t)queue
                         options:(nullable NSDictionary<NSString *, id> *)options NS_AVAILABLE(10_9, 7_0) NS_DESIGNATED_INITIALIZER;
@property(nonatomic, assign, readonly) BOOL isScanning NS_AVAILABLE(10_13, 9_0);
- (NSArray<CBPeripheral *> *)retrievePeripheralsWithIdentifiers:(NSArray<NSUUID *> *)identifiers NS_AVAILABLE(10_9, 7_0);

- (NSArray<CBPeripheral *> *)retrieveConnectedPeripheralsWithServices:(NSArray<CBUUID *> *)serviceUUIDs NS_AVAILABLE(10_9, 7_0);
- (void)scanForPeripheralsWithServices:(nullable NSArray<CBUUID *> *)serviceUUIDs options:(nullable NSDictionary<NSString *, id> *)options;

- (void)stopScan;
- (void)connectPeripheral:(CBPeripheral *)peripheral options:(nullable NSDictionary<NSString *, id> *)options;
- (void)cancelPeripheralConnection:(CBPeripheral *)peripheral;
- (void)centralManagerDidUpdateState:(CBCentralManager *)central;
主要是获取当前中心外设状态:
typedef NS_ENUM(NSInteger, CBManagerState) {
    CBManagerStateUnknown = 0, // 未知外设类型
    CBManagerStateResetting,  // 正在重置蓝牙外设
    CBManagerStateUnsupported,
    CBManagerStateUnauthorized,
    CBManagerStatePoweredOff,
    CBManagerStatePoweredOn,
} NS_ENUM_AVAILABLE(10_13, 10_0);
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary<NSString *, id> *)advertisementData RSSI:(NSNumber *)RSSI;
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral;
- (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(nullable NSError *)error;
- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(nullable NSError *)error;
六、外设设备-相关函数
@property(retain, readonly, nullable) NSString *name;
@property(retain, readonly, nullable) NSNumber *RSSI NS_DEPRECATED(10_7, 10_13, 5_0, 8_0);
@property(readonly) CBPeripheralState state;
typedef NS_ENUM(NSInteger, CBPeripheralState) {
    CBPeripheralStateDisconnected = 0, // 断开连接状态
    CBPeripheralStateConnecting, // 正在连接状态
    CBPeripheralStateConnected, // 已连接状态
    CBPeripheralStateDisconnecting NS_AVAILABLE(10_13, 9_0), // 正在断开状态
} NS_AVAILABLE(10_9, 7_0);
@property(retain, readonly, nullable) NSArray<CBService *> *services;
- (void)discoverServices:(nullable NSArray<CBUUID *> *)serviceUUIDs;
- (void)discoverIncludedServices:(nullable NSArray<CBUUID *> *)includedServiceUUIDs forService:(CBService *)service;
- (void)discoverCharacteristics:(nullable NSArray<CBUUID *> *)characteristicUUIDs forService:(CBService *)service;
- (NSUInteger)maximumWriteValueLengthForType:(CBCharacteristicWriteType)type NS_AVAILABLE(10_12, 9_0);
- (void)writeValue:(NSData *)data forCharacteristic:(CBCharacteristic *)characteristic type:(CBCharacteristicWriteType)type;
- (void)discoverDescriptorsForCharacteristic:(CBCharacteristic *)characteristic;
- (void)writeValue:(NSData *)data forDescriptor:(CBDescriptor *)descriptor;
- (void)peripheralDidUpdateName:(CBPeripheral *)peripheral NS_AVAILABLE(10_9, 6_0);
- (void)peripheral:(CBPeripheral *)peripheral didModifyServices:(NSArray<CBService *> *)invalidatedServices NS_AVAILABLE(10_9, 7_0);
- (void)peripheralDidUpdateRSSI:(CBPeripheral *)peripheral error:(nullable NSError *)error NS_DEPRECATED(10_7, 10_13, 5_0, 8_0);

- (void)peripheral:(CBPeripheral *)peripheral didReadRSSI:(NSNumber *)RSSI error:(nullable NSError *)error NS_AVAILABLE(10_13, 8_0);
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(nullable NSError *)error;

- (void)peripheral:(CBPeripheral *)peripheral didDiscoverIncludedServicesForService:(CBService *)service error:(nullable NSError *)error;
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(nullable NSError *)error;
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(nullable NSError *)error;
 - (void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(CBCharacteristic *)characteristic error:(nullable NSError *)error;
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverDescriptorsForCharacteristic:(CBCharacteristic *)characteristic error:(nullable NSError *)error;
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForDescriptor:(CBDescriptor *)descriptor error:(nullable NSError *)error;
- (void)peripheral:(CBPeripheral *)peripheral didWriteValueForDescriptor:(CBDescriptor *)descriptor error:(nullable NSError *)error;
七、扫描外设设备和停止扫描
// 扫描可用蓝牙外设
- (void)fs_scanPeripheralsSuccess:(FSScanPerpheralsSuccess)success
                          failure:(FSScanPeripheralFailure)failure {
    _scanPerpheralSuccess = success;
    _scanPerpheralFailure = failure;
    NSString *msg = nil;
// 在扫描设备前,需要判断当前中心设备的蓝牙状态,只有开启后才能进行扫描工作
    switch (_centralManager.state) {
        case CBManagerStatePoweredOn:{
            msg = @"蓝牙已开启,允许连接蓝牙外设";
            // 扫描的核心方法
            [_centralManager scanForPeripheralsWithServices:nil options:nil];
            FSLog(@"扫描阶段 -- %@",msg);
            return;
        }
            break;
        case CBManagerStatePoweredOff:{
            msg = @"蓝牙是关闭状态,需要打开才能连接蓝牙外设";
        }
            break;
        case CBManagerStateUnauthorized: {
            msg = @"蓝牙权限未授权";
        }
            break;
        case CBManagerStateUnsupported:{
            msg = @"平台不支持蓝牙";
        }
            break;
        case CBManagerStateUnknown: {
            msg = @"未知状态";
        }
            break;
        default:
            break;
    }
    [self initBluetoothConfig];
    FSLog(@"%@",msg);
}
// 停止扫描
- (void)fs_stopScan {
    [_centralManager stopScan];
}
#pragma mark - CBCentralManagerDelegate - 中央设备的代理方法
// 获取当前中央设备的蓝牙状态,如果蓝牙不可用,这回调回去,若蓝牙可用,则搜索设备
- (void)centralManagerDidUpdateState:(CBCentralManager *)central {
    if(central.state != CBManagerStatePoweredOn) {
        if(_scanPerpheralFailure) {
            _scanPerpheralFailure(central.state);
        }
    }else {
        [central scanForPeripheralsWithServices:nil options:nil];
    }
    FSLog(@"中央设备的蓝牙状态: %ld", central.state);
}
// 扫描蓝牙设备
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary<NSString *,id> *)advertisementData RSSI:(NSNumber *)RSSI {
    // 在扫描的过程中会有很多不可用的蓝牙设备信息,name为nil,需要排除掉
    if(peripheral.name.length <= 0 || peripheral == nil) {
        return;
    }
    
    // 在扫描过程中,存在同一台设备被多次扫描到,所以在添加到可用设备集合中需要进行筛选,相同的设备不需要重复添加
    if(_peripherals.count == 0) {
        [_peripherals addObject:peripheral];
        [_rssis addObject:RSSI];
    } else {
        __block BOOL isExist = NO; // block中获取外部变量,若要改值,需要__block处理
        // UUIDString是每台设备的唯一标识,所以通过UUIDString查询集合中是否已存在蓝牙外设
        [_peripherals enumerateObjectsUsingBlock:^(CBPeripheral *   _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
            CBPeripheral *per = [_peripherals objectAtIndex:idx];
            if ([per.identifier.UUIDString isEqualToString:peripheral.identifier.UUIDString]) {
                isExist = YES;
                [_peripherals replaceObjectAtIndex:idx withObject:peripheral];
                [_rssis replaceObjectAtIndex:idx withObject:RSSI];
            }
        }];
        // 集合中不存在,则添加,存在如上则代替
        if (!isExist) {
            [_peripherals addObject:peripheral];
            [_rssis addObject:RSSI];
        }
    }
    // 来这里说明成功扫描到蓝牙设备,回调出去
    if(_scanPerpheralSuccess){
        _scanPerpheralSuccess(_peripherals, _rssis);
    }
    // 自动连接上一次连接的外设
    if (_isAutoConnect) {
        NSString *uuid = [self fs_previousConnectionPeripheralUUID];
        if ([peripheral.identifier.UUIDString isEqualToString:uuid]) {
            peripheral.delegate = self;
            [_centralManager connectPeripheral:peripheral options:nil];
        }
    }
    FSLog(@"扫描到的外设名称: %@", peripheral.name);
}
八、连接外设
// 连接指定蓝牙设备
- (void)fs_connectPeripheral:(CBPeripheral *)peripheral
                  completion:(FSConnectPeripheralCompletion)completion {
    _connectCompletion = completion;
    if(_connectedPerpheral) { // 如果正在连接的有蓝牙外设,需要先取消连接后再连接新的蓝牙设备
        [self fs_canclePeripheralConnected:peripheral];
    }
    [self connectPeripheral:peripheral];
    
    // 连接超时的相关处理
   // TODO: ......
    
}

// 连接蓝牙设备
- (void)connectPeripheral:(CBPeripheral *)peripheral{
    [_centralManager connectPeripheral:peripheral options:nil];
    peripheral.delegate = self;
}

// 自动连接
- (void)fs_autoConnectPreviousPeripheral:(FSConnectPeripheralCompletion)completion {
    _connectCompletion = completion;
    _isAutoConnect = YES;
    if (_centralManager.state == CBManagerStatePoweredOn) {
        // 扫描外设
        [_centralManager scanForPeripheralsWithServices:nil options:nil];
    }
}

// 取消蓝牙连接
- (void)fs_canclePeripheralConnected:(CBPeripheral *)peripheral {
    if (!peripheral) return;
    
    // 取消后需要清除保存的蓝牙外设的uuid
    [self fs_removePreviousConnectionPeripheralUUID];
    [_centralManager cancelPeripheralConnection:peripheral];
    _connectedPerpheral = nil;
    
    // 既然取消了连接,那么就不能发送数据, 所以需要将发送数据的数组清除掉
    [_writeChatacterDatas removeAllObjects];
}
// 蓝牙外设连接成功后的代理回调
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral {
    
    // 蓝牙设备
    _connectedPerpheral = peripheral;
    
    // 连接成功后停止扫描
    [_centralManager stopScan];
    
    // 保存当前蓝牙外设,便于下次自动连接
    [self fs_savePreviousConnectionPeripheralUUID:peripheral.identifier.UUIDString];
    
    // 成功连接后的结果回调出去
    if(_connectCompletion) {
        _connectCompletion(peripheral, nil);
    }
    // 处于连接状态
    _state = kFSBLEStageConnection;
    
    // 外设代理
    peripheral.delegate = self;
    
    // 发现服务
    [peripheral discoverServices:nil];
    FSLog(@"成功连接蓝牙外设: %@", peripheral.identifier.UUIDString);
}

// 连接失败后的回调
- (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(nullable NSError *)error{
    if (_connectCompletion) {
        _connectCompletion(peripheral,error);
    }
    _state = kFSBLEStageConnection;
    FSLog(@"连接蓝牙外设失败Error: %@", error);
}

// 断开蓝牙连接
- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error {
    _connectedPerpheral = nil;
    [_writeChatacterDatas removeAllObjects];
 
    if (_disConnectCompletion) {
        _disConnectCompletion(peripheral,error);
    }
    _state = kFSBLEStageConnection;
    FSLog(@"断开蓝牙外设连接:%@ -- %@", peripheral, error);
}
九、发现服务和特征
#pragma mark - CBPeripheralDelegate - 外设的代理方法
// 发现服务的回调
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error {
    if(error) {
        FSLog(@"发现服务错误: %@", error);
        return;
    }
    FSLog(@"发现服务数组:%@",peripheral.services);
    for (CBService *service in peripheral.services) {
        [peripheral discoverCharacteristics:nil forService:service];
    }
    _state = kFSBLEStageSeekServices;
}

// 发现特性
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(nullable NSError *)error{
    if (error) {
        FSLog(@"发现特性出错 错误原因: %@",error.domain);
    }else{
        for (CBCharacteristic *character in service.characteristics) {
            CBCharacteristicProperties properties = character.properties;
            if (properties & CBCharacteristicPropertyWrite) {
                NSDictionary *dict = @{@"character":character,@"type":@(CBCharacteristicWriteWithResponse)};
                [_writeChatacterDatas addObject:dict];
            }
        }
    }
    if (_writeChatacterDatas.count > 0) {
        _state = kFSBLEStageSeekCharacteristics;
    }
}
十、写数据操作
// 发送数据
- (void)fs_writeData:(NSData *)data completion:(FSWriteCompletion)completion {
    if (!_connectedPerpheral) {
        if (completion) {
            completion(NO,_connectedPerpheral,@"蓝牙设备未连接");
        }
        return;
    }
    if (self.writeChatacterDatas.count == 0) {
        if (completion) {
            completion(NO,_connectedPerpheral,@"该蓝牙设备不支持发送数据");
        }
        return;
    }
    NSDictionary *dict = [_writeChatacterDatas lastObject];
    _writeCount = 0;
    _responseCount = 0;
    if (_limitLength <= 0) {
        _results = completion;
        [_connectedPerpheral writeValue:data forCharacteristic:dict[@"character"] type:[dict[@"type"] integerValue]];
        _writeCount ++;
        return;
    }
    
    if (data.length <= _limitLength) {
        _results = completion;
        [_connectedPerpheral writeValue:data forCharacteristic:dict[@"character"] type:[dict[@"type"] integerValue]];
        _writeCount ++;
    } else {
        // 分段发送
        NSInteger index = 0;
        for (index = 0; index < data.length - _limitLength; index += _limitLength) {
            NSData *subData = [data subdataWithRange:NSMakeRange(index, _limitLength)];
            [_connectedPerpheral writeValue:subData forCharacteristic:dict[@"character"] type:[dict[@"type"] integerValue]];
            _writeCount++;
        }
        _results = completion;
        NSData *leftData = [data subdataWithRange:NSMakeRange(index, data.length - index)];
        if (leftData) {
            [_connectedPerpheral writeValue:leftData forCharacteristic:dict[@"character"] type:[dict[@"type"] integerValue]];
            _writeCount++;
        }
    }
}
- (void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error {
    if (!_results) {
        return;
    }
    _responseCount ++;
    if (_writeCount != _responseCount) {
        return;
    }
    if (error) {
        FSLog(@"发送数据失败: %@",error);
        _results(NO,_connectedPerpheral,@"数据发送失败");
    } else {
        _results(YES,_connectedPerpheral,@"数据已成功发送至蓝牙设备");
    }
}
十一、开发过程中遇到的问题

可以在App启动的方法中可以检测后台是蓝牙的处理情况如图:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    
    UIDevice *device = [UIDevice currentDevice];
    BOOL backgroundSupported = NO;
    if([device respondsToSelector:@selector(isMultitaskingSupported)]) {
        backgroundSupported = YES;
    }
    
    if (backgroundSupported) {
        __block int index = 0;
        NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
            // 执行蓝牙相关操作
            NSLog(@"[SDK - Background] - %d", index++); // 检测后台是蓝牙的执行情况
        }];
        [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
        [timer fire]; // 用了fire方法之后会立即执行定时器的方法
    }
    return YES;
}
for(Model *model in _peripheralMarr) {
CBPeripheral  *perip = mo.peripheral;
[self.centralManager connectPeripheral:perip options:nil];
perip.delegate =self;
[self.perip discoverServices:nil];
}
 if (data.length <= _limitLength) {
        _results = completion;
        [_connectedPerpheral writeValue:data forCharacteristic:dict[@"character"] type:[dict[@"type"] integerValue]];
        _writeCount ++;
  //根据接收模块的处理能力做相应延时,因为蓝牙设备处理指令需要时间,所以我这边给了400~500毫秒
        usleep(400 * 1000);
    } else {
        // 分段发送
        NSInteger index = 0;
        for (index = 0; index < data.length - _limitLength; index += _limitLength) {
            NSData *subData = [data subdataWithRange:NSMakeRange(index, _limitLength)];
            [_connectedPerpheral writeValue:subData forCharacteristic:dict[@"character"] type:[dict[@"type"] integerValue]];
            _writeCount++;
            usleep(400 * 1000);
        }
        _results = completion;
        NSData *leftData = [data subdataWithRange:NSMakeRange(index, data.length - index)];
        if (leftData) {
            [_connectedPerpheral writeValue:leftData forCharacteristic:dict[@"character"] type:[dict[@"type"] integerValue]];
            _writeCount++;
            usleep(400 * 1000);
        }
    }

// 公共的 自动重连函数
- (void)fs_autoConnectPreviousPeripheral:(FSConnectPeripheralCompletion)completion {
    _connectCompletion = completion;
    _isAutoConnect = YES;
    if (_centralManager.state == CBManagerStatePoweredOn) {
        [_centralManager scanForPeripheralsWithServices:nil options:nil];
    }
}

在函数- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary<NSString *,id> *)advertisementData RSSI:(NSNumber *)RSSI中实现。

    // 自动连接上一次连接的外设
    if (_isAutoConnect) {
        NSString *uuid = [self fs_previousConnectionPeripheralUUID];
        if ([peripheral.identifier.UUIDString isEqualToString:uuid]) {
            peripheral.delegate = self;
            [_centralManager connectPeripheral:peripheral options:nil];
        }
    }

方式二: 通过系统提供的函数retrieveConnectedPeripheralsWithServices

NSArray *temp = [_centralManager retrieveConnectedPeripheralsWithServices:@[[CBUUID UUIDWithString:ServiceUUID]]];
 
if(temp.count>0) {
    CBPeripheral *per = temp[0];
    per.delegate = self;
    [_centralManager connectPeripheral:peripheral options:nil];
}

方式三: 通过系统提供的函数retrievePeripheralsWithIdentifiers

    NSArray<CBPeripheral *> *knownPeripherals = [_centralManager retrievePeripheralsWithIdentifiers:@[peripheral.identifier]];
    if (knownPeripherals.count == 0) {
        return;
    }
    self.peripheral = knownPeripherals[0];
    self.peripheral.delegate = self;
    [_centralManager connectPeripheral:self.peripheral
                                   options:@{CBConnectPeripheralOptionNotifyOnDisconnectionKey: @YES}];
十二、阶段性总结

上述代码基本完成了App扫描外设设备、连接外设设备到发送数据的基本流程,需要深化的点在用户体验相关,比如:连接超时后的处理等。后续分享会加入发送数据后的打印操作,待续。

上一篇 下一篇

猜你喜欢

热点阅读