低功耗蓝牙 Mesh 方案(llsync_mesh)
llsync_mesh 是杰理 AC63 蓝牙 SDK 中集成的腾讯云 IoT(qiot)低功耗蓝牙 Mesh 方案(LLSync)适配模块,提供基于 BLE 广播/扫描与厂商 Model 的轻量级配网、认证、数据下发与上报能力,位于 apps/common/third_party_profile/llsync_mesh/ 目录。
Purpose and Scope
本页面向负责集成、移植与二次开发 llsync_mesh 的嵌入式工程师,完整说明:
- 模块目录结构与分层职责(公开 API / HAL 适配 / 数据模板 / SDK 内核 / 配置)
- 公开 API(
ble_qiot_export.h)每个接口的语义、实现细节与设计意图 - 厂商数据模板机制(
ble_property_t属性注册与 set/get 回调) - 设备 UUID 广播格式、扫描响应数据与 AuthValue 安全认证算法
- 未入网广播 / 静默广播状态机的配置语义
- 配置宏(
ble_qiot_mesh_cfg.h)的完整清单、默认值及调优含义
本页不覆盖:BLE 协议栈本身的广播/扫描/连接实现、其他第三方集成方案(如微信小程序、涂鸦等)以及应用层业务逻辑。这些内容属于 SDK 协议栈或 9-third-party-integrations 目录下相邻条目。
Overview
LLSync(Low Latency Sync)是腾讯云 IoT 面向低功耗设备的 BLE Mesh 通信方案。其核心思想是:设备在未入网阶段通过厂商自定义广播(Vendor ADV)携带 UUID 供 Provisioner(配网器/小程序)发现;配网成功后通过厂商 Model 的 Opcode(SET / GET / SET_UNACK / CONFIRMATION)完成属性读写与确认;设备侧使用 HMAC-SHA256 + 异或折叠算法基于设备 PSK 计算 AuthValue,完成配网过程中的双向认证。
llsync_mesh 在 AC63 SDK 中的角色是一个可移植的中间件:它不直接操作芯片寄存器,而是通过 ble_qiot_import(导入/适配接口)调用协议栈能力(如 llsync_mesh_dev_info_get 获取设备信息、llsync_mesh_vendor_data_report 触发上报等),因此可以跨杰理不同芯片平台复用。
关键设计意图
- 分层解耦:
export侧(对外实现)与import侧(平台适配)分离,中间件不感知具体协议栈 API,平台移植只改hal/ble_qiot_import.c。 - 模板化数据模型:属性(如开关、HSL 颜色、色温)以
ble_property_t数组注册,SDK 内核按type匹配属性并回调应用层,业务无需关心帧格式解析细节。 - 安全优先:AuthValue 由随机数 + 产品 ID + 设备名的 HMAC-SHA256 签名再异或折叠生成,且
ble_qiot_utils_hmac256、ble_qiot_utils_sha256、ble_qiot_utils_base64均为 SDK 自带实现,避免依赖平台加密库。 - 低功耗广播策略:未入网广播高密度(150ms 时长 / 500ms 间隔)持续 10 分钟用于快速被发现;超时未配网则退化为 60s 间隔的静默广播,仅保留被 Provisioner 发现的最小可见性,显著降低功耗。
架构
flowchart TD
subgraph sg_App["应用层 / 蓝牙协议栈"]
BTStack["BLE 协议栈事件<br/>扫描/广播/接收"]
AppLogic["应用业务逻辑"]
end
subgraph sg_Public["公开 API (include)"]
ExportH["ble_qiot_export.h"]
end
subgraph sg_HAL["HAL 层 (hal)"]
ExportC["ble_qiot_export.c"]
ImportC["ble_qiot_import.c"]
ImportH["ble_qiot_import.h"]
end
subgraph sg_Cfg["配置 (cfg)"]
CfgH["ble_qiot_mesh_cfg.h"]
end
subgraph sg_Tpl["数据模板 (data_template)"]
TplH["ble_qiot_template.h"]
TplC["ble_qiot_template.c"]
end
subgraph sg_Sdk["SDK 内核 (sdk_src)"]
MeshCore["core/ble_qiot_utils_mesh.c"]
Utils["utils: sha256 / hmac256 / base64 / log"]
end
BTStack -->|"接收/扫描事件"| ExportC
ExportC --> ExportH
ExportC -->|"调用平台适配函数"| ImportC
ImportC --> ImportH
ExportC --> CfgH
ExportC --> TplC
TplC --> TplH
ExportC --> MeshCore
MeshCore --> Utils
AppLogic -->|"注册属性回调"| TplC
架构说明:
include/ble_qiot_export.h:对协议栈/应用层暴露的 6 个公共接口,是模块的"门面"。hal/ble_qiot_export.c:公共接口的具体实现,包括 Opcode 分发、扫描响应拼装、UUID 构造、AuthValue 计算。hal/ble_qiot_import.c/ble_qiot_import.h:平台适配层,声明并实现llsync_mesh_dev_info_get等由平台提供的函数(设备名、产品 ID、PSK 等),export.c通过它获取设备凭据。data_template/ble_qiot_template.{c,h}:厂商属性模板——定义属性类型枚举、负载结构体与ble_property_t注册表,是业务与 SDK 之间的"翻译层"。sdk_src/core/ble_qiot_utils_mesh.c:Mesh 核心工具,实现 vendor data 的 set/get/set_unack/confirmation 处理与配网状态管理。sdk_src/utils/:纯软件算法库(SHA-256、HMAC-SHA256、Base64、日志),无平台依赖。cfg/ble_qiot_mesh_cfg.h:所有可调参数(Flash 地址、广播时长/间隔、日志宏),集中配置。
数据模板机制(data_template)
数据模板是 llsync_mesh 连接"协议帧"与"业务属性"的桥梁。SDK 不要求业务直接解析字节流,而是把属性抽象为"类型 + set/get 回调",由模板层统一翻译。
属性注册表
核心类型定义如下:
typedef int (*property_set_cb)(const char *data, uint16_t len);
typedef int (*property_get_cb)(char *buf, uint16_t buf_len);
typedef struct {
property_set_cb set_cb; //set callback
property_get_cb get_cb; //get callback
uint16_t type; //attr type
uint16_t data_len;
} ble_property_t;
Source: ble_qiot_template.h
每个 ble_property_t 描述一个可读写的设备属性:type 是厂商属性类型标识,set_cb 负责把下发的负载写入业务状态,get_cb 负责把业务状态序列化供上报,data_len 声明负载长度。
厂商属性类型
模板预定义了通用属性与电工照明属性两类:
typedef enum {
// General Properties
LLSYNC_MESH_VENDOR_ONOFF_TYPE = 0xF000,
LLSYNC_MESH_VENDOR_UNIX_TIME_TYPE = 0xF001,
LLSYNC_MESH_VENDOR_POWER_PERCENT_TYPE = 0xF002,
// Electrician Lighting
LLSYNC_MESH_VENDOR_HSL_TYPE = 0xF100,
LLSYNC_MESH_VENDOR_LIGHTNESS_LEVEL_TYPE = 0xF101,
LLYNSC_MESH_VENDOR_COLOR_TEMPRETURE_TYPE = 0xF102,
LLSYNC_MESH_VENDOR_STRUCT_TEST_TYPE = 0xF103,
} mesh_vendor_type_e;
Source: ble_qiot_template.h
类型编码采用 0xF0xx(通用)/ 0xF1xx(电工照明)分段,便于 Provisioner 端按行业分类解析;业务可在同一枚举空间内追加自定义类型。配套的负载结构体均为 __attribute__((packed)) 紧凑布局,避免编译器对齐填充导致线上帧格式与本地结构不一致:
typedef struct {
uint8_t onoff;
} __attribute__((packed)) llsync_mesh_onoff;
typedef struct {
uint16_t HSL[3];
} __attribute__((packed)) llsync_mesh_HSL;
typedef struct {
uint32_t testa;
uint32_t testb;
} __attribute__((packed)) llsync_mesh_struct_test;
Source: ble_qiot_template.h
模板同时暴露两个查询接口:llsync_mesh_vendor_size_get() 返回属性总数,llsync_mesh_property_array_get() 返回 ble_property_t 数组指针,供 SDK 内核(utils_mesh)在收到 vendor data 时遍历匹配 type。
设备信息与广播载荷
设备信息获取
export.c 中所有涉及设备凭据的逻辑都通过 llsync_mesh_dev_info_get() 一次性取得 ble_device_info_t(设备名、产品 ID、PSK),该函数由平台适配侧(import)实现,使 SDK 内核不直接持有设备凭据:
memset(&dev_info, 0, sizeof(ble_device_info_t));
llsync_mesh_dev_info_get(&dev_info);
memcpy(&scan_resp_data[offset], dev_info.device_name, strlen(dev_info.device_name));
Source: ble_qiot_export.c
扫描响应数据
llsync_mesh_scan_data_get() 拼装 BLE 扫描响应包,固定前缀 0x02 0x01 0x06(Flags:可发现 + 不支持 BR/EDR)与 0x0d 0x09(完整本地名,长度 13),随后填充设备名:
uint8_t scan_resp_data[LLSYNC_MESH_ADV_MAX_LEN] = {0x02, 0x01, 0x06, 0x0d, 0x09};
uint8_t offset = 5;
ble_device_info_t dev_info;
memset(&dev_info, 0, sizeof(ble_device_info_t));
llsync_mesh_dev_info_get(&dev_info);
memcpy(&scan_resp_data[offset], dev_info.device_name, strlen(dev_info.device_name));
offset += strlen(dev_info.device_name);
memcpy(data, scan_resp_data, offset);
*data_len = offset;
Source: ble_qiot_export.c
设计意图:扫描响应不携带产品 ID,只携带可读设备名,供手机端扫描列表直接展示;入网相关的身份信息放在厂商广播 UUID 中,减少扫描响应长度、提高低功耗扫描效率。
设备 UUID 广播格式
llsync_mesh_dev_uuid_get() 构造厂商广播 UUID,字节序为:CID 低字节、CID 高字节、协议版本 | 广播类型位、RFU 标志、产品 ID(LLSYNC_MESH_PRODUCT_ID_LEN 字节)、2 字节 RFU、结束符:
data[index++] = LLSYNC_MESH_CID_VENDOR_LOW;
data[index++] = LLSYNC_MESH_CID_VENDOR_HIGH;
if (type == LLSYNC_MESH_UNNET_ADV_BIT) {
data[index++] = LLSYNC_MESH_PROTOCOL_VERSION | LLSYNC_MESH_UNNET_ADV_BIT;
} else {
data[index++] = LLSYNC_MESH_PROTOCOL_VERSION | LLSYNC_MESH_SILENCE_ADV_BIT;
}
data[index++] = LLSYNC_MESH_ADV_RFU_FLAG2;
memset(&dev_info, 0, sizeof(ble_device_info_t));
llsync_mesh_dev_info_get(&dev_info);
memcpy(&data[index], dev_info.product_id, LLSYNC_MESH_PRODUCT_ID_LEN);
Source: ble_qiot_export.c
type 参数(LLSYNC_MESH_UNNET_ADV_BIT / LLSYNC_MESH_SILENCE_ADV_BIT)直接决定广播类型位,Provisioner 据此区分"待配网设备"与"静默待发现设备"。函数在缓冲区不足(LLSYNC_MESH_DEVICE_UUID_LEN > data_len)时打印错误日志并返回 LLSYNC_MESH_RS_ERR_PARA,防止越界写。
安全认证(AuthValue)
配网认证的核心是 llsync_mesh_auth_clac():输入 16 字节随机数,输出 16 字节 AuthValue。算法分为三步:
- 拼接待签名数据:随机数的十六进制字符串 + 产品 ID + 设备名;
- HMAC-SHA256 签名:以设备 PSK 为密钥;
- 异或折叠:将 32 字节摘要前后 16 字节逐字节异或,压缩为 16 字节 AuthValue。
uint8_t temp_data[LLSYNC_MESH_RAMDOM_DATA_LEN] = {0};
uint8_t sign[HMAC_SHA256_DIGEST_SIZE] = {0};
uint8_t buf[128] = {0};
uint8_t buf_len = 0;
uint8_t i = 0;
ble_device_info_t dev_info;
memset(&dev_info, 0, sizeof(ble_device_info_t));
llsync_mesh_dev_info_get(&dev_info);
buf_len += llsync_mesh_hex(random, LLSYNC_MESH_RAMDOM_DATA_LEN, buf);
memcpy(buf + buf_len, dev_info.product_id, LLSYNC_MESH_PRODUCT_ID_LEN);
buf_len += LLSYNC_MESH_PRODUCT_ID_LEN;
memcpy(buf + buf_len, dev_info.device_name, strlen(dev_info.device_name));
buf_len += strlen(dev_info.device_name);
llsync_hmac_sha256(sign, (const uint8_t *)buf, buf_len,
(const uint8_t *)dev_info.psk, sizeof(dev_info.psk));
for (i = 0; i < HMAC_SHA256_DIGEST_SIZE / 2; i++) {
temp_data[i] = sign[i] ^ sign[i + HMAC_SHA256_DIGEST_SIZE / 2];
}
memcpy(auth_data, temp_data, LLSYNC_MESH_RAMDOM_DATA_LEN);
Source: ble_qiot_export.c
设计意图:随机数以十六进制文本(llsync_mesh_hex,每字节转 2 个 ASCII 字符)参与签名,保证挑战值不可预测,防重放;PSK 不出设备、不进广播帧,只有持有相同 PSK 的 Provisioner 才能计算出匹配的 AuthValue,从而完成"设备证明自己持有 PSK"的鉴权。异或折叠将 32 字节摘要压缩为 16 字节,适配 LLSync 短帧载荷并增加破解难度(无法从 AuthValue 反推完整摘要)。签名/摘要算法全部使用 SDK 自带实现(ble_qiot_utils_hmac256.c、ble_qiot_utils_sha256.c),不依赖平台加密硬件,保证可移植性。
广播状态机
配置宏共同定义了两阶段广播策略,对应 LLSync 配网流程:
flowchart TD
Start([设备上电]) --> Unnet{"已入网?"}
Unnet -->|"否:未入网"| Adv1["未入网广播 UNNET_ADV<br/>时长 150ms / 间隔 500ms"]
Adv1 --> Total{"累计超过<br/>UNNET_ADV_TOTAL_TIME<br/>10 分钟?"}
Total -->|"否"| Adv1
Total -->|"是"| Silent{"SILENCE_ADV_ENABLE<br/>== 1 ?"}
Silent -->|"是"| Adv2["静默广播 SILENCE_ADV<br/>间隔 60s,仅被发现"]
Silent -->|"否"| Stop["停止广播"]
Adv1 -->|"配网成功"| Net["入网状态<br/>正常组网通信"]
Adv2 -->|"被 Provisioner 配网"| Net
Net --> Report["llsync_mesh_vendor_data_report<br/>主动上报 / net_status_inform 通知"]
Adv2 --> Wait["等待 Provisioner 发现"]
Wait --> Adv2
- 未入网广播(UNNET_ADV):单次时长 150ms、间隔 500ms,总时长上限 10 分钟——高占空比保证配网器扫到即连;
- 静默广播(SILENCE_ADV):超时后进入,间隔拉长到 60s,仅维持最低可发现性以省电;由
LLSYNC_MESH_SILENCE_ADV_ENABLE控制是否启用(默认 1); - 入网后:
llsync_mesh_net_status_inform(net_status)通知 SDK 广播/Flash 写入切换,llsync_mesh_vendor_data_report()触发厂商数据主动上报。
配网状态与网络配置(如密钥、组网地址)持久化在 Flash 的 LLSYNC_MESH_RECORD_FLASH_ADDR(默认 0xFE000),页大小 4096 字节,保证掉电重启后仍保持入网状态。
核心数据流:厂商数据接收与处理
协议栈收到 Mesh 厂商帧后,最终会调用 llsync_mesh_recv_data_handle()。该函数是数据下发的唯一入口,先做指针校验,再按 Opcode 分发到内核处理函数:
ble_qiot_ret_status_t llsync_mesh_recv_data_handle(uint32_t Opcode, uint8_t *data, uint16_t data_len)
{
LLSYNC_MESH_POINTER_CHECK(data, LLSYNC_MESH_RS_ERR_PARA);
switch (Opcode) {
case LLSYNC_MESH_VND_MODEL_OP_SET: {
llsync_mesh_vendor_data_set(data, data_len);
break;
}
case LLSYNC_MESH_VND_MODEL_OP_GET: {
llsync_mesh_vendor_data_get(data, data_len);
break;
}
case LLSYNC_MESH_VND_MODEL_OP_SET_UNACK: {
llsync_mesh_vendor_data_set_unack(data, data_len);
break;
}
case LLSYNC_MESH_VND_MODEL_OP_CONFIRMATION: {
llsync_mesh_vendor_op_confirmation(data, data_len);
break;
}
default:
break;
}
return LLSYNC_MESH_RS_OK;
}
Source: ble_qiot_export.c
sequenceDiagram
participant Stack as BLE 协议栈
participant Export as llsync_mesh_recv_data_handle<br/>hal/ble_qiot_export.c
participant Core as SDK 内核<br/>ble_qiot_utils_mesh.c
participant Tpl as 数据模板<br/>ble_property_t 注册表
participant App as 应用属性回调
Stack->>Export: Mesh 接收事件 (Opcode, data, data_len)
Export->>Export: LLSYNC_MESH_POINTER_CHECK 指针校验
alt Opcode == VND_MODEL_OP_SET
Export->>Core: llsync_mesh_vendor_data_set(data, len)
Core->>Tpl: 按属性 type 匹配 ble_property_t
Tpl->>App: 调用 set_cb 写入业务状态
else Opcode == VND_MODEL_OP_GET
Export->>Core: llsync_mesh_vendor_data_get(data, len)
Core->>Tpl: 按属性 type 匹配 ble_property_t
Tpl->>App: 调用 get_cb 读取业务状态
else Opcode == VND_MODEL_OP_SET_UNACK
Export->>Core: llsync_mesh_vendor_data_set_unack(data, len)
Core->>Tpl: 设置属性(无需应答)
else Opcode == VND_MODEL_OP_CONFIRMATION
Export->>Core: llsync_mesh_vendor_op_confirmation(data, len)
Core->>Core: 配网确认处理 / 状态持久化
end
Export-->>Stack: 返回 LLSYNC_MESH_RS_OK
流程要点:
- 未知 Opcode 走
default分支静默忽略,但函数仍返回LLSYNC_MESH_RS_OK,避免协议栈因单帧异常终止整个连接——这是"尽力而为"的 Mesh 语义设计。 SET(带应答)与SET_UNACK(不带应答)走同一数据处理路径,区别在于是否要求 Provisioner 应答,适配不同 QoS 场景(如灯光调色可容忍丢帧,不产生重传拥塞)。- 属性匹配发生在模板层:内核解析出属性
type后,遍历llsync_mesh_property_array_get()返回的注册表,命中则回调set_cb/get_cb,业务只关心自己注册的属性。
使用示例
1. 接收 Mesh 下发数据(协议栈事件回调内)
在协议栈的 Mesh 接收回调中调用导出接口,按 Opcode 交给 SDK 处理:
ble_qiot_ret_status_t ret = llsync_mesh_recv_data_handle(opcode, data, data_len);
if (ret != LLSYNC_MESH_RS_OK) {
// 参数非法(data 为空)等错误处理
}
Source: ble_qiot_export.h
2. 构造配网广播 UUID
Provisioner 扫描阶段,把 llsync_mesh_dev_uuid_get() 的输出填入厂商广播:
uint8_t uuid_buf[LLSYNC_MESH_DEVICE_UUID_LEN];
uint8_t len = llsync_mesh_dev_uuid_get(LLSYNC_MESH_UNNET_ADV_BIT, uuid_buf, sizeof(uuid_buf));
if (len <= 0) {
// 缓冲区过短等错误处理
}
Source: ble_qiot_export.h
3. 配网认证:计算 AuthValue
收到配网器下发的随机数挑战后,计算并回传 AuthValue:
uint8_t random[LLSYNC_MESH_RAMDOM_DATA_LEN] = {0}; // 来自配网器挑战
uint8_t auth_data[LLSYNC_MESH_RAMDOM_DATA_LEN] = {0};
llsync_mesh_auth_clac(random, auth_data);
// 将 auth_data 回传给 Provisioner 完成认证
Source: ble_qiot_export.h
4. 注册自定义属性
业务按模板约定,把设备能力注册进属性表,SDK 内核即可自动路由 set/get:
static ble_property_t g_property_table[] = {
{ my_onoff_set, my_onoff_get, LLSYNC_MESH_VENDOR_ONOFF_TYPE,
sizeof(llsync_mesh_onoff) },
{ my_hsl_set, my_hsl_get, LLSYNC_MESH_VENDOR_HSL_TYPE,
sizeof(llsync_mesh_HSL) },
};
// 供 llsync_mesh_property_array_get() 返回
Source: ble_qiot_template.h
配置选项(ble_qiot_mesh_cfg.h)
所有可调参数集中定义在 ble_qiot_mesh_cfg.h,按"持久化 / 广播策略 / 平台"分组:
| 宏 | 类型 | 默认值 | 说明 |
|---|---|---|---|
LLSYNC_MESH_RECORD_FLASH_ADDR | 地址 | 0xFE000 | 配网状态/网络配置的 Flash 存储起始地址,需避开固件与文件系统分区 |
LLSYNC_MESH_RECORD_FLASH_PAGESIZE | 整数 | 4096 | Flash 页大小(见芯片数据手册),决定擦写粒度 |
LLSYNC_MESH_UNNET_ADV_TOTAL_TIME | 时长(ms) | 10 * 60 * 1000 | 未入网广播总时长上限,10 分钟后进入静默广播或停止 |
LLSYNC_MESH_SILENCE_ADV_ENABLE | 布尔 | 1 | 超时未配网是否进入静默广播(仅用于让 Provisioner 发现设备) |
LLSYNC_MESH_UNNET_ADV_DURATION | 时长(ms) | 150 | 未入网广播单次广播时长 |
LLSYNC_MESH_SILENCE_ADV_DURATION | 时长(ms) | 150 | 静默广播单次广播时长 |
LLSYNC_MESH_UNNET_ADV_INTERVAL | 时长(ms) | 500 | 未入网广播间隔 |
LLSYNC_MESH_SILENCE_ADV_INTERVAL | 时长(ms) | 60 * 1000 | 静默广播间隔(60s,低占空比省电) |
MESH_LOG_PRINT(...) | 宏 | printf(...) | 日志输出口,可重定向到自有日志系统 |
BLE_QIOT_USER_DEFINE_HEXDUMP | 布尔 | 0 | 是否由用户自定义 hexdump 打印(配合日志) |
Source: ble_qiot_mesh_cfg.h
调优指引:
- 更快被发现:缩短
LLSYNC_MESH_UNNET_ADV_INTERVAL或延长LLSYNC_MESH_UNNET_ADV_DURATION,代价是配网阶段功耗上升; - 更省电:延长
LLSYNC_MESH_UNNET_ADV_TOTAL_TIME会让设备在未配网状态停留更久;配网成功率高的场景可缩短该值更快进入静默态; - Flash 分区冲突:
LLSYNC_MESH_RECORD_FLASH_ADDR = 0xFE000与页面大小需与链接脚本、升级区对齐,改址前务必核对内存布局。
API 参考(ble_qiot_export.h)
以下 6 个接口构成 llsync_mesh 的对外契约,由协议栈/应用层调用,hal/ble_qiot_export.c 实现。
ble_qiot_ret_status_t llsync_mesh_recv_data_handle(uint32_t Opcode, uint8_t *data, uint16_t data_len)
处理设备收到的 Mesh 厂商数据。按 Opcode 分发:LLSYNC_MESH_VND_MODEL_OP_SET → vendor set;LLSYNC_MESH_VND_MODEL_OP_GET → vendor get;LLSYNC_MESH_VND_MODEL_OP_SET_UNACK → 无应答 set;LLSYNC_MESH_VND_MODEL_OP_CONFIRMATION → 配网确认。
参数:
Opcode(uint32_t):设备厂商 Model 操作码;data(uint8_t*):数据内容;不可为空;data_len(uint16_t):数据长度。
返回:成功返回 LLSYNC_MESH_RS_OK;data 为空返回 LLSYNC_MESH_RS_ERR_PARA。
Source: ble_qiot_export.h
ble_qiot_ret_status_t llsync_mesh_scan_data_get(uint8_t *data, uint8_t *data_len)
获取扫描响应数据(Flags + 完整本地名 + 设备名)。
参数:
data(uint8_t*):输出缓冲区;data_len(uint8_t*):输出长度。
返回:成功返回 LLSYNC_MESH_RS_OK;参数为空返回 LLSYNC_MESH_RS_ERR_PARA。
Source: ble_qiot_export.h
int llsync_mesh_dev_uuid_get(uint8_t type, uint8_t *data, uint8_t data_len)
构造设备 UUID(厂商广播载荷)。
参数:
type(uint8_t):LLSYNC_MESH_UNNET_ADV_BIT(未入网广播)或LLSYNC_MESH_SILENCE_ADV_BIT(静默广播);data(uint8_t*):输出缓冲区;data_len(uint8_t):缓冲区容量。
返回:成功返回有效数据长度;data 为空或 data_len 小于 LLSYNC_MESH_DEVICE_UUID_LEN 返回错误码(LLSYNC_MESH_RS_ERR_PARA)。
Source: ble_qiot_export.h
ble_qiot_ret_status_t llsync_mesh_auth_clac(uint8_t *random, uint8_t *auth_data)
配网认证:输入 16 字节随机数,输出 16 字节 AuthValue(HMAC-SHA256 + 异或折叠,见上文算法)。
参数:
random(uint8_t*):16 字节随机数(配网器挑战值);auth_data(uint8_t*):输出 16 字节认证值。
返回:成功返回 LLSYNC_MESH_RS_OK;任一参数为空返回 LLSYNC_MESH_RS_ERR_PARA。
Source: ble_qiot_export.h
void llsync_mesh_vendor_data_report(void)
触发设备主动上报厂商数据(如状态变化、定时上报)。无参数、无返回值;内部读取各属性 get_cb 组织上报帧。
Source: ble_qiot_export.h
void llsync_mesh_net_status_inform(uint8_t net_status)
通知 SDK 网络状态变化(入网/退网),SDK 据此切换广播模式或写 Flash 持久化状态。
参数:
net_status(uint8_t):网络状态(入网/未入网)。
Source: ble_qiot_export.h
模板查询接口(data_template)
| 函数 | 说明 |
|---|---|
uint16_t llsync_mesh_vendor_size_get(void) | 返回已注册属性数量 |
ble_property_t *llsync_mesh_property_array_get(void) | 返回属性注册表数组指针 |
Source: ble_qiot_template.h
故障模式、边界情况与并发
空指针与参数校验
llsync_mesh_recv_data_handle、llsync_mesh_scan_data_get、llsync_mesh_dev_uuid_get、llsync_mesh_auth_clac 均以 LLSYNC_MESH_POINTER_CHECK(ptr, ret) 宏做入口防御,空指针立即返回 LLSYNC_MESH_RS_ERR_PARA,避免协议栈事件回调传入非法指针导致整机崩溃。dev_uuid_get 还对输出缓冲区长度做了上限检查,并在过短时打印错误日志(ble_qiot_log_e)。
未知 Opcode 的容错
recv_data_handle 对未识别的 Opcode 走 default 分支静默忽略并仍返回 LLSYNC_MESH_RS_OK。这是有意为之:Mesh 广播链路天然存在帧丢失与乱序,把未知帧视为"非本设备可处理"而不是错误,可避免 Provisioner 侧因单帧异常而中断交互。代价是无效帧不会留下任何日志,排查问题时需在协议栈侧另行抓包。
广播缓冲区溢出防护
llsync_mesh_hex 内部将待转换长度钳制在 (sizeof(hexbufs) - 1) / 2(137 字节缓冲),确保任意长度输入都不会写穿栈上缓冲;dev_uuid_get 的构造过程同样受 LLSYNC_MESH_DEVICE_UUID_LEN 约束,若产品 ID 超长会导致错误返回而不是溢出。
认证随机数与重放
AuthValue 依赖配网器下发的随机数,因此协议本身抗重放的前提是随机数每次不同。若平台侧的随机数源质量差(如固定值),认证将形同虚设——移植时务必确保 random 来自真随机源。此外 llsync_mesh_hex 的十六进制文本拼接待签名数据,任何一侧拼接规则不一致(大小写、分隔符)都会导致 HMAC 校验失败,协议两端必须严格一致。
并发与中断上下文
本模块 API 均为同步、无锁实现,没有内部全局状态机上的互斥保护。BLE 协议栈的事件回调(接收、扫描、定时广播)通常运行在中断/任务上下文中,若应用层在多个任务同时调用 llsync_mesh_vendor_data_report() 或读写属性,需由集成方在应用层保证互斥(如关中断或任务锁)。Flash 写入(配网状态持久化)耗时较长,若在低优先级任务中执行可能阻塞扫描/广播时序,建议放到专门的任务或使用芯片的 Flash 管理接口。
性能与运维注意事项
- 认证计算开销:
llsync_mesh_auth_clac含一次 HMAC-SHA256(对 ≤ 约 64 字节输入),在 AC63 这类 MCU 上耗时在毫秒级且计算期间占用 CPU;配网流程中仅需调用一次,可接受,但不要在广播回调中反复计算。 - 广播占空比:未入网阶段 150ms/500ms 的广播占空比约 30%,是配网期功耗的主要来源;进入静默广播(60s 间隔)后功耗显著下降。量产前应根据目标功耗与配网成功率调优
cfg中的时长宏。 - Flash 寿命:
LLSYNC_MESH_RECORD_FLASH_ADDR = 0xFE000、页大小 4096 字节。反复配网/退网会反复擦写该页,需评估 Flash 擦写寿命与产品生命周期匹配;多次配网失败导致的频繁写 Flash 应加节流。 - 日志:
MESH_LOG_PRINT默认printf,调试时可临时打开;量产固件建议关闭或降级,避免日志输出挤占广播时序(尤其 UART 阻塞式输出)。 - 未读源码说明:
sdk_src/core/ble_qiot_utils_mesh.c与hal/ble_qiot_import.c的内部实现未在本页逐一展开,其对外行为(vendor set/get/set_unack/confirmation 处理、llsync_mesh_dev_info_get设备信息适配)已通过export.c的调用关系确认;如需深入了解组网状态机细节,请直接阅读这两个文件。
扩展点
- 新增厂商属性:在
mesh_vendor_type_e追加类型(沿用0xF1xx或自定义段),定义packed负载结构体,并在ble_property_t注册表中登记set_cb/get_cb与data_len。SDK 内核自动完成路由,无需改动协议栈。 - 平台移植(换芯片/换协议栈):只需实现
hal/ble_qiot_import.{c,h}侧声明的适配函数(如llsync_mesh_dev_info_get返回设备名/产品 ID/PSK),并保证export.c依赖的常量(CID、协议版本、广播位)与 Provisioner 端一致。 - 自定义安全策略:
llsync_mesh_auth_clac算法可整体替换(如改用 SM4/国密),只要在export.h保持同一签名、两端约定一致即可;ble_qiot_utils_sha256/hmac256/base64为独立实现,亦可替换为硬件加速版本。 - 日志与告警接入:重定义
MESH_LOG_PRINT与BLE_QIOT_USER_DEFINE_HEXDUMP可将 SDK 日志接入自有日志系统。
测试与验证建议
- 单元级:验证
llsync_mesh_hex的边界(0 长度、超长输入钳制);用固定随机数与 PSK 比对llsync_mesh_auth_clac输出与上位机计算结果是否一致(大小端敏感)。 - 集成级:Provisioner 分别下发 SET / GET / SET_UNACK / CONFIRMATION 四种 Opcode,确认
set_cb/get_cb被正确调用、未知 Opcode 不影响后续帧;扫描验证 UNNET 与 SILENCE 两种 UUID 广播位。 - 异常场景:模拟配网超时进入静默广播、Flash 写失败、配网中掉电重启(验证状态从 Flash 恢复)。
Related Links
- ble_qiot_export.h(公开 API)
- ble_qiot_export.c(HAL 实现)
- ble_qiot_mesh_cfg.h(配置项)
- ble_qiot_template.h(数据模板)
- ble_qiot_import.h(平台适配接口)
- ble_qiot_utils_mesh.h(Mesh 内核工具)
- ble_qiot_common.h(公共定义)
- 其他第三方集成方案见
9-third-party-integrations目录下的相邻条目。