蓝牙 Mesh OTA SDK

本文介绍iOS SDK提供蓝牙Mesh OTA业务的APP端解决方案,提供了蓝牙Mesh设备固件升级的能力。

概述

依赖 SDK

概述

蓝牙 Breeze SDK

Breeze SDK是按照规范实现的手机端蓝牙 SDK,方便合作厂商在手机端快速接入蓝牙功能。Breeze SDK 包含的主要功能有:设备发现连接,设备通信,加密传输,大数据传输等。

日志SDK

基础依赖SDK,提供客户端统一日志打印,日志等级控制,分模块日志隔离等能力。

API 通道 SDK

提供API通道能力,和基础环境配置信息。

使用说明

连接到OTA升级网络

在mesh设备升级前,需要保证当前APP断开Mesh网络并且停止蓝牙扫描。

///依赖到的sdk头文件如下
#import <ALSBluetoothGATT/ALSBluetoothGATTManager.h>
#import <ALSBluetoothBLE/ALSBLEDevice.h>
#import <IMLDeviceCenter/IMLDeviceCenter.h>
#import <ALBBluetoothMesh/ALBBluetoothMesh.h>

///断开当前的蓝牙mesh网络的连接
[[IMLMeshHelper sharedHelper] disconectMesh];

///停止蓝牙扫描
[self stopScan];

- (void)stopScan {
    IMSOTALogInfo(@"stopScan");
    [[ALSBluetoothGATTManager sharedInstance] stopScan:NO];
}

初始化升级

连接设备

在开始OTA之前,要跟目标设备建立蓝牙GATT连接。

- (void)connectDevice:(NSString *)mac timeOut:(NSTimeInterval)timeOut {
    IMSOTALogInfo(@"connectDevice:%@ timeOut:%@", mac, @(timeOut));
    [self reset];
    self.mac = mac;
    NSArray *deviceArray = [[ALSBluetoothGATTManager sharedInstance] getDeviceArray];
    if (deviceArray) {
        for (ALSBluetoothDevice *bleDevice in deviceArray)  {
            if ([bleDevice.macAddress isEqualToString:mac]) {
                if (![bleDevice isKindOfClass:[ALSBLEDevice class]]) {
                    [self startScan:timeOut];
                    return;
                }
                IMSOTALogInfo(@"BLEDevice connectDevice mac:%@", bleDevice.macAddress);
                self.bleDevice = bleDevice;
                self.bleDevice.delegate = self;
                [self connectDevice:bleDevice];
                [self.bleDevice setBLEDeviceDelegate:self];
            }
        }
        if (!self.bleDevice) {
            [self startScan:timeOut];
        }
    } else {
        [self startScan:timeOut];
    }
}


///开始蓝牙GATT扫描方法
- (void)startScan:(NSTimeInterval)timeOut {
    IMSOTALogInfo(@"startScan:%@", @(timeOut) );
    [[ALSBluetoothGATTManager sharedInstance] addGATTDelegate:self];
    [[ALSBluetoothGATTManager sharedInstance] startScan:timeOut];
    ///self.isScanning = YES;
}



- (void)connectDevice:(ALSBluetoothDevice *)device {
    //IMSOTALogInfo(@"connectDevice:%@", device.macAddress);
    ////开始真正连接目标设备
    [[ALSBluetoothGATTManager sharedInstance] removeGATTDelegate:self];
    [[ALSBluetoothGATTManager sharedInstance] connectDevice:device];
    
    [self removeConnectTimer];
    ////增加Timer,以防止连接过程超时。
    NSTimeInterval interval = 20.0;
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    self.connectTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
    dispatch_time_t start = dispatch_time(DISPATCH_TIME_NOW, (interval * NSEC_PER_SEC));
    dispatch_source_set_timer(self.connectTimer, start, DISPATCH_TIME_FOREVER, 0);
    __weak typeof(self) weakSelf = self;
    dispatch_source_set_event_handler(self.connectTimer, ^{
        __strong __typeof(weakSelf)self = weakSelf;
        ///IMSOTALogInfo(@"content device timeout:%@", self.bleDevice.macAddress);
        [self removeConnectTimer];
        
        ////TODO,走到这里代表连接超时了,可以提示OTA失败
        [self stopScan];
    });
    dispatch_resume(self.connectTimer);;
}

- (void)removeConnectTimer {
    IMSOTALogInfo(@"removeConnectTimer");
    if (self.connectTimer) {
        dispatch_source_cancel(self.connectTimer);
        self.connectTimer = nil;
    }
}

参数说明

名称

类型

必填

默认值

描述

mac

NSSting *

目标设备的Mac地址。

timeOut

NSTimeInterval

连接等待的最大时间,单位:S。

连接设备、设备OTA过程需要实现SDK中定义的2个Delegate。

