跳转至

LuatOS ble(二)

王世豪

Hello ,大家好,我是王世豪。

欢迎大家来到合宙 LuatOS 直播课堂,一起学习 LuatOS 课程。

第一部分:LuatOS 课程背景

因为今天是我们 LuatOS 系列课程的第 007 讲,同时也是 LuatOS BLE 专题课程的第二讲,所以在这里我就不重复讲解整个 LuatOS 课程的背景了;

如果您还不清楚 LuatOS 课程背景,可以访问:LuatOS 课程背景 这个链接,进行了解;

第二部分:LuatOS BLE 课程讲哪些内容

今天是 LuatOS BLE 的第二讲:BLE 的中心设备模式,广播和扫描模式 ;

关于 BLE 的历史以及 BLE 协议栈介绍,请看 BLE 专题的第一讲内容,本次课程不再赘述:https://docs.openluat.com/luatos_lesson/005_luatos_ble/

LuatOS BLE 第二讲课程主要包含以下几个部分:

  1. 复习 LuatOS BLE 的四种工作模式
  2. LuatOS 上的中心设备模式应用开发流程;
  3. LuatOS 上的广播模式应用开发流程;
  4. LuatOS 上的扫描模式应用开发流程;
  5. 常见问题

第三部分:复习 LuatOS BLE 的四种工作模式

关于 BLE 的四种模式,以及 LuatOS 的 BLE api,在 BLE 专题的第一讲介绍过,带大家再复习一遍:

https://docs.openluat.com/luatos_lesson/005_luatos_ble/

3.1 外围设备模式(peripheral)

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

和广播模式有区别的地方在于,外围设备模式的设备是可以被连接的,定期的和中心设备进行连接和数据传输,在数据传输过程中作外围设备。

代码基本流程:

1、初始化蓝牙框架

​ bluetooth_device = bluetooth.init()

2、创建BLE对象

​ local ble_device = bluetooth_device:ble(ble_callback)

3、创建GATT描述

​ local att_db = {xxx}

4、创建广播信息

​ ble_device:adv_create(adv_data)

5、开始广播

​ ble_device:adv_start()

6、等待连接

7、在回调函数中处理连接事件, 如接收数据, 发送数据等

3.2 中心设备模式(central)

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

代码基本流程:

1、初始化蓝牙框架

bluetooth_device = bluetooth.init()

2、创建BLE对象

​ local ble_device = bluetooth_device:ble(ble_callback)

3、扫描目标BLE设备

​ ble_device:scan_start()

4、建立与目标设备的连接

​ ble_device:connect(mac, add_type)

5、处理各类BLE事件(连接、断开连接、扫描报告、GATT操作完成等)

3.3 广播者模式(ibeacon)

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

代码基本流程:

1、初始化蓝牙底层框架

​ bluetooth_device = bluetooth.init()

2、创建BLE对象

​ local ble_device = bluetooth_device:ble(ble_callback)

3、配置ibeacon广播数据包

​ 包含厂商特定数据格式,ibeacon类型标识符

​ 设置UUID、Major、Minor等关键参数

4、启动BLE广播功能

​ ble_device:adv_start()

3.4 观察者模式(scan)

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

代码基本流程:

1、初始化蓝牙框架

​ bluetooth_device = bluetooth.init()

2、创建BLE对象

​ local ble_device = bluetooth_device:ble(ble_callback)

3、开始扫描

​ ble_device:scan_start()

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

5、按需停止扫描

​ ble_device:scan_stop()

第四部分:LuatOS 上的中心设备模式应用开发流程

BLE——中心设备模式代码:

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

BLE——中心设备模式文档:

https://docs.openluat.com/air8000/luatos/app/BLE/central/

4.1 前置知识了解

4.1.1 UUID

4.1.1.1 UUID 是什么?

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

蓝牙协议通过 UUID 实现设备间的标准化通信,使用蓝牙对外提供服务的设备,需要有对应的服务功能,服务是蓝牙设备中功能划分的单元,每个服务都对应着一种特定的功能或数据传输需求。

例如,当一个蓝牙设备(如智能手环)向外界广播服务时,会携带对应的 UUID,其他设备(如手机)通过识别这些 UUID,就能知道该设备提供哪些功能(如心率监测、数据传输等),并建立针对性的连接。

一个蓝牙设备可以包含多个服务(Service),每个服务通过一个 UUID(服务 UUID) 进行标识。

每个服务下包含多个特征(Characteristic),每个特征都拥有一个独立的 UUID(特征 UUID)作为其唯一标识。

特征是最小的数据单元,我们通过读写特征来与设备交互。

1、服务(Service)

是什么:服务是蓝牙设备功能的逻辑分组,类似于一个"功能模块"。

例如:心率监测服务、电池电量服务等。

作用:定义设备能做什么(如测量心率、监控电量)。通过 UUID 唯一标识。

示例

设备 → 服务 1(心率监测)

    → 服务2(电池电量)

    → 服务3(自定义控制)

2、特征(Characteristic)

是什么:特征是服务的子元素,是服务中的具体数据点,用于实际的数据读写操作。

例如:心率值、电池电量百分比。

作用:存储具体数据(如 71 bpm)。通过 UUID 和 属性(Properties) 定义操作权限(读/写/通知等)。

