美文网首页
iOS 蓝牙开发流程总结

iOS 蓝牙开发流程总结

作者: 一代枭雄 | 来源:发表于2024-09-13 11:18 被阅读0次

    一、技术背景

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

    二、基本概念

    蓝牙,指的是BLE(Bluetooth Low Energy/低功耗蓝牙),一般应用苹果的官方框架基于<CoreBluetooth/CoreBluetooth.h>框架进行开发。

    中心设备:用于扫描周边蓝牙外设的设备,比如我们上面所说的中心者模式,此时我们的手机就是中心设备。

    外设:被扫描的蓝牙设备,比如我们上面所说的用我们的手机连接小米手环,这时候小米手环就是外设。

    广播:外部设备不停的散播的蓝牙信号,让中心设备可以扫描到,也是我们开发中接收数据的入口。

    服务(Service):外部设备在与中心设备连接后会有服务,可以理解成一个功能模块,中心设备可以读取服务,筛选我们想要的服务,并从中获取出我们想要特征。(外设可以有多个服务)

    特征(Characteristic):服务中的一个单位,一个服务可以多个特征,而特征会有一个value,一般我们向蓝牙设备写入数据、从蓝牙设备读取数据就是这个value

    UUID:区分不同服务和特征的唯一标识,使用该字端我们可以获取我们想要的服务或者特征

    核心类:CBCentralManager 中心设备管理类、CBCentral 中心设备、CBPeripheralManager 外设设备管理类、CBPeripheral 外设设备、CBUUID 外围设备服务特征的唯一标志、CBService 外围设备的服务、CBCharacteristic 外围设备的特征。

    三、申请权限

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

    四、核心重点:蓝牙数据接收的一般流程

    1、蓝牙开启后,不断地在进行广播信号

    2、扫描蓝牙

    3、发现(discover)外设设备(可根据service的UUID来辨别是否是我们连接的设备)

    4、成功连接外设设备

    5、调用代理方法发现「服务」

    6、调用代理方法发现「服务」里的「特征」

    7、发现硬件用于传输数据的「特征」(App发送数据给硬件时,会用到这个「特征)

    8、发现硬件用于数据输出的「特征」,进行「监听」(硬件就是从这个「特征」中发送数据给手机端)

    9、利用数据输入「特征」发送数据,或者等待数据输出「特征」发出来的数据

    五、中心设备-相关函数

    1、创建一个中心设备

    - (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;

    2、中心设备是否正在扫描

    @property(nonatomic, assign, readonly) BOOL isScanning NS_AVAILABLE(10_13, 9_0);

    3、获取已配对过的蓝牙外设

    - (NSArray *)retrievePeripheralsWithIdentifiers:(NSArray *)identifiers NS_AVAILABLE(10_9, 7_0);- (NSArray *)retrieveConnectedPeripheralsWithServices:(NSArray *)serviceUUIDs NS_AVAILABLE(10_9, 7_0);

    4、扫描外设(如果参数传nil,表示扫描所有外设)和停止扫描

    - (void)scanForPeripheralsWithServices:(nullable NSArray *)serviceUUIDs options:(nullable NSDictionary *)options;- (void)stopScan;

    5、连接指定外设

    - (void)connectPeripheral:(CBPeripheral *)peripheral options:(nullable NSDictionary *)options;

    6、取消指定外设

    - (void)cancelPeripheralConnection:(CBPeripheral *)peripheral;

    7、监听中心设备的状态

    - (void)centralManagerDidUpdateState:(CBCentralManager *)central;

    主要是获取当前中心外设状态:

    typedef NS_ENUM(NSInteger, CBManagerState) {

        CBManagerStateUnknown = 0, // 未知外设类型

        CBManagerStateResetting,  // 正在重置蓝牙外设

        CBManagerStateUnsupported,

        CBManagerStateUnauthorized,

        CBManagerStatePoweredOff,

        CBManagerStatePoweredOn,

    } NS_ENUM_AVAILABLE(10_13, 10_0);

    8、扫描到外设就会调用一次的代理方法

    - (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI;

    9、成功连接指定外设的代理回调

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

    10、连接失败后的代理回调

    - (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(nullable NSError *)error;

    11、连接外设失败后的代理回调

    - (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(nullable NSError *)error;

    六、外设设备-相关函数

    1、外设名称

    @property(retain, readonly, nullable) NSString *name;

    2、外设信号强度

    @property(retain, readonly, nullable) NSNumber *RSSI NS_DEPRECATED(10_7, 10_13, 5_0, 8_0);

    3、外设设备连接状态

    @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);

    4、获取外设服务

    @property(retain, readonly, nullable) NSArray *services;

    5、发现服务

    - (void)discoverServices:(nullable NSArray *)serviceUUIDs;

    6、发现子服务

    - (void)discoverIncludedServices:(nullable NSArray *)includedServiceUUIDs forService:(CBService *)service;

    7、发现特征

    - (void)discoverCharacteristics:(nullable NSArray *)characteristicUUIDs forService:(CBService *)service;

    8、蓝牙发送数据有字节长度大小限制,该函数是获取允许最大字节长度限制

    - (NSUInteger)maximumWriteValueLengthForType:(CBCharacteristicWriteType)type NS_AVAILABLE(10_12, 9_0);

    9、发送数据

    - (void)writeValue:(NSData *)data forCharacteristic:(CBCharacteristic *)characteristic type:(CBCharacteristicWriteType)type;

    10、发现特征的描述

    - (void)discoverDescriptorsForCharacteristic:(CBCharacteristic *)characteristic;

    11、 发送数据通过描述

    - (void)writeValue:(NSData *)data forDescriptor:(CBDescriptor *)descriptor;

    12、外设名称改变的监听

    - (void)peripheralDidUpdateName:(CBPeripheral *)peripheral NS_AVAILABLE(10_9, 6_0);

    13、 服务修改的监听

    - (void)peripheral:(CBPeripheral *)peripheral didModifyServices:(NSArray *)invalidatedServices NS_AVAILABLE(10_9, 7_0);

    14、信号强度改变的监听

    - (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);

    15、 发现服务和子服务

    - (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(nullable NSError *)error;- (void)peripheral:(CBPeripheral *)peripheral didDiscoverIncludedServicesForService:(CBService *)service error:(nullable NSError *)error;

    16、通过服务获取特征

    - (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(nullable NSError *)error;

    17、特征发生改变后的监听

    - (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(nullable NSError *)error;

    18、数据发送结果的回调

    - (void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(CBCharacteristic *)characteristic error:(nullable NSError *)error;

    19、发现描述通过特征

    - (void)peripheral:(CBPeripheral *)peripheral didDiscoverDescriptorsForCharacteristic:(CBCharacteristic *)characteristic error:(nullable NSError *)error;

    19、描述值发生改变的监听

    - (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForDescriptor:(CBDescriptor *)descriptor error:(nullable NSError *)error;

    120、发送描述结果的回调

    - (void)peripheral:(CBPeripheral *)peripheral didWriteValueForDescriptor:(CBDescriptor *)descriptor error:(nullable NSError *)error;

    七、扫描外设设备和停止扫描

    1、检测中心设备的蓝牙状态

    // 扫描可用蓝牙外设

    - (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];

    }

    2、 代理方法获取中心设备蓝牙状态的回调

    #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);

    }

    3、 获取扫描到的外设设备: 需要做几个核心操作:

    3.1、筛选出peripheral为nil的外设信息

    3.2、根据唯一的标识UUID,避免相同外设重复添加到集合中

    3.3、自动重连:记录上一次连接的外设UUID,然后通过UUID获取peripheral进行重连

    // 扫描蓝牙设备

    - (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);

    }

    八、连接外设

    1、在连接外设前需要判断是否正在连接有其他外设,如果有需要先取消连接后再重新连接外设,需要注意的是,取消连接时需要清除保存的此时连接的外设UUID,以及保存在集合可打印的数据。

    // 连接指定蓝牙设备

    - (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];

    }

    2、外设设备管理类连接的代理方法

    // 蓝牙外设连接成功后的代理回调

    - (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);

    }

    九、发现服务和特征

    1、连接成功后会调用外设代理方法,通过下列几个函数发现服务和特征

    #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;

        }

    }

    十、写数据操作

    1、发送数据有两种情况:1,当发送的数据小于蓝牙支持的最大长度,直接发送即可。2,如果发送的数据长度大于蓝牙支持最大长度, 需要进行分包发送,每段长度设置成当前蓝牙支持的指定长度,若有剩余,则直接发送即可。

    // 发送数据

    - (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++;

            }

        }

    }

    2、数据发送之后结果的回调也是外设管理类的代理函数

    - (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,@"数据已成功发送至蓝牙设备");

        }

    }

    十一、开发过程中遇到的问题

    问题1:直接调用- (void)fs_scanPeripheralsSuccess:(FSScanPerpheralsSuccess)success failure:(FSScanPeripheralFailure)failure函数时,搜索不到设备的问题,返回的nil。

    答:当首次调用函数搜索设备外设时,无法获取外设设备信息的原因是central的state为CBCentralManagerStateUnknown,这个状态表示手机设备的蓝牙状态为未开启。解决方法:需要在此委托方法中监听蓝牙状态的状态改变为ON时,去开启扫描操作(具体看外设蓝牙状态代理方法)。

    问题2:外设蓝牙名称被修改后可能搜索不到的问题

    答: 在测试的过程中正常获取蓝牙名称是通过peripheral.name获取,但是可能存在这种情况是当修改连接过的蓝牙名称后,可能存在搜索不到的情况。解决方法:在蓝牙的广播数据中 根据@"kCBAdvDataLocalName"这个key便可获得准确的蓝牙名称。

    问题3:调用断开蓝牙的接口,手机蓝牙并没有马上与外设断开连接,而是等待5秒左右的时间后才真正断开。

    答:解决方法:可以与硬件开发的同事沟通,从设备收到数据后主动断开连接即可。

    问题4:是否能长时间处于后台

    答:可以,后台长时间执行需要开启Background Modes,并勾选如图选项。

    Background Modes

    可以在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;}

    问题5:蓝牙允许连接的最大距离支持是多少

    答: iOS 蓝牙允许连接的最大距离的限制是10m。

    问题6:蓝牙连接成功需要多长时间

    答:正常连接周围的蓝牙外设一般时在5秒内,如图下:

    连接成功的时间差

    连接成功的时间差

    问题7:蓝牙成功发送数据需要多少时间

    答:下图的发送数据时一张图

    发送的是图片

    问题8:多台设备是否能同时连接

    答:官方文档,以及蓝牙底层协议,说明理论上可以支持到同时连接 7 个,但这 7 个能同时正常工作么?貌似不能(三个蓝牙耳机测试的结果),毕竟对于 iOS 而言,蓝牙也是一种资源,同时连接和同时使用消耗,占用的资源肯定不同,而且不同手机,性能也不同。

    实现思路:

    一个中心设备CBCentralManager,连接多个外设设备CBPeripheral,创建一个中心设备,需要连接何种设备,就单独去连接即可(换句话说就是多次实现单连接)。

    for(Model *model in _peripheralMarr) {CBPeripheral  *perip = mo.peripheral;[self.centralManager connectPeripheral:perip options:nil];perip.delegate =self;[self.perip discoverServices:nil];}

    1、将成功添加的外设CBPeripheral添加到外设数组中(连接成功后处理)

    2、 每个外设设备都对应一个唯一的peripheral.identifier或ServiceUUID,所以可以利用他们获取到之前连接的外设数组,根据这个标识,匹配到对应的设备和实现重连机制。

    3、若手动断开外设连接,需要将之从外设数组中移除掉。

    问题9:如何保证发送数据的完整性

    答:在做水下无人机的蓝牙发送指令给摄像头时,存在一个问题就是发送的指令过长,大约200字节左右,但是海思提供的摄像头蓝牙内部能接收的缓冲区长度只有16~18字节左右的长度,所以做 了一个分包发送的操作,保证了数据的完整性。_limitLength表示自定义每次发包限制的长度大小。系统提供了函数可根据写入类型CBCharacteristicWriteWithResponse、CBCharacteristicWriteWithoutResponse获取最大的写入长度:- (NSUInteger)maximumWriteValueLengthForType:(CBCharacteristicWriteType)type

    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);

            }

        }

    问题10:如何实现重连机制

    答: 文中提供的重连机制是,自动重连函数被调用之后,会设置一个全局标识为_isAutoConnect=YES,然后判断手机设备的蓝牙是否开启,若开启,则重连扫描外设设备,当扫到上一次连接的蓝牙设备后就会调用连接的代理函数,并停止扫描。

    原理:如果手动杀掉APP,那么再次打开APP的时候APP是不会自动连接设备的,但是由于系统蓝牙此时还是与手表连接中的,所以需要重新扫描设备(因为在扫描的代理函数中添加了自动连接的逻辑),经过测试,当扫描到上次连接上的蓝牙外设后就会停止。

    方式一:直接扫描重连

    // 公共的 自动重连函数

    - (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}];

    问题11:如何获取已经配对过的蓝牙外设

    答: 系统一共提供了两个函数来获取已经配对过的蓝牙外设,NSArray *[_centralManager retrieveConnectedPeripheralsWithServices:<#(nonnull NSArray<CBUUID *> *)#>];( CBUUID指的是ServiceUUID)、[_centralManager retrievePeripheralsWithIdentifiers:<#(nonnull NSArray<NSUUID *> *)#>];参数是个已连接的ServiceUUID或Identifiers的数组,是个必填项,若传@[]空数组,则返回值是nil。

    问题12:开发蓝牙 APP,有什么工具可以协助蓝牙测试

    答: 首先测试蓝牙必须时真机,其次安装了蓝牙调试助手或LightBlue等第三方App来调试蓝牙的开发

    IMG_9657.PNG

    问题13:App作为中心设备端,连接到蓝牙设备之后,如何获取外设设备的Mac地址。

    答:iOS端是无法直接获取设备的Mac地址,但是可以间接获取,但都需要和硬件工程师进行沟通。

    1,将蓝牙外设广播里,提供Mac地址,这样中心设备端在扫描阶段,可以直接读取广播里的值,从而获取到外设设备的Mac地址。

    2,可以在外设设备的某个服务的特征中,提供Mac地址,但是前提是要确定是读取哪个特征,UUID是多少。

    问题14:为什么两个 iPhone 手机的都打开蓝牙之后,却相互搜不到彼此手机上的同个蓝牙Demo。

    答:在蓝牙通信中,分为中心端和设备端。而通常手机蓝牙Demo都处在中心端状态,也就是只能接收广播,而自己没有向周围发送广播。所以两台手机之间一般是无法发现对方的(因为大家都是中心端)。

    十二、阶段性总结

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

    相关文章

      网友评论

          本文标题:iOS 蓝牙开发流程总结

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