2 功能说明

2.1 目录浏览

2.1.1 功能描述

管理设备sd、usb、flash的文件, 可以实现文件的浏览、删除。

2.1.2 示例代码

在进行目录浏览之前需要先从 RcspOperateWrapper 中获取到 OPDirectoryBrowse.IOperateDirectoryBrowse 目录浏览操作对象

const operateWrapper: RcspOperateWrapper | undefined = undefined;//此处需获取到真实的RcspOperateWrapper
//获取目录浏览操作对象
const RCSPOpDirectoryBrowse: OPDirectoryBrowse.IOperateDirectoryBrowse = operateWrapper?.getOperaterByClass(OPDirectoryBrowse.OperateDirectoryBrowse.prototype)

2.1.2.1 获取在线的存储设备

存储设备列表有:SD Card 0 ,SD Card 1,Flash,Flash2。常见手表的存储设备有SD Card 0,Flash

const storageDevs: File[] = RCSPOpDirectoryBrowse.getOnLineStorageDevs()

2.1.2.2 从存储设备的根目录开始浏览

目前浏览目录,需要从存储设备的根目录开始一层层地往下浏览。默认浏览层级上限是8层(算上存储设备文件夹和ROOT文件夹),如需修改层级上限,请在 appendBrowse 函数传递参数 maxDepth (仅当次生效)

const storageFile: OPDirectoryBrowse.File | undefined = undefined//存储设备文件夹
if (storageFile) {
    RCSPOpDirectoryBrowse?.appendBrowse(storageFile).then(_devFolder => {
        const rootFolder = _devFolder.listFile()?.[0]//root文件夹
        if (rootFolder) {
            RCSPOpDirectoryBrowse?.appendBrowse(rootFolder).then(_rootFolder => {
                //根目录下的文件列表
                 const tempList = _rootFolder.listFile()
            })
        }
    })
}

2.1.2.3 进入下一级文件夹

默认浏览子文件数量是10个,如需修改,请在 appendBrowse 函数传递参数 readPageSize (仅当次生效)

const file: OPDirectoryBrowse.File | undefined = undefined//目标文件夹
if (file) {
    RCSPOpDirectoryBrowse?.appendBrowse(file).then(_folder => {
        //子文件列表
        const tempList = _folder.listFile()
        //该文件夹是否加载完毕
        const isLoadFinished = _folder.isLoadFinished()
    })
}

2.1.2.4 加载更多文件

目标文件夹加载更多子文件,默认加载子文件数量是10个,如需修改,请在 loadMore 函数传递参数 readPageSize (仅当次生效)

const file: OPDirectoryBrowse.File | undefined = undefined//目标文件夹
if (file) {
    RCSPOpDirectoryBrowse?.loadMore(file).then(_folder => {
        //子文件列表
        const tempList = _folder.listFile()
        //该文件夹是否加载完毕
        const isLoadFinished = _folder.isLoadFinished()
    }).catch(error=>{
        //失败
    })
}

2.1.2.5 返回上一级文件夹

目标文件夹返回上一级文件夹

const file: OPDirectoryBrowse.File | undefined = undefined//目标文件夹
if (file) {
    RCSPOpDirectoryBrowse?.backBrowse(file).then(_folder => {
        //上一级文件夹
        const parentFoldet = _folder
        //子文件列表
        const tempList = _folder.listFile()
        //该文件夹是否加载完毕
        const isLoadFinished = _folder.isLoadFinished()
    }).catch(error=>{
        //失败
    })
}

2.1.2.6 根据路径获取文件

根据绝对路径获取文件对象,文件路径例如:SD Card 0/ROOT

const targetPath: string = "SD Card 0/ROOT";//目标文件夹路径
RCSPOpDirectoryBrowse?.getFileByPath(targetPath).then(_folder => {
    //目标文件对象
    const targetFolder = _folder
    //子文件列表
    const tempList = _folder.listFile()
    //该文件夹是否加载完毕
    const isLoadFinished = _folder.isLoadFinished()
}).catch(error=>{
    //失败
})

2.1.2.7 删除文件

先删除设备端的文件,后删除app缓存数据。(注:目前只支持删除文件,不支持删除文件夹)

const fileList: OPDirectoryBrowse.File[];//删除文件列表
RCSPOpDirectoryBrowse?.deleteFile(fileList).then(() => {
    //删除成功
}).catch((error)=>{
    //删除失败
})

2.1.2.8 重读文件夹

重新读取文件夹

let targetFolder: OPDirectoryBrowse.File;//目标文件夹
RCSPOpDirectoryBrowse?.reloadFolder(targetFolder).then(_folder => {
    //重读成功
    targetFolder = _folder
}).catch((error)=>{
    //失败
})

2.1.3 事件通知

当目录发生变化时,通过注册的回调,回调事件给上层。上层根据事件的 type 进行判断事件类型是不是自己要处理的类型。

const callback: OPDirectoryBrowse.OperaterEventCallbackDirectoryBrowse = {
    onEvent: (_event: OPDirectoryBrowse.OperaterEventDirectoryBrowse) => {
        switch (_event.type) {
            case 'NONE':
                break;
            case 'onDirectoryChange'://目录发生变化
                //变化的文件
                const changeFolder = _event.onDirectoryChange?.folder
                break;
            default:
                break;
        }
    }
}
//注册回调
RCSPOpDirectoryBrowse?.registerEventCallback(callback)
//注销回调
RCSPOpDirectoryBrowse?.unregisterEventCallback(callback)
//移除全部回调
RCSPOpDirectoryBrowse?.removeAllEventCallback()

2.1.4 File对象

File对象是目录浏览时,最多使用的操作对象。有些属性方法会影响到本地缓存的数据结构,请谨慎修改(如: path , updateLoadFinished)。

declare class File {
    private _fs;
    /** 路径,格式:dev/root/xxx/xxx   */
    path: string;
    constructor(fs: FileSystem, path: string);
    /**文件名**/
    getName(): string | undefined;
    /**列举子文件-绝对路径**/
    list(): string[] | undefined;
    /**列举子文件-文件对象**/
    listFile(): File[] | undefined;
    /**父文件-绝对路径**/
    getParent(): string | undefined;
    /**父文件-文件对象**/
    getParentFile(): File | undefined;
    /**文件路径簇号**/
    getPathCluster(): number[] | undefined;
    /**存储设备序号**/
    getDevIndex(): number | undefined;
    /**当前文件夹下的子文件是否都已加载*/
    isLoadFinished(): boolean;
    /**是不是文件夹**/
    isDirectory(): boolean;
    /**是不是unicode格式**/
    isFormatUnicode(): boolean;
    /**删除该文件的本地缓存*/
    delete(): boolean;
    /**删除该文件的全部子文件本地缓存*/
    deleteAllChild(): boolean;
    /**更新子文件全部加载状态**/
    updateLoadFinished(isLoadFinished: boolean): void;
    /**文件是否存在*/
    exist(): boolean;
}

2.2 文件下载(App->设备)

该功能对应小程序中的大文件传输(App->Dev)

将文件传输到存储设备的默认文件夹下(SD Card设备是Root/Download目录,Flash设备是直接放在Root目录下)。
  • 目前文件名处理规则:短文件名进行gbk编码,长文件名进行utf16le编码。长短文件名判断依据:去掉后缀名(例如.mp3)后,gbk编码字节数小于9

  • 只能执行一个文件下载任务,不能同时进行多个文件下载任务。

  • 当在目录浏览或者文件读取时,是不能进行文件下载任务,反之亦然。两者是互斥的。

在进行文件下载之前需要先从 RcspOperateWrapper 中获取到 OPLargerFileTrans.IOperateLargeFileTrans 大文件传输操作对象。

const operateWrapper: RcspOperateWrapper | undefined = undefined;//此处需获取到真实的RcspOperateWrapper
//获取目录浏览操作对象
const RCSPOpLargeFileTransfer: OPLargerFileTrans.IOperateLargeFileTrans = operateWrapper?.getOperaterByClass(OPLargerFileTrans.OperateLargeFileTrans.prototype)

2.2.1 传输任务回调

interface TransferTaskCallback {
/**
 *传输失败
 * @param code
 *               1//创建文件失败
 *               2//环境准备失败
 *               3//读取crc失败
 *               4//重命名失败
 *               5//传输过程中-数据越界
 *               6//传输超时
 *               1001:大文件传输结束-写失败
 *               1002:大文件传输结束-数据超出范围
 *               1003:大文件传输结束-crc校验失败
 *               1004:大文件传输结束-内存不足
 *
 */
onError(code: number): void;
/**传输开始**/
onStart(): void;
/**传输进度**/
onProgress(progress: number): void;
/**传输成功**/
onSuccess(): void;
/**取消传输
 * @param code 0:app取消,1:dev取消
*/
onCancel(code: number): void;
}

2.2.2 执行传输任务

执行任务前先要获取目标存储设备的SdCardBeans对象

const operateWrapper: RcspOperateWrapper | undefined = undefined;//此处需获取到真实的RcspOperateWrapper
//获取文件对象(负责设备支持的存储设备,在线的存储设备)
const RCSPOpFile: OPFile.IOperateFile = operateWrapper?.getOperaterByClass(OPFile.OperateFile.prototype)
//在线存储设备列表
const storageDevs = RCSPOpFile.getOnLineSdCardBeans()
//目标存储设备
let targetSDCardBean: OPFile.SDCardBean | undefined = undefined
storageDevs.forEach(item => {
    if(item.index == OPFile.SDCardBean.INDEX_FLASH){
        //flash存储设备
        targetSDCardBean = item
    }else if(item.index == OPFile.SDCardBean.INDEX_SD0){
        //sd card 0存储设备
    }
    //还有其他的存储设备:sd1,flash2,usb
})

执行传输任务

const callback: OPLargerFileTrans.TransferTaskCallback = {
    onError: (code) => {
       //传输失败
    }, onStart: () => {
       //传输开始
    }, onProgress: (progress) => {
        //传输进度
    }, onCancel: (code) => {
        //传输取消,0:app取消,1:dev取消
    }, onSuccess: () => {
        //传输成功
    }
}
const sdCardBean = targetSDCardBean//目标存储设备
const fileBuffer = new Unit8Array()//文件数据
RCSPOpLargeFileTransfer?.isFree()//当前是不是空闲状态(没有进行大文件传输)
RCSPOpLargeFileTransfer?.excuteTransferTask({
    fileBuffer: fileBuffer,//文件数据
    sdCardBean: sdCardBean,//目标存储设备
    isSupportPackageCrc16: true,//设备是否支持crc16,公版默认支持
    fileName: fileName,//文件名
    lastModifyTime: tempFilePaths[0].time,//文件最新修改时间,flash文件需要根据这个进行覆盖
    fileNameEncodeFormat: 0,
    callback
})

2.2.3 取消传输任务

RCSPOpLargeFileTransfer?.cancelTransfer()

2.3 文件读取(设备->App)

该功能对应小程序中的大文件传输(Dev->App)

将设备端的文件读取到手机端
  • 只能执行一个文件读取任务,不能同时进行多个文件读取任务。

  • 当在目录浏览或者文件下载时,是不能进行文件读取任务,反之亦然。两者是互斥的。

在进行文件读取之前需要先从 RcspOperateWrapper 中获取到 OPLargerFileGet.IOperateLargeFileGet 文件读取操作对象。

const operateWrapper: RcspOperateWrapper | undefined = undefined;//此处需获取到真实的RcspOperateWrapper
//获取目录浏览操作对象
const RCSPOpLargeFileGet: OPLargerFileGet.IOperateLargeFileGet = operateWrapper?.getOperaterByClass(OPLargerFileGet.OperateLargeFileGet.prototype)

2.3.1 传输任务回调

interface TransferTaskCallback {
    /**
    *
    * @param code
    * 1:开始读取文件失败
    * 2:总数据长度不对
    * 3:传输超时
    *
    * 100x:对应设备端协议 结束命令的错误码 x
    */
    onError(code: number): void
    /**传输开始**/
    onStart(): void
    /**传输进度
    * @param packectData 分包数据
    * **/
    onProgress(progress: number, packectData?: ArrayBuffer): void
    /**传输成功
    * @param data 总包数据
    * **/
    onSuccess(data: ArrayBuffer): void
    /**
    * @param code
    * 0:主动取消
    * 1:丢包
    * 2:crc错误
    */
    onCancel(code: number): void
}

2.3.2 执行传输任务

传输任务有三种接口方式。
  • 1.以文件名方式读取

  • 2.以文件簇号方式读取

  • 3.以文件名方式读取指定存储设备

