
Friendship 工作流程与空口交互概述BLE Mesh 中Friend和LPN(Low Power Node)是配对的低功耗方案,二者建立的关系称为Friendship。本文档基于 mynewt-nimble 源码(lpn.c/friend.c/transport.h)梳理完整流程与空口格式。一、前置条件:必须配网完成后才能建立 Friendship(问题 1)是的,Friend 和 LPN 都必须先完成配网,才能建立 Friendship。原因Friendship 的所有交互都走Mesh Network PDU(AD Type0x2A),属于Control 消息(CTL=1),需要:必要条件来源Unicast 地址配网时 Provisioner 在 Provisioning Data 中下发NetKey配网时 Provisioner 下发,派生 Encryption Key / Privacy KeyIV Index配网时下发,与全网同步DevKey配网时 ECDH 派生(Friendship 不直接用,但节点本身需要)没有 NetKey = 不能发 Network PDU = 不能发 Friend Req。配网前节点只能发0x2BUnprovisioned Beacon,根本无法参与 Friendship。关键区分:Friendship ≠ BLE 链路层 Pairing概念层级是否需要配网BLE Pairing链路层(L2CAP/SMP)与 Mesh 无关,Mesh 不用Mesh ProvisioningMesh 配网协议「加入网络」Mesh FriendshipMesh 网络层 Control 消息必须在配网之后顺序:设备出厂 → 配网(获得 NetKey + Unicast)→建立 Friendship(LPN 找 Friend)→ 稳态运行。同网约束LPN 和 Friend必须在同一 Mesh 网络(共享 NetKey),因为:Friend Req 用 LPN 持有的 NetKey 加密Friend 必须能用同一 NetKey 解密 Friend Req多子网时 LPN 会逐个尝试自己加入的子网(源码bt_mesh_subnet_next(NULL))二、Friendship 建立流程(4 步握手)重要:4 步缺一不可Friendship 建立必须经过 4 步:Req → Offer → Poll → Update。只看 Req/Offer 不够——第一次 Poll/Update 和稳态 Poll/Update空口格式相同但语义完全不同。步骤Friend 侧状态语义① Req创建 LPN 上下文 + 派生凭证,established=0协商请求:LPN「我想找 Friend」② Offerestablished=0协商响应:Friend「我可以服务你」③第一个 Pollestablished=1← 关键!建立触发:LPN「我确认选你」+ Friend「正式接受」④第一个 Update回带 Flags + IV Index建立确认 + 网络状态同步后续 Poll/Updateestablished保持 1稳态查询:问「有消息吗」答「有/没有」源码证据:Friend 何时标记"Friendship 已建立"/* friend.c - bt_mesh_friend_poll() 内 */if(!frnd-established){BT_DBG("Friendship established with 0x%04x",frnd-lpn);frnd-established=1;/* ← 第一个 Poll 才置 1 */}Friend 收到第一个 Poll 时才把established标记为 1!在此之前虽然创建了上下文、派生了凭证,但不会为 LPN 缓存消息,很多操作不执行(多处if (!frnd-established)检查)。为什么 Req/Offer 不够?3 个关键原因1. 双向链路确认(最关键)① Req: LPN → 0xC001(广播) ← LPN 能发,但 Friend 不知道 LPN 是否存在 ② Offer: Friend → LPN(单播) ← Friend 能发给 LPN,但不知道 LPN 收到没 ③ Poll: LPN → Friend(单播) ← LPN 收到 Offer 后回 Poll,Friend 才确认双向可达如果只有 Req/Offer:Friend 发完 Offer 不知道 LPN 收到没有。Poll 是 LPN 对 Offer 的「确认收到」。2. Friend 需要第一个 Poll 才正式生效源码多处检查established,未建立时:不会为 LPN 缓存消息收到发给 LPN 的消息会丢弃不执行 Friend Subscription 更新只有第一个 Poll 触发established=1后,Friend 才真正开始为 LPN 服务。3. 第一个 Update 同步网络状态LPN 长时间睡眠,可能错过全网的 IV Update / Key Refresh。第一个 Update带回当前网络状态:structbt_mesh_ctl_friend_update{uint8_tflags;// KR + IVU 状态uint32_tiv_index;// 当前 IV Indexuint8_tmd;// More Data};相当于「你睡觉期间网络变了这些,现在同步一下」。后续稳态 Update 也带这些字段,但第一个 Update 的同步意义最大。完整交互时序(区分建立 vs 稳态)═══════════════════════════════════════════════════════════ 阶段 A:Friendship 建立(4 步握手,一次性) ═══════════════════════════════════════════════════════════ LPN Friend │ │ │ ① Friend Req (DST=0xC001) │ established=0 │ [LPNCounter, RSSI 要求, 超时...] │ 创建 LPN 上下文 + 派生凭证 │ ────────────────────────────────── │ │ │ │ ② Friend Offer │ established=0 │ [FriendCounter, QueueSize, RSSI] │ 回报能力 │ ────────────────────────────────── │ │ │ │ LPN 本地派生凭证 │ │ │ │ ③ 第一个 Poll (FSN=0) │ ★ established=1 ★ │ 「我确认选你」 │ 「我正式接受你」 │ ────────────────────────────────── │ │ │ │ ④ 第一个 Update │ 确认建立 + 同步网络状态 │ [Flags, IVIndex, MD=0] │ (LPN 可能错过 IV Update) │ ────────────────────────────────── │ │ │ │ Friendship 正式建立 ✅ │ ═══════════════════════════════════════════════════════════ 阶段 B:稳态运行(Poll/Update 循环,周期性) ═══════════════════════════════════════════════════════════ │ Poll (FSN=1) │ established=1 │ 「有消息吗」 │ 查缓存队列 │ ────────────────────────────────── │ │ │ │ Update (MD=0) │ 「没有,继续睡」 │ ────────────────────────────────── │ │ │ │ 睡眠 N 秒 │ 持续扫描 + 缓存类比理解Mesh Friendship现实类比① Req投简历(广播求职)② Offer公司发 offer(意向)③ 第一个 Poll你回 offer 签字(确认接受)④ 第一个 Update公司发入职通知 + 同步公司现状后续 Poll/Update每天打卡 + 问「有我邮件吗」只投简历 + 收 offer 不算入职——必须你签字(第一个 Poll)+ 公司确认(第一个 Update)才算正式建立雇佣关系。之后才是日常打卡。状态机(LPN 侧,源码lpn.c)BT_MESH_LPN_DISABLED │ bt_mesh_lpn_set(true) ▼ BT_MESH_LPN_ENABLED │ send_friend_req() ▼ BT_MESH_LPN_WAIT_OFFER ── 收到 Friend Offer ── 派生凭证 + send_friend_poll() │ │ │ 超时无 Offer ▼ ▼ BT_MESH_LPN_WAIT_UPDATE BT_MESH_LPN_REQ_RETRY ── 重发 Friend Req │ │ 收到 Friend Update ▼ BT_MESH_LPN_ESTABLISHED (稳态)三、各 Control 消息空口格式Friendship 消息都是CTL=1(Control 消息),走 Network PDU,NetMIC=8B(注意比 Access 消息的 4B 长)。通用封装AD Structure: [Length][0x2A] └─ Network PDU (CTL=1) ├─ IVI/NID 1B ├─ CTL=1/TTL 1B ← CTL 固定 1 ├─ SEQ 3B ├─ SRC 2B ← 发送方 Unicast ├─ DST 2B ← Friend Req 用 0xC001;后续用对方 Unicast ├─ Transport PDU 变长 ← Control Opcode + 载荷 └─ NetMIC 8B ← Control 消息固定 8BTransport 层 Control 头部┌─────────────────────────────────┐ │ Seg(1b) | Opcode(7b) │ 1 字节 └─────────────────────────────────┘ - Seg = 1 表示分段,Friendship 消息一般 Seg=0(短消息) - Opcode 见下方各消息① Friend Req(Opcode0x03)LPN 广播寻找 Friend,DST =0xC001(all-friends 固定组地址)。源码结构体(transport.h):structbt_mesh_ctl_friend_req{uint8_tcriteria;// 选择 Friend 的 criteria(RSSI/队列