跳转至

01 BLE 中心设备模式(central)

一、BLE 概述

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

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

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

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

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

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

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

3.广播者模式(ibeacon)

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

4.观察者模式(scan)

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

蓝牙中的重要概念

1. GATT(通用属性配置文件)

定义 BLE 设备如何组织和传输数据,以 “服务(Service)” 和 “特征(Characteristic)” 为单位。

示例:心率监测设备的 GATT 服务包含 “心率特征”,手机通过读取该特征获取心率数据。

2. 服务和特征

服务是特征的容器,通过逻辑分组简化复杂功能的管理;

特征是数据交互的最小单元,通过属性定义实现灵活的读写与推送机制;

两者结合构成 GATT 协议的核心框架,支撑蓝牙设备间的标准化数据交互(如智能穿戴、医疗设备、物联网传感器)。

3. 特征的关键属性(Properties)

特征通过 “属性” 定义数据的操作方式,常见属性包括:

a. 可读(Read):允许客户端读取特征值(如读取电池电量)。

b. 可写(Write):允许客户端写入特征值(如设置设备参数)。

c. 通知(Notification):服务端主动发送特征值更新(如心率变化时推送给手机)。

d. 指示(Indication):比通知更可靠的推送(需客户端确认接收)

4. UUID

UUID 是蓝牙 GATT 协议的 “数字身份证”,通过标准化的唯一标识机制,实现了跨厂商设备的功能互认(标准 UUID)与厂商个性化功能的扩展(自定义 UUID)

Air8000 的所有操作,都通过 UUID 来索引和管理

二、演示功能概述

本示例使用两个 Air8000 核心板来演示 BLE 中心设备模式:一个核心板做中心设备,另一个核心板做外围设备。

Central 模式的基本流程(概要描述)如下:

1、初始化蓝牙底层框架

bluetooth_device = bluetooth.init()

2、创建 BLE 对象实例

local ble_device = bluetooth_device:ble(ble_event_cb)

3、创建 BLE 扫描

ble_device:scan_create({})

4、开始 BLE 扫描

ble_device:scan_start()

5、在回调函数中处理各种事件

a. 扫描报告事件(ble.EVENT_SCAN_REPORT)

b. 连接事件(ble.EVENT_CONN)

c. 断开连接事件(ble.EVENT_DISCONN)

d. 读写数据事件(ble.EVENT_READ_VALUE, ble.EVENT_WRITE)

e. 通知事件(ble.EVENT_NOTIFY)

f. GATT 事件(ble.EVENT_GATT_ITEM, ble.EVENT_GATT_DONE)

三、准备硬件环境

3.1 Air8000 核心板

使用 Air8000 开发套件,如下图所示:

淘宝购买链接:Air8000 开发套件淘宝购买链接

此开发套件的详细使用说明参考:硬件手册和证书 - product@air8000 - 合宙模组资料中心中的 Core_Air8000 核心板使用说明 V1.3.pdf

3.2 PC 电脑

WIN10 以及以上版本的 WINDOWS 系统。

3.3 数据通信线

USB 数据线(其一端为 Type-C 接口,用于连接 Air8000 开发板)。

四、准备软件环境

4.1 下载调试工具

使用说明参考:如何使用 LuaTools 烧录软件 - luatos@air8000 - 合宙模组资料中心

五、软硬件资料

5.1 源码及固件

1、Air8000 固件:https://docs.openluat.com/air8000/luatos/firmware/

2、源码链接:

centralhttps://gitee.com/openLuat/LuatOS/tree/master/module/Air8000/demo/ble/master

peripheralhttps://gitee.com/openLuat/LuatOS/tree/master/module/Air8000/demo/ble/peripheral

5.2 demo 使用 api 介绍

本教程使用 api 接口为:

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

六、代码解析

6.1 初始化蓝牙框架

-- 初始化蓝牙框架, 仅需要调用一次
-- 若创建失败, 会返回nil, 请检查内存是否足够
bluetooth_device = bluetooth.init()

6.2 创建 BLE 对象

ble_callback 是自定义函数,用于处理 BLE 事件,详见 6.4

-- 创建BLE对象, 需要先调用bluetooth.init()
ble_device = bluetooth_device:ble(ble_callback)

6.3 扫描外围设备

ble_device:scan_create({})
ble_device:scan_start()

6.4 在回调函数中处理各种事件

-- function ble_callback(dev, evt, param)
-- ble_callback 是自定义函数, 用于处理BLE事件
-- BLE事件回调函数, 回调时的参数如下:
-- dev 是BLE设备对象, 可以通过dev:adv_create()等方法进行操作
-- evt 是BLE事件类型, 可以是以下几种:
    -- ble.EVENT_CONN: 连接成功
        -- param 是事件参数, 包含以下字段:
        -- param.addr: 对端设备的地址, 6字节的二进制数据, 代表BLE设备的MAC地址
    -- ble.EVENT_DISCONN: 断开连接
        -- param 是事件参数, 包含以下字段:
        -- param.reason: 断开连接的原因
    -- ble.EVENT_WRITE: 收到写请求
        -- param 是事件参数, 包含以下字段:
        -- param.uuid_service: 服务的UUID
        -- param.uuid_characteristic: 特征的UUID
        -- param.data: 写入的数据
    -- ble.EVENT_READ_VALUE: 收到读请求
        -- param 是事件参数, 包含以下字段:
        -- param.uuid_service: 服务的UUID
        -- param.uuid_characteristic: 特征的UUID
        -- param.data: 读取的数据
    -- ble.EVENT_SCAN_REPORT: 扫描到设备
        -- param 是事件参数, 包含以下字段:
        -- param.rssi: 信号强度
        -- param.adv_addr: 广播地址, 6字节的二进制数据
        -- param.data: 广播数据, 二进制数据
    -- ble.EVENT_GATT_ITEM : 读取GATT信息
        -- param: 事件参数
    -- ble.EVENT_GATT_DONE : GATT操作完成
        -- param 是事件参数, 包含以下字段:
        -- service_num: 扫描到的服务数量

local function ble_callback(ble_device, ble_event, ble_param)
    if ble_event == ble.EVENT_CONN then -- 连接成功
        log.info("ble", "connect 成功")
    elseif ble_event == ble.EVENT_DISCONN then -- 断开连接,并打印断开原因
        log.info("ble", "disconnect", ble_param.reason)
        sys.timerStart(function() ble_device:scan_start() end, 1000) -- 1秒后重新开始扫描
    elseif ble_event == ble.EVENT_WRITE then --收到写请求
        log.info("ble", "write", ble_param.handle,ble_param.uuid_service:toHex(),ble_param.uuid_characteristic:toHex())
        log.info("ble", "data", ble_param.data:toHex())
    elseif ble_event == ble.EVENT_READ_VALUE then -- 收到读请求
        log.info("ble", "read", ble_param.handle,ble_param.uuid_service:toHex(),ble_param.uuid_characteristic:toHex(),ble_param.data)
    elseif ble_event == ble.EVENT_SCAN_REPORT then -- 扫描到设备
        print("ble scan report",ble_param.addr_type,ble_param.rssi,ble_param.adv_addr:toHex(),ble_param.data:toHex())
        scan_count = scan_count + 1
        if scan_count > 100 then
            log.info("ble", "扫描次数超过100次, 停止扫描, 15秒后重新开始")
            scan_count = 0
            ble_device:scan_stop()
            sys.timerStart(function() ble_device:scan_start() end, 15000)
        end
        -- 注意, 这里是连接到另外一个设备, 设备名称带LuatOS字样
        if ble_param.addr_type == 0 and ble_param.data:find("LuatOS") then
            log.info("ble", "停止扫描, 连接设备", ble_param.adv_addr:toHex(), ble_param.addr_type)
            ble_device:scan_stop()
            ble_device:connect(ble_param.adv_addr,ble_param.addr_type)
        end
    elseif ble_event == ble.EVENT_GATT_ITEM then -- 读取GATT信息
        -- 读取GATT完成
        log.info("ble", "gatt item", ble_param)
    elseif ble_event == ble.EVENT_GATT_DONE then -- GATT操作完成
        -- GATT操作完成,打印服务数量
        log.info("ble", "gatt done", ble_param.service_num)

        -- 开启指定服务和特征值的通知
        local wt = {uuid_service = string.fromHex("FA00"), uuid_characteristic = string.fromHex("EA01")}
        ble_device:notify_enable(wt, true) -- 开启通知

        -- 主动写入数据, 但不带通知, 带通知是 write_notify
        local wt = {uuid_service = string.fromHex("FA00"), uuid_characteristic = string.fromHex("EA02")}
        ble_device:write_value(wt, string.fromHex("1234")) -- 写入数据, 这里是写入十六进制数据"1234"

        -- 读取特征值数据
        -- 注意, 这里是读取另外一个设备的特征值数据
        local wt = {uuid_service = string.fromHex("FA00"), uuid_characteristic = string.fromHex("EA03")}
        ble_device:read_value(wt)
    end
end

七、结果展示分析

7.1 中心设备订阅外围设备特征通知(Notify)

1、中心设备端实现

-- 开启指定服务和特征值的通知
local wt = {uuid_service = string.fromHex("FA00"), uuid_characteristic = string.fromHex("EA01")}
ble_device:notify_enable(wt, true) -- 开启通知

2、外围设备端要求

服务 UUID 和特征 UUID 需要和上面中心设备端实现的一致,同时特征 UUID 的属性需包含 NOTIFY,才能正常通信。

服务 UUID:FA00,特征 UUID:EA01。

local wt = {
    uuid_service = string.fromHex("FA00"),
    uuid_characteristic = string.fromHex("EA01"), 
}
local result = ble_device:write_notify(wt, "123456" .. os.date())
log.info("ble", "发送数据", result)

3、luatools 日志

中心设备部分:

外围设备部分:

7.2 中心设备写入特征值(Write)

1、中心设备端实现

-- 主动写入数据, 但不带通知, 带通知是 write_notify
    local wt = {uuid_service = string.fromHex("FA00"), uuid_characteristic = string.fromHex("EA02")}
    ble_device:write_value(wt, string.fromHex("1234")) -- 写入数据, 这里是写入十六进制数据"1234"

2、外围设备端要求

服务 UUID 和特征 UUID 需要和上面central端实现的一致,同时特征 UUID 的属性需包含 WRITE。

服务 UUID:FA00,特征 UUID:EA02。

触发 ble.EVENT_WRITE 事件,接收到写请求并通过 luatools 打印出来。

3、luatools 日志

中心设备部分:

外围设备部分:

7.3 中心设备读取特征值(Read)

1、中心设备端实现

-- 读取特征值数据
-- 注意, 这里是读取另外一个设备的特征值数据
local wt = {uuid_service = string.fromHex("FA00"), uuid_characteristic = string.fromHex("EA03")}
ble_device:read_value(wt)

2、外围设备端要求

服务 UUID 和特征 UUID 需要和上面中心设备端实现的一致,同时特征 UUID 的属性需包含 READ。

服务 UUID:FA00,特征 UUID:EA03。

local wt = {
    uuid_service = string.fromHex("FA00"),
    uuid_characteristic = string.fromHex("EA03"), 
}
ble_device:write_value(wt, "8888 123454")

3、luatools 日志

中心设备部分:

八、总结

本篇文档介绍了 Air8000 的 BLE central 模式,通过两个 Air8000 核心板:一个作中心设备,一个作外围,利用 3 个示例,分别是中心设备订阅外围特征通知(Notify),中心设备写入特征值(Write),中心设备读取特征值(Read)

本篇文档通过双 Air8000 核心板实战演示了 BLE 中心设备三大核心操作:

1)订阅通知(Notify)实现外围设备数据主动推送,

2)写入特征值(Write)用于发送控制指令,要求外围设别特征支持 WRITE 属性;

3)读取特征值(Read)获取外围设备状态,需外围设备开放 READ 权限。

三种操作均需中心设备和外围设备的服务/特征 UUID 严格匹配。