第一种方式目前已弃用。 第二种方式适用于绝大多数情况。 第三种方式仅使用于读取FLASH存储设备或者读取SD卡设备Download文件夹。

const callback: OPLargerFileTrans.TransferTaskCallback = {
    onError: (code) => {
    //传输失败
    }, onStart: () => {
    //传输开始
    }, onProgress: (progress, packectData) => {
        //传输进度
    }, onCancel: (code) => {
        //传输取消, 0:主动取消1:丢包 2:crc错误
    }, onSuccess: (data) => {
        //传输成功
    }
}
const sdCardBean = targetSDCardBean//目标存储设备,(簇号方式不需要)
const isFlash = sdCardBean.index == OPFile.SDCardBean.INDEX_FLASH
if(isFlash){
    //flash用文件名方式
    RCSPOpLargeFileGet.excuteGetTaskByFileNameAndDevHandle({
        offset: 0,
        sdCardBean: sdCardBean,
        fileName: fileName,
        callback: callback
    })
}else{
    //sd卡用簇号方式。Download文件夹可用文件名方式
    RCSPOpLargeFileGet.excuteGetTaskByFileCluster({
        offset: 0,
        sdCardBean: sdCardBean,
        cluster: cluster,
        callback: getFileCallback
    })
}

2.3.3 取消传输任务

RCSPOpLargeFileGet?.cancelGet()

2.4 表盘操作

表盘功能管理,包括:获取手表Flash资源文件列表,获取正在使用的表盘,添加手表Flash资源文件,设置当前表盘,获取表盘版本信息,获取表盘背景,设置自定义表盘背景,删除表盘,删除自定义表盘背景等等

在进行表盘操作之前需要先从 RcspOperateWrapper 中获取到 OPWatchDial.IOperateWatchDial 表盘操作对象。

const operateWrapper: RcspOperateWrapper | undefined = undefined;//此处需获取到真实的RcspOperateWrapper
//获取目录浏览操作对象
const RCSPOpWatchDial: OPWatchDial.IOperateWatchDial = operateWrapper?.getOperaterByClass(OPWatchDial.OperateWatchDial.prototype)

2.4.1 表盘操作事件回调

表盘事件通知,如:当前使用表盘变化,手表Flash资源文件列表发生变化

const callback: OPWatchDial.OperaterEventCallbackWatchDial = {
    onEvent: (_event: OPWatchDial.OperaterEventWatchDial) => {
        switch (_event.type) {
            case 'NONE':
                break;
            case 'UseDialChange'://当前表盘变化
                //当前使用的表盘
                const usingDail = _event.UseDialChange?.dial
                break;
            case 'WatchResourseFileListChange'://手表Flash资源文件列表变化
                //资源文件列表
                const watchResourceList = _event.WatchResourseFileListChange?.fileList
                break;
            default:
                break;
        }
    }
}
//注册回调
RCSPOpWatchDial?.registerEventCallback(callback)
//注销回调
RCSPOpWatchDial?.unregisterEventCallback(callback)
//移除全部回调
RCSPOpWatchDial?.removeAllEventCallback()

2.4.2 获取手表Flash资源文件列表

获取手表Flash资源文件列表默认是过滤了部分系统资源文件(例如:JL,FONT,SIDEBAR)。

可自定义修改过滤列表覆盖原有过滤列表。

const ignoreFileList: Array<string> = ["JL","FONT","SIDEBAR"];
RCSPOpWatchDial.setIgnoreFileList(ignoreFileList)

获取手表Flash资源文件列表(除系统资源文件:JL,FONT,SIDEBAR)

RCSPOpWatchDial.getWatchResourseFileList().then((res) => {
    //资源文件列表
    const watchResourceList = res
    //表盘文件名字过滤前缀
    const watchFilt = "WATCH"
    //表盘背景文件名字过滤前缀
    const bgpFilt = "BGP"
    for (let index = 0; index < watchResourceList.length; index++) {
        const element = watchResourceList[index];
        if (element.getName()?.toUpperCase().includes(watchFilt)) {
            //表盘文件
        } else if(element.getName()?.toUpperCase().includes(bgpFilt)) {
            //表盘背景文件
        }
    }
}).catch( error => {
    //失败
})

