跳转至

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 三个必须弄清的概念

  1. service(服务):一组功能的集合,用 UUID 标识。一个从机可以暴露多个服务,本库的从机模式默认提供一个服务,UUID 默认 0x180D(心率服务);
  2. characteristic(特征,简称 chr):服务下面的具体数据项,由 UUID 加 flags 组成。flags 决定它可读、可写、可订阅通知;本库从机模式最多支持 3 个特征(setChr 的索引 0~2);
  3. 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)

注意事项

  1. 该接口只记录模式,不会立刻启动协议栈,真正的初始化在 nimble.init();
  2. init 之后再调只会改变量、不会切换角色,必须 deinit 后重新 init;
  3. 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)

注意事项

  1. 任意模式都可用,但要在设置 UUID 之前调用,否则已经写进去的 UUID 不会重排;
  2. 改了字节序之后,对端看到的 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"))

注意事项

  1. 必须在 nimble.init() 之前调用;
  2. 只对从机模式有效,主机模式不需要;
  3. 该接口在 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)

注意事项

  1. 源码注释里写的是「默认 0-3」,但编译上限是 3,实际可用索引只有 0、1、2,写 3 会越界;
  2. 必须在 nimble.init() 之前调用;
  3. 索引与 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")

注意事项

  1. 必须在 nimble.init() 之前设置;
  2. 从机模式下 conn_mode 与 disc_mode 会被协议栈自动改写(要能被连接、能被发现),手动设置这两项意义不大;
  3. 广播间隔越短功耗越高,电池设备建议 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

注意事项

  1. 该接口在 2023-03-18 之后编译的固件才有;
  2. 只能在 iBeacon 模式生效,从机模式请用 setUUID / setChr;
  3. 广播包总长受 BLE 限制(传统广播 31 字节,扩展广播可到更大),128 是缓冲区上限,不是空中包上限;
  4. 与 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")

注意事项

  1. 仅 iBeacon 模式可用,且必须在 nimble.init() 之前调用才能生效;
  2. data 不足 16 字节会直接失败,别拿短 UUID 凑;
  3. 该接口在 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)

注意事项

  1. 调用前会先做两次 Lua GC,所以 init 会有一点停顿,不要在业务中断里调;
  2. 从机模式下 init 之后立刻开始广播,主机模式下 init 之后才能调 scan;
  3. 重复 init 不会报错,但会重置状态,正在维持的连接会断;
  4. 内存紧张时 init 可能失败,先确认固件里 BLE 协议栈的堆是否够用。

4.9 nimble.deinit()

功能

关闭 BLE,释放协议栈占用的资源。

参数

无。

返回值

boolean,成功返回 true。

示例

nimble.deinit()

注意事项

  1. 源码注释明确写着「仅部分设备支持,当前可能都不支持」,调用后不保证真的释放;
  2. 想切换角色(从机换主机)的正确顺序是 deinit、再 mode、再 init,但受上一条限制,实际更可靠的做法是重启设备;
  3. 关闭后 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")

注意事项

  1. MAC 是二进制,打印时要 :toHex(),否则日志里是乱码;
  2. 该接口在 2023-02-25 之后编译的固件才有;
  3. 不同设备的 BLE MAC 与 4G 的 IMEI 没有换算关系,不要拿 IMEI 反推。

4.11 nimble.connok()

功能

查询当前是否已建立连接。

参数

无。

返回值

boolean,已连接 true,否则 false。

示例

log.info("ble", "connected?", nimble.connok())

注意事项

  1. 从机模式下表示「是否已被主机连上」,主机模式下表示「是否已连上对端」;
  2. iBeacon 模式没有连接概念,该接口恒返回 false,不要拿它做业务判断;
  3. 它读的是内存里的状态位,比等事件更即时,适合放在定时任务里轮询。

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")

注意事项

  1. 新代码不要用它,改用 sendNotify / sendIndicate,能指定特征、行为也更明确;
  2. 它发的是「第一个可通知特征」,多特征场景容易发错地方;
  3. 发送是异步的,返回 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))

注意事项

  1. 对端必须先订阅(写 CCCD),没订阅就发会失败,这是最常见的“发不出去”;
  2. 特征 flags 里没有 CHR_F_NOTIFY 时同样发不出去;
  3. 单包超 MTU 要自己分片,本库不做分包重装;
  4. 该接口 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")

注意事项

  1. 可靠性高于 notify,但每次都要等对端确认,连续发送时吞吐明显更低;
  2. 同一特征不能在 notify 与 indicate 之间随意切换,要按 setChr 里给的 flags 用;
  3. 一次只能有一个未完成的 indicate,上一条没确认前再发会失败;
  4. 该接口 2023-07-31 新增。

4.15 nimble.scan(timeout)

