跳转到内容
新建笔记

固件发布与诊断:差分更新的边界和现场反馈

原笔记包含两个独立目标:用二进制差分减少升级传输量,以及让现场人员打开诊断页面。二者分别属于发布可靠性和故障反馈,不能仅靠两条补丁命令与一个隐藏手势完成。

本文给出设计边界和可复用示例。实际产品的分区、引导程序、硬件型号、固件版本和认证机制未提供,因此不宣称已经实现可部署的 OTA 系统。

差分文件解决什么问题

跳转到“差分文件解决什么问题”

bsdiff 根据旧文件和新文件生成补丁;bspatch 根据旧文件和补丁重建新文件。它们处理二进制文件,不负责下载认证、硬件兼容、引导切换或掉电回滚。作者项目说明

在使用兼容版本的工具、具有足够磁盘和内存的离线工作目录中,可以验证:

终端窗口
# 制包端:old_firmware.bin 必须是明确版本的完整旧镜像
bsdiff old_firmware.bin new_firmware.bin update.patch
# 离线验证:输出到新文件,保留原镜像
bspatch old_firmware.bin candidate.bin update.patch
cmp -s candidate.bin new_firmware.bin
sha256sum old_firmware.bin new_firmware.bin candidate.bin update.patch

cmp 返回成功才能证明本次重建字节与目标一致。各文件的哈希应与发布记录关联,而不是只生成一串数字就算完成验证。以上命令尚未在本次环境执行;使用的 bsdiff 实现与补丁格式也应在项目中固定。

差分不保证总是更小:压缩、加密和镜像布局变化都可能影响收益。还要测量设备端重建所需 RAM、临时空间与时间。普通 MCU 未必能运行针对桌面文件设计的 bspatch;需要结合存储介质和更新框架选择算法,而不是照搬 Linux 命令。

一个可审查的更新包需要明确:设备型号及硬件修订、允许的基础版本或基础镜像哈希、目标版本与目标哈希、补丁类型、长度、引导程序要求,以及经过认证的发布者信息。基础镜像不匹配时应拒绝该差分包,或转用已验证的完整镜像路径,不能尝试“将就打补丁”。

下面是过程关系,具体实现应由选定的更新框架承担:

确认包来源与适用设备
→ 检查基础版本和空间
→ 在非运行区域重建候选镜像
→ 验证目标内容
→ 请求引导程序试启动
→ 健康检查通过后确认新版本
→ 失败时按既定策略恢复可启动版本

哈希能检测内容是否符合预期,但预期哈希本身必须来自可信的发布元数据;它不能单独认证发布者。签名验证、公钥更新和版本回退限制需要明确的信任设计。也不应在系统仍使用的文件或 Flash 区域上直接原地覆写,然后假定掉电后一定能恢复。

Linux 设备可以参考 RAUC 的签名 bundle、兼容性标识和 slot 机制。资源受限设备可以参考 MCUboot 的镜像验证、试启动与确认设计。二者有不同的存储和引导要求,不是给任意项目追加一个命令就能互换的库。RAUC 基础、MCUboot 设计

验证项需要回答的问题
基础版本支持哪些旧版本,镜像被修改时如何处理?
兼容性错误型号、硬件版本和引导程序版本能否被拒绝?
内容与认证截断、损坏、非授权或不允许回退的包能否被拒绝?
掉电下载、重建、写入、切换和首次启动中断后能否恢复?
启动健康怎样确认核心服务与设备接口正常,失败如何回退?
参数迁移新版本改动配置格式后,旧版本还是否能读取?
资源限制RAM、存储空间、耗时和写入寿命是否符合产品预算?

配置迁移尤其不能只测试“程序能启动”:新固件若覆盖了旧配置格式,回到旧镜像也可能无法工作。镜像与持久化数据要一起设计回滚边界。

诊断入口:手势只是发现方式

跳转到“诊断入口:手势只是发现方式”

Qt TapHandler 支持触摸和鼠标,tapCount 的连续点击受平台的时间和距离阈值影响。原示例只接受 TouchScreen,鼠标点击不会触发;也未给 Rectangle 明确尺寸或实现 showDebugMenu(),因此只是片段。Qt TapHandler 文档

下面给出可单独加载的 Qt 6 QML 组件。连续五次点按版本区域打开只读诊断,每次间隔超过 1.5 秒或组件隐藏时重置计数;同时接受鼠标和触摸。此规则使用独立计数器,避免把系统双击间隔当成产品的五击规则。

import QtQuick
import QtQuick.Controls
Rectangle {
id: root
width: 360
height: 240
color: "#f5f7fa"
property int tapStreak: 0
property bool diagnosticsOpen: false
property string buildVersion: "demo"
function resetGesture() {
tapStreak = 0
gestureTimeout.stop()
}
function registerTap() {
tapStreak += 1
if (tapStreak === 5) {
resetGesture()
diagnosticsOpen = true
} else {
gestureTimeout.restart()
}
}
onVisibleChanged: if (!visible) resetGesture()
Timer {
id: gestureTimeout
interval: 1500
onTriggered: root.tapStreak = 0
}
Rectangle {
x: 20; y: 20; width: 320; height: 56
radius: 8
color: "white"
Text {
anchors.centerIn: parent
text: "版本 " + root.buildVersion
color: "#27364b"
}
TapHandler {
acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchScreen
acceptedButtons: Qt.LeftButton
onTapped: root.registerTap()
onCanceled: root.resetGesture()
}
}
Rectangle {
visible: root.diagnosticsOpen
x: 20; y: 90; width: 320; height: 130
radius: 8
color: "#e6edf4"
Text {
x: 12; y: 12
text: "只读诊断\n构建版本:" + root.buildVersion
color: "#27364b"
}
Button {
x: 220; y: 78
width: 84; height: 40
text: "关闭"
onClicked: root.diagnosticsOpen = false
}
}
}

把它作为界面组件使用,buildVersion 由应用提供真实构建版本。示例显示诊断信息,不执行升级、清空参数或直接控制硬件。若诊断页包含修改系统的功能,需要在后端核验相应权限和设备状态;知道五击手势不应成为执行特权操作的唯一条件。

项目中更易发现的“关于 → 诊断信息”入口也可以复用同一页面。原先希望隐藏入口,是交互选择,不是数据保密或权限校验机制。

诊断记录应包含应用与固件版本、硬件修订、问题时间、操作步骤、错误码、最近的状态变化和必要的性能指标。原始采样数据或日志导出应有明确范围,避免把口令、令牌或无关个人信息混入反馈包。

将反馈关联到可复现案例和对应修复版本,再用相同步骤复测。只显示一个隐藏菜单,却没有保存版本、输入与事件过程,仍然难以定位问题。

本次 Qt 6.10.2 检查已完成组件加载、五次鼠标点击打开、关闭按钮、超时和隐藏时重置。测试使用离屏软件渲染,未连接真实触摸屏。差分命令、签名更新、真实分区切换和掉电恢复尚未实施;它们需要具体产品工程与硬件验收。