#pragma mark - ALSBluetoothGATTManagerDelegate
////搜到设备的回调
- (void)onGenieDeviceFound:(ALSBluetoothDevice *)device {
    IMSOTALogInfo(@"onGenieDeviceFound:%@", device.macAddress);
    //if (!self.isScanning) {
    //    IMSOTALogInfo(@"onGenieDeviceFound:%@, isScanning == NO", device.macAddress);
    //}
    if ([device.macAddress isEqualToString:self.mac]) {
        if(![device isKindOfClass:[ALSBLEDevice class]]) {
            ////TODO,设备类型错误,直接退出OTA
            return;
        }
        ///搜到目标设备,建立蓝牙GATT连接
        self.bleDevice = device;
        self.bleDevice.delegate = self;
        [self connectDevice:device];
        [self.bleDevice setBLEDeviceDelegate:self];
        self.isScanning = NO;
        [self stopScan];
    }
//    if (!self.mac && [self.productId isEqualToString:[[NSNumber numberWithUnsignedInt:device.productId] stringValue]]) {
//        [self connectDevice:device.macAddress timeOut:kIMSOTAGATTContentTimeOut];
//    }
}

- (void)onScanStop:(NSArray<ALSBluetoothDevice *> *)deviceArray timeOut:(BOOL)timeOut {
    IMSOTALogInfo(@"onScanStop:%@", @(timeOut));
    if (self.isScanning) {
       ////TODO,
    }
}

#pragma mark - ALSBluetoothDeviceDelegate

- (void)onGenieDeviceDisconnect:(ALSBluetoothDevice *)device {
    IMSOTALogInfo(@"onGenieDeviceDisconnect:%@", device.macAddress);
}

///设备连接成功回调
- (void)onGenieDeviceConnect:(ALSBluetoothDevice *)device {
    IMSOTALogInfo(@"onGenieDeviceConnect:%@", device.macAddress);
    if ([device.macAddress isEqualToString:self.bleDevice.macAddress]) {
        [self removeConnectTimer];
    }
}

////连接设备失败回调
- (void)onGenieDeviceFailToConnect:(ALSBluetoothDevice *)device Error:(NSError *)error {
    IMSOTALogInfo(@"onGenieDeviceFailToConnect:%@ error:%@", device.macAddress, error);
    if ([device.macAddress isEqualToString:self.bleDevice.macAddress]) {
        [self removeConnectTimer];
        ////NSError *error = [self createError:IMSOTAMeshConnectCodeFail];
        ////TODO
    }
}
 

////连接成功后,APP&设备双向认证结果回调
- (void)afterGenieDeviceAuth:(ALSBluetoothDevice *)device authSuc:(BOOL)authSuc error:(NSError *)error {
    IMSOTALogInfo(@"afterGenieDeviceAuth:%@ authSuc:%@ error:%@", device.macAddress, @(authSuc), error);
    [self removeConnectTimer];
    if (authSuc) {
        ////连接认证成功后,去获取设备固件版本
        [self.bleDevice getDeviceVersion];
    } else {
        ////连接后,双向认证失败,也需要视作OTA流程终止
        ////NSError *error = [self createError:IMSOTAMeshConnectCodeFail];
    }
}

///获取到设备固件版本号的回调
- (void)didGetDeviceVersion:(NSString *)version withError:(NSError *)error {
    IMSOTALogInfo(@"didGetDeviceVersion:%@ error:%@", version, error);
    [self removeConnectTimer];

    ///self.updateVersion为设备想要升级的版本
    if ([version isEqualToString:self.updateVersion]) {
        IMSOTALogInfo(@"device version == updateVersion:%@", self.updateVersion);
        ///要升级的版本跟当前固件版本一样,则不需要升级
        ///需要给出提示,并终止OTA
    } else {

        self.isOTAing = YES;
        [self startDownloadNewPackage];
    }
}

- (void)didDownloadingWithProgress:(double)progress
                       andFinished:(BOOL)isFinished
                         withError:(nullable NSError *)error {
    IMSOTALogInfo(@"didDownloadingWithProgress:%@ isFinished:%@ error:%@", @(progress), @(isFinished), error);
    if (error) {
        if (error.code == ALSOTAErrorTimeout) {
            ///OTA包下载超时
            ///error = [self createError:IMSOTAMeshFailCodeTimeOut];
        } else if (error.code == ALSOTAErrorTypeDownloadInfoMissing) {
           ///缺少固件信息
           // error = [self createError:IMSOTAMeshFailCodeGetFirmwareError];
        } else if (error.code == ALSOTAErrorTypePackageMissing) {
           ///云端没有相应固件包
           /// error = [self createError:IMSOTAMeshFailCodeGetFirmwareError];
        } else {
        ////未知错误
           /// error = [self createError:IMSOTAMeshFailCodeUNKNOWN];
        }
        ////OTA包下载过程中,发现失败
    } else if (isFinished) {
        ////开始OTA包升级
        [self.bleDevice startOtaUpgrade];
    }
}