重新获取手表Flash资源文件列表(除系统资源文件:JL,FONT,SIDEBAR)

RCSPOpWatchDial.reloadWatchResourseFileList().then((res) => {
    //资源文件列表
    const watchResourceList = res
    //表盘文件名字过滤前缀
    const watchFilt = "WATCH"
    //表盘背景文件名字过滤前缀
    const bgpFilt = "BGP"
    for (let index = 0; index < watchResourceList.length; index++) {
        const element = watchResourceList[index];
        if (element.getName()?.toUpperCase().includes(watchFilt)) {
            //表盘文件
        } else if(element.getName()?.toUpperCase().includes(bgpFilt)) {
            //表盘背景文件
        }
    }
}).catch( error => {
    //失败
})

2.4.3 获取正在使用的表盘

先进行获取手表Flash资源文件列表(目录浏览),才可以获取到对应的正在使用表盘。

RCSPOpWatchDial?.getUsingDial().then((res) => {
    //正在使用的表盘
    const usingDial = res
}).catch((error) => {
    //失败
    console.error("当前使用表盘,失败:", error);
})

2.4.4 设置正在使用的表盘

设置切换表盘。目标表盘有自定义背景,会一起显示。

//目标表盘
const targetDial: OPDirectoryBrowse.File;
RCSPOpWatchDial?.setUsingDial(targetDial).then((res) => {
    //设置成功
}).catch((error) => {
    //失败
})

2.4.5 设置自定义表盘背景

设置自定义表盘背景,仅能设置当前正在使用表盘的自定义背景。

//目标自定义背景。undefined时,恢复为默认背景
const targetCustomBackground: OPDirectoryBrowse.File | undefined;//
RCSPOpWatchDial?.setDialCustomBackground(targetCustomBackground).then((res) => {
    //设置成功
}).catch((error) => {
    //失败
})

2.4.6 获取表盘的自定义背景

获取指定表盘的自定义背景

//目标表盘
const targetDial: OPDirectoryBrowse.File;
RCSPOpWatchDial?.getDialCustomBackground(targetDial).then((res)=>{
    const customBackground: OPDirectoryBrowse.File | null = res
    //自定义背景为null时,说明用的是默认背景
}).catch((error) => {
    //失败
})

2.4.7 获取表盘版本信息

获取指定表盘的版本信息。应用端从表盘文件内获取表盘的版本信息,需要借用表盘文件解析库(jl_packResFormat.js)。

//目标表盘
const targetDial: OPDirectoryBrowse.File;
RCSPOpWatchDial?.getDialVersionInfo(targetDial).then((res)=>{
    //版本信息:例如:W002,ECF2E7ED-6EC7-4B75-858B-87D2ECE6CA11
    const versionString: string = res
}).catch((error) => {
    //失败
})

2.4.8 添加手表Flash资源文件

添加资源文件(表盘文件和自定义背景)到手表的Flash存储设备。(这里的文件传输是基于大文件传输,只能一次传输一个文件)。 如何把png或jpg图片转换成bgp格式的自定义背景文件,请参考demo;

//传输回调
const transferCallback: OPLargerFileTrans.TransferTaskCallback = {
    onError: (code: number) => {
        //传输失败,code
    },
    onStart: () => {
        //开始传输
    },
    onProgress: (progress: number) => {
        //正在传输,进度
    },
    onSuccess: () => {
        //传输成功
    },
    onCancel: (_code: number) => {
        //传输取消
    }
}
const data: ArrayBuffer;//文件数据
const fileName: string;//文件名,英文或数字
const lastModifyTime: number;//更新时间,文件覆盖更新判断依据
const refreshDirectory = true;//添加完成后,是否刷新目录
RCSPOpWatchDial?.addWatchResourseFile(data, fileName, lastModifyTime, refreshDirectory, transferCallback).then((res) => {
    if (res instanceof OPDirectoryBrowse.File) {
        //传输成功且刷新目录

        //传输自定义背景完成后,设置为当前使用的自定义背景
        //RCSPOpWatchDial?.setDialCustomBackground(res)
        //传输表盘完成后,设置为当前使用的表盘
        //RCSPOpWatchDial?.setUsingDial(res)
    } else if (res == undefined) {
        //传输完成后,目录浏览找不到该文件
        //传输完成后找不到该文件,可能是文件名不符合标准,太长或者带中文
    } else {
        //传输成功且不刷新目录
        //res是目录浏览的相对路径
    }
}).catch((_error) => {
    //失败
})