示例

服务(心率监测) → 特征 1(心率测量值)
                → 特征2(传感器位置)

3、描述符(Descriptor)

是什么:描述符是特征的子元素,是特征的附加信息,用于细化特征的行为或配置。

例如:启用数据通知、设置数据格式。

作用:配置特征的行为(如开启实时通知)。补充描述特征(如单位、数据范围)。

最常见描述符:

Client Characteristic Configuration Descriptor (CCCD)

用于启用/禁用特征的 NOTIFY 或 INDICATE 功能。

三者的层级关系:

蓝牙设备(Device

└── 服务(Service         功能模块(如心率服务)
    
    └── 特征(Characteristic  具体的数据点(如心率值)
        
        └── 描述符(Descriptor  配置或描述特征(如启用通知)

实际示例(手环):

智能手环
├── 电池服务
   ├── 电池电量特征
      ├── 值:85%
      ├── 属性:可读、可通知
      └── 描述符:通知开关(开)
   └── 充电状态特征
       ├── 值:未充电
       └── 属性:可读
├── 心率服务
   └── 心率特征
       ├── 值:72/分钟
       ├── 属性:可通知
       └── 描述符:通知开关(开)
└── 运动服务
    ├── 步数特征
       ├── 值:12560
       └── 属性:可读、可通知
    └── 卡路里特征
        ├── 值:385千卡
        └── 属性:可读
4.1.1.2 UUID 的格式:

蓝牙 UUID 的标准格式为 128 位,通常表示为 32 个十六进制字符,以 8-4-4-4-12 的格式分组,共 36 个字符(包括 4 个连字符):

格式:8-4-4-4-12

示例:0000180D-0000-1000-8000-00805F9B34FB

标准 UUID:

由蓝牙技术联盟(Bluetooth SIG)定义,用于常见服务。

标准的 UUID 为:0000xxxx-0000-1000-8000-00805F9B34FB

为了节省带宽,标准 UUID 通常使用 16 位或 32 位短格式,实际通信时自动扩展为 128 位。每一个蓝牙技术联盟定义的属性有一个唯一的 16 位 UUID,以代替上面的基本 UUID 的‘x’部分。

若 16 bit UUID 为 xxxx,那么 128 bit UUID 为 0000xxxx-0000-1000-8000-00805F9B34FB

若 32 bit UUID 为 xxxxxxxx,那么 128 bit UUID 为 xxxxxxxx-0000-1000-8000-00805F9B34FB

自定义 UUID:

用于私有服务或厂商特定功能,需开发者自行生成,通常使用 UUID 随机生成器生成 128 位 UUID,确保全球唯一性

4.1.1.3 UUID 的分类

蓝牙规定好的 uuid 如下:

  • 0x180x 开头 → 只能当 Service UUID
  • 0x2Axx 开头 → 只能当 Characteristic UUID
  • 0x29xx 开头 → 只能当 Descriptor UUID

0x180x 开头 UUID(Service UUID)

0x180x 区间只能当 Service UUID

UUID
Service name
用途
0x1800
Generic Access
必含,设备名、外观、连接参数
0x1801
Generic Attribute
必含,Service Changed(服务改变)
0x180A
Device Information
厂商字符串/版本号
0x180F
Battery Service
电池电量,手环/键鼠必备
...
...
...

0x2Axx 开头 UUID(Characteristic UUID)

0x2Axx (0x2A00 – 0x2AFF) 区间只能当 Characteristic UUID

UUID
Service name
用途
0x2A00
Device Name
设备名称,Generic Access 必含
0x2A01
Appearance
外观图标(键盘/鼠标/温度计…)
0x2A04
Peripheral Preferred Connection Parameters
外设首选连接参数
0x2A05
Service Changed
Generic Attribute 必含,OTA 必备
0x2A19
Battery Level
电池百分比,0-100
0x2A23
System ID
系统ID
0x2A24
Model Number String
型号
0x2A25
Serial Number String
序列号
0x2A29
Manufacturer Name String
厂商名
......
......
......

0x29xx 开头 UUID(Descriptor UUID)

0x29xx 开头只能当 Descriptor UUID,即描述符 UUID

这里仅介绍一个使用最频繁的 UUID:0x2902

0x2902 - Client Characteristic Configuration(CCC,客户端特征配置描述符)

这是一个可写的描述符,允许客户端设备(如手机、电脑)配置如何从服务器设备(BLE 外设)接收数据更新。主要用于启用或禁用通知(Notifications) 和指示(Indications) 功能。

BLE 实时数据推送(如传感器数据、状态更新)都需要通过正确配置 0x2902 描述符来实现。

UUID 格式:

  • 完整 UUID:00002902-0000-1000-8000-00805f9b34fb
  • 缩写:0x2902
  • 类型:Descriptor(描述符)

有效值:

0xFFxx 开头 UUID(Vendor Specific)

0xFF00 ~ 0xFFFF 区间为 SIG 的公共预留池(Vendor-Specific 区间) 任何厂商都可以临时借用。

这个区间 SIG 作为公共预留区间,一般用作示例测试参考,属于临时方案,想要作为产品,要么去 SIG 申请正式分配,要么直接用 128-bit 自建 UUID。

注意事项:

  1. 整个 16-bit 空间都由 Bluetooth SIG 的官方文档 《Assigned Numbers》统一管理,也就是下面的文档链接
  2. 商用产品不要使用已存在的 UUID,可以用 128-bit 自定义 UUID,或者在 SIG 申请正式分配。
  3. 同一设备,不同特征 UUID 任何时候都不能一样。
  4. 自定义的 128-bit UUID,服务 UUID 可以和 特征 UUID 一样,SIG 规定的 16-bit 的服务和特征值的 UUID 不能一样。

更详细的 UUID 介绍,请参考蓝牙联盟的 Assigned_Numbers 文档: https://www.bluetooth.com/wp-content/uploads/Files/Specification/HTML/Assigned_Numbers/out/en/Assigned_Numbers.pdf

4.1.2 Properties

特征的关键属性(Properties)

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

ble.READ:可读 - 客户端可从特征读取数据(如读取电池电量)
ble.WRITE:有响应写入 - 客户端写入,服务端需确认(如设置设备参数)
ble.WRITE_CMD:无响应写入 - 客户端写入,服务端不确认,快速但可能丢失(如实时控制)
ble.NOTIFY:通知 - 服务端主动推送数据,客户端不确认(如心率实时推送)
ble.INDICATE:指示 - 服务端主动推送数据,客户端需确认(如重要状态更新)

4.2 demo 项目总体设计框图

demo 项目的总体设计框图如下:

4.3 源码分析

4.3.1 文件说明

  1. main.lua:主程序入口文件。
  2. ble_client_main.lua:BLE 中心设备主程序,进行 BLE 初始化,处理各类 BLE 事件(连接、断开连接、扫描报告、GATT 操作完成等)。
  3. ble_client_receiver.lua:BLE 中心设备接收数据处理。
  4. ble_client_sender.lua:BLE 中心设备发送数据处理。
  5. ble_timer_app.lua:BLE 中心设备定时器处理逻辑,启动两个循环定时器,一个用于定时读取外围设备特征值 UUID 数据,一个用于定时向外围设备特征值 UUID 发送数据。
  6. ble_uart_app.lua:BLE 中心设备接 uart 处理逻辑,将收到的 notify 数据,通过 uart 发送到 pc 端串口工具。
  7. check_wifi.lua:Air8000 的蓝牙功能依赖 WiFi 协处理器,需确保 WiFi 固件为最新版本。本脚本文件检查当前 Air8000 模组的 WiFi 固件是否为最新版本,若不是则自动启动升级(需插入可联网的 SIM 卡)

4.3.2 ble_client_main.lua

本文件为 ble client 主应用功能模块,整个应用通过 sysplus.taskInitEx 启动主任务,实现了完整的 BLE 中心设备功能,包括设备发现、连接管理、数据收发和异常恢复机制。核心业务逻辑为:

4.3.2.1 初始化与配置
  1. 加载依赖模块( ble_client_receiver 用于数据接收处理, ble_client_sender 用于数据发送处理)。
  2. 定义配置参数(目标设备名称、服务 UUID、特征值 UUID、超时时间等)。
  3. 调用 ble_init(),初始化蓝牙功能。
4.3.2.2 设备扫描与连接
  1. 创建并启动 BLE 扫描(默认参数:公共地址模式、扫描间隔 100ms、扫描窗口 100ms)。
  2. 通过 is_target_device 函数过滤扫描到的设备(匹配设备名称)。
  3. 发现目标设备后停止扫描并发起连接。
  4. 连接超时处理和重连机制。
4.3.2.3 事件处理

通过 ble_event_cb 回调函数处理各类 BLE 事件:

  1. 连接成功(EVENT_CONN)
  2. 断开连接(EVENT_DISCONN)
  3. 扫描报告(EVENT_SCAN_REPORT)
  4. GATT 操作完成(EVENT_GATT_DONE)
  5. 读取特征值完成(EVENT_READ_VALUE)
4.3.2.4 业务功能
  1. GATT 服务发现完成后,自动启用目标特征值的通知监听。
  2. 接收并处理来自其他模块的读取请求( READ_REQ )。
  3. 通过消息队列与其他模块通信。
4.3.2.5 异常处理
  1. 扫描超时或连接失败时触发异常处理。
  2. 断开连接后自动清理消息队列并尝试重连。
  3. 异常情况下 5 秒后重新开始扫描连接。

4.3.3 ble_client_receiver.lua

4.3.3.1 主要功能
  1. 数据分类处理 :接收两类数据(外围设备通知数据和主动读取到的数据)。
  2. 数据分发 :根据数据类型(通过特征值 UUID 区分)发布到不同的消息队列,供其他模块处理。
4.3.3.2 核心实现
  1. 提供 proc(service_uuid, char_uuid, data) 接口函数,接收服务 UUID、特征值 UUID 和数据。
  2. 通过判断特征值 UUID 是否匹配配置中的 target_notify_chartarget_read_char 来区分数据类型。
  3. 对于外围设备的通知数据,通过 sys.publish("RECV_BLE_NOTIFY_DATA", ...) 发布。
  4. 对于主动读取到的数据,通过 sys.publish("RECV_BLE_READ_DATA", ...) 发布。
4.3.3.3 数据流转
  1. ble_client_main 模块接收到 EVENT_READ_VALUE 事件时,会调用此模块的 proc 函数。
  2. 本模块将数据分类后发布到对应的消息主题。
  3. 其他订阅了这些消息主题的模块可以接收并处理数据。

4.3.4 ble_client_sender.lua

其他模块只需发布 "SEND_DATA_REQ" 消息即可请求中心设备向外围设备发送数据。

4.3.4.1 主要功能
  1. 消息订阅:订阅 "SEND_DATA_REQ" 消息,接收其他模块的发送请求。
  2. 队列管理:维护发送队列 send_queue,存储待发送的数据项(包含服务 UUID、特征值 UUID、数据、回调信息)。
  3. 任务调度:通过任务处理函数 ble_client_sender_task_func 处理各类 BLE 事件。
  4. 数据发送:按顺序发送队列中的数据,并通过回调通知发送结果。
4.3.4.2 核心实现
  1. 其他模块通过 sys.publish("SEND_DATA_REQ", ...) 发布 发送请求。
  2. send_data_req_proc_func 函数将请求数据加入发送队列,并通知任务。
  3. 任务处理函数根据 BLE 事件状态(连接成功、断开连接等)处理队列数据。
  4. send_item_func 函数负责实际发送数据。
  5. send_item_cbfunc 函数处理发送结果,调用用户回调。
4.3.4.3 事件处理
  1. CONNECT_OK :BLE 连接成功,开始发送队列数据。
  2. SEND_REQ :有新数据需要发送,继续处理队列。
  3. DISCONNECTED :连接断开,清空队列并通知所有发送请求失败。

4.3.5 ble_uart_app.lua

  1. UART 初始化:打开 UART1 接口,配置波特率 115200、数据位 8、停止位 1、无奇偶校验。
  2. 数据接收:订阅 "RECV_BLE_NOTIFY_DATA" 消息,接收来自 BLE 外围设备的通知数据。
  3. 数据转发:将接收到的 BLE 数据(包含服务 UUID、特征值 UUID 和实际数据)格式化后通过 UART1 发送到 PC 端。

4.3.6 ble_timer_app

4.3.6.1 主要功能
  1. 创建两个独立的 5 秒循环定时器

    一个用于定时 发送 数据到外围设备特定特征值 UUID。

    一个用于定时 读取 外围设备特定特征值 UUID 的数据。

4.3.6.2 核心实现
  1. 定义了目标服务 UUID( FA00 )和特征值 UUID(写: EA02 ,读: EA03 )。
  2. 实现数据发送结果回调函数 send_data_cbfunc ,用于处理发送成功/失败的通知。
  3. 实现两个定时器回调函数: send_data_req_timer_cbfunc :发布 SEND_DATA_REQ 消息到 ble_client_sender 模块。 read_data_req_timer_cbfunc :发送 READ_REQ 请求到 ble_client_main 模块。

  4. 启动两个 5 秒循环定时器,分别绑定上述两个回调函数。

4.3.6.3 数据流转
  1. 发送流程:定时器触发 → 发布 SEND_DATA_REQ 消息 → ble_client_sender 处理 → 回调通知结果。
  2. 读取流程:定时器触发 → 发送 READ_REQ 请求 → ble_client_main 处理 → 结果通过事件返回。

4.4 日志分析

接下来用两个 Air8000 核心板,一个烧录外围设备的 demo,另一个烧录中心设备的 demo,来演示中心设备如何读,写操作。

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

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

3、中心设备主动读取特征值数据(Read)

第五部分:LuatOS 上的广播模式应用开发流程

BLE——广播模式代码:

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

BLE——广播模式文档:

https://docs.openluat.com/air8000/luatos/app/BLE/ibeacon/

接下来通过典型应用 ibeacon 来了解广播模式:

5.1 前置知识了解

5.1.1 ibeacon 介绍

1、ibeacon 技术是 Apple 公司在 2013 年 9 月发布的一种基于 BLE 蓝牙的通信协议,主要用于短距离传送少量数据。

它通过周期性广播包含唯一标识符(UUID、Major、Minor)的数据包,使智能设备在接收信号后,结合信号强度(RSSI)估算距离,实现室内定位、场景触发等功能。

2、ibeacon 规定了一个 30 个字节的广播包。其中需要重点解析的是后 21 个字节(即从 UUID 开始),此前字节重在标识是否为 ibeacon 协议。

ibeacon 广播数据包的完整格式如下:

字段
长度

标志位
3字节
0x02,0x01,0x06
长度
1字节
0x1A
类型
1字节
0xFF
公司标识符
2字节
自定义
ibeacon类型标识符
1字节
0x02
ibeacon数据长度
1字节
0x15
Proximity UUID
16字节
自定义
Major
2字节
自定义
Minor
2字节
自定义
Tx Power
1字节
自定义

一个 30 字节的完整的 iBeacon 可做如下拆解:

  1. AD Structure1(Advertising Data Structure

  2. 长度字段(1 字节):此处一般为 0x02(即十进制 2),表示该 AD Structure 后续数据的总字节数

  3. 类型字段(1 字节):0x01(Flags,广播标志位)
  4. 数据(1 字节):0x06(Flags 的值,表示可被发现且支持 BLE 通用模式,Flags 见 7.2 章节)
  5. AD Structure2

  6. 长度字段(1 字节):此处一般为 0x1A(即十进制 26)表示后续数据的总字节数

  7. 类型字段(1 字节):固定为 0xFF,表示厂商特定数据。
  8. 数据(此处为公司标识符)(2 字节):苹果的公司 ID 为 0x004C(小端存储为 0x4C 0x00)。
  9. iBeacon 有效载荷(仍属于 AD Structure2)

  10. iBeacon 类型标识符(1 字节):固定为 0x02,表示子类型为 iBeacon

  11. iBeacon 数据长度(1 字节):固定为 0x15,表示 ibeacon 后续数据长度为 21 字节 后续数据长度为需要重点解析的 21 字节
  12. Proximity UUID(16 字节):设备的唯一标识符(如 UUID)。
  13. Major(2 字节):用于区分区域(如建筑楼层)。
  14. Minor(2 字节):用于更细粒度的定位(如具体房间)。
  15. Tx Power(1 字节):校准信号强度的参考值(RSSI at 1m)。

AD Structures 是什么?

在 BLE 协议中,设备通过广播包(Advertising Packet)向外发送数据。

一个广播包可能包含多个 AD Structure(Advertising Data Structure),每个 AD Structure 用于描述不同的信息(例如设备名称、服务 UUID、厂商数据等)。

每个 AD Structure 的格式固定为:

    [长度(1字节)] + [类型(1字节)] + [数据(N字节)]
  • 长度字段(1 字节):表示 类型 + 数据 的总字节数(即 类型1字节 + 数据N字节)。
  • 类型字段(1 字节):定义数据的用途(例如 0xFF 表示厂商数据,0x09 表示设备名称)。
  • 数据字段(N 字节):具体内容,长度由长度字段-1 决定(因为类型占 1 字节)。

AD Structure 的解析规则:

  • 顺序无关:AD Structure 的顺序不固定。
  • 长度限制:总长度不超过 31 字节(若为扩展广播,可更长,但 iBeacon 不支持)。
  • 类型唯一性:同一类型可能重复出现(例如多个厂商数据块)。

在解析时,可以通过以下步骤遍历所有 AD Structure:

  • 从广播包首字节开始。
  • 读取长度字段(1 字节),确定当前 AD Structure 的总长度(包括类型和数据)。
  • 读取类型字段(1 字节),判断数据类型。
  • 根据类型,处理后续数据。

常见 AD Structure 类型对照表:

详见:https://www.bluetooth.com/specifications/assigned-numbers/ 2.3Common Data Types 章节

其中标志位,长度,类型我们不需要处理,我们只需要关注 ibeacon 的有效载荷部分,接下来详细介绍下这部分字段内容:

厂商标识符(2 字节):是蓝牙技术中用于唯一标识设备制造商或品牌方的 2 字节(16 位)编码,范围是 0x0000 ~ 0xFFFF(即 0~65535),它的核心作用是区分不同厂商的蓝牙设备,确保数据解析和兼容性。

ibeacon 类型标识符(1 字节):固定为 0x02, 表示子类型为 ibeacon。

ibeacon 数据长度(1 字节):固定为 0x15,表示 ibeacon 数据长度 21 字节。

Proximity UUID(16 字节):通用唯一标识符。

这是一个 128 位(16 字节)的唯一标识符,用于区分你所在的 iBeacon 网络。 例如,一个商店的所有 iBeacon 可以使用同一个 UUID,这样你的应用就可以知道用户进入了该商店区域。 通常,一个组织或一个应用使用同一个 UUID,然后通过 Major 和 Minor 来进一步细分区域和设备。

Major(2 字节):主标识,用于区分同一组织(UUID 相同)下的不同区域或组别。

Major 是一个 16 位的无符号整数,用于将一组相关的设备进行分组。 例如,一个连锁商店的每个分店可以使用相同的 Major 值,这样应用就知道用户进入了哪个分店。

Minor(2 字节):次标识,用于在同一个 Major 组内进行更细粒度的区分。

Minor 也是一个 16 位的无符号整数,用于标识特定的 iBeacon。 例如,在一个分店内,每个货架或区域可以有一个唯一的 Minor 值。这样,应用就可以知道用户接近哪个具体的货架。

Signal Power(1 字节):校准信号强度的参考值,单位 dBm。(该位为 8 位有符号数据,范围为-128 到 127,最高位是符号位

这个字段是 8 位有符号整数,表示在距离 iBeacon 设备 1 米处测量到的信号强度(RSSI)。这个值用于校准,帮助估算与设备之间的实际距离。设备接收到信标的信号强度(RSSI)后,与这个校准值进行比较,通过信号衰减模型来估算距离。 例如:0xC0 对应-64dBm

  • 十六进制 0xC0 的二进制表示为 11000000
  • 作为有符号整数,最高位 1 表示负数
  • 取反加 1 计算补码值:01000000 = 64
  • 因此,0xC0 对应十进制-64

注:

1、如何获取厂商标识符?

(1)向蓝牙技术联盟(SIG)直接申请。

(2)使用已授权厂商的 ID,申请得到其授权。

2、Signal Power 是 8 位有符号整数,例如:0xC0 的二进制表示是 1100 0000,最高位 1 代表负数,对应十进制是-64。

3、厂商标识符是按照小端序存储的,例如:Apple 的公司 ID 是 0x004C(大端序),但在蓝牙数据包中按规范存储为 4C 00(小端序)。

注意:Apple 的 ID 是 0x004C,但是 demo 中需按照 0x4C,0x00 的方式写入存储。

原因:在 BLE 协议中,公司 ID 按照小端序存储。

大端序:高位字节存储在低地址,低位字节存储在高地址,0x004C 在大端序中存储为00 4C

低地址       高地址
+---------+---------+
| 0x00    | 0x4C    |  
+---------+---------+

小端序:高位字节存储在高地址,低位字节存储在低地址,0x004C 在小端序中存储为4C 00

低地址       高地址
+---------+---------+
| 0x4C    | 0x00    |  
+---------+---------+

5.1.2 ibeacon 如何利用 Signal Power 和 RSSI 进行距离估算?

ibeacon 通过 Signal Power(出厂校准信号强度) 和 RSSI(接收信号强度) 估算设备与 ibeacon 之间的距离,其核心原理基于蓝牙信号衰减模型。简单来讲就是设备出厂前先根据实际情况,测算在 1 米距离时的信号强度是多少,作为基准值,然后基于蓝牙信号衰减模型,根据实际信号值 RSSI 推算出实际距离。以下是具体方法和实现步骤:

注意:不同的蓝牙设备或相同设备不同的工况甚至不同的场地环境,都会影响 Signal Power 值,因此这个值虽然可以测量,但一定程度上是个经验值,很难测准。故 ibeacon 的距离估算功能只是估算大概范围,并不能用于精准定位。

1、关键概念

2、距离估算公式

iBeacon 使用 对数路径损耗模型(Log-distance Path Loss Model)计算距离:

  • d:估算距离(米)。
  • n:环境衰减因子(通常 2~4,空旷环境=2,普通室内=2.5-3,复杂环境多障碍物=3.5-4),环境越复杂信号衰减越快。
  • Tx Power:Beacon 的校准信号强度(通过实际环境测量拟合,需预先标定)。
  • RSSI:手机实际测得的信号强度。

3、示例计算

Tx Power = -60 dBm

RSSI = -80 dBm

n = 2.5(室内环境)

5.2 源码分析

5.2.1 文件说明

  1. main.lua:主程序入口文件。
  2. ble_ibeacon.lua:iBeacon 功能的具体实现,负责蓝牙初始化、广播配置和异常处理。
  3. check_wifi.lua:Air8000 的蓝牙功能依赖 WiFi 协处理器,需确保 WiFi 固件为最新版本。本脚本文件检查当前 Air8000 模组的 WiFi 固件是否为最新版本,若不是则自动启动升级(需插入可联网的 SIM 卡)

5.2.2 ble_ibeacon.lua

5.2.2.1 全局变量定义

device_name :广播设备名称,

adv_state :广播状态标志,用于跟踪 iBeacon 广播的开启/关闭状态,

ibeacon_data :iBeacon 广播数据包,包含以下结构:

  • 厂商标识符(2 字节):0x004C,本例演示的是 ibeacon,所以采用 Apple 的 ID:0x004C(iBeacon 是 Apple 的专有技术,采用 Apple 的 ID 才能显示成 ibeacon)
  • ibeacon 类型标识符(2 字节):固定为 0x02,0x15, 表示子类型为 ibeacon。
  • Proximity UUID(16 字节):0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10
  • Major(2 字节):0x00,0x01
  • Minor(2 字节):0x00,0x02
  • Signal Power(1 字节):0xC0
-- 广播状态
local adv_state = false

-- 配置ibeacon广播数据包
local ibeacon_data = string.char(0x4C, 0x00, -- Manufacturer ID(2字节)
                            0x02, -- ibeacon数据类型(1字节)
                            0x15, -- ibeacon数据长度(1字节)
                            0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, -- UUID(16字节)
                            0x00, 0x01, -- Major(2字节)
                            0x00, 0x02, -- Minor(2字节)
                            0xC0) -- Signal Power(1字节)
5.2.2.2 事件回调函数 (ble_callback)

处理 BLE 设备的各种事件:

  • ble.EVENT_ADV_START :广播成功启动时设置 adv_state 为 true
  • ble.EVENT_ADV_STOP :广播停止时设置 adv_state 为 false
-- 事件回调函数
function ble_callback(ble_device, ble_event)
    if ble_event == ble.EVENT_ADV_START then
        log.info("iBeacon", "广播已成功启动")
        adv_state = true
    elseif ble_event == ble.EVENT_ADV_STOP then
        log.info("iBeacon", "广播已停止,等待重新启动")
        adv_state = false
    end
end
5.2.2.3 核心任务函数 (ble_ibeacon_task_func)

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

  • 初始化蓝牙核心 :创建 bluetooth_device 实例
  • 初始化 BLE 功能 :创建 ble_device 实例并注册回调
  • 配置广播参数 :通过 adv_create 方法设置:
  • 广播地址模式 (ble.PUBLIC)
  • 广播通道 (ble.CHNLS_ALL)
  • 广播间隔 (intv_min/max = 120)
  • 广播数据 (包含标志位、iBeacon 数据和设备名称)
  • 启动广播 :调用 adv_start()方法
  • 状态监控 :通过 while adv_state 循环监控广播状态
  • 异常处理 :使用 goto EXCEPTION_PROC 标签统一处理各种初始化失败情况
  • 资源清理与重试 :在异常情况下停止广播并重新初始化,间隔 5 秒后重试
function ble_ibeacon_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

        -- 设置广播内容
        -- 由于没有 "COMPLETE_LOCAL_NAME" ,故仅安卓可用
        adv_create = adv_create or ble_device:adv_create({
            addr_mode = ble.PUBLIC, -- 广播地址模式, 仅支持: ble.PUBLIC
            channel_map = ble.CHNLS_ALL, -- 广播的通道, 可选值: ble.CHNL_37, ble.CHNL_38, ble.CHNL_39, ble.CHNLS_ALL
            intv_min = 120, -- 广播间隔最小值, 单位为0.625ms, 最小值为20, 最大值为10240
            intv_max = 120, -- 广播间隔最大值, 单位为0.625ms, 最小值为20, 最大值为10240
            adv_data = { -- 支持表格形式, 也支持字符串形式(255字节以内)
                {ble.FLAGS, string.char(0x06)}, -- 广播标志位
                {ble.MANUFACTURER_SPECIFIC_DATA, ibeacon_data}, -- 厂商特定数据, 包含ibeacon数据
            }
        })

        if not adv_create then
            log.error("BLE", "BLE创建广播失败")
            goto EXCEPTION_PROC
        end

        log.info("开始广播")
        if not ble_device:adv_start() then
            log.error("BLE", "BLE广播启动失败")
            goto EXCEPTION_PROC
        end

        adv_state = true

        -- 等待直到广播停止
        while adv_state do
            sys.wait(1000)
        end
        ::EXCEPTION_PROC::

        log.info("iBeacon", "检测到广播停止,准备重新初始化")
        -- 停止广播
        if ble_device then
            ble_device:adv_stop()
            ble_device = nil
        end

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

5.3 日志分析

luatools 日志比较简单,只有开启广播的 log 打印

重点看 nrf connect 扫描出来的 ibeacon 信息:

第六部分:LuatOS 上的扫描模式应用开发流程

BLE——扫描模式代码:

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

BLE——扫描模式文档:

https://docs.openluat.com/air8000/luatos/app/BLE/scan/

接下来结合 ibeacon 应用,通过分析扫描到的 ibeacon 广播包数据,来了解扫描模式:

6.1 前置知识了解

6.1.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

6.1.2 扫描到的广播数据如何解码?

ble.EVENT_SCAN_REPORT 事件的 ble.param 包含如下内容:

  • addr_type :整数类型,表示蓝牙设备的地址类型
  • adv_addr :设备的广播地址,可以通过 :toHex() 方法转换为十六进制字符串形式的 MAC 地址
  • rssi :整数类型,表示接收信号强度,单位为 dBm
  • data :二进制数据类型,表示接收到的原始广播数据,可以通过 :toHex() 方法转换为十六进制字符串

其中的广播数据 data 可以通过 ble_device:adv_decode(data) 来解码。

ble_device:adv_decode(data)

功能

解码广播数据

注意事项

广播数据通常来自于 ble.EVENT_SCAN_REPORT 事件的 ble_param.data

参数

data

含义说明:原始广播数据;
数据类型:string
取值范围:广播数据的字符串表示;
是否必选:是;
注意事项:注意参数根据实际情况填写;

返回值

local adv_data = ble_device:adv_decode(data)

adv_data

含义说明:广播数据的解码结果,为table类型,表结构如下:
         索引型表(索引从1开始);
         每个索引对应一个子表,代表一个广告数据单元(AD Structure)
         每个子表包含以下三个字段:
             len :整数类型,表示数据长度
             tp :整数类型,表示广播数据类型(AD Type)
             data :字符串类型(二进制数据),表示广播数据内容
数据类型:table/nil
取值范围:解码成功返回table对象,失败返回nil
注意事项:返回的数据需要自行解析提取
返回示例:成功返回:table: 0C7F6570
         失败返回:nil
         local result = ble_device:adv_decode(data)
         log.info("result", result)

示例

-- 解码广播数据
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", "adv data", v.len, v.tp, v.data:toHex())
    end
end

6.2 源码分析

6.2.1 文件说明
  1. main.lua:主程序入口文件。
  2. ble_scan.lua:ble_scan.lua 是 Air8000 的蓝牙扫描功能实现模块,主要负责初始化蓝牙框架、配置并执行设备的扫描操作,并通过回调函数处理扫描到的设备信息。
  3. check_wifi.lua:Air8000 的蓝牙功能依赖 WiFi 协处理器,需确保 WiFi 固件为最新版本。本脚本文件检查当前 Air8000 模组的 WiFi 固件是否为最新版本,若不是则自动启动升级(需插入可联网的 SIM 卡)

6.2.2 ble_scan.lua

6.2.2.1 全局变量定义

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

-- 扫描状态
local scan_state = false
6.2.2.2 处理扫描报告事件 (handle_scan_report)
  • 功能:当扫描到 BLE 设备时,处理扫描报告事件。
  • 处理内容:记录并输出发现设备的信息,包括 RSSI 值、设备地址和广播数据等,可以根据需求筛选数据。
  • 示例演示了如何筛选 ibeacon 广播数据。
-- 处理扫描报告事件
local function handle_scan_report(ble_device, ble_param)
    -- 1. 打印基础设备信息
    log.info("ble_scan", "发现设备", 
            "RSSI:", ble_param.rssi, 
            "地址:", ble_param.adv_addr:toHex(),
            "数据:", ble_param.data:toHex())

    -- 2. 打印解析的广播数据
    -- 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.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.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)

6.3 日志分析

luatools 日志,可以查看扫描到的 ibeacon 的广播包数据信息

最后再结合 ibeacon 应用,进行简单的距离估算:

代码:

-- 计算大致距离 (米)
-- 使用公式: d = 10^((Tx Power-RSSI)/(10*n))
-- n为环境因子,通常在2-4之间,根据实际环境调整
local function calculate_distance(rssi, tx_power)
    log.info("ble_scan", "计算距离", "RSSI:", rssi, "TxPower:", tx_power)

    -- 避免无效值
    if rssi == 0 or tx_power == 0 then
        return -1 -- 无法计算
    end

    -- 设置环境因子n(可根据实际环境调整)
    local n = 2.7 -- 环境因子,通常在2-4之间

    -- 计算距离: d = 10^((Tx Power-RSSI)/(10*n))
    local exponent = (tx_power - rssi) / (10 * n)
    local distance = math.pow(10, exponent)

    return distance
end

local distance_str = string.format("%.2f 米", calculate_distance(ble_param.rssi, tx_power))

第七部分:常见问题

7.1 ibeacon 应用,为什么无法显示名称?

答:

一个完整的 BLE 广播数据包最长可以有 37 个字节,其中前 6 个字节固定用于设备 MAC 地址,剩下的 31 个字节才是我们可以自由配置的广播数据区域。这 31 个字节会被划分为若干个广播数据结构体(AD Structure)。

也就是 BLE 广播包 广播数据区域不超过 31 字节,那么 iBeacon 包含的数据和包含 flag 的 AD Structure 已经占了 30 个字节,如果要设置设备命名,会超出 31 字节。

可能有人会问,不是还有 1 个字节的空间吗,设备名称用一个字节不可以吗?

答:

  • 名称"1"(1 字符)。
  • AD Structure 长度:1(数据) + 1(类型) + 1(长度) = 3 字节。
  • 总长度:27(iBeacon) + 3(Flags) + 3(名称) = 33 字节 → 仍超出。

7.2 什么是 Flags?

ble.FLAGS 说明

在 BLE 中,标志(flags)通常出现在广播数据中,这些标志用于指示设备的能力和可发现性等。

根据蓝牙核心规范(Bluetooth Core Specification),广播数据中的 Flags 字段是一个 8 位的位图(bitmask),每个位代表特定的含义,以下是各个位的定义(从 LSB 到 MSB):

  • Bit 0: LE Limited Discoverable Mode(有限可发现模式)
  • Bit 1: LE General Discoverable Mode(通用可发现模式,设备持续可被发现)
  • Bit 2: BR/EDR Not Supported(不支持经典蓝牙)
  • Bit 3: Simultaneous LE and BR/EDR to Same Device Capable(同一设备同时支持 BLE 和经典蓝牙)
  • Bit 4: Simultaneous LE and BR/EDR to Different Devices Capable(支持同时连接不同设备的 BLE 和经典蓝牙)
  • Bit 5-7: 保留(Reserved)

注意:Bit 0 和 Bit 1 不能同时被设置。如果同时设置,则视为无效。

Bit1 和 Bit2 被设置时,Flags 字段的值为 0x06(二进制 0000 0110),

Bit2 被设置时,Flags 字段的值为 0x04(二进制 0000 0100)。

7.3 常见的 BLE 断开连接 reason


原因
0x08 (8)
Connection Timeout(数据传输过程中超时断开连接)
0x09 (9)
Maximum Number Of Connections(已达到最大连接数限制)
0x0C (12)
Command Disallowed(当前状态下不允许执行此命令)
0x13 (19)
Remote User Terminated Connection(远程用户终止连接,表示对方设备主动断开连接)
0x16 (22)
Connection Terminated By Local Host(本地主机终止连接,表示本设备主动断开连接)
0x18 (24)
Unacceptable Connection Parameters(不可接受的连接参数,如连接间隔、超时时间等参数不符合要求)
0x3E (62)
Connection Timeout("连接超时"或"未在规定时间内建立连接")

示例:

1、中心设备和外围设备在数据传输过程中,把外围设备断电

中心设备会提示 8 的错误码,即数据传输过程中超时断开连接。

2、中心设备和外围设备连接时,中心设备主动发起 disconnect

中心设备会提示 22 的错误码,即本地主机终止连接。

外围设备会提示 19 的错误码,即远程用户终止连接。