【星闪 SLE 学习笔记 04】深入理解设备发现、连接建立与配对机制

前三篇分别完成了 SLE 技术体系梳理、双板串口透传和 SSAP 服务模型分析。继续沿着通信链向前追溯,这一篇重点研究:两块设备在交换 SSAP 数据之前,究竟怎样从 announce、seek 走到连接与配对。
文章摘要: 本文结合国际星闪联盟公开标准范围与 BearPi-Pico H3863 官方公开 SDK,分析 SLE UART 示例中的 announce 参数设置、公开数据组织、Client seek、目标筛选、连接状态回调和配对流程,并通过两块开发板的受控断开实验观察 Server 重新 announce、Client 重新 seek 以及链路恢复过程。全文区分标准定义、SDK 可观察实现与实板结果,不把 Demo 的名称筛选和删除配对记录策略写成所有 SLE 产品的固定机制。
关键词: 星闪;SLE;设备发现;Announce;Seek;连接;配对;BearPi-Pico H3863
一、为什么连接之前还要研究设备发现
第二篇跑通双板通信时,我已经见过以下日志:
set announce data success
sle announce enable callback
sle uart scan data
[Connected]
pair complete
exchange mtu
当时关注的是“最终能否通信”,所以这些日志主要被当作成功检查点。第三篇继续分析 SSAP 后,我逐渐意识到:这些提示其实对应一条完整的状态链。
Server 配置并启动 announce
→ Client 启动 seek
→ Client 从公开数据中识别目标
→ Client 停止 seek 并发起连接
→ 连接状态变为 CONNECTED
→ Client 判断当前配对状态
→ 未配对时发起 pairing
→ 配对完成
→ 交换 SSAP 信息
→ 发现服务并开始数据交互
因此,只看到 [Connected] 还不能说明整条应用链路已经准备完成。后面至少还要观察配对、SSAP 信息交换和服务发现。
本文源码分析继续基于固定提交:
08d2880956249642cc90d7eae9b80ed721646967
固定提交的目的,是避免 SDK 后续修改后,文章中的函数和实际代码无法对应。
二、先明确本文的信息边界
1. 标准公开范围
国际星闪联盟 T/XS 20001《基础服务层 设备发现与服务管理》的公开页面说明,其范围包括设备发现角色、设备公开信息、设备发现过程、广播数据格式,以及服务及其元素和服务交互机制。T/XS 20001-2022
联盟标准目录还列出了 T/XS 20001-2025 V1.1.0。本文对具体函数和字段的分析来自固定版本 H3863 SDK,不能把厂商 API 名称直接写成标准接口。国际星闪联盟标准目录
2. SDK 可观察实现
以下接口名称来自 H3863 SDK:
sle_set_announce_param()
sle_set_announce_data()
sle_start_announce()
sle_set_seek_param()
sle_start_seek()
sle_connect_remote_device()
sle_pair_remote_device()
本文可以根据公开源码解释它们在当前 Demo 中的调用关系,但其他芯片或其他 SDK 不一定使用相同名称。
3. 实板结果
状态回调顺序、断开原因和恢复过程,需要以本次两块 H3863 的串口日志为依据。串口工具显示的时间是主机接收日志的时间,不能当作协议保证的空口时延。
三、Server 如何进入 announce
Server 的设备公开逻辑主要位于:
application/samples/products/sle_uart/
└─ sle_uart_server/sle_uart_server_adv.c
初始化调用链可以概括为:
sle_uart_server_adv_init()
├─ sle_set_default_announce_param()
│ └─ sle_set_announce_param()
├─ sle_set_default_announce_data()
│ └─ sle_set_announce_data()
└─ sle_start_announce()
1. announce 参数
当前源码可以观察到以下配置:
param.announce_mode = SLE_ANNOUNCE_MODE_CONNECTABLE_SCANABLE;
param.announce_handle = 1;
param.announce_level = SLE_ANNOUNCE_LEVEL_NORMAL;
param.announce_channel_map = 0x07;
param.announce_interval_min = 0xC8;
param.announce_interval_max = 0xC8;
param.announce_tx_power = 18;
示例还显式设置本地地址:
01:02:03:04:05:06
announce_mode 的枚举名表明当前模式具备 connectable 和 scannable 属性。源码注释把 0xC8 按 125 μs 单位解释为 25 ms。这里记录的是当前示例配置,不能外推为 SLE 的固定 announce 间隔。
2. announce handle
当前示例使用:
#define SLE_ADV_HANDLE_DEFAULT 1
所以日志中会看到 announce ID 或 handle 为 1。这个 1 是 Demo 中定义的值,不应与第三篇的 Service Handle 1 混为一谈。虽然数值相同,但两者属于不同对象和不同接口上下文。
四、逐字节读懂公开数据
Server 在设置公开数据时会主动打印字节序列。第二篇曾记录:
announce_data_len = 6
announce_data:
0x01 0x02 0x01 0x02 0x02 0x00
结合 sle_uart_server_adv.h 中的数据类型枚举和当前组包代码,可以按以下方式理解:
| 第一段 | 0x01:discovery level | 0x02 | 0x01:normal |
| 第二段 | 0x02:access mode | 0x02 | 0x00 |
seek response 日志为:
0x0c 0x02 0x0a
0x10 0x0b
0x73 0x6c 0x65 0x5f 0x75 0x61 0x72 0x74
0x5f 0x73 0x65 0x72 0x76 0x65 0x72
其中:
0x0C → TX power level
0x02 → 当前字段长度
0x0A → 当前示例填写的值
0x10 → 当前完整名称字段长度
0x0B → complete local name
后续字节 → "sle_uart_server"
这里的字段解释来自 H3863 示例自身的枚举和组包过程。不能因为它看起来与其他无线协议的数据结构相似,就直接套用其他协议的字段顺序。
本次保存的 Server 完整日志与源码配置一致:
[21:30:10:646] sle set announce param, handle:1, mode:3,
min_interval:c8, max_interval:c8, tx_power:18
[21:30:10:666] data.announce_data_len = 6
[21:30:10:666] data.announce_data:
0x01 0x02 0x01 0x02 0x02 0x00
[21:30:10:687] data.seek_rsp_data_len = 20
[21:30:10:687] data.seek_rsp_data:
0x0c 0x02 0x0a 0x10 0x0b …
[21:30:10:708] set announce data success
[21:30:10:708] sle start announce in, adv_id:1
[21:30:10:739] sle announce enable callback id:01, state:0
state:0 与前面的 set announce data success 共同说明,本次 Server 已成功设置数据并进入 announce。原始带时间戳日志已作为本文实验材料保存,而不是只保留单行成功截图。
五、Client 怎样找到目标 Server
Client 逻辑主要位于:
application/samples/products/sle_uart/
└─ sle_uart_client/sle_uart_client.c
1. 注册 seek 回调
初始化阶段注册:
sle_enable_cb
seek_enable_cb
seek_result_cb
seek_disable_cb
这些回调共同组成了 Client 的发现状态机。
2. 设置 seek 参数
sle_uart_start_scan() 设置:
param.filter_duplicates = 0;
param.seek_filter_policy = 0;
param.seek_phys = 1;
param.seek_type[0] = 1;
param.seek_interval[0] = 100;
param.seek_window[0] = 100;
随后调用:
sle_set_seek_param()
→ sle_start_seek()
本文只记录固定 SDK 中的原始值。在没有进一步核实对应版本定义之前,不自行给 100 添加时间单位。
3. 按名称筛选目标
seek result 回调检查返回数据中是否包含:
sle_uart_server
匹配后执行:
保存对端地址
→ sle_stop_seek()
这里要注意:按本地名称筛选目标,是当前教学示例采用的应用策略,不代表 SLE 标准要求所有设备必须依赖名称才能连接。
4. 示例中的长度处理边界
sle_seek_result_info_t 同时提供:
uint8_t data_length;
uint8_t *data;
但示例直接把 data 交给 %s 和 strstr()。这与第三篇遇到的字符串边界问题相似:严格实现应按 data_length 解析各个公开数据字段,而不是默认整个回调缓冲区一定以 \\0 结尾。
本篇先记录这个实现边界,不把它扩展成完整的公开数据解析器,以免偏离设备发现与连接主线。
六、从停止 seek 到发起连接
匹配到目标后,Client 并没有立即在 seek result 回调中调用连接接口,而是先执行 sle_stop_seek()。
等 seek disable 回调返回成功后,再执行:
sle_remove_paired_remote_device(remote_addr)
→ sle_connect_remote_device(remote_addr)
这说明当前示例的顺序是:
找到目标
→ 停止 seek
→ 等待停止回调
→ 删除旧配对记录
→ 发起连接
“连接前删除旧配对记录”是当前 Demo 的选择。它会使后续连接重新经历配对流程,不能写成所有正式产品都应该采用的策略。
七、连接状态不等于配对状态
H3863 公开头文件中,连接状态定义为:
| SLE_ACB_STATE_NONE | 0x00 | 未连接 |
| SLE_ACB_STATE_CONNECTED | 0x01 | 已连接 |
| SLE_ACB_STATE_DISCONNECTED | 0x02 | 已断接 |
配对状态则是另一组枚举:
| SLE_PAIR_NONE | 0x01 | 未配对 |
| SLE_PAIR_PAIRING | 0x02 | 正在配对 |
| SLE_PAIR_PAIRED | 0x03 | 已完成配对 |
Client 的连接状态回调在进入 CONNECTED 后继续判断:
if (pair_state == SLE_PAIR_NONE) {
sle_pair_remote_device(&g_sle_uart_remote_addr);
}
所以完整逻辑是:
先建立连接
→ 再检查是否已经配对
→ 未配对时主动发起 pairing
这就是为什么日志中 [Connected] 和 pair complete 会分别出现。
Client 日志完整呈现了发现、连接和配对的先后顺序:
[adv_report] event_type:0x03, data length:6,
data:0x01 0x02 0x01 0x02 0x02 0x00
[adv_report] event_type:0x0b, data length:20,
data:0x0c 0x02 0x0a 0x10 0x0b 0x73 …
[sle uart client] sle uart scan data: … sle_uart_server
[Connected]
addr:01:**:**:**:05:06, conn_id:00
[sle uart client] conn state changed disc_reason:0x0
[sle uart client] SLE_ACB_STATE_CONNECTED
[sle uart client] pair complete conn_id:0, addr:01***0506
这里可以观察到两类报告:长度 6 的 announce data,以及长度 20 的 seek response data。名称 sle_uart_server 位于第二类数据中。Client 找到名称后才进入 [Connected],之后又单独出现 pair complete,实测顺序与源码调用链一致。
八、配对完成后怎样进入 SSAP
Client 的 pair complete 回调在 status == 0 时构造:
ssap_exchange_info_t info = {0};
info.mtu_size = 520;
info.version = 1;
ssapc_exchange_info_req(0, g_sle_uart_conn_id, &info);
信息交换成功后,Client 才开始:
ssapc_find_structure()
→ 查找 Property
→ 保存 Property Handle
→ 准备应用数据交互
因此,连接、配对、SSAP 信息交换和服务发现是连续但不同的阶段:
CONNECTED
≠ pair complete
≠ exchange info complete
≠ property discovery complete
第三篇已经详细分析 SSAP 服务模型,本篇只说明它在设备连接流程中的位置。
九、断开后 Demo 怎样恢复
1. Client 的恢复路径
Client 在连接状态变成 DISCONNECTED 时执行:
删除对端配对记录
→ sle_uart_start_scan()
→ 再次 seek
2. Server 的恢复路径
Server 的连接状态回调在断开后:
清理连接/配对状态
→ 向消息队列写入 sle_dis_connect
Server 任务读到该消息后执行:
sle_start_announce(SLE_ADV_HANDLE_DEFAULT);
因此,当前 Demo 的恢复状态机可以概括为:
连接断开
├─ Server:再次启动 announce
└─ Client:再次启动 seek
↓
重新发现 → 重新连接 → 重新配对 → 重新交换 SSAP 信息
这里的“自动恢复”来自示例应用代码的两端配合,不能笼统写成“协议栈会自动重连”。
十、实板实验:观察断开与恢复状态机
本次实验继续使用:
| 连接外置 2.4 GHz 天线的开发板 | Server | COM4 |
| 未连接外置天线的开发板 | Client | COM3 |
两端保留第三篇验证通过的固件,不需要再次修改源码。
实验过程
RECONNECT-OK-04
实测结果一:Server 检测到断开并重新 announce
本次 Client 复位后,Server 保存到以下连续日志:
[21:32:52:173] [Disconnected]
[21:32:52:173] connect state changed callback
conn_id:0x00
conn_state:0x2
pair_state:0x3
disc_reason:0x7
[21:32:52:173] sle start announce in, adv_id:1
[21:32:52:195] sle announce enable callback id:01, state:0
根据当前 SDK 头文件:
conn_state:0x2 → SLE_ACB_STATE_DISCONNECTED
pair_state:0x3 → SLE_PAIR_PAIRED
这表示回调发生时,连接状态已经变为断开,而记录的配对状态为已配对。日志中的 disc_reason:0x7 是本次实测值。继续检查同一版本 sle_connection_manager.h,公开的 sle_disc_reason_t 只列出 0x10(remote)和 0x11(local),没有给出 0x07 的映射。因此本文只记录原始值,不把它强行解释成某个公开枚举原因。
Server 在同一个时间点调用 sle_start_announce(),随后约 22 ms 后记录 announce enable 成功。这 22 ms 只是串口工具时间戳之间的观察差值,包含日志输出与调度影响,不是协议保证的恢复时延。
实测结果二:重新连接、配对与交换 SSAP 信息
Server 随后记录:
[21:32:58:116] [Connected]
[21:32:58:116] conn_state:0x1, pair_state:0x1, disc_reason:0x0
[21:32:58:133] sle announce terminal callback id:01
[21:32:58:367] pair complete conn_id:00, status:0
[21:32:58:383] ssaps_mtu_changed_cbk … mtu_size:208, status:0
这里的 conn_state:0x1 对应已连接,pair_state:0x1 对应尚未配对;大约 251 ms 后,Server 记录配对成功。随后出现 SSAP MTU 更新。
Server 源码使用 %x 打印 mtu_size,所以日志中的 208 是十六进制 0x208,即十进制 520,与 Client 日志的 mtu size:520 一致。
Client 端也完整记录了恢复后的上层准备过程:
pair complete conn_id:0
ssapc exchange info, conn_id:0, err_code:0
exchange_info_cbk … status:0
exchange mtu, mtu size:520, version:1
discovery character cbk complete in
operate ind:13, descriptors count:1, status:0, property->handle 2
到 property->handle 2 为止,可以确认恢复后的连接已经重新完成配对、SSAP 信息交换和 Property 发现,而不是只停留在链路层 [Connected]。
实测结果三:恢复后的应用数据成功到达 Server
为了确认恢复的不只是连接和服务发现,我又进行了一次独立的 Client 复位与重连,并在 Property 发现完成后从 Client 发送:
RECONNECT-OK-04
第二次实验中,Client 的关键时间线为:
[21:53:08.113] 收到 announce report
[21:53:08.132] 收到 seek response report
[21:53:08.205] Connected
[21:53:08.455] pair complete
[21:53:08.485] SSAP exchange info 成功,MTU 520
[21:53:08.501] 发现 Property Handle 2
[21:53:23.785] write response,err_code:0
同一次实验的 Server 日志为:
[21:53:02.238] Disconnected,conn_state:0x2,disc_reason:0x7
[21:53:02.258] 重新调用 sle_start_announce
[21:53:02.284] announce enable,state:0
[21:53:08.198] Connected,conn_state:0x1,pair_state:0x1
[21:53:08.440] pair complete,status:0
[21:53:08.455] MTU 更新,mtu_size:0x208
[21:53:23.770] write request,handle:2,status:0
[21:53:23.770] received data, len:15, data:RECONNECT-OK-04
RECONNECT-OK-04 共 15 个 ASCII 字节,Server 日志报告的 len:15 与内容完全对应。Client 随后收到 Handle 2 的写响应,err_code:0。至此,本次实验已经从断开、重新发现、重新连接和配对,一直验证到 SSAP Property 写入和应用数据接收。
Server 记录写请求的时间为 21:53:23.770,Client 记录写确认的时间为 21:53:23.785。两者相差 15 ms,但这是两个串口窗口的主机接收时间差,不能据此宣称协议单次写入时延为 15 ms。
十一、源码与实板日志共同支持的结论
结合固定版本源码与本次两端日志,可以确认:
仍不能确认或外推:
- disc_reason:0x7 对应的规范化原因名称;
- 串口时间差能够代表精确空口时延;
- 每次断开都一定恢复成功;
- 串口日志能够证明全部空口协议时序;
- Demo 的名称筛选和删除配对记录策略适合正式产品;
- 两端时间戳之差能够直接作为协议性能指标。
十二、我的阶段性理解
这次把两端完整日志按时间顺序排开后,我对连接流程有了三个更具体的认识。
第一,设备“被看到”、建立连接、完成配对和具备应用数据能力是不同阶段。Client 能看到 sle_uart_server,只表示发现结果中出现了目标;看到 [Connected],也还不能说明已经完成 pairing 和 SSAP 服务发现。
第二,回调驱动代码不能只看单个函数。只有把 seek result、seek disable、connect state、pair complete 和 exchange info 等回调连起来,才能理解为什么程序会从公开数据走到 Property Handle。
第三,当前 Demo 的自动恢复不是一个孤立接口。Server 在断开后重新 announce,Client 重启后重新 seek,两端状态机重新相遇,才完成后续连接和配对。本次两次重连日志不仅验证到 Property 发现,还用 RECONNECT-OK-04 完成了应用数据闭环。