Important

添加资源文件后,需要调用 RCSPOpWatchDial?.setDialCustomBackgroundRCSPOpWatchDial?.setUsingDial 设置使用。

取消添加手表Flash资源文件

//取消添加手表Flash资源文件
RCSPOpWatchDial?.cancelAddWatchResourseFile()

2.4.9 删除表盘

const targetDial: OPDirectoryBrowse.File
RCSPOpWatchDial?.deleteDial(targetDial).then(()=>{
    //删除成功
}).catch((error)=>{
    //失败
})

2.4.10 删除自定义表盘背景

const targetCustomBackground: OPDirectoryBrowse.File
RCSPOpWatchDial?.deleteDialCustomBackground(targetCustomBackground).then(()=>{
    //删除成功
}).catch((error)=>{
    //失败
})

2.4.11 如何从表盘文件中读取表盘的版本信息

jl_packResFormat_1.0.0.js 是用来解析表盘文件信息的库。

const packResFormat = new PackResFormat()
//文件名,如:watch1
const fileName = tempFilePaths[0].name
//获取文件名.json的数据
const fileInfoData = packResFormat.getFileData(uint8, fileName + '.json')
//Uint8Array 转 String
const fileInfo = Uint8ArrayToString(fileInfoData)

2.4.12 如何用JPG文件生成自定义背景文件

jl_bmpConvert_1.0.0.js 是用来将RGB数组数据或ARGB数组数据转换成bgp自定义背景格式文件的库。

Important

制作自定义背景文件前,需要 通过 RCSPOpSystemInfo?.getSystemInfo() 获取到设备的屏幕的宽高。否则会出现大小尺寸不符

制作自定义背景的步骤总共分为3步:

  • 第一步:将jpg图片加载到canvas A上,可对图片进行放大缩小偏移进行选取裁剪范围。这一步主要是负责选取要显示的范围,canvas A的宽高是不满足设备屏幕宽高,所以无法从canvas A获取到rgba数据。

  • 第二步:canvas B的宽高与设备屏幕的宽高一致,在canvas B上对图片进行第一步中对应的放大缩小,然后获取canvas B的rgba数据(wx.canvasGetImageData) 。这一步是要在跟设备屏幕等宽高的canvas B中获取到足够像素点的rgba数据。

  • 第三步:调用bmpConvert将gbra数据转换成bgp文件数据。

2.5 OTA升级

OTA升级包含单备份升级和双备份升级,单备份升级需要回连设备,双备份不需要回连设备。升级成功后,会重启设备。 OTA升级流程是通过 RcspOpImpl 进行OTA数据的传输。

2.5.1 升级过程回调

单备份设备需要在 onNeedReconnect 实现回连流程,回连成功后需要把新的 RcspOpImpl 更新给 RcspOTAManager,目前 RcspOpImpl 是一个对应一次蓝牙连接。 更新 RcspOpImpl 成功后,才能在回连后继续进行OTA的数据传输。

/** 升级流程的回调 */
interface OnUpgradeCallback {
    /** OTA开始*/
    onStartOTA(): void;
    /**需要回连的回调
    * <p>
    * 注意: 1.仅连接通讯通道(BLE or  SPP)
    * 2.用于单备份OTA</p>
    *
    * @param reConnectMsg 回连设备信息
    */
    onNeedReconnect(reConnectMsg: ReConnectMsg): void;
    /** 进度回调
    *
    * @param type     类型
    * @param progress 进度
    */
    onProgress(type: UpgradeType, progress: number): void;
    /** OTA结束*/
    onStopOTA(): void;
    /** OTA取消*/
    onCancelOTA(): void;
    /** OTA失败
    * @param error   错误码
    * @param message 错误信息
    */
    onError(error: number, message: string): void;
}

2.5.2 开始升级

单备份升级需要扫描设备回连,需要监听扫描状态。这部分根据项目实际情况进行修改。

