跳转至

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.crx_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_NOSYSE_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 的读模式、readfile_sha1 一律回应 E_DENIEDlsdir/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.lsmountfsstat 依赖 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)。

搜索