功能

主机模式启动扫描。函数立即返回,扫描结果通过 BLE_SCAN_RESULT 消息异步上报。

参数

timeout

参数含义:扫描时长
数据类型:number
取值范围:单位秒,默认 28
是否必选:否
注意事项:超时只是停止上报,不会自动重扫,需要自己再调一次。

返回值

boolean,启动成功返回 true。

示例

nimble.scan(28)

注意事项

  1. 调用前必须先 nimble.init(),且模式为 MODE_BLE_CLIENT;
  2. 扫描期间功耗明显升高,找到目标设备后及时 disconnect() 或停止扫描;
  3. 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)

注意事项

  1. 连接结果要看 BLE_CONN_RESULT,不要用返回值判断;
  2. 连接成功后协议栈会自动发起一次服务发现,稍等再用 listSvr();
  3. 同一时刻只维护一个连接,重复调用会先断开前一个。

4.17 nimble.disconnect()

功能

断开当前连接;在扫描阶段调用等效于停止扫描。

参数

无。

返回值

无返回值。

示例

nimble.disconnect()

注意事项

  1. 主机模式适用;从机模式不要用它断连,由主机侧决定;
  2. 断开后订阅状态会被清掉,重连后要重新 subChr;
  3. 断开是异步的,紧接着重连可能撞上“上一个连接还没释放”,建议隔 200ms 以上。

4.18 nimble.discSvr()

功能

主动扫描对端的服务列表。

参数

无。

返回值

无返回值,结果异步返回。

示例

nimble.discSvr()

注意事项

  1. 通常不需要手动调:连接成功后协议栈会自动发现一次;
  2. 只有对端在连接后新增了服务(极少见)才需要重扫;
  3. 发现完成后用 listSvr() 取结果。

4.19 nimble.listSvr()

功能

取出已发现的服务 UUID 列表。

参数

无。

返回值

table,服务 UUID 的数组,可能为空表。

示例

for i, uuid in ipairs(nimble.listSvr()) do
    log.info("ble", "svr", i, uuid:toHex())
end

注意事项

  1. 存的是二进制 UUID,打印要 :toHex();
  2. 连接刚建立时列表可能还没填好,要等 BLE_CHR_DISC_RESULT 或延时几百毫秒再读;
  3. 最多缓存 4 个服务(编译期 MAX_PER_SERV),服务特别多的设备会被截断。

4.20 nimble.discChr(svr_uuid)

功能

扫描指定服务下的特征列表。

参数

svr_uuid

参数含义:服务 UUID
数据类型:string
取值范围:从 listSvr() 取到的二进制 UUID
是否必选:是

返回值

boolean,启动成功返回 true。

示例

nimble.discChr(svr)

注意事项

  1. 结果异步回传,收到 BLE_CHR_DISC_RESULT 后再调 listChr;
  2. 必须先有服务列表,没连上或没发现服务时调用会失败。

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

注意事项

  1. 字段名以实际返回为准,不同固件版本可能有差异,联调时先把整个表打出来看;
  2. flags 可以对照 3.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)

注意事项

  1. 一般不需要调,subChr 内部会处理 CCCD;
  2. 需要读特征的格式说明(0x2904)之类信息时才用;
  3. 结果没有专门的 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))

注意事项

  1. 对端特征必须带写权限,否则会被协议栈拒绝;
  2. 写入是异步的,连续写要留间隔,否则容易丢包;
  3. 需要“写完再读回校验”时,中间要加延时。

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)

注意事项

  1. 返回值只代表“请求已发出”,数据要等消息;
  2. 读失败时 data 为 nil,日志里要判空,否则 :toHex() 会报错;
  3. 特征只能写不能读时调用会失败,先用 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)

注意事项

  1. 不订阅就收不到推送,这是 BLE 的机制,不是 bug;
  2. 断线重连后订阅状态会丢,必须重新订阅;
  3. 订阅需要写对端的 CCCD,写完到真正生效有一小段延时,别紧接着就等数据。

4.26 nimble.unsubChr(svr_uuid, chr_uuid)

功能

取消对指定特征的订阅。

参数

svr_uuid / chr_uuid

参数含义:服务 UUID / 特征 UUID
数据类型:string / string
取值范围:二进制 UUID
是否必选:都是

返回值

boolean,启动成功返回 true。

示例

nimble.unsubChr(svr, chr)

注意事项

  1. 取消后该特征不再上报 BLE_GATT_TX_DATA;
  2. 需要降功耗时及时取消订阅,订阅态会持续收包、抬高空闲电流;
  3. 连接断开后不必再取消,协议栈会自己清。

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

各型号的固件支持列表见:

Air780EPM/Air780EHx 系列固件支持列表

Air8000 系列固件支持列表

搜索