this.scanCallback = {
    onScanFinish: () => {
        //扫描结束,通知回连,回连会再次调用扫描
        if (_Reconnect != null) {
            _Reconnect.onScanStop()
        }
    },
    onFound: (devs) => {
        //发现设备,回调给回连
        if (_Reconnect != null) {
            for (let index = 0; index < devs.length; index++) {
                const element: any = devs[index];
                _Reconnect.onDiscoveryDevice(element.device)
            }
        }
    }
}
RCSPBluetooth.bleScan.addCallback(this.scanCallback)
RCSPBluetooth.bleScan.removeCallback(this.scanCallback)

单备份升级回连成功后,监听RCSP协议库初始化状态获取到新的 RcspOpImpl 更新给 RcspOTAManager

this.rcspWrapperEventCallback = {
    onEvent: (_res) => {
        //RCSP协议库初始化成功
        if (_res.type == "onRcspInit" && _res.onRcspInitEvent) {//单备份升级会切换ble地址,导致rcspOpImpl变换
            if (_res.onRcspInitEvent.isInit && _res.onRcspInitEvent.device.deviceId.toUpperCase() == this.reconnectingDeviceId.toUpperCase()) {
                const bluetoothDevice = RCSPManager.getBluetoothDeviceByDeviceId(_res.onRcspInitEvent.device.deviceId)
                if (bluetoothDevice) {//若多设备连接时,可以在这里判断deviceId是不是回连目标的deviceId
                    const rcspOperateWrapper = RCSPManager.getRcspOperateWrapper(bluetoothDevice)
                    const rcspOpImpl = rcspOperateWrapper?.getRcspOpImpl()
                    if (rcspOpImpl) {
                        _Reconnect?.onDeviceConnected(new Device(bluetoothDevice.deviceId,bluetoothDevice.name))
                        this.rcspOTAManager.updateRcspOpImpl(rcspOpImpl)
                    }
                }
            }
        }
    }
}
RCSPManager.observe(this.rcspWrapperEventCallback)
RCSPManager.removeObserve(this.rcspWrapperEventCallback)

开始执行OTA升级

const upgradeCallback :OnUpgradeCallback= {
    onStartOTA: () => {
       //开始升级
    },
    onNeedReconnect: (reConnectMsg: ReConnectMsg) => {
        //单备份:传输Loader成功后,正在回连设备
        //###实现回连,这一部分可以自己实现
        const op: ReconnectOp = {
            startScanDevice(): any {
                //开始扫描设备
                RCSPBluetooth.bleScan.startScan()
            },
            isReconnectDevice(scanDevice: WechatMiniprogram.BlueToothDevice): boolean {
                //判断是不是回连设备
                //判断条件:
                //旧回连方式:根据deviceId进行回连
                //新回连方式:根据蓝牙广播包中的mac地址进行回连
                let result = false;
                //获取设备的deviceId
                const oldDevice = that.rcspOTAManager.getCurrentOTADevice()
                //获取设备的Mac
                const oldDeviceMac = that.rcspOTAManager.getCurrentOTADeviceMac()
                if (oldDeviceMac != undefined) {
                    //跟设备交互后,根据App和设备情况决定是否走新回连方式
                    if (reConnectMsg.isSupportNewReconnectADV) {//使用新回连方式-广播包广播BLE地址
                        const advertisStr = ab2hex(scanDevice.advertisData).toUpperCase()
                        const index = advertisStr.indexOf("D60541544F4C4A");
                        if (index != -1) {
                            const unit8Array = new Uint8Array(scanDevice.advertisData)
                            const macArray = unit8Array.slice(index + 8, index + 14).reverse()
                            // console.log("新回连广播包 newMAC : " + ab2hex(macArray).toUpperCase())
                            result = oldDeviceMac.toUpperCase() == that.hex2Mac(macArray).toUpperCase()
                        }
                        // console.log("新回连广播包 oldMAC : " + oldDeviceMac + " scanMAC: " + scanDevice.deviceId + " result: " + result + " rawData: " + ab2hex(scanDevice.advertisData));
                    } else {//旧方式回连-匹配BLE地址
                        result = oldDevice!.deviceId == scanDevice.deviceId
                        // console.log("旧方式回连 : oldMAC: " + oldDevice!.deviceId + " scanMAC: " + scanDevice.deviceId + " result: " + result);
                    }
                }
                return result
            },
            connectDevice(device: WechatMiniprogram.BlueToothDevice): any {
                //判断是目标设备后,开始连接设备
                const deviceTemp = new BluetoothDevice()
                deviceTemp.RSSI = device.RSSI
                deviceTemp.advertisData = device.advertisData
                deviceTemp.advertisServiceUUIDs = device.advertisServiceUUIDs
                deviceTemp.connectable = device.connectable
                deviceTemp.deviceId = device.deviceId
                deviceTemp.localName = device.localName
                deviceTemp.serviceData = device.serviceData
                console.log(" 回连,连接设备:" + device.deviceId);
                that.reconnectingDeviceId = device.deviceId
                RCSPBluetooth.bleConnect.connectDevice(deviceTemp)
            }
        }
        const callback: ReconnectCallback = {
            onReconnectSuccess(device: Device) {
                //回连设备成功
                that.rcspOTAManager.updateOTADevice(device)
                _Reconnect = null;
            },
            onReconnectFailed() {//不用处理,库里会自动超时
                //Reconnect执行回连任务失败,不是sdk库回连失败
                _Reconnect = null;
            }
        }
        _Reconnect = new Reconnect(op, callback)
        _Reconnect.startReconnect(OTAImpl.RECONNECT_DEVICE_TIMEOUT);
    },
    onProgress: (type: UpgradeType, progress: number) => {
        let msg = type == UpgradeType.UPGRADE_TYPE_FIRMWARE ? '发送sdk升级数据' : '发送uboot升级数据'
        this.setData({
            otaProgressText: "正在" + msg + "...,进度:" + (new Number(progress).toFixed(2))
        })
    },
    onStopOTA: () => {
        //升级成功
        this.rcspOTAManager.release()
        //升级完成,释放设备
        const connectedDeviceIds = RCSPBluetooth.bleConnect.getConnectedDeviceIds()
        if (connectedDeviceIds != null) {
            RCSPBluetooth.bleConnect.disconnect(connectedDeviceIds[0])
        }
    },
    onCancelOTA: () => {
        //升级取消,释放设备
        const connectedDeviceIds = RCSPBluetooth.bleConnect.getConnectedDeviceIds()
        if (connectedDeviceIds != null) {
            RCSPBluetooth.bleConnect.disconnect(connectedDeviceIds[0])
        }
        this.rcspOTAManager.release()
    },
    onError: (error: number, message: string) => {
        if (_Reconnect != null) {
            _Reconnect.stopReconnect()
        }
        //升级失败,释放设备
        const connectedDeviceIds = RCSPBluetooth.bleConnect.getConnectedDeviceIds()
        if (connectedDeviceIds != null) {
            RCSPBluetooth.bleConnect.disconnect(connectedDeviceIds[0])
        }
        this.rcspOTAManager.release()
    }
}
const otaConfig: OTAConfig = new OTAConfig()
otaConfig.isSupportNewRebootWay = true//是否支持新回连方式,默认是支持。##新回连方式,会在单备份回连时,改变设备的BLE地址,用于处理一些手机蓝牙问题。
otaConfig.updateFileData = this.otaData//升级数据
//初始化RCSPOTAManager
this.rcspOTAManager = new RcspOTAManager(rcspOpImpl)
//开始升级
this.rcspOTAManager.startOTA(otaConfig, upgradeCallback)

取消OTA升级(仅双备份支持)

const rcspOpImpl = RCSPManager.getCurrentRcspOperateWrapper()?.wrapper.getRcspOpImpl()
const currentDevice = rcspOpImpl?.getUsingDevice()
if (currentDevice != null) {
    //获取设备是不是双备份升级
    if (rcspOpImpl?.getDeviceInfo(currentDevice)?.isSupportDoubleBackup == true) {
        //取消双备份升级
        this.rcspOTAManager.cancelOTA()
    } else {
        wx.showToast({ title: "单备份不支持" })
    }
}

2.5.3 升级回连广播包

JLOTA字符的二进制数组为 D60541544F4C4A

0~4Bytes

5Byte

6 ~11Bytes

12~13Bytes

14~15Bytes

16Byte

17Byte

18~26Bytes

标识

版本

JLOTA

0

原BLE地址

JLOTA

1

原BLE地址

UID

PID

Bit7-4:Type,Bit3-0:Version

电量

保留位