杰理 SDK 文档中心
首页
首页
  • 项目概览与入门

    • 项目概述
    • 快速开始
  • OTA SDK 核心库

    • RCSP 认证库(jl_auth)
    • OTA 流程库(jl_ota)
    • RCSP-OTA 协议库(jl_rcsp_ota)
    • OTAWrapper 高层封装
  • 蓝牙通信与设备管理

    • 蓝牙连接生命周期管理
    • BLE 数据发送与 MTU 管理
    • 自动回连机制
  • 参考 Demo 小程序

    • 应用入口与页面导航
    • 设备连接页(pageConnect)
    • 固件升级页(pageUpdate)
    • 设置与调试页(pageSetting)
    • 自定义 UI 组件
    • 固件文件解析工具(upgradeFileUtil)
    • 日志系统

日志系统

本页介绍 WeChat-Mini-Program-OTA 工程中由杰理 OTA/RCSP SDK 内置的日志系统:JLOTASDK 与 JLRCSP 两条日志管道的级别体系、外部 logger 注入机制、日志过滤规则,以及小程序侧的使用与扩展方式。

Purpose and Scope

本页覆盖范围:

  • SDK 日志管道的架构与数据流(jl_ota_2.1.1.js 与 jl_rcsp_ota_2.1.1.js 中的日志实现)
  • 日志级别(logv / logd / logi / logw / loge)与阈值过滤规则
  • setLogger() / setLogGrade() 两个配置入口的设计意图
  • 小程序侧如何接入日志(包括适配 console 与微信实时日志)
  • 日志相关的边界行为、性能与运维注意事项

不涉及的内容(属于其他目录页的边界):

  • 蓝牙设备扫描、连接与数据收发流程(参见「蓝牙连接」相关页面)
  • OTA 升级状态机与命令协议细节(参见「OTA 升级流程」相关页面)
  • 用户可见的提示弹窗封装 toast / showBtStatusMsg 属于 util.js 的 UI 工具,本页仅在「扩展点」中提及

Overview

该工程的 OTA 固件升级能力由两套 JS 库组成,形成分层结构:

  1. jl_rcsp_ota_2.1.1.js —— RCSP(杰理自有通信协议)协议层,负责 BLE 链路上的命令封装、分包、重发、解析,标签为 JLRCSP。
  2. jl_ota_2.1.1.js —— OTA 业务层,负责升级状态机(查询设备、进入升级模式、分块传输、查询结果、重连等),标签为 JLOTASDK,且 require 了 RCSP 层。

每一层内部都内嵌了一套独立的日志管道:模块级变量持有「外部 logger 对象」与「日志级别阈值」,对外导出五个日志方法与两个配置方法。任何业务代码(如 OTAImpl 的状态机、RCSPDataHandler 的数据收发)在需要记录信息时,都直接调用本模块内部的日志函数(如 l(...)、a(...)、n(...)),这些函数统一做「级别过滤 + 委托给外部 logger」两件事。

这种设计的核心意图是:

  • 解耦:SDK 自身不依赖 console 或微信任何 API,宿主小程序通过 setLogger() 决定日志最终去向(控制台、实时日志、远程上报等),SDK 对宿主零感知。
  • 性能:日志调用是热路径(BLE 收包/发包、进度回调高频触发),级别过滤在 SDK 内部先行完成,未达到阈值时根本不调用 logger 方法,避免无意义的字符串拼接与函数调用开销。
  • 可观测性:开发者可以在不修改 SDK 源码的前提下,通过自定义 logger 收集升级过程的关键节点日志,用于问题定位。

两个库在仓库中存在两份拷贝:工程内引用路径 code/JLOTA/miniprogram/lib/jl_lib/ 与发布路径 libs/,内容一致(均为压缩构建产物),本页以工程内路径为准。

Architecture

flowchart TD
    subgraph sg_App["小程序宿主 (miniprogram)"]
        App["业务页面 / 工具代码"]
        CustomLogger["自定义 Logger 对象<br/>(logv/logd/logi/logw/loge 方法)"]
    end

    subgraph sg_OtaLib["jl_ota_2.1.1.js (标签 JLOTASDK)"]
        OTA_Logger["模块级 logger 引用 s"]
        OTA_Grade["级别阈值 n (默认 1)"]
        OTA_LogFns["logv/logd/logi/logw/loge 导出函数"]
        OTA_Impl["OTAImpl 状态机调用点<br/>(startOTA/cancelOTA/重连/错误回调)"]
    end

    subgraph sg_RcspLib["jl_rcsp_ota_2.1.1.js (标签 JLRCSP)"]
        RCSP_Logger["模块级 logger 引用 s"]
        RCSP_Grade["级别阈值 r (默认 1)"]
        RCSP_LogFns["logv/logd/logi/logw/loge 导出函数"]
        RCSP_Impl["RCSPDataHandler / RcspParser 调用点<br/>(发包/收包/重发/解析)"]
    end

    App -->|"require 引入"| OTA_Impl
    OTA_Impl -->|"内部调用"| OTA_LogFns
    OTA_LogFns -->|"级别过滤: n <= 阈值?"| OTA_Logger
    OTA_Impl -->|"require 依赖"| RCSP_Impl
    RCSP_Impl -->|"内部调用"| RCSP_LogFns
    RCSP_LogFns -->|"级别过滤: r <= 阈值?"| RCSP_Logger
    App -->|"setLogger() 注入"| CustomLogger
    CustomLogger -->|"委托输出"| OTA_Logger
    CustomLogger -->|"委托输出"| RCSP_Logger

架构说明

  • 两条独立管道:OTA 层与 RCSP 层各自持有独立的 logger 引用(压缩后均为 s)与级别阈值(OTA 层为 n,RCSP 层为 r)。因此两个库的 setLogger() / setLogGrade() 是分别调用的——只配置其一,另一层仍保持默认状态。
  • 单向依赖:jl_ota_2.1.1.js 依赖 jl_rcsp_ota_2.1.1.js(require("./jl_rcsp_ota_2.1.1.js")),日志同样分层:RCSP 层记录协议收发细节,OTA 层记录升级业务状态,二者通过同一个宿主 logger 输出,但以标签 JLRCSP / JLOTASDK 区分来源。
  • 零运行时依赖:SDK 内部不 import 任何微信 API 或 console,全部输出经由注入的 logger 对象完成,因此可以运行于任意 JS 宿主(微信小程序、真机调试、单元测试环境)。

日志级别与过滤机制

级别体系

SDK 采用 Android Log 风格的 5 级日志体系,数值越小越详细:

导出函数语义内部阈值判定典型场景(源码证据)
logvVERBOSE(冗余)n <= 1设备 ADV 广播信息、数据缓冲排队提示(RCSPDataHandler: 放入数据缓冲区,等待发送)
logdDEBUG(调试)n <= 2exports.logd 导出但 OTA 层内部无直接调用点(供宿主使用)
logiINFO(信息)n <= 3连接初始化、inquiryDeviceCanOTA : >>>>>>>>>>>>、queryUpdateResult 结果打印
logwWARN(警告)n <= 4命令重发计数提示(reSendCount)、未知 ADV 类型告警
logeERROR(错误)n <= 5协议解析失败、callbackOTAError、超时重连失败、OnSendDataCallback is null 等

过滤判定规则

以 RCSP 层(压缩产物)为例,模块头部定义了默认阈值与五个级别函数:

const t = "JLRCSP";
var s, e, r = 1;
function n(...e) { r <= 1 && null != s && s.logv(t, ...e) }
function i(...e) { r <= 3 && null != s && s.logi(t, ...e) }
function a(...e) { r <= 4 && null != s && s.logw(t, ...e) }
function h(...e) { r <= 5 && null != s && s.loge(t, ...e) }
// exports.logd = function(...e) { r <= 2 && null != s && s.logd(t, ...e) }
// exports.setLogGrade = function(t) { r = t }
// exports.setLogger = function(t) { s = t }

Source: jl_rcsp_ota_2.1.1.js

OTA 层(压缩产物)采用完全相同的结构,标签为 JLOTASDK、阈值变量为 n:

var t = require("./jl_rcsp_ota_2.1.1.js");
const e = "JLOTASDK";
var s, i, n = 1;
function r(...t) { n <= 1 && null != s && s.logv(e, ...t) }
function a(...t) { n <= 3 && null != s && s.logi(e, ...t) }
function l(...t) { n <= 5 && null != s && s.loge(e, ...t) }
// exports.logd = function(...t) { n <= 2 && null != s && s.logd(e, ...t) }
// exports.logw = function(...t) { n <= 4 && null != s && s.logw(e, ...t) }
// exports.setLogGrade = function(t) { n = t }
// exports.setLogger = function(t) { s = t }

Source: jl_ota_2.1.1.js

关键点:

  1. 双重守卫:每条日志都要同时满足「级别阈值」与「logger 已注入」两个条件,任一不满足则整条调用被短路(&& 左侧为 false 时右侧不执行)。这是刻意为之的零开销过滤——阈值过滤发生在任何字符串模板拼接之前。
  2. 默认阈值 1(VERBOSE):SDK 默认记录全部级别;但默认 s 为 undefined,即未调用 setLogger 之前所有日志全部丢弃。二者结合意味着:默认配置下 SDK 静默运行,只有显式注入 logger 后才开始产出日志。
  3. 注意:OTA 层内部仅定义了 logv/logi/loge 三个内部函数(对应压缩名 r/a/l),logd/logw 只在 exports 中按同一阈值规则补齐,供宿主直接调用导出 API 时使用。

阈值与可见性对照

setLogGrade(grade)可见级别备注
1全部(v/d/i/w/e)默认值,最详细
2d/i/w/e隐藏 VERBOSE
3i/w/e常见生产配置
4w/e仅警告与错误
5e仅错误
其他值视比较结果而定SDK 不做合法性校验,传入非 1-5 数值会导致所有/部分级别被过滤,需自行保证

核心实现剖析

日志委托链路(以一次错误回调为例)

OTA 升级失败时,OTAImpl.D(t, e)(压缩名)会构造错误描述并调用内部 l(...)(即 loge)记录错误码(十六进制带 0x 前缀)与附加信息,随后才触发上层回调 onError:

D(t, e) {
    this.v(null), this.O(), l("callbackOTAError :  has an exception, code = " +
        function (t) {
            let e; e = t < 0 ? -t : t;
            const s = e.toString(16).toUpperCase();
            return "" === s ? "0x00" : t < 0 ? "-0x" + s : "0x" + s
        }(t) + ", " + e),
        this.m.onError(t, e), this.m.callback = null
}

Source: jl_ota_2.1.1.js

这段代码揭示了两点设计意图:

  • 日志先行、回调在后:先记录完整上下文(错误码十六进制 + 描述),再触发用户回调,保证即使回调抛出异常或回调处理逻辑复杂,错误现场也已落盘/输出。
  • 日志与业务解耦:D() 同时负责状态清理(v(null)、O())、日志记录、回调通知三个职责,日志只是旁路,不影响主流程。

RCSP 协议层的高频日志点

协议层在数据收发热路径上埋有大量 logv/logw 日志,是排查 BLE 链路问题的主要依据:

// 发送队列:入队等待发送
W(t) {
    if (this.B(t)) {
        if (this.H.push(t), this.H.length > 1)
            return void n("RCSPDataHandler: 放入数据缓冲区,等待发送");
        const s = { complete: () => { this.X(this.H, s) } };
        this.X(this.H, s)
    }
}
// 重发超时:记录重发计数
X(t, s) {
    // ...
    let t = setTimeout((t => {
        const s = t, e = s._;
        a("RCSPDataHandler: " + s.command + ", reSendCount: " + e + ", limit: " + this.T);
        // ... 未达上限则重发,达到上限则回调 ERROR_RESPONSE_TIMEOUT
    }), e.timeoutMs, e);
}

Source: jl_rcsp_ota_2.1.1.js

reSendCount 日志属于 WARN 级(a = logw),用于提示链路不稳定、命令重发中;若连续重发超过上限(limit: 3)则升级为 ERROR_RESPONSE_TIMEOUT 错误并通过 loge 记录。这样的分级让开发者可以依据日志级别快速筛选问题:WARN 级大量出现说明信道质量差,ERROR 级出现则说明命令已失败。

字符串与二进制数据的日志化

SDK 还导出了若干辅助函数,用于在日志中展示二进制数据,属于日志系统的「格式化工具层」:

  • ab2hex(buffer):ArrayBuffer/Uint8Array 转十六进制字符串(如设备信息、license)。
  • toHexWithPrefix(value)(RCSP 层):负数取绝对值后输出 -0x…,非负数输出 0x…,用于日志中展示错误码。
  • hexDataCovetToAddress(RCSP 层):字节数组转大写冒号分隔的 MAC 地址格式,用于日志中展示设备地址。

这些函数在 SDK 内部日志调用中高频使用(例如 fillTargetInfo: number:0 value: …、callbackOTAError : code = -0x67),保证二进制信息以人类可读形式进入日志流。

使用示例

基础用法:注入 console 作为 logger

两个库都导出 setLogger,宿主可在 app.js 或工具模块中一次性注入。由于 console 自带 log/info/warn/error 方法,而 SDK 要求的 logger 接口是 logv/logd/logi/logw/loge,直接传 console 只能命中部分级别,推荐做一层适配:

// utils/logger.js —— 宿主侧日志适配层(示意)
const jlRcsp = require('../lib/jl_lib/jl_rcsp_ota_2.1.1.js');
const jlOta = require('../lib/jl_lib/jl_ota_2.1.1.js');

const sdkLogger = {
    logv(tag, ...args) { console.log(`[${tag}]`, ...args) },
    logd(tag, ...args) { console.log(`[${tag}]`, ...args) },
    logi(tag, ...args) { console.info(`[${tag}]`, ...args) },
    logw(tag, ...args) { console.warn(`[${tag}]`, ...args) },
    loge(tag, ...args) { console.error(`[${tag}]`, ...args) }
};

// 注意:两条管道必须分别注入
jlRcsp.setLogger(sdkLogger);
jlOta.setLogger(sdkLogger);

// 建议同时将级别收敛到 INFO(3),避免 VERBOSE 刷屏
jlRcsp.setLogGrade(3);
jlOta.setLogGrade(3);

Source: jl_rcsp_ota_2.1.1.js · jl_ota_2.1.1.js

高级用法:对接微信实时日志(RealtimeLogManager)

生产环境排障时,可将 SDK 日志委托给 wx.getRealtimeLogManager(),使 JLOTASDK/JLRCSP 标签日志进入微信实时日志系统(支持真机远程查看):

const realtimeLogger = {
    logv(tag, ...args) { wx.getRealtimeLogManager().info(tag, ...args) },
    logd(tag, ...args) { wx.getRealtimeLogManager().info(tag, ...args) },
    logi(tag, ...args) { wx.getRealtimeLogManager().info(tag, ...args) },
    logw(tag, ...args) { wx.getRealtimeLogManager().warn(tag, ...args) },
    loge(tag, ...args) { wx.getRealtimeLogManager().error(tag, ...args) }
};
// 注入后,OTA 升级全过程的关键节点均可在微信开发者工具/实时日志面板回溯

Source: jl_ota_2.1.1.js

说明:wx.getRealtimeLogManager() 属于宿主侧 API,SDK 本身不感知。上述示例展示的是「利用 setLogger 扩展点把 SDK 日志接入微信基础设施」的典型做法;具体 API 以微信官方文档为准。

直接调用导出日志 API

宿主业务代码也可以直接使用两个库导出的 logv/logd/logi/logw/loge(它们会自动携带 SDK 标签):

const otaLib = require('../lib/jl_lib/jl_ota_2.1.1.js');
otaLib.logi('用户点击了升级按钮,开始准备 OTA 配置');   // 输出标签为 JLOTASDK
otaLib.loge('升级文件读取失败');

Source: jl_ota_2.1.1.js

用户可见反馈通道(非日志)

util.js 提供了面向用户的弹窗与蓝牙错误提示封装,与日志系统互补:日志面向开发者(可追溯、可分级),toast/showBtStatusMsg 面向用户(即时反馈):

