54 nimble-低功耗蓝牙(nimble版)
一、概述
nimble 是 LuatOS 的 BLE(低功耗蓝牙)协议栈库,封装自 Apache NimBLE。它和 ble 是两套相互独立的实现:ble 是较早的自研协议栈,nimble 是后来的 NimBLE 封装,两者 API 名字相近但不能混用,同一份固件里通常只编进其中一个。
nimble 把 BLE 角色分成三种,选型时先确定自己属于哪一种:
- peripheral(从机 / 外设):主动对外广播,等待手机或主机来连接,自己提供 service 与 characteristic,数据由对端来读或写;
- central(主机 / 中心):扫描周围设备、发起连接,读写对端的特征值;
- ibeacon:只做周期性广播,不建立连接,适合做信标、室内定位的锚点。
⚠️ 适用范围:源码注释给的口径是「本库当前支持 Air101 / Air103 / ESP32 / ESP32C3 / ESP32S3」。Air780 系列(EC7xx)上如果固件没有编进 nimble,require "nimble" 会直接失败;这类型号请先看 ble 库那一页。拿不准时最简单的方法是读一下常量:nimble.MODE_BLE_SERVER 返回 nil,说明当前固件里没有本库。
1.1 三个必须弄清的概念
- service(服务):一组功能的集合,用 UUID 标识。一个从机可以暴露多个服务,本库的从机模式默认提供一个服务,UUID 默认
0x180D(心率服务); - characteristic(特征,简称 chr):服务下面的具体数据项,由 UUID 加 flags 组成。flags 决定它可读、可写、可订阅通知;本库从机模式最多支持 3 个特征(
setChr的索引 0~2); - UUID:服务与特征的标识,支持 2 字节(缩短版)、4 字节、16 字节三种长度。写代码时要用二进制而不是十六进制字符串,即
string.fromHex("FF01")或string.char(0xFF, 0x01)。
还有一个容易踩的点:UUID 的字节序。老代码默认按小端处理,看到的是反的;nimble.config(nimble.CFG_ADDR_ORDER, 1) 之后 UUID 的书写顺序才和 fromHex 里写的一致。
1.2 事件订阅
本库所有异步结果都通过 sys 的发布订阅机制回传,用 sys.subscribe 订阅下面这些消息名即可,不需要注册回调函数。
| 消息名 | 适用模式 | 参数 | 说明 |
|---|---|---|---|
BLE_SERVER_STATE_UPD |
peripheral | state(number) | 从机连接状态变化,值对应 STATE_* 常量 |
BLE_GATT_WRITE_CHR |
peripheral | chr(table)、data(string) | 对端往本机特征写入数据,chr.chr_uuid 是被写的特征 UUID |
BLE_SCAN_RESULT |
central | addr(string)、name(string)、info(table)、mfg_data(string) | 扫描到一个设备。addr 是 7 字节二进制(6 字节 MAC 加 1 字节地址类型),info.rssi 是信号强度 |
BLE_CONN_RESULT |
central | ok(boolean)、status(number) | 发起连接的结果 |
BLE_CONN_STATUS |
central | connected(boolean)、status(number) | 连接状态变化,status 为 0 表示成功、0xFF 表示断开 |
BLE_CHR_DISC_RESULT |
central | ok(boolean)、status(number)、count(number) | 特征发现结束,count 是发现到的数量 |
BLE_GATT_READ_CHR |
central | data(string) | 读取特征的结果,失败时为 nil |
BLE_GATT_TX_DATA |
central | data(string) | 收到对端 notify / indicate 的数据 |
二、核心示例
2.1 从机(peripheral):广播 + 等待写入 + 主动通知
local nimble = require "nimble"
-- 1. 先定模式,必须在 init 之前
nimble.mode(nimble.MODE_BLE_SERVER)
-- 2. 配置 UUID(可选,不配就用默认的 180D / FFF1 / FFF2 / FFF3)
nimble.setUUID("srv", string.fromHex("380D"))
nimble.setUUID("write", string.fromHex("FF31"))
nimble.setUUID("indicate", string.fromHex("FF32"))
-- 3. 配置特征:一个可写,一个可读+通知
nimble.setChr(0, string.fromHex("FF01"), nimble.CHR_F_WRITE_NO_RSP | nimble.CHR_F_NOTIFY)
nimble.setChr(1, string.fromHex("FF02"), nimble.CHR_F_READ | nimble.CHR_F_NOTIFY)
-- 4. 启动广播
nimble.init("LuatOS-BLE")
-- 5. 订阅事件
sys.subscribe("BLE_GATT_WRITE_CHR", function(chr, data)
log.info("ble", "write", chr.chr_uuid, data and data:toHex() or "nil")
-- 收到数据后回一条 notify
nimble.sendNotify(nil, string.fromHex("FF02"), "OK")
end)
sys.subscribe("BLE_SERVER_STATE_UPD", function(state)
log.info("ble", "state", state, "connected:", nimble.connok())
end)
2.2 主机(central):扫描 + 连接 + 读特征
local nimble = require "nimble"
nimble.mode(nimble.MODE_BLE_CLIENT)
nimble.init("LuatOS-Central")
sys.subscribe("BLE_SCAN_RESULT", function(addr, name, info, mfg)
log.info("ble", "scan", addr:toHex(), name, info.rssi)
-- 按名字挑设备,连接后停止扫描
if name == "LuatOS-BLE" then
nimble.disconnect()
nimble.connect(addr)
end
end)
sys.subscribe("BLE_CONN_RESULT", function(ok, status)
log.info("ble", "conn", ok, status)
if ok then
-- 连接成功后一般会自动发起一次服务发现,稍等即可 listSvr
sys.timerStart(function()
log.info("ble", "svr count", #nimble.listSvr())
end, 3000)
end
end)
sys.subscribe("BLE_GATT_TX_DATA", function(data)
log.info("ble", "rx", data and data:toHex() or "nil")
end)
nimble.scan(28)
2.3 iBeacon:只广播不连接
local nimble = require "nimble"
nimble.mode(nimble.MODE_BLE_BEACON)
-- 前 16 字节是 UUID,后面两个是 major / minor
nimble.ibeacon(string.fromHex("0112233445566778899AABBCCDDEEFF0"), 2, 10, 0)
nimble.init("beacon")
-- init 之后仍可反复调用 ibeacon / advData 更新广播内容
while true do
sys.wait(60000)
nimble.ibeacon(crypto.trng(16), 2, math.random(1, 65535), 0)
end
三、常量详解
3.1 状态常量
参数含义:蓝牙适配器的状态
数据类型:number
取值范围:
nimble.STATE_OFF -- 0,未启动
nimble.STATE_ON -- 1,已启动(广播或扫描进行中)
nimble.STATE_CONNECTED -- 2,已建立连接
nimble.STATE_DISCONNECT -- 3,连接已断开
是否必选:否,仅用于比较
这些值会作为参数出现在 BLE_SERVER_STATE_UPD 消息里。判断“是否已连接”更推荐直接调 nimble.connok(),不用自己比状态码。
3.2 模式常量
参数含义:BLE 工作模式,传给 nimble.mode()
数据类型:number
取值范围:
nimble.MODE_BLE_SERVER -- 0,从机 / 外设模式(默认)
nimble.MODE_BLE_CLIENT -- 1,主机 / 中心模式
nimble.MODE_BLE_BEACON -- 2,iBeacon 广播模式
nimble.MODE_BLE_MESH -- 3,Mesh 模式(保留,当前未实现)
nimble.SERVER -- 0,MODE_BLE_SERVER 的别名
nimble.CLIENT -- 1,MODE_BLE_CLIENT 的别名
nimble.BEACON -- 2,MODE_BLE_BEACON 的别名
nimble.MESH -- 3,MODE_BLE_MESH 的别名
是否必选:否
注意事项:必须在 nimble.init() 之前设置;init 之后再改不会重新初始化协议栈。
3.3 特征标志常量(CHR_F_*)
参数含义:characteristic 的访问权限,可位或组合后传给 nimble.setChr()
数据类型:number
取值范围:
nimble.CHR_F_READ -- 0x02,可读
nimble.CHR_F_WRITE_NO_RSP -- 0x04,可写、对端不需要等响应
nimble.CHR_F_WRITE -- 0x08,可写、对端需要等响应
nimble.CHR_F_NOTIFY -- 0x10,可订阅,推送不需要对端回复
nimble.CHR_F_INDICATE -- 0x20,可订阅,推送需要对端回复
是否必选:是
注意事项:NOTIFY 与 INDICATE 是两套订阅状态,对端订阅了哪一个,就只能用对应的 sendNotify / sendIndicate 发;发错方式对端收不到。
3.4 配置项常量
参数含义:nimble.config() 的配置项 id
数据类型:number
取值范围:
nimble.CFG_ADDR_ORDER -- 数值固定为 1,控制 UUID 的字节序转换
是否必选:否
注意事项:值为 0 时按老代码的字节序处理;设为 1 后,服务与特征 UUID 的书写顺序与 string.fromHex 一致,便于对照规格书。
四、函数详解
4.1 nimble.mode(tp)
功能
设置工作模式。不调用时默认是从机(MODE_BLE_SERVER)。
参数
tp
参数含义:工作模式
数据类型:number
取值范围:3.2 节的模式常量,MODE_BLE_SERVER / CLIENT / BEACON
是否必选:否,不传则返回当前模式
注意事项:必须在 nimble.init() 之前调用;重复设置以最后一次为准。
返回值
number,当前生效的模式值。
示例
nimble.mode(nimble.MODE_BLE_CLIENT)
注意事项
- 该接口只记录模式,不会立刻启动协议栈,真正的初始化在
nimble.init(); - init 之后再调只会改变量、不会切换角色,必须 deinit 后重新 init;
MODE_BLE_MESH目前只是占位,选了也没有 Mesh 功能。
4.2 nimble.config(id, value)
功能
设置协议栈的配置项。当前只有一个可配项:UUID 的字节序。
参数
id
参数含义:配置项 id
数据类型:number
取值范围:nimble.CFG_ADDR_ORDER
是否必选:是
value
参数含义:配置值
数据类型:number
取值范围:CFG_ADDR_ORDER 为 0 或 1
是否必选:是
注意事项:默认 0,为兼容老代码;新项目建议设 1。
返回值
无返回值。
示例
nimble.config(nimble.CFG_ADDR_ORDER, 1)
注意事项
- 任意模式都可用,但要在设置 UUID 之前调用,否则已经写进去的 UUID 不会重排;
- 改了字节序之后,对端看到的 UUID 字符串会“换个方向”,联调时两端要统一。
4.3 nimble.setUUID(tp, addr)
功能
设置从机模式下的服务与特征 UUID。不设置时用默认值:服务 0x180D、写入特征 0xFFF1、indicate 特征 0xFFF2、notify 特征 0xFFF3。
参数
tp
参数含义:要设置哪一类 UUID
数据类型:string
取值范围:"srv" 服务主 UUID;"write" 对端写数据的特征;"indicate" 订阅本机数据的特征
是否必选:是
addr
参数含义:UUID 的二进制值
数据类型:string
取值范围:2 / 4 / 16 字节的二进制,用 string.fromHex 或 string.char 构造
是否必选:是
注意事项:不能直接传十六进制字符串文本,那样是按 ASCII 字节解释的。
返回值
boolean,设置成功返回 true。
示例
nimble.setUUID("srv", string.fromHex("380D"))
nimble.setUUID("write", string.fromHex("FF31"))
nimble.setUUID("indicate", string.fromHex("FF32"))
注意事项
- 必须在
nimble.init()之前调用; - 只对从机模式有效,主机模式不需要;
- 该接口在 2023-02-25 之后编译的固件才有,老固件上调用会报 nil。
4.4 nimble.setChr(index, uuid, flags)
功能
配置从机模式下的特征列表。每个特征由 UUID 与访问标志组成。
参数
index
参数含义:特征索引
数据类型:number
取值范围:0 ~ 2(编译期上限 LUAT_BLE_MAX_CHR = 3,即最多 3 个特征)
是否必选:是
uuid
参数含义:特征的 UUID
数据类型:string
取值范围:2 / 4 / 16 字节二进制
是否必选:是
flags
参数含义:特征的访问权限
数据类型:number
取值范围:3.3 节 CHR_F_* 常量按位或组合
是否必选:是
注意事项:至少给一个权限,否则该特征既不能读写也不能订阅。
返回值
无返回值。
示例
nimble.setChr(0, string.fromHex("FF01"), nimble.CHR_F_WRITE_NO_RSP | nimble.CHR_F_NOTIFY)
nimble.setChr(1, string.fromHex("FF02"), nimble.CHR_F_READ | nimble.CHR_F_NOTIFY)
nimble.setChr(2, string.fromHex("FF03"), nimble.CHR_F_WRITE_NO_RSP)
注意事项
- 源码注释里写的是「默认 0-3」,但编译上限是 3,实际可用索引只有 0、1、2,写 3 会越界;
- 必须在
nimble.init()之前调用; - 索引与 UUID 一一对应,后续
sendNotify/sendIndicate要靠 UUID 找特征,UUID 不要重复。
4.5 nimble.advParams(conn_mode, disc_mode, itvl_min, itvl_max, channel_map, filter_policy, high_duty_cycle)
功能
设置广播参数。用于 iBeacon 模式,或需要在从机模式下自定义广播行为时。
参数
conn_mode
参数含义:广播是否允许被连接
数据类型:number
取值范围:0 不可连接;1 定向连接;2 未定向连接
是否必选:否,默认 0
disc_mode
参数含义:广播是否可被发现
数据类型:number
取值范围:0 不可发现;1 限制发现;3 通用发现
是否必选:否,默认 0
itvl_min / itvl_max
参数含义:最小 / 最大广播间隔
数据类型:number
取值范围:0 用默认值;1 ~ 65535,单位 0.625ms
是否必选:否,默认 0
注意事项:部分手机对 20ms 以下的间隔会过滤,太快反而不容易扫到。
channel_map / filter_policy
参数含义:广播信道 / 过滤规则
数据类型:number
取值范围:0 用默认值,一般不需要设置
是否必选:否
high_duty_cycle
参数含义:定向连接时是否用高占空比
数据类型:number
取值范围:0 否;1 是
是否必选:否,默认 0
返回值
无返回值。
示例
-- 不可连接 + 限制发现
nimble.advParams(0, 1)
nimble.init("beacon")
注意事项
- 必须在
nimble.init()之前设置; - 从机模式下
conn_mode与disc_mode会被协议栈自动改写(要能被连接、能被发现),手动设置这两项意义不大; - 广播间隔越短功耗越高,电池设备建议 100ms 以上。
4.6 nimble.advData(data, flags)
功能
自定义广播内容。iBeacon 模式下用来放自定义载荷,也可以放厂商自定义数据。
参数
data
参数含义:广播数据
数据类型:string
取值范围:长度不超过 128 字节
是否必选:是
flags
参数含义:广播标识(AD Flags)
数据类型:number
取值范围:0 ~ 255,默认 0x06(不支持传统蓝牙 + 普通发现模式)
是否必选:否
返回值
boolean,设置成功返回 true。
示例
local data = string.fromHex("12348765123487651234876512348765")
nimble.advData(data)
nimble.init("beacon")
-- init 之后仍可反复调用,实现内容动态刷新
while true do
sys.wait(60000)
nimble.advData(crypto.trng(25))
end
注意事项
- 该接口在 2023-03-18 之后编译的固件才有;
- 只能在 iBeacon 模式生效,从机模式请用
setUUID/setChr; - 广播包总长受 BLE 限制(传统广播 31 字节,扩展广播可到更大),128 是缓冲区上限,不是空中包上限;
- 与
nimble.ibeacon()同时用时,后调用的覆盖前一个。
4.7 nimble.ibeacon(data, major, minor, measured_power)
功能
配置 iBeacon 广播的三个标准字段。
参数
data
参数含义:iBeacon 的 UUID / proximity UUID
数据类型:string
取值范围:必须是 16 字节二进制
是否必选:是
major / minor
参数含义:主 / 次分组号
数据类型:number
取值范围:0 ~ 65535
是否必选:否,默认 2 / 10
measured_power
参数含义:1 米处的名义信号强度,用于测距
数据类型:number
取值范围:-126 ~ 20
是否必选:否,默认 0
注意事项:这个值要按实测填,填 0 时手机侧算出的距离没有参考价值。
返回值
boolean,成功返回 true。
示例
nimble.ibeacon(string.fromHex("0112233445566778899AABBCCDDEEFF0"), 2, 10, -59)
nimble.init("beacon")
注意事项
- 仅 iBeacon 模式可用,且必须在
nimble.init()之前调用才能生效; data不足 16 字节会直接失败,别拿短 UUID 凑;- 该接口在 2023-02-25 之后编译的固件才有。
4.8 nimble.init(name)
功能
初始化 BLE 协议栈并开始按当前模式广播或扫描。这是整个库的启动开关,所有模式都要调。
参数
name
参数含义:设备名,会出现在广播包里
数据类型:string
取值范围:建议 1~20 字节,最长 31 字节
是否必选:否,建议填写,否则手机端看到的是无名设备
注意事项:名字越长,广播包剩余空间越少。
返回值
boolean,成功返回 true;失败时额外返回一个 number 错误码。
示例
local ok, err = nimble.init("LuatOS-BLE")
log.info("ble", "init", ok, err)
注意事项
- 调用前会先做两次 Lua GC,所以 init 会有一点停顿,不要在业务中断里调;
- 从机模式下 init 之后立刻开始广播,主机模式下 init 之后才能调
scan; - 重复 init 不会报错,但会重置状态,正在维持的连接会断;
- 内存紧张时 init 可能失败,先确认固件里 BLE 协议栈的堆是否够用。
4.9 nimble.deinit()
功能
关闭 BLE,释放协议栈占用的资源。
参数
无。
返回值
boolean,成功返回 true。
示例
nimble.deinit()
注意事项
- 源码注释明确写着「仅部分设备支持,当前可能都不支持」,调用后不保证真的释放;
- 想切换角色(从机换主机)的正确顺序是 deinit、再 mode、再 init,但受上一条限制,实际更可靠的做法是重启设备;
- 关闭后
connok()返回 false。
4.10 nimble.mac(mac)
功能
读取本机 BLE 的 MAC 地址;传参时用于设置 MAC(仅个别型号支持)。
参数
mac
参数含义:待设置的 MAC 地址
数据类型:string
取值范围:6 字节二进制
是否必选:否,不传即只读取
注意事项:设置 MAC 当前仅 Air601 支持,且修改后需要重启生效。
返回值
string,6 字节的 MAC;失败返回 nil。
示例
local mac = nimble.mac()
log.info("ble", "mac", mac and mac:toHex() or "unknown")
注意事项
- MAC 是二进制,打印时要
:toHex(),否则日志里是乱码; - 该接口在 2023-02-25 之后编译的固件才有;
- 不同设备的 BLE MAC 与 4G 的 IMEI 没有换算关系,不要拿 IMEI 反推。
4.11 nimble.connok()
功能
查询当前是否已建立连接。
参数
无。
返回值
boolean,已连接 true,否则 false。
示例
log.info("ble", "connected?", nimble.connok())
注意事项
- 从机模式下表示「是否已被主机连上」,主机模式下表示「是否已连上对端」;
- iBeacon 模式没有连接概念,该接口恒返回 false,不要拿它做业务判断;
- 它读的是内存里的状态位,比等事件更即时,适合放在定时任务里轮询。
4.12 nimble.send_msg(conn, handle, data)
功能
从机模式向对端发数据的老接口。当前实现只认数据,前两个参数是历史遗留。
参数
conn
参数含义:连接 id
数据类型:number
取值范围:当前固定填 1
是否必选:是
handle
参数含义:处理 id
数据类型:number
取值范围:当前固定填 0
是否必选:是
data
参数含义:待发送的数据
数据类型:string
取值范围:不超过 256 字节,受 MTU 限制
是否必选:是
注意事项:空字符串会被忽略并返回 false。
返回值
boolean,发送成功返回 true。
示例
nimble.send_msg(1, 0, "hello")
注意事项
- 新代码不要用它,改用
sendNotify/sendIndicate,能指定特征、行为也更明确; - 它发的是「第一个可通知特征」,多特征场景容易发错地方;
- 发送是异步的,返回 true 只代表已进队列,不代表对端已收到。
4.13 nimble.sendNotify(srv_uuid, chr_uuid, data)
功能
从机模式向已订阅该特征的主机推送数据(不需要对端回复,吞吐更高)。
参数
srv_uuid
参数含义:服务 UUID
数据类型:string 或 nil
取值范围:当前为预留参数,填 nil 即可
是否必选:是(可填 nil)
chr_uuid
参数含义:特征 UUID
数据类型:string
取值范围:2 / 4 / 16 字节二进制,必须是 setChr 里配置过且带 CHR_F_NOTIFY 的那个
是否必选:是
data
参数含义:待推送的数据
数据类型:string
取值范围:一般不超过 256 字节,实际按协商后的 MTU
是否必选:是
返回值
boolean,成功返回 true。
示例
nimble.sendNotify(nil, string.fromHex("FF02"), string.char(0x31, 0x32, 0x33))
注意事项
- 对端必须先订阅(写 CCCD),没订阅就发会失败,这是最常见的“发不出去”;
- 特征 flags 里没有
CHR_F_NOTIFY时同样发不出去; - 单包超 MTU 要自己分片,本库不做分包重装;
- 该接口 2023-07-31 新增。
4.14 nimble.sendIndicate(srv_uuid, chr_uuid, data)
功能
从机模式向已订阅的主机发送带应答的推送,对端收到后会回确认。
参数
srv_uuid / chr_uuid / data
参数含义:同 sendNotify,服务 UUID 预留填 nil,特征 UUID 必须带 CHR_F_INDICATE
数据类型:string / string / string
取值范围:数据一般不超过 256 字节
是否必选:srv_uuid 可 nil,其余必填
返回值
boolean,成功返回 true。
示例
nimble.sendIndicate(nil, string.fromHex("FF03"), "ACK")
注意事项
- 可靠性高于 notify,但每次都要等对端确认,连续发送时吞吐明显更低;
- 同一特征不能在 notify 与 indicate 之间随意切换,要按
setChr里给的 flags 用; - 一次只能有一个未完成的 indicate,上一条没确认前再发会失败;
- 该接口 2023-07-31 新增。
4.15 nimble.scan(timeout)
功能
主机模式启动扫描。函数立即返回,扫描结果通过 BLE_SCAN_RESULT 消息异步上报。
参数
timeout
参数含义:扫描时长
数据类型:number
取值范围:单位秒,默认 28
是否必选:否
注意事项:超时只是停止上报,不会自动重扫,需要自己再调一次。
返回值
boolean,启动成功返回 true。
示例
nimble.scan(28)
注意事项
- 调用前必须先
nimble.init(),且模式为MODE_BLE_CLIENT; - 扫描期间功耗明显升高,找到目标设备后及时
disconnect()或停止扫描; timeout参数 2023-07-11 加入,老固件上不生效。
4.16 nimble.connect(mac)
功能
主机模式向指定设备发起连接。函数立即返回,结果通过 BLE_CONN_RESULT 上报。
参数
mac
参数含义:目标设备的地址
数据类型:string
取值范围:`BLE_SCAN_RESULT` 回调里拿到的 7 字节地址(6 字节 MAC + 1 字节地址类型),原样传入
是否必选:是
注意事项:不要自己拼 6 字节 MAC,少了地址类型那一位会连不上。
返回值
boolean,启动成功返回 true(不代表连接成功)。
示例
nimble.connect(addr)
注意事项
- 连接结果要看
BLE_CONN_RESULT,不要用返回值判断; - 连接成功后协议栈会自动发起一次服务发现,稍等再用
listSvr(); - 同一时刻只维护一个连接,重复调用会先断开前一个。
4.17 nimble.disconnect()
功能
断开当前连接;在扫描阶段调用等效于停止扫描。
参数
无。
返回值
无返回值。
示例
nimble.disconnect()
注意事项
- 主机模式适用;从机模式不要用它断连,由主机侧决定;
- 断开后订阅状态会被清掉,重连后要重新
subChr; - 断开是异步的,紧接着重连可能撞上“上一个连接还没释放”,建议隔 200ms 以上。
4.18 nimble.discSvr()
功能
主动扫描对端的服务列表。
参数
无。
返回值
无返回值,结果异步返回。
示例
nimble.discSvr()
注意事项
- 通常不需要手动调:连接成功后协议栈会自动发现一次;
- 只有对端在连接后新增了服务(极少见)才需要重扫;
- 发现完成后用
listSvr()取结果。
4.19 nimble.listSvr()
功能
取出已发现的服务 UUID 列表。
参数
无。
返回值
table,服务 UUID 的数组,可能为空表。
示例
for i, uuid in ipairs(nimble.listSvr()) do
log.info("ble", "svr", i, uuid:toHex())
end
注意事项
- 存的是二进制 UUID,打印要
:toHex(); - 连接刚建立时列表可能还没填好,要等
BLE_CHR_DISC_RESULT或延时几百毫秒再读; - 最多缓存 4 个服务(编译期
MAX_PER_SERV),服务特别多的设备会被截断。
4.20 nimble.discChr(svr_uuid)
功能
扫描指定服务下的特征列表。
参数
svr_uuid
参数含义:服务 UUID
数据类型:string
取值范围:从 listSvr() 取到的二进制 UUID
是否必选:是
返回值
boolean,启动成功返回 true。
示例
nimble.discChr(svr)
注意事项
- 结果异步回传,收到
BLE_CHR_DISC_RESULT后再调listChr; - 必须先有服务列表,没连上或没发现服务时调用会失败。
4.21 nimble.listChr(svr_uuid)
功能
取出指定服务下已发现的特征列表。
参数
svr_uuid
参数含义:服务 UUID
数据类型:string
取值范围:从 listSvr() 取到的二进制 UUID
是否必选:是
返回值
table,每个元素包含特征的 UUID 与 flags。
示例
for i, chr in ipairs(nimble.listChr(svr)) do
log.info("ble", "chr", i, chr.chr_uuid and chr.chr_uuid:toHex() or "?", chr.flags)
end
注意事项
- 字段名以实际返回为准,不同固件版本可能有差异,联调时先把整个表打出来看;
- flags 可以对照 3.3 节的常量解释,判断该特征能不能订阅;
- 列表同样是缓存,最多 16 项(4 个服务 × 每服务 4 个特征)。
4.22 nimble.discDsc(svr_uuid, chr_uuid)
功能
扫描指定特征的描述符(descriptor),比如取 CCCD、用户描述、数据格式。
参数
svr_uuid / chr_uuid
参数含义:服务 UUID / 特征 UUID
数据类型:string / string
取值范围:二进制 UUID
是否必选:都是
返回值
boolean,启动成功返回 true。
示例
nimble.discDsc(svr, chr)
注意事项
- 一般不需要调,
subChr内部会处理 CCCD; - 需要读特征的格式说明(0x2904)之类信息时才用;
- 结果没有专门的 Lua 表可查,属于调试用途。
4.23 nimble.writeChr(svr_uuid, chr_uuid, data)
功能
主机模式往对端指定特征写数据。
参数
svr_uuid / chr_uuid
参数含义:服务 UUID / 特征 UUID
数据类型:string / string
取值范围:二进制 UUID,必须是对端支持写的特征
是否必选:都是
data
参数含义:待写入的数据
数据类型:string
取值范围:不超过 MTU,一般为 20~256 字节
是否必选:是
返回值
boolean,启动成功返回 true(不代表对端已写入)。
示例
nimble.writeChr(svr, chr, string.char(0x01, 0x02))
注意事项
- 对端特征必须带写权限,否则会被协议栈拒绝;
- 写入是异步的,连续写要留间隔,否则容易丢包;
- 需要“写完再读回校验”时,中间要加延时。
4.24 nimble.readChr(svr_uuid, chr_uuid)
功能
主机模式读对端指定特征的值,结果通过 BLE_GATT_READ_CHR 异步返回。
参数
svr_uuid / chr_uuid
参数含义:服务 UUID / 特征 UUID
数据类型:string / string
取值范围:二进制 UUID,必须是对端支持读的特征
是否必选:都是
返回值
boolean,启动成功返回 true。
示例
sys.subscribe("BLE_GATT_READ_CHR", function(data)
log.info("ble", "read", data and data:toHex() or "nil")
end)
nimble.readChr(svr, chr)
注意事项
- 返回值只代表“请求已发出”,数据要等消息;
- 读失败时
data为 nil,日志里要判空,否则:toHex()会报错; - 特征只能写不能读时调用会失败,先用
listChr看 flags。
4.25 nimble.subChr(svr_uuid, chr_uuid)
功能
主机模式订阅对端特征的通知(notify / indicate),订阅后对端推送会通过 BLE_GATT_TX_DATA 上报。
参数
svr_uuid / chr_uuid
参数含义:服务 UUID / 特征 UUID
数据类型:string / string
取值范围:二进制 UUID,特征的 flags 里要有 NOTIFY 或 INDICATE
是否必选:都是
返回值
boolean,启动成功返回 true。
示例
sys.subscribe("BLE_GATT_TX_DATA", function(data)
log.info("ble", "rx", data and data:toHex() or "nil")
end)
nimble.subChr(svr, chr)
注意事项
- 不订阅就收不到推送,这是 BLE 的机制,不是 bug;
- 断线重连后订阅状态会丢,必须重新订阅;
- 订阅需要写对端的 CCCD,写完到真正生效有一小段延时,别紧接着就等数据。
4.26 nimble.unsubChr(svr_uuid, chr_uuid)
功能
取消对指定特征的订阅。
参数
svr_uuid / chr_uuid
参数含义:服务 UUID / 特征 UUID
数据类型:string / string
取值范围:二进制 UUID
是否必选:都是
返回值
boolean,启动成功返回 true。
示例
nimble.unsubChr(svr, chr)
注意事项
- 取消后该特征不再上报
BLE_GATT_TX_DATA; - 需要降功耗时及时取消订阅,订阅态会持续收包、抬高空闲电流;
- 连接断开后不必再取消,协议栈会自己清。
4.27 已移除的接口(保留名字,仅为兼容)
下面三个名字仍在注册表里,但实现已被移除,调用时只在日志里打一行提示、不做任何实际动作。老代码里出现它们说明是早期版本的写法,应当直接删掉:
nimble.server_init() -- 打印 "nimble.server_init is removed",空实现
nimble.server_deinit() -- 打印 "nimble.server_deinit is removed",空实现
nimble.debug() -- 打印 "nimble.debug is removed",返回 0
新代码不用管它们:从机的服务与特征由 setUUID / setChr 配置,调试开关没有对外接口。
五、产品支持说明
nimble 属于组件级协议栈,只有编进了 LUAT_USE_NIMBLE 的固件才有。源码注释给的支持口径是 Air101 / Air103 / ESP32 / ESP32C3 / ESP32S3;Air780 系列(EC7xx)等型号的 BLE 能力请先看 ble 库那一页,两者不要混用。
确认某型号固件里到底有没有本库,最直接的办法是读一个常量:
local ok, nimble = pcall(require, "nimble")
if ok and nimble.MODE_BLE_SERVER then
log.info("ble", "nimble 可用")
else
log.info("ble", "本固件没有 nimble")
end
各型号的固件支持列表见: