2.使用自定义的蓝牙连接API进行OTA

2.1. JL_OTALib工作时序

../../_images/ota_seq.png

参考Demo:「JL_OTA项目的BleManager文件夹」

支持的功能:

  • BLE设备握手连接;

  • 获取设备信息;

  • OTA升级能实现;

用到的库和相关类:

用到的库:

  • JL_OTALib.framework —-OTA升级业务库

  • JL_AdvParse.framework——杰理蓝牙设备广播包解析业务库

  • JL_HashPair.framework——设备认证业务库

重要

在OTA过程中不允许进行其他命令的交互

相关类说明: JL_OTAManager: OTA升级业务管理类

  • 获取设备信息(是否需要升级/强制升级)

    • cmdTargetFeature

  • 设备端发过来的数据解析接口

    • cmdOTADataReceive:

  • 请求进入loader状态,需要携带升数据bfu

    • cmdOTAData:Result:

  • 进行数据传输(进入loader,重连后需要再次调用)

    • cmdOTAData:Result:

  • 重启设备命令

    • cmdRebootDevice

JLHashHandler: 设备认证业务类

JL_AdvParse: 广播包内容解析类

BLE特征参数

  • 【服务号】 :AE00

  • 【写】特征值 :AE01

  • 【读 】特征值 :AE02

2.2. 初始化SDK

 1@interface JLBleManager() <JL_OTAManagerDelegate,JLHashHandlerDelegate>
 2@property (strong, nonatomic) JLHashHandler *pairHash;
 3
 4@end
 5- (instancetype)init {
 6    self = [super init];
 7    if (self) {
 8        _otaManager = [[JL_OTAManager alloc] init];
 9        _otaManager.delegate = self;
10        self.pairHash = [[JLHashHandler alloc] init];
11        self.pairHash.delegate = self;
12
13        [JL_OTAManager logSDKVersion];
14        [JLHashHandler sdkVersion];
15    }
16    return self;
17}

2.3. BLE设备特征回调

 1#pragma mark 设备特征回调
 2- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(nullable NSError *)error {
 3    if (error) { NSLog(@"Err: Discovered Characteristics fail."); return; }
 4
 5    if ([service.UUID.UUIDString isEqual:FLT_BLE_SERVICE]) {
 6
 7        for (CBCharacteristic *character in service.characteristics) {
 8            /*--- RCSP ---*/
 9            if ([character.UUID.UUIDString isEqual:FLT_BLE_RCSP_W]) {
10                NSLog(@"BLE Get Rcsp(Write) Channel ---> %@",character.UUID.UUIDString);
11                self.mRcspWrite = character;
12            }
13
14            if ([character.UUID.UUIDString isEqual:FLT_BLE_RCSP_R]) {
15                NSLog(@"BLE Get Rcsp(Read) Channel ---> %@",character.UUID.UUIDString);
16                self.mRcspRead = character;
17                [peripheral setNotifyValue:YES forCharacteristic:character];
18
19                if(self.mRcspRead.properties == CBCharacteristicPropertyRead){
20                    [peripheral readValueForCharacteristic:character];
21                    NSLog(@"BLE  Rcsp(Read) Read Value For Characteristic.");
22                }
23            }
24        }
25    }
26}

2.4. BLE更新通知特征的状态

 1#pragma mark 更新通知特征的状态
 2- (void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(nonnull CBCharacteristic *)characteristic error:(nullable NSError *)error {
 3
 4    if (error) { NSLog(@"Err: Update NotificationState For Characteristic fail."); return; }
 5
 6    if ([characteristic.service.UUID.UUIDString isEqual:FLT_BLE_SERVICE] &&
 7        [characteristic.UUID.UUIDString isEqual:FLT_BLE_RCSP_R]          &&
 8        characteristic.isNotifying == YES)
 9    {
10
11        __weak typeof(self) weakSelf = self;
12        self.bleMtu = [peripheral maximumWriteValueLengthForType:CBCharacteristicWriteWithoutResponse];
13        NSLog(@"BLE ---> MTU:%lu",(unsigned long)self.bleMtu);
14        if (self.isPaired == YES) {
15            [_pairHash hashResetPair];
16            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
17                //设备认证
18                [self->_pairHash bluetoothPairingKey:self.pairKey Result:^(BOOL ret) {
19                    if(ret){
20                        weakSelf.lastUUID = peripheral.identifier.UUIDString;
21                        weakSelf.lastBleMacAddress = nil;
22                        [[NSNotificationCenter defaultCenter] postNotificationName:kFLT_BLE_PAIRED object:peripheral];
23                        [weakSelf.otaManager noteEntityConnected];
24                        weakSelf.pairStatus = YES;
25                    }else{
26                        NSLog(@"JL_Assist Err: bluetooth pairing fail.");
27                        [weakSelf.bleManager cancelPeripheralConnection:peripheral];
28                    }
29                }];
30            });
31        }else{
32            self.lastUUID = peripheral.identifier.UUIDString;
33            self.lastBleMacAddress = nil;
34            [[NSNotificationCenter defaultCenter] postNotificationName:kFLT_BLE_PAIRED object:peripheral];
35            [self.otaManager noteEntityConnected];
36        }
37    }
38    self.isConnected = YES;
39}

2.5. BLE设备返回的数据

设备端返回的数据需要放入到SDK中解析,其中 设备认证部分通讯部分 的处理是分开的

#pragma mark 设备返回的数据 GET
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error {
    if (error) { NSLog(@"Err: receive data fail."); return; }

    if(_isPaired == YES && _pairStatus == NO){
        //收到设备的认证交互数据
        [_pairHash inputPairData:characteristic.value];
    }else{
        //收到的设备数据,正常通讯数据
        [_otaManager cmdOtaDataReceive:characteristic.value];
    }

}

2.6. BLE设备断开连接

#pragma mark 设备断开连接
- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(nullable NSError *)error {
    NSLog(@"BLE Disconnect ---> Device %@ error:%d", peripheral.name, (int)error.code);

    [_otaManager noteEntityDisconnected];
    self.isConnected = NO;
    self.pairStatus = NO;
    /*--- UI刷新,设备断开 ---*/
    [[NSNotificationCenter defaultCenter] postNotificationName:kFLT_BLE_DISCONNECTED object:peripheral];
}

2.7. 手机蓝牙状态更新

 1//外部蓝牙,手机蓝牙状态回调处,实现以下:
 2#pragma mark - 蓝牙初始化 Callback
 3- (void)centralManagerDidUpdateState:(CBCentralManager *)central
 4{
 5_mBleManagerState = central.state;
 6
 7    if (_mBleManagerState != CBManagerStatePoweredOn) {
 8        self.mBlePeripheral = nil;
 9        self.blePeripheralArr = [NSMutableArray array];
10    }
11}

2.8. 获取设备信息

BLE连接且配对后必须执行一次

1    [_otaManager cmdTargetFeature];

2.9. 固件OTA升级

 1/**
 2*  ota升级
 3*  @param otaFilePath ota升级文件路径
 4*/
 5- (void)otaFuncWithFilePath:(NSString *)otaFilePath {
 6    NSLog(@"current otaFilePath ---> %@", otaFilePath);
 7    self.selectedOtaFilePath = otaFilePath;
 8    NSData *otaData = [[NSData alloc] initWithContentsOfFile:otaFilePath];
 9
10    [_otaManager cmdOTAData:otaData Result:^(JL_OTAResult result, float progress) {
11        for (id<JLBleManagerOtaDelegate> objc in self.delegates) {
12            if([objc respondsToSelector:@selector(otaProgressWithOtaResult:withProgress:)]){
13                [objc otaProgressWithOtaResult:result withProgress:progress];
14            }
15        }
16
17    }];
18}

2.10. 取消固件OTA升级

1- (void)otaFuncCancel:(CANCEL_CALLBACK _Nonnull)result{
2
3    [_otaManager cmdOTACancelResult:^(uint8_t status, uint8_t sn, NSData * _Nullable data) {
4        result(status);
5    }];
6}

2.11. OTAManager 的delegate回调处理

 1//MARK: - ota manager delegate callback
 2
 3///取消升级回调
 4-(void)otaCancel{
 5    //TODO: 取消OTA升级回调
 6}
 7
 8///升级状态进度回调
 9-(void)otaUpgradeResult:(JL_OTAResult)result Progress:(float)progress{
10    //TODO: 设备升级过程回调,包括进度状态
11}
12
13///即将发送给设备的数据
14-(void)otaDataSend:(NSData *)data{
15    //TODO: 开发者需要在这里要把数据发送到设备
16    [self writeDataByCbp:data];
17}
18///设备信息回调
19-(void)otaFeatureResult:(JL_OTAManager *)manager{
20
21    NSLog(@"getDeviceInfo:%d",__LINE__);
22    if (manager.otaStatus == JL_OtaStatusForce) {
23        NSLog(@"---> 进入强制升级.");
24        if (self.selectedOtaFilePath) {
25            [self otaFuncWithFilePath:self.selectedOtaFilePath];
26        } else {
27            _getCallback(true);
28        }
29        return;
30    } else {
31        if (manager.otaHeadset == JL_OtaHeadsetYES) {
32            NSLog(@"---> 进入强制升级: OTA另一只耳机.");
33            if (self.selectedOtaFilePath) {
34                [self otaFuncWithFilePath:self.selectedOtaFilePath];
35            } else {
36                _getCallback(true);
37            }
38            return;
39        }
40    }
41    NSLog(@"---> 设备正常使用...");
42    dispatch_async(dispatch_get_main_queue(), ^{
43        /*--- 获取公共信息 ---*/
44        [self->_otaManager cmdSystemFunction];
45        self->_getCallback(false);
46    });
47
48}

2.12. 设备认证的delegate回调

1//MARK: - Hash pair delegate callback
2///即将发送到设备的认证数据
3-(void)hashOnPairOutputData:(NSData *)data{
4//TODO: 开发者需要在这里要把数据发送到设备
5[self writeDataByCbp:data];
6}

2.13. 数据发送

  • iOS设备与固件通讯之间的最大MTU需要在连上后获取

1elf.bleMtu = [peripheral maximumWriteValueLengthForType:CBCharacteristicWriteWithoutResponse];
  • 发送到设备的数据需要进行分包发送

代码参考如下:

 1//MARK: - data send manager
 2
 3/// 需要分包发送
 4/// - Parameter data: 数据
 5-(void)writeDataByCbp:(NSData *)data{
 6    //    NSLog(@"%s:data:%@",__func__,data);
 7        if (_mBlePeripheral && self.mRcspWrite) {
 8            if (data.length > 0 ) {
 9                NSInteger len = data.length;
10                while (len>0) {
11                    if (len <= _bleMtu) {
12                        NSData *mtuData = [data subdataWithRange:NSMakeRange(0, data.length)];
13                        [_mBlePeripheral writeValue:mtuData
14                        forCharacteristic:self.mRcspWrite
15                                    type:CBCharacteristicWriteWithoutResponse];
16                        len -= data.length;
17                    }else{
18                        NSData *mtuData = [data subdataWithRange:NSMakeRange(0, _bleMtu)];
19                        [_mBlePeripheral writeValue:mtuData
20                        forCharacteristic:self.mRcspWrite
21                                    type:CBCharacteristicWriteWithoutResponse];
22                        len -= _bleMtu;
23                        data = [data subdataWithRange:NSMakeRange(_bleMtu, len)];
24                    }
25                }
26            }
27        }else{
28            //需要先赋值写特征的内容
29            NSLog(@"need to init");
30        }
31}