export function showBtStatusMsg(code, errMsg) {
    switch (code) {
        case 10000: toast('未初始化蓝牙适配器'); break;
        case 10001: toast('未检测到蓝牙,请打开蓝牙重试!'); break;
        case 10002: toast('没有找到指定设备'); break;
        case 10003: toast('连接失败'); break;
        // ... 其余系统错误码映射
        default: toast(errMsg);
    }
}

Source: util.js

设计意图:SDK 日志管道负责「发生了什么」(技术细节),showBtStatusMsg 负责「用户该怎么办」(可操作的提示),二者在集成时通常配合使用——先记日志定位,再弹提示引导。

配置选项

选项类型默认值作用范围说明
setLogGrade(grade)number1(VERBOSE)单条管道独立设置日志级别阈值,低于阈值的日志被过滤;OTA 层变量 n、RCSP 层变量 r,互不影响
setLogger(logger)objectnull单条管道独立注入外部 logger 对象,需提供 logv/logd/logi/logw/loge 方法;未注入时所有日志静默丢弃

两个库必须分别调用上述方法(共 4 次调用)才能完整开启日志。仓库内无 appsettings/环境变量类配置,日志行为完全由代码注入决定。

API 参考

以下 API 同时存在于 jl_ota_2.1.1.js 与 jl_rcsp_ota_2.1.1.js 的导出中(OTA 层标签 JLOTASDK,RCSP 层标签 JLRCSP)。

setLogger(logger): void

注入日志输出目标。

参数:

  • logger(object):需实现 logv(tag, ...args)、logd(tag, ...args)、logi(tag, ...args)、logw(tag, ...args)、loge(tag, ...args) 五个方法;tag 由 SDK 传入(JLOTASDK 或 JLRCSP)。

返回: 无。

说明: 内部实现为 s = t(直接赋值模块级引用)。重复调用会覆盖之前的 logger。传入 null/undefined 可关闭日志输出。

setLogGrade(grade): void

设置日志级别阈值。

参数:

  • grade(number):1=VERBOSE,2=DEBUG,3=INFO,4=WARN,5=ERROR。

返回: 无。

说明: 内部实现为 n = t(OTA 层)/ r = t(RCSP 层)。SDK 不做取值校验,需由调用方保证范围。

logv(...args) / logd(...args) / logi(...args) / logw(...args) / loge(...args): void

按级别输出日志。

参数: 任意可打印参数,SDK 内部调用时第一个参数通常为字符串消息(标签由 SDK 自动附带)。

返回: 无。

行为: 级别号 ≤ 当前阈值且已注入 logger 时才调用 logger.logX(tag, ...args);否则静默返回。注意 SDK 内部调用不经过导出函数(直接调用内部闭包),因此即使宿主只使用导出 API,内部日志也照常工作。

失败模式、边界情况与并发

logger 未注入(最常见的「日志丢了」场景)

默认 s = null,未调用 setLogger 前所有日志静默丢弃。排查日志缺失时,第一步应确认两条管道都已注入(jlRcsp.setLogger(...) 与 jlOta.setLogger(...) 缺一不可),且注入发生在 SDK 产生日志之前(建议在 app.js 启动早期完成)。

级别阈值设置不当导致日志「过少」或「过多」

  • 设为 5 时仅保留 ERROR,升级成功的正常路径(onRcspInit、queryUpdateResult 等 INFO 日志)全部不可见,无法确认流程走到了哪一步。
  • 保持默认 1 且接入实时日志时,VERBOSE 级高频日志(数据缓冲、ADV 信息)会产生大量噪音并占用实时日志配额。
  • SDK 不做范围校验,传入 0 或负数会导致全部级别被过滤;传入大于 5 的值则只有 loge 可能可见。

版本分裂风险:libs/ 与 code/ 双拷贝

仓库同时存在 libs/jl_ota_2.1.1.js、libs/jl_rcsp_ota_2.1.1.js 与 code/JLOTA/miniprogram/lib/jl_lib/ 下的同名文件,当前内容一致(压缩构建产物)。若后续升级 SDK,必须保证两处同步更新,否则会出现「工程内引用的代码与发布包不一致」的排查陷阱;日志标签与版本号(2.1.1)是判断文件版本的第一线索。

日志回调抛异常

SDK 对 logger 方法的调用没有 try/catch 保护。若宿主 logger 实现抛异常(如实时日志 API 在低版本基础库不可用),异常会沿日志调用点向上传播,可能中断 RCSPDataHandler 的数据处理循环。建议宿主 logger 内部自行兜底:

function safeCall(fn, ...args) {
    try { fn(...args) } catch (e) { /* 日志失败绝不影响主流程 */ }
}

并发与重入

SDK 为单线程 JS 模型,日志调用天然串行,无锁竞争问题。但两个库共享同一个宿主 logger 实例时,日志会以标签区分来源而非进程区分;若宿主在 logger 中做异步上报(如 wx.request 上传日志),需自行处理顺序与去重,SDK 不提供日志缓冲/批量上报能力。

高频路径上的字符串拼接

loge/logi 在错误回调、进度通知等路径上会拼接大段字符串(如 JSON.stringify(deviceInfo))。虽然阈值过滤发生在拼接之前(守卫短路),但一旦达到阈值,拼接开销即产生。生产环境建议将阈值收敛到 3(INFO)及以上,避免 VERBOSE/DEBUG 级的大对象序列化拖慢 BLE 数据处理热路径。

性能与运维注意事项

  • 过滤先行:SDK 的 n <= X && s.logX(...) 短路求值保证未达阈值的日志零成本,这是日志设计中最值得保持的性能特性;宿主 logger 不应再重复做级别判断。
  • 推荐生产配置:setLogGrade(3) + 实时日志/远程上报 logger,兼顾可观测性与性能;仅在真机联调阶段临时降为 1。
  • 日志量预估:一次完整 OTA 升级会产生数百条协议级日志(每块文件传输、每次重发计数),其中 VERBOSE 占大头;对日志有配额限制的平台(微信实时日志)需留意。
  • 发布包同步:升级 SDK 时同步更新 libs/ 与 code/ 两份拷贝,并以文件内 2.1.1 版本号核对。

扩展点

  1. 自定义 Logger:setLogger 是唯一的日志出口扩展点,可对接 console、wx.getRealtimeLogManager()、wx.request 远程日志、文件系统落盘等任意目标,且无需改动 SDK。
  2. 级别策略:setLogGrade 支持运行时动态调整,可依据调试开关/用户设置切换详细程度。
  3. 标签路由:宿主 logger 收到 JLOTASDK/JLRCSP 标签后可按来源路由(如只采集 OTA 层、忽略协议层),实现精细化的采集策略。
  4. 配套 UI 提示:结合 util.js 的 toast/showBtStatusMsg(util.js)将错误码映射为用户可读提示,形成「开发者日志 + 用户提示」双通道。

测试

仓库未包含针对日志系统的独立单元测试文件;日志行为由 SDK 运行期自检(如 g 类构造时 null == s && l("IHandleResult is null.") 这类内部断言日志)间接验证。实际验证方式以真机/开发者工具控制台观察 JLOTASDK/JLRCSP 前缀日志为准。集成时的自检清单:

  1. 注入 logger 后触发一次蓝牙连接,确认 JLRCSP 标签出现 onRcspInit INFO 日志;
  2. 触发一次 OTA 升级,确认 JLOTASDK 标签出现 inquiryDeviceCanOTA、queryUpdateResult 等节点日志;
  3. 调低 setLogGrade(5) 后复现一次失败升级,确认仅 ERROR 级输出且 callbackOTAError 带十六进制错误码。

Related Links

  • OTA SDK 业务层(日志标签 JLOTASDK)
  • RCSP 协议层(日志标签 JLRCSP)
  • UI 提示工具 util.js(toast / showBtStatusMsg)
  • 发布版 SDK 副本 libs/
  • 相关目录页:OTA 升级流程、蓝牙连接、RCSP 命令协议(参见目录导航中的对应页面)
Prev
固件文件解析工具(upgradeFileUtil)