- (void)didUpgradingWithProgress:(double)progress
                     andFinished:(BOOL)isFinished
                       withError:(nullable NSError *)error {
    IMSOTALogInfo(@"didUpgradingWithProgress:%@ isFinished:%@ error:%@", @(progress), @(isFinished), error);
    if(error) {
        if(error.code == ALSOTAErrorTimeout) {
             ///OTA 过程超时
            //error = [self createError:IMSOTAMeshFailCodeTimeOut];
        } else if(error.code == ALSOTAErrorDisconnect) {
            ///OTA 过程连接断开
            //error = [self createError:IMSOTAMeshFailCodeDisconnect];
        } else if(error.code == ALSOTAErrorCheckFailed) {
            /// OTA 固件检查失败
            //error = [self createError:IMSOTAMeshFailCodeCheckFirmwareFail];
        } else if(error.code == ALSOTAErrorTypePackageMissing) {
            ///OTA找不到固件包
            //error = [self createError:IMSOTAMeshFailCodeGetFirmwareError];
        } else if(error.code == ALSOTAErrorRefused) {
           ///设备拒绝升级
            //error = [self createError:IMSOTAMeshFailCodeDeviceRefused];
        } else if(error.code == ALSOTAErrorIsOTAing) {
           ///已经有一个OTA正在进行
            //error = [self createError:IMSOTAMeshFailCodeIsDoing];
        } else {
           ////未知错误
           // error = [self createError:IMSOTAMeshFailCodeUNKNOWN];
        }
         ////真正OTA过程时,发现失败,需要上报失败
        [self.bleDevice reportOtaProgressFeiyan:[@(progress * 100) stringValue] version:self.updateVersion iotId:self.iotId];
    } else {
        if (isFinished && progress == 100) {
            [self.bleDevice reportOtaProgressFeiyan:@"100" version:self.updateVersion iotId:self.iotId];
             
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                ////OTA成功,业务层告知用户
            });
        }
    }
}

下载升级固件

在获取到设备的固件版本号,可以开始下载OTA包。方法入参需要依赖获取固件升级信息API接口。

- (void)startDownloadNewPackage {
    IMSOTALogInfo(@"startDownloadNewPackage");
    if (!self.bleDevice) {
        ///NSError *error = [self createError:IMSOTAMeshFailCodeNotConnect];
        ////通知OTA失败
        return;
    } else if (!(self.otaURL && self.md5)) {
        ///otaURL, md5,是调用云端api查询设备有待升级包时会返回.如果没有的话,无法OTA
        return;
    }
    
    ///OTA包的保存地址
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory,NSUserDomainMask,YES);
    NSString *otaPath = [paths.firstObject stringByAppendingPathComponent:@"ble_ota"];
    BOOL createPathOk = YES;
    if (![[NSFileManager defaultManager] fileExistsAtPath:otaPath isDirectory:&createPathOk]) {
        // 目录不存在先创建
        [[NSFileManager defaultManager] createDirectoryAtPath:otaPath withIntermediateDirectories:YES attributes:nil error:nil];
    }
    IMSOTALogInfo(@"downloadNewPackageToDirectory:%@ \n otaUrl:%@ \n md5:%@ \n version:%@", otaPath, self.otaURL, self.md5, self.updateVersion);
    [self.bleDevice downloadNewPackageToDirectory:otaPath otaUrl:self.otaURL md5:self.md5 version:self.updateVersion];
}

参数说明

名称

类型

必填

默认值

描述

otaURL

NSString *

ota包下载地址。

md5

NSString *

ota包的MD5值,下载完成后会做校验。

updateVersion

NSString *

要升级的OTA包版本。

以上参数,均可以在请求云端API/living/ota/firmware/file/get获取 。请参见获取设备固件信息

开始升级设备

在OTA包下载完成后,进入真正OTA包更新流程。

[self.bleDevice startOtaUpgrade];

停止升级设备

- (void)stopUpgrade {
    IMSOTALogInfo(@"stopUpgrade");
//    if (self.isOTAing) {
//        [self otaProgressReportFailed];
//    }
    [self removeConnectTimer];
    [[ALSBluetoothGATTManager sharedInstance] disconnectDevice:self.bleDevice];
    [[ALSBluetoothGATTManager sharedInstance] removeGATTDelegate:self];
    [[ALBBluetoothMesh sharedInstance] connectMesh];
    //[self reset];
    [self stopScan];
    //TODO:停止下载,停止OTA
    
}