跳转至

03 BLE 观察者模式(scan)

作者:王世豪 | 最后修改:2026-04-10

一、BLE 概述

BLE(Bluetooth Low Energy),也称为 Bluetooth Smart,是蓝牙 4.0 及更高版本引入的低功耗无线通信技术,专为低带宽、间歇性数据传输的物联网(IoT)和穿戴设备设计。

Air8101 支持最新的 BLE 5.4 版本,BLE 5.4 在上一代基础上继续优化了功耗和性能,为用户提供了更高效、更稳定的蓝牙连接体验。具体的 BLE 版本区别若有兴趣请自行上网查询,本处不再赘述。

Air8101 的蓝牙发射功率为 6dBm,不可调整;最远通讯距离在10米左右。

BLE 支持的四种模式

Air8101 的 BLE 支持 4 种模式,分别是中心设备模式(central),外围设备模式(peripheral),广播者模式(ibeacon),以及观察者模式(scan)。

1、中心设备模式(central):

中心设备模式是能够搜索别人并主动建立连接的一方,从扫描状态转化而来的。它会定期的扫描周围的广播状态设备发送的广播信息,可以对周围设备进行搜索并选择所需要连接的从设备进行配对连接,建立通信链路成功后,主从双方就可以发送接收数据。

2、外围设备模式(peripheral):

外围设备模式是从广播者模式转化而来的,未被连接的外围设备首先进入广播状态,等待被中心设备搜索,当中心设备扫描到外围设备建立连接后,就可以和中心设备进行数据的收发,其不能主动的建立连接,只能等别人来连接自己。和广播模式有区别的地方在于,外围设备模式的设备是可以被连接的,定期的和中心设备进行连接和数据传输,在数据传输过程中作外围设备。

3、广播者模式(ibeacon)

处于广播模式的设备,会周期性的广播 beacon 信息, 可以被扫描, 但一般不会被连接,典型应用 ibeacon。

4、观察者模式(scan)

观察者模式,该模式下模块为非连接,相对广播者模式的一对多发送广播,观察者可以一对多接收数据。在该模式中,设备可以仅监听和读取空中的广播数据。和中心设备唯一的区别是不能发起连接,只能持续扫描外围设备。

二、演示功能概述

本示例将演示如何使用 Air8101 在观察者模式下工作。

观察者模式(scan)的基本流程(概要描述)

1. 初始化蓝牙框架

bluetooth_device.init()

2. 创建 BLE 对象

local ble_device = bluetooth_device:ble(ble_event_cb)

3. 设置扫描模式

ble_device:scan_create() -- 使用默认参数, addr_mode=0, scan_interval=100, scan_window=100

4. 开始扫描

ble_device:scan_start()

5. 在回调函数中处理扫描事件, 如接收设备信息等

6. 按需停止扫描

ble_device:scan_stop()

三、准备硬件环境

3.1 Air8101 核心板

使用 Air8101 核心板,如下图所示:

淘宝购买链接:Air8101 核心板淘宝购买链接

此核心板的详细使用说明参考:硬件手册和证书 中的 Air8101 核心板使用说明

3.2 PC 电脑

WINDOWS 系统,其他暂无特别要求;

3.3 数据通信线

USB 数据线(其一端为 Type-C 接口,用于连接 Air8101 核心板)。

四、准备软件环境

4.1 软件环境

  1. 烧录工具:Luatools 下载调试工具

  2. 本demo开发测试时使用的固件为Air8101 V1006 版本固件,本demo对固件版本没有什么特殊要求,所以你如果要测试本demo时,可以直接使用最新版本的内核固件;如果发现最新版本的内核固件测试有问题,可以使用我们开发本demo时使用的内核固件版本来对比测试;

  3. 脚本文件:https://gitee.com/openLuat/LuatOS/tree/master/module/Air8101/demo/ble/scan

  4. LuatOS 运行所需要的 lib 文件:使用 Luatools 烧录时,勾选 添加默认 lib 选项,使用默认 lib 脚本文件。

准备好软件环境之后,接下来查看如何烧录项目文件到 Air8101 核心板中,将本篇文章中演示使用的项目文件烧录到 Air8101 核心板中。

4.2 API 介绍

ble 库:https://docs.openluat.com/osapi/core/ble/

五、程序结构

ble/
├── ibeacon/
   ├── main.lua
   ├── ble_scan.lua
   |── readme.md

5.1 文件说明

  1. main.lua:主程序入口文件。

  2. ble_scan.lua:ble_scan.lua 是 Air8101 的蓝牙扫描功能实现模块,主要负责初始化蓝牙框架、配置并执行设备的扫描操作,并通过回调函数处理扫描到的设备信息。

六、代码详解

6.1 main.lua

主程序文件 main.lua 是整个项目的入口点。它负责初始化系统环境。

6.2 ble_scan.lua

6.2.1 全局变量定义

scan_state :扫描状态标志,用于跟踪 BLE 扫描的开启/关闭状态,初始值为 false 表示未扫描

-- 扫描状态
local scan_state = false

6.2.2 处理扫描报告事件 (handle_scan_report)

  • 功能:当扫描到 BLE 设备时,处理扫描报告事件。

  • 处理内容:记录并输出发现设备的信息,包括 RSSI 值、设备地址和广播数据等,可以根据需求筛选数据。

  • 示例演示了如何筛选 ibeacon 广播数据。

-- 处理扫描报告事件
local function handle_scan_report(ble_device, ble_param)
    -- 基础设备信息
    log.info("ble_scan", "发现设备", 
            "RSSI:", ble_param.rssi, 
            "地址:", ble_param.adv_addr:toHex(),
            "数据:", ble_param.data:toHex())

    -- 解析广播数据
    local adv_data = ble_device:adv_decode(ble_param.data)
    if adv_data then
        for k, v in pairs(adv_data) do
            -- log.info("ble_scan", "广播数据", "长度:", v.len, "类型:", v.tp, "数据:", v.data:toHex())

            -- 以下是演示如何筛选ibeacon广播数据
            -- 检查Manufacturer Specific Data (类型0xFF)
            if v.tp == 0xFF then
                local mfg_data = v.data
                -- iBeacon格式检查
                if mfg_data:len() >= 25 then
                    local company_id = mfg_data:byte(1) + mfg_data:byte(2) * 256
                    local beacon_type = mfg_data:byte(3)      -- 0x02
                    local data_length = mfg_data:byte(4)      -- 0x15 (21字节)

                    if beacon_type == 0x02 and data_length == 0x15 then
                        log.info("ble_scan", "发现iBeacon设备")

                        -- 解析iBeacon数据
                        local uuid = mfg_data:sub(5, 20):toHex()
                        local major = mfg_data:byte(21) * 256 + mfg_data:byte(22)
                        local minor = mfg_data:byte(23) * 256 + mfg_data:byte(24)
                        local tx_power_byte = mfg_data:byte(25)
                        local tx_power
                        if tx_power_byte > 127 then
                            tx_power = tx_power_byte - 256
                        else
                            tx_power = tx_power_byte
                        end

                        log.info("ble_scan", "iBeacon详情", 
                                "UUID:", uuid,
                                "Major:", major,
                                "Minor:", minor,
                                "TxPower:", tx_power.. " dBm",
                                "RSSI:", ble_param.rssi .. " dBm")
                    end
                end
            end
        end
    end
end

6.2.3 事件回调函数 (ble_callback)

处理 BLE 设备的各种扫描事件:

  • ble.EVENT_SCAN_INIT :扫描初始化成功时,记录日志并设置 scan_state 为 true

  • ble.EVENT_SCAN_REPORT :接收到扫描报告时,调用 handle_scan_report 函数处理扫描数据

  • ble.EVENT_SCAN_STOP :扫描停止时,记录日志并设置 scan_state 为 false

-- 事件回调函数
local function ble_callback(ble_device, ble_event, ble_param)
    -- 扫描初始化事件
    if ble_event == ble.EVENT_SCAN_INIT then
        log.info("ble_scan", "scan init")
        scan_state = true
    -- 扫描报告事件
    elseif ble_event == ble.EVENT_SCAN_REPORT then
        handle_scan_report(ble_device, ble_param)
    -- 停止扫描事件
    elseif ble_event == ble.EVENT_SCAN_STOP then
        log.info("ble_scan", "scan stop")
        scan_state = false
        -- 在这里可以添加自己的扫描停止后的处理逻辑
    end
end

6.2.4 核心任务函数 (ble_scan_task_func)

这是模块的主要功能实现,采用无限循环结构确保扫描稳定运行:

  • 初始化蓝牙核心 :创建 bluetooth_device 实例

  • 初始化 BLE 功能 :创建 ble_device 实例并注册回调

  • 配置扫描参数 :通过 scan_create()方法设置扫描参数(地址模式、扫描间隔、扫描窗口等)

  • 启动扫描 :调用 scan_start()方法开始扫描周围 BLE 设备

  • 状态监控 :通过 while scan_state 循环监控扫描状态

  • 异常处理 :使用 goto EXCEPTION_PROC 标签统一处理各种初始化失败情况

  • 资源清理与重试 :在异常情况下停止扫描并重新初始化,间隔 5 秒后重试

function ble_scan_task_func()
    while true do
        -- 初始化蓝牙核心
        bluetooth_device = bluetooth_device or bluetooth.init()
        if not bluetooth_device then
            log.error("BLE", "蓝牙初始化失败")
            goto EXCEPTION_PROC
        end

        -- 初始化BLE功能
        ble_device = ble_device or bluetooth_device:ble(ble_callback)
        if not ble_device then
            log.error("BLE", "当前固件不支持完整的BLE")
            goto EXCEPTION_PROC
        end

        -- 创建扫描
        if not ble_device:scan_create() then
            log.error("BLE", "BLE创建扫描失败")
            goto EXCEPTION_PROC
        end

        log.info("ble_scan", "开始扫描")
        if not ble_device:scan_start() then
            log.error("ble_scan", "扫描启动失败")
            goto EXCEPTION_PROC
        end

        scan_state  = true

        -- 等待直到扫描停止
        while scan_state do
            sys.wait(1000)
        end
        ::EXCEPTION_PROC::

        log.info("ble_scan", "检测到扫描异常,准备重新初始化")
        -- 停止扫描
        if ble_device then
            ble_device:scan_stop()
            ble_device = nil
        end

        -- 5秒后跳转到循环体开始位置,重新扫描
        sys.wait(5000)
    end
end

-- 启动扫描任务
sys.taskInit(ble_scan_task_func)

七、运行结果展示

7.1 完整代码

完整代码请参考:https://gitee.com/openLuat/LuatOS/blob/master/module/Air8101/demo/ble/scan

7.2 结果演示

八、总结

本文介绍了 Air8101 的 BLE 的观察者模式(SCAN),通过示例演示了如何开启蓝牙设备的观察者模式(SCAN),扫描附近的蓝牙设备信息并通过 luatools 日志打印出来。

九、常见问题

9.1 扫描窗口和扫描间隔

扫描窗口(scan_window):

是指 BLE 设备在扫描过程中,打开接收器去监听广播设备的时间段。这个时间段是设备实际进行扫描操作的时间,也称为扫描事件的持续时间。扫描窗口的单位通常是 0.625ms,并且它的值必须小于或等于扫描间隔。

扫描间隔(scan_interval):

表示两次扫描事件之间的间隔时间。扫描间隔的单位与扫描窗口相同,单位也是 0.625ms。

注:

1. 如果扫描窗口与扫描间隔一样长,表明主机一直在扫描。

2. 扫描窗口和扫描间隔是在 ble_device:scan_create 创建扫描需要填写的参数。

默认参数, addr_mode=0, scan_interval=100, scan_window=100
ble_device:scan_create(addr_mode, scan_interval, scan_window)
-- addr_mode:地址模式,在BLE规范中,0表示公共地址模式(public address mode)
-- scan_interval:扫描间隔,单位为0.625ms,最小值为20,最大值为10240
-- scan_window:扫描窗口,单位为0.625ms,最小值为20,最大值为10240