36 hzadb-日志口adb文件管理(仅Air1601/Air1602)
作者:Wendal | 最后修改:2026-09-22
一、概述
合宙 adb(hzadb)是 LuatOS 提供的一个日志口用户指令协议(usercmd v2)设备端协议栈,由 Lua 脚本实现,不需要额外占用一路 UART。它把日志口复用为命令通道,让上位机(Luatools 的“设备文件管理”界面,或自带的 Python 上位机库)可以读写设备文件、查询设备运行状态。
协议具备以下能力:
- 协议版本号、命令序号、滑动窗口 + 逐片确认 + 自动重传
- HELLO 握手做分片大小协商、控制类回应去重
- open/read/write/close 文件模型,支持带 offset 的随机读写
- 可选 HMAC 挑战应答鉴权(AUTH)
- 挂载点枚举(LSMOUNT)、文件系统空间查询(FSSTAT)、文件指纹(FILE_SHA1)、内存状态(MEMINFO)、网络状态(NETSTAT)
hzadb 库只实现协议栈本身(帧解析、序号、去重、分片、鉴权门控),不内置任何业务逻辑:文件系统操作由 hzadb.fs() 安装,状态查询由 hzadb.mem() / hzadb.netdrv() 安装,也可以只用 hzadb.reg_op() 注册自己的指令。因此设备端开放哪些能力、哪些路径可读可写,完全由应用脚本控制。
当前产品支持:截至 2026-09-22,仅 Air1601/Air1602 支持本库,详见本文第六节“产品支持说明”。
1.1 依赖库
| 依赖 | 用途 | 说明 |
|---|---|---|
log.set_usercmd_cb / log.usercmd_write |
收发协议帧 | 需固件开启 LUAT_USE_LOG_USER_CMD,未开启时这两个函数不存在,hzadb.start() 返回 false |
io / os |
文件系统操作 | hzadb.fs() 安装的指令使用 |
io.lsmount / io.fsstat |
挂载点与空间查询 | 老固件可能没有,缺失时对应指令回应 E_NOSYS |
crypto.md_file |
文件指纹(FILE_SHA1) | 缺失时 file_sha1 回应 E_NOSYS |
crypto.hmac_sha256 |
鉴权(AUTH) | hzadb.set_auth() 时检测,缺失则直接报错 |
rtos.meminfo |
内存状态(MEMINFO) | 缺失时回应 E_NOSYS |
netdrv / socket |
网络状态(NETSTAT) | 缺失时回应 E_NOSYS |
1.2 固件要求
- 上下行都是 A5 命令帧,
cmd = SOC_CMD_USER_CMD(20),上行走独占命令帧通道、不占用日志显示 - 多字节一律小端(LE),协议版本当前为
0x01 - 下行单帧长度受固件
am_log.c的rx_cache1/rx_cache2限制,由 HELLO 握手协商分片大小;厂商新固件(2026-09-18 起)为 1024 字节,旧固件自动收缩到 476 / 90 字节,协议栈会自动适配 - 固件必须打开宏
LUAT_USE_LOG_USER_CMD(截至 2026-09-22 仅 Air1601/Air1602 固件提供)
1.3 指令一览
约定:errno 非 0 时,回应帧置 flags 的 ERR 位,且回应体首字节为 errno(由协议栈统一封装,业务处理函数只需返回 errno)。
| subcmd | 名称 | 由谁安装 | 请求体 | 回应体(成功时) |
|---|---|---|---|---|
| 0 | HELLO | 协议栈内建 | u32 nonce + u16 propose_chunk | u32 nonce + u16 chunk + u8 version + u16 caps |
| 1 | OPEN | hzadb.fs() |
u8 mode + path(余下全部字节) | u8 fd |
| 2 | CLOSE | hzadb.fs() |
u8 fd | u32 size |
| 3 | WRITE_DATA | hzadb.fs() |
u8 fd + u32 offset + data | u8 fd + u32 offset |
| 4 | READ_DATA | hzadb.fs() |
u8 fd + u32 offset + u16 len | u8 fd + u32 offset + u16 len + data |
| 5 | LSDIR | hzadb.fs() |
u8 pathlen + path + u32 offset + u16 count | u32 remaining + u16 entries_len + entries |
| 6 | MKDIR | hzadb.fs() |
path | 空 |
| 7 | RMDIR | hzadb.fs() |
path | 空 |
| 8 | REMOVE | hzadb.fs() |
path | 空 |
| 9 | STAT | hzadb.fs() |
path | u8 type + u32 size |
| 10 | EXISTS | hzadb.fs() |
path | u8 exists |
| 11 | AUTH | 协议栈内建 | u8 maclen + mac(64 字节 ASCII hex) | u8 status |
| 12 | LSMOUNT | hzadb.fs() |
空 | u16 entries_len + entries |
| 13 | FSSTAT | hzadb.fs() |
path | u32 total + u32 used + u32 block_size + u8 fstype_len + fstype |
| 14 | FILE_SHA1 | hzadb.fs() |
path | 40 字节 ASCII hex(SHA1,小写) |
| 15 | MEMINFO | hzadb.mem() |
空 | 9×u32 LE:sys total/used/max、lua total/used/max、psram total/used/max |
| 16 | NETSTAT | hzadb.netdrv() |
空 | u8 count,每适配器:u8 id + u8 flags(bit0=link, bit1=ready, bit2=napt) + u32 ipv4 LE |
mode 取值:0=读,1=写(覆盖),2=追加,3=读写不截断(随机写场景)。
1.4 上位机
- Luatools 的“设备文件管理”界面基于同一套协议,可直接浏览/上传/下载设备文件
- 独立的 Python 上位机库位于 LuatOS 仓库
olddemo/demo/hzadb/host/luat_usercmd.py,用法示例:
from luat_usercmd import UserCmd
dev = UserCmd("COM6")
dev.wait_ready() # 等设备启动 + 握手 + 分片协商
if not dev.auth("your-token"): # 可选: 设备端配置了 token 时才需要
print("device does not require auth")
dev.write_file("/abc.txt", b"hello") # 自动分片 + 窗口 + 重传
data = dev.read_file("/abc.txt")
for e in dev.lsdir("/"): # 自动翻页聚合
print(e["name"], e["type"], e["size"])
print(dev.lsmount())
print(dev.fsstat("/"))
- 协议细节(帧格式、时序、实测数据)见
olddemo/demo/hzadb/PROTOCOL.md
二、核心示例
1、核心示例是指:使用本库文件提供的核心 API,开发的基础业务逻辑的演示代码;
2、核心示例的作用是:帮助开发者快速理解如何使用本库,所以核心示例的逻辑都比较简单;
3、更加完整和详细的 demo,请参考 LuatOS仓库
日志口文件管理(hzadb)
--[[
本核心示例的业务逻辑为:
1、引入 hzadb 库,安装标准操作集(文件系统、内存状态、网络状态);
2、接管日志口启动协议栈,上位机即可通过日志口读写设备文件、查询设备状态;
3、固件未开启 LUAT_USE_LOG_USER_CMD 时只打印一条提示,脚本继续正常运行。
]]
PROJECT = "hzadb_demo"
VERSION = "001.000.000"
local sys = require "sys"
local hzadb = require "hzadb"
-- 可选: 开启鉴权(HMAC 挑战应答), 上位机需 dev.auth("同款token")
-- 生产建议 >=16 字节随机串; 默认关闭, 保持开箱即用
-- hzadb.set_auth("0123456789abcdef")
-- 心跳日志: 验证协议帧与日志帧在日志口共存时互不干扰
sys.timerLoopStart(function()
log.info("hzadb", "heartbeat")
end, 5000)
hzadb.fs() -- 文件系统指令 + file_sha1(默认拦截 /luadb 的读文件请求)
hzadb.mem() -- 内存状态指令(rtos.meminfo)
hzadb.netdrv() -- 网络状态指令(netdrv 适配器状态)
local ok = hzadb.start()
-- 启动信息: 挂载点与根分区空间
if io.lsmount then
local parts = {}
for _, m in ipairs(io.lsmount()) do
parts[#parts + 1] = (m.path == "" and "/" or m.path) .. ":" .. tostring(m.fs)
end
log.info("hzadb", "mounts", table.concat(parts, " "))
end
if io.fsstat then
local s_ok, tb, ub, bs, fst = io.fsstat("/")
if s_ok then
log.info("hzadb", "fsstat /", fst, string.format("%d/%d bytes, block %d", ub * bs, tb * bs, bs))
end
end
if ok then
log.info("hzadb", "demo ready, lib", hzadb.version())
else
log.info("hzadb", "日志口用户指令未启用(固件需打开 LUAT_USE_LOG_USER_CMD), 仅保留心跳")
end
sys.run()
三、常量详解
hzadb 扩展库的常量由库文件定义,require "hzadb" 之后即可直接使用。
3.1 hzadb.VERSION
含义说明:库版本号,年月日时分;
数据类型:string;
取值范围:12 位数字字符串;
注意事项:与协议版本(PROTO_VERSION)无关,和 hzadb.version() 的返回值一致;
示例:hzadb.VERSION --> "202609221555"
3.2 hzadb.PROTO_VERSION
含义说明:协议版本号;
数据类型:number;
取值范围:当前为 0x01;
注意事项:设备端会静默丢弃版本号不匹配的帧(不回应,由上位机超时重传兜底);
示例:hzadb.PROTO_VERSION --> 1
3.3 hzadb.MAX_CHUNK
含义说明:单个协议帧数据区的上限(单位:字节);
数据类型:number;
取值范围:当前为 1024;
注意事项:必须与固件 am_log.c 的 rx 缓冲配套;实际使用的分片大小由 HELLO 握手协商(设备回应 min(设备能力, 上位机提议值));
示例:hzadb.MAX_CHUNK --> 1024
3.4 错误码
| 常量 | 值 | 含义 |
|---|---|---|
hzadb.E_OK |
0 | 成功 |
hzadb.E_NOENT |
1 | not found(文件/目录/挂载点不存在) |
hzadb.E_DENIED |
2 | denied(脚本拒绝执行该操作,例如读拦截命中、未通过鉴权) |
hzadb.E_IO |
3 | io error(读写失败、落盘校验失败、处理函数抛异常) |
hzadb.E_BADREQ |
4 | bad request(格式/参数非法,例如 fd 与 offset 字段不足、补零空洞超限) |
hzadb.E_BADFD |
5 | bad fd(句柄无效或已关闭) |
hzadb.E_TOOLONG |
6 | path too long(路径长度超过 127 字节或含结束符) |
hzadb.E_BUSY |
7 | busy(同时打开的文件句柄已达上限) |
hzadb.E_NOSYS |
8 | nosys(子指令不存在:未安装对应操作集或旧固件) |
E_NOSYS 与 E_DENIED 的区别很重要:收到 8 说明设备上没有安装这个能力(例如没有调用 hzadb.mem()),收到 2 说明能力存在但被策略拒绝(例如 /luadb 的读拦截)。
四、函数详解
4.1 hzadb.set_auth(token)
功能
配置鉴权 token(可选),开启后未鉴权的连接只允许执行 HELLO/AUTH,其余指令一律回应 E_DENIED。
注意事项
1、必须在 hzadb.start() 之前调用;
2、token 长度必须为 8..64 字节,生产环境建议使用不少于 16 字节的随机串;
3、依赖固件的 crypto.hmac_sha256,缺失时本函数直接 error,可先用 pcall 探测;
4、鉴权流程:HELLO 时设备记录挑战 nonce(并在 caps.bit0 告知需要鉴权),上位机用 token 对 nonce 做 HMAC-SHA256 后以 64 字节 ASCII hex 发送;HELLO 会重置鉴权状态,重握后必须重新鉴权;
5、已知限制:MAC 以 ASCII hex 字符串比对,不是恒定时间实现;token 保存在设备脚本里。
参数
token
含义说明:鉴权 token;
数据类型:string 或者 nil/false;
取值范围:8..64 字节;传 nil 或 false 表示关闭鉴权;
是否必选:可选传入此参数,默认不启用鉴权;
注意事项:须在 hzadb.start() 之前调用生效;
参数示例:"0123456789abcdef"
返回值
local result = hzadb.set_auth(token)
result
含义说明:返回 hzadb 自身,便于链式调用;
数据类型:table;
注意事项:无;
示例
local hzadb = require "hzadb"
-- 开启鉴权, 上位机需 dev.auth("0123456789abcdef")
hzadb.set_auth("0123456789abcdef")
hzadb.fs()
hzadb.start()
-- 关闭鉴权(默认状态), 也可以显式关闭
-- hzadb.set_auth(nil)
4.2 hzadb.reg_op(name, fn)
功能
注册业务处理函数,把某个子指令名绑定到自己的实现上。
注意事项
1、子指令名与指令号的对应关系见 1.3 节“指令一览”,其中 HELLO(0)与 AUTH(11)由协议栈内建,不经过本函数;
2、处理函数被 pcall 包裹:函数内部抛异常时,协议栈回应 E_IO,不会导致协议栈崩溃;
3、没有注册处理函数的子指令,一律回应 E_NOSYS;
4、数据类指令(WRITE_DATA / READ_DATA)不做控制类去重,必须保证按 offset 幂等,否则重传会损坏数据。
参数
name
含义说明:操作名,如 open/close/write/read/lsdir/mkdir/rmdir/remove/stat/exists/lsmount/fsstat/file_sha1/meminfo/netstat,也支持自定义名称;
数据类型:string;
取值范围:无特别限制;
是否必选:必须传入此参数;
注意事项:同名重复注册会覆盖前一次注册;
参数示例:"custom"
fn
含义说明:处理函数,签名为 fn(body) -> errno, resp_body, resp_flags(可选);
数据类型:function;
取值范围:任意有效的函数;
是否必选:必须传入此参数;
注意事项:
body 为请求体(不含 5 字节固定头);
errno 为错误码,0 表示成功,非 0 时协议栈自动置 ERR 位并把 errno 放进回应体首字节;
resp_body 为回应体,可省略;
resp_flags 为附加 flags,可省略,例如 LSDIR 翻页的 MORE(0x02);
参数示例:
hzadb.reg_op("custom", function(body)
return hzadb.E_OK, "hello"
end)
返回值
local result = hzadb.reg_op(name, fn)
result
含义说明:返回 hzadb 自身,便于链式调用;
数据类型:table;
注意事项:无;
示例
local hzadb = require "hzadb"
-- 注册一个自定义指令, 上位机发送后原样回一个问候
hzadb.reg_op("custom", function(body)
log.info("hzadb", "custom request", #body)
return hzadb.E_OK, "hello"
end)
hzadb.start()
4.3 hzadb.fs()
功能
一键安装标准文件系统操作集,共 13 个子指令:open、close、write、read、lsdir、mkdir、rmdir、remove、stat、exists、lsmount、fsstat、file_sha1。
注意事项
1、设备端最多同时打开 4 个文件句柄,超出回应 E_BUSY;
2、路径长度上限 127 字节,超长回应 E_TOOLONG;
3、默认拦截对 /luadb 的读文件请求:open 的读模式、read、file_sha1 一律回应 E_DENIED;lsdir/stat/exists 与写操作不受影响。如需调整,修改库内的 READ_DENY_PREFIXES 表后重新调用 hzadb.fs();
4、write 以 offset 为权威写入位置:offset 小于等于当前文件大小时原地覆盖(重传幂等);大于时先补零到 offset 再写(补零上限 64KiB,超出回应 E_BADREQ),写完回读文件大小校验,避免顺序写文件系统静默丢写;
5、lsdir 单次最多 100 条,单个回应帧的条目预算约 470 字节,装不下时置 flags.MORE,由上位机按 offset + count 翻页;
6、lsmount 依赖 io.lsmount,fsstat 依赖 io.fsstat,缺失时回应 E_NOSYS;
7、file_sha1 依赖 crypto.md_file 流式计算,回应 40 字节小写 ASCII hex;缺失时回应 E_NOSYS。
参数
无参数
返回值
local result = hzadb.fs()
result
含义说明:返回 hzadb 自身,便于链式调用;
数据类型:table;
注意事项:无;
示例
local hzadb = require "hzadb"
-- 安装文件系统操作集, 默认拦截 /luadb 的读文件请求
hzadb.fs()
hzadb.start()
-- 上位机侧: dev.write_file("/abc.txt", b"hello") / dev.read_file("/abc.txt")
4.4 hzadb.mem()
功能
一键安装内存状态指令(MEMINFO),回应 9 个 u32(小端):sys total/used/max、lua total/used/max、psram total/used/max。
注意事项
1、依赖 rtos.meminfo,缺失时回应 E_NOSYS;
2、老固件没有某类内存参数时,对应的 3 个字段填 0;
3、单位为字节。
参数
无参数
返回值
local result = hzadb.mem()
result
含义说明:返回 hzadb 自身,便于链式调用;
数据类型:table;
注意事项:无;
示例
local hzadb = require "hzadb"
-- 安装内存状态指令
hzadb.mem()
hzadb.start()
4.5 hzadb.netdrv()
功能
一键安装网络状态指令(NETSTAT),回应首字节为适配器数量,之后每个适配器 6 个字节:u8 id + u8 flags(bit0=link、bit1=ready、bit2=napt)+ u32 ipv4(小端,未配置为 0)。
注意事项
1、逐个查询 socket.LWIP_STA / LWIP_AP / LWIP_ETH / LWIP_GP 中存在的适配器常量(不同平台常量集合不同),相同 id 去重;
2、依赖 netdrv,缺失时回应 E_NOSYS;
3、每个适配器的查询都做了 pcall 保护,单个接口不可用时该位为 0。
参数
无参数
返回值
local result = hzadb.netdrv()
result
含义说明:返回 hzadb 自身,便于链式调用;
数据类型:table;
注意事项:无;
示例
local hzadb = require "hzadb"
-- 安装网络状态指令
hzadb.netdrv()
hzadb.start()
4.6 hzadb.start()
功能
启动协议栈,接管日志口。
注意事项
1、本函数注册 log.set_usercmd_cb 回调,之后日志口收到的协议帧都会交给 hzadb 分发处理;
2、固件未开启 LUAT_USE_LOG_USER_CMD 时返回 false,并打印一条 warn;此时功能不可用,但脚本可以继续运行(心跳、其它业务不受影响)。截至 2026-09-22,仅 Air1601/Air1602 固件提供该能力;
3、版本号不为 0x01 的帧、长度不足 5 字节的帧会被静默丢弃;
4、set_auth() / fs() / mem() / netdrv() / reg_op() 建议都在本函数之前调用。
参数
无参数
返回值
local result = hzadb.start()
result
含义说明:true 表示已成功接管日志口;false 表示固件未开启 LUAT_USE_LOG_USER_CMD;
数据类型:boolean;
取值范围:true / false;
注意事项:返回 false 时不要认为设备异常, 只是这个功能不可用;
示例
local hzadb = require "hzadb"
hzadb.fs()
if not hzadb.start() then
log.warn("hzadb", "firmware without LUAT_USE_LOG_USER_CMD")
end
4.7 hzadb.version()
功能
获取库版本信息。
注意事项
无。
参数
无参数
返回值
local version = hzadb.version()
version
含义说明:库版本号,年月日时分;
数据类型:string;
取值范围:12 位数字字符串;
返回示例:"202609221555"
示例
local hzadb = require "hzadb"
log.info("hzadb", "version -> " .. hzadb.version())
-- 输出: hzadb version -> 202609221555
五、版本更新说明
版本号:202609221555
1、更新时间:2026-09-22 15:55
2、更新内容
- 新增 hzadb 扩展库(日志口 adb 协议栈):提供
set_auth()、reg_op()、fs()、mem()、netdrv()、start() - 新增
hzadb.version()接口,库版本号改为年月日时分格式,与其它扩展库保持一致 - 当前仅 Air1601/Air1602 支持(截至 2026-09-22),其他产品请等待固件适配
六、产品支持说明
截至 2026-09-22,本库仅支持 Air1601/Air1602。
hzadb 依赖固件提供的 log.set_usercmd_cb / log.usercmd_write,这两个接口需要固件开启宏 LUAT_USE_LOG_USER_CMD;截至上述日期,只有 Air1601/Air1602 固件提供该能力。
在其它产品上:
require "hzadb"、hzadb.fs()等配置代码都可以正常执行,不会报错hzadb.start()返回false,并打印一条 warn(提示固件未开启LUAT_USE_LOG_USER_CMD)- 日志口不会收到任何协议帧,上位机表现为握手超时
如需在这些产品上使用,请先确认所用固件是否已开启该宏(可用 log.set_usercmd_cb 是否为 function 判断)。
历史说明:宏
LUAT_USE_LOG_USER_CMD曾于 2026-09-17 因日志口 RX 抽帧适配问题在 ccm42xx 侧暂时关闭,等原厂完成适配后再开启;具体固件版本是否已开启,以对应固件的发布说明为准。
另外,使用 hzadb.set_auth() 开启鉴权时,固件还需要带 crypto(LUAT_USE_CRYPTO)。