
1. Provision到底是什么从配对到入网一次说清这是蓝牙学习系列的第三篇。前两篇我分别聊了蓝牙协议栈的基本分层以及一条经典蓝牙连接从寻呼到建链的完整状态机算是把“链路怎么通”这件事讲透了。这一篇要聊的是Provision。很多刚入行的朋友一听到Provision就头大因为这个词在不同语境下含义差别很大——经典蓝牙里有配对BLE里有配对绑定Mesh里有节点入网国产IoT领域还有各种厂商所谓的“配网”。如果不先把概念边界划清楚后面读任何代码都会越看越乱。我最早接触Provision这个词其实是做企业级设备部署的时候当时指的是给一批设备写配置、灌证书、下发固件的过程。后来切入蓝牙Mesh开发才发现这个词在蓝牙标准里被专门用来描述“未配网设备被Provisioner授权加入Mesh网络”的仪式。可以说Provision是蓝牙里“从裸设备变成可用设备”的那一步它回答的核心问题是两个互不认识的设备凭什么相互信任又凭什么一起协作。具体看蓝牙生态里至少有三类容易被混淆的Provision场景。第一类是BR/EDR经典蓝牙的配对绑定手机和蓝牙耳机第一次连接时输入PIN码或者走Numeric Comparison最终产生一个Link Key后续重连靠它认证。第二类是BLE传统配对也就是LE Legacy Pairing或者LE Secure Connections生成的是LTK、IRK这一组密钥。第三类是BLE Mesh的Provisioning由Provisioner给Unprovisioned Device分配NetKey、单播地址、Flags和IV Index设备从此成为一个完整意义上的Mesh节点。这三类场景经常被大家混着叫甚至在同一个产品里同时出现但底层机制完全不同。拿一张表来对比会更直观类型信任基础输出结果典型场景经典蓝牙配对PIN / Just Works / Numeric ComparisonLink Key蓝牙耳机、车载免提BLE传统配对Passkey / Just Works / OOBLTK、IRK等手环、Beacon、BLE键盘BLE Mesh ProvisioningECDH P-256 OOB认证NetKey、单播地址、AppKey智能照明、传感器网络厂商私有配网厂商云端机制网络凭据智能家电App配网这里我想多说一句为什么Mesh没有沿用BLE传统配对的思路非要另起炉灶搞一套Provisioning。因为传统配对是“一对一”的关系两台设备之间协商一把会话密钥如果网络里有100台设备理论上最坏情况要有4950对密钥关系。Mesh网络面向的是成百上千的设备节点再搞两两配对无论是存储还是维护都扛不住。所以Mesh换了一种思路由唯一的Provisioner统一负责把这些“新生儿”接入网络入网后所有节点共享一把Network Key节点之间再通过AppKey做应用层访问控制。这样一来设备入网的仪式只需要发生一次之后整个网络的密钥体系建立在一个中心化的准入节点上。这个设计代价是Provisioner成为单点但它换来了大规模组网的可行性。还有一个容易被忽略的点蓝牙6.0引入Channel Sounding之后距离感知能力成为安全配网的新变量。设备可以只在物理距离非常近的范围内完成Provision这能显著降低中间人攻击的风险。虽然目前市场上支持蓝牙6.0的芯片还不算多但如果你现在做新产品选型尤其是准备做Mesh网关或者高安全等级的IoT设备建议把支持Channel Sounding这件事列入评估清单。配网安全这一块迟早要从“密码猜一猜”进化到“距离证明一下”。2. BLE Mesh Provisioning协议逐阶段拆解理解了Provision的定位接下来就得扎进协议细节里看它到底怎么跑。Mesh Provisioning整个流程在蓝牙Mesh Profile规范里被划分成五个阶段Beaconing、Invitation、Exchange Public Keys、Authentication、Provisioning Data Distribution。名字看着多其实核心就三件事互相确认身份、协商一把临时会话密钥、把网络凭据安全地交给新设备。先说承载层这是新手最容易懵的地方。Provisioning的报文可以走两种通道PB-ADV和PB-GATT。PB-ADV是直接把Provisioning PDU塞在广播报文里传来传去设备不需要建立连接适合设备量不大、周围环境干扰可控的场合格。PB-GATT则是让未配网设备临时提供一个GATT服务手机这类Provisioner通过连接这个服务来交互适合大多数手持设备的配网红外。实际产品里两种承载往往同时开启因为手机App可能只支持PB-GATT而网关类Provisioner可能更习惯PB-ADV。我在测试的时候习惯把两种都打开免得排查的时候还要躲一边骂“明明开了广播为什么扫不到”。五个阶段里Beaconing最简单也最容易被忽略。未配网设备会周期性广播一个Unprovisioned Device Beacon里面带上自己的Device UUID和OOB信息。值得注意的是一旦设备完成Provision这个广播就永久停了——这是正常行为。很多人拿了一个已经被配过网的开发板改程序发现手机死活扫不到设备第一反应是改广播间隔而不是去想设备是不是早就不是“未配网”状态。这个坑我在后面排查章节还会专门展开。Invitation阶段是Provisioner先发一个Invite消息给设备设备回Capabilities告诉Provisioner自己支持哪些算法、有哪些OOB能力、最大接收PDU长度是多少。从这里开始后续所有消息的报文格式都是有讲究的。比如Capabilities消息里会带Attention Timer Duration字段这个字段用来让设备闪烁LED提醒用户“我在这儿”实用价值很大尤其是排查多设备重叠环境的时候。Exchange Public Keys阶段是安全握手的关键。双方交换ECDH P-256公钥协商出DHKey。注意这个阶段的消息如果走PB-ADV承载单条广播消息塞不下64字节的公钥必须做分片与重组。很多开发板在这个阶段失败不是算法问题而是分片重组超时。广播环境一旦拥挤丢一两个分片整个Provision会话就废了。Authentication阶段是决定安全等级的分水岭。标准定义了四种方式No OOB、Static OOB、Output OOB和Input OOB。字面上看No OOB最省事但安全等级也最低适合开发调试Static OOB是双方预置同一把密钥适合生产环境批量预烧录Output OOB是设备上显示一个数字用户在Provisioner上输入Input OOB反过来用户在设备端输入Provisioner发来的数字。我用Output OOB举过最直观的例子——智能灯泡入网时灯泡以固定频率闪烁N次用户在App里输入闪的次数双方确认“你看到的就是我看到的”。这个设计本质上是在对抗中间人攻击即使攻击者能截获所有无线报文他也无法凭空猜到闪烁次数。最后的Provisioning Data Distribution阶段Provisioner把NetKey、Key Index、Flags、IV Index、单播地址这些关键信息用会话密钥加密后发给设备。设备收到后回一个Provisioning Complete整个入网仪式就算结束了。这里有个细节会话密钥是用DHKey配合双方随机数通过K1函数派生出来的K1底层是AES-CMAC。所以这整条链路里真正保护网络凭据的是ECDH协商结果加AES-CMAC派生出来的会话密钥而不是直接用NetKey裸传。蓝牙规范在这一点上设计得相当严谨。我把整个流程的时间敏感要求列一下方便大家做状态机的时候有个参照Invitation发出后如果一段时间内没收到CapabilitiesProvisioner要重发或者超时退出Public Key交换过程中同样有类似的重传机制。实际开发时我不建议自己去从头实现这套状态机——无论是ESP-IDF、Zephyr还是Nordic的nRF5 SDK都已经把Provisioning协议栈封装好了。你要做的其实是把配置填对把回调事件处理明白。3. 实战记录ESP32未配网节点 nRF Mesh手机端入网全流程理论聊完必须落实到工程上才有说服力。我这边的常用实验组合是ESP32开发板做Unprovisioned Device手机装nRF Mesh App当Provisioner把一盏模拟灯的OnOff Server模型跑起来完成入网。这套组合成本低、资料多、踩坑经验也最容易复现非常适合作为Provision第一个实战项目。硬件上我用的就是普通的ESP32 DevKitC。真正要注意的不是开发板本身而是周围环境。2.4GHz频段在家用环境里非常拥挤微波炉、USB 3.0接口、邻居家的Wi-Fi都会干扰广播。我第一次在办公室做实验时设备离手机不到一米Provision还是经常中断气到我直接把USB Hub拔了立刻好转。所以如果你的Provision过程莫名失败先排查物理环境别急着怀疑代码。设备端代码我以ESP-IDF里的ble_mesh_onoff_server为底子。重点要理解的是esp_ble_mesh_prov_t这个配置结构体它里面定义了设备UUID、OOB方式、认证值、是否支持PB-ADV/PB-GATT等关键字段。我的习惯是一个设备接一颗按键短按之后才进入Unprovisioned状态并开启广播而不是上电就一直在那广播。原因很实际批量部署或者低功耗场景下设备不该长期暴露在可配网状态这既费电也不安全。按键触发配网模式虽然多一步操作但整个产品的生命周期管理会清晰很多。Provisioner端我用nRF Mesh App操作路径是扫描设备、点击连接、选择认证方式、输入或确认认证值。如果你在设备端配置了Output OOBApp界面会跳出一个数字输入框要求你把开发板OLED或者串口打印出来的数字输进去。这里有一个特别容易翻车的点认证书上显示的位数和App要求的位数必须严格一致。例如设备端那边用6位数字做认证值App那边默认可能也是6位但某些版本会把前导零去掉或者要求固定4位两边对不上就是认证失败。遇到这种情况别慌去确认两边的配置参数是不是同一个位数定义。跑通流程之后一定要养成看日志的习惯。设备端完整流程会在串口里依次出现Provisioning Beacon发出、收到Invite、Capabilities发出、公钥交换完成、认证值匹配成功、Provisioning Data接收完成、最终触发一个类似PROV_COMPLETE的事件。这个事件回调里会带上Provisioner分配的单播地址我用过的所有协议栈都会在这里打印出来。看到这个地址就说明Provision在协议层面已经成功。但请注意“协议层成功”和“业务层可控”是两码事。很多朋友做到这一步就以为万事大吉结果发现手机App往设备发模型消息设备毫无反应。原因就是模型还没有绑定AppKey也没有配置订阅地址。Provision只是发了一把Network Key把你拉进网络就好比给你办了张门禁卡但具体能进哪个房间、能开哪扇门得靠Provisioner后续通过配置模型Configuration Client Model下发AppKey和订阅关系。这个后续动作我在第五部分单独讲。我测过京东上二三十块的HC05模块很多初学者的理解误区就在这里。HC05是经典蓝牙SPP模块走的是RFCOMM串口透传根本没有BLE Mesh能力自然也无从谈起Provision。如果你拿HC05做Mesh实验那不是配置问题而是选型问题。别在错误的硬件上浪费调试时间。4. 入网失败排查链路从Beacon消失到消息不通这一部分我打算按真实排查的顺序来写因为我踩过的每一个坑基本都能对应到一个具体症状。你以后遇到入网失败直接对着症状来查效率会高很多。第一个典型症状手机扫不到未配网设备。先别急着看代码按我的顺序做三件事。第一确认设备当前确实是Unprovisioned状态。开发板擦掉整片Flash之后重新烧录才能从“已配网”恢复到“未配网”如果模块里已经存了NetKey它是不会发Unprovisioned Beacon的。第二确认PB-ADV和PB-GATT至少有一个开着因为某些模块厂商在SDK里默认只开了其中一种而手机App恰好只支持另一种。你可以在App里看到“不支持”“No Provisioned Device”之类的提示来反推。第三确认广播确实在物理层面发出来了用nRF Connect这类工具抓一下空中的广播包。如果抓不到再检查天线、距离、附近是不是有大功率设备在干扰。我甚至遇到过一次开发板天线虚焊距离一超过30厘米就完全不可见的情况那种问题能让你排查到怀疑人生。第二个典型症状Provision过程中卡在Invitation或Public Key交换。这类问题十有八九出在分片与重组。PB-ADV的信道容量有限一条完整的公钥消息要拆成好几个广播包分片发送设备侧要收齐全部碎片才能重组。广播环境一拥挤或者设备主频太低处理不过来就会超时。我这边最有效的两个优化手段一是尽量改用PB-GATT连接通道有重传机制抗干扰能力强很多二是提高广播间隔减少碰撞概率。如果你是拿ESP32做实验还有一个隐藏雷区——某些SDK版本在配网期间默认把Wi-Fi也开着Wi-Fi和蓝牙共用天线会互相挤占射频资源。把Wi-Fi关掉再试Provision成功率能提升一大截。第三个典型症状认证阶段失败。这是最直观也最好定位的一类问题。设备端配的是Output OOB你却在App里选了No OOB双方在验证环节必然失败两边认证值位数不一致也必然失败用户输入超时同样失败。我建议在做日志记录的时候把设备端上报的认证方法和认证值一并打出来与App配置的做交叉核对。很多国产SDK在这里会有一些“优化”例如某些SDK只实现了Static OOB而把OOB方式名称改了表面看文档写的是支持Output OOB实际代码里路径根本不通。遇到这种情况直接上协议分析仪抓包看Confirmation和Random两个消息的交换过程是标准做法。没有协议分析仪的话可以在设备端回调里加打印看到哪一步事件没触发问题就锁定在哪一步。第四个典型症状更隐蔽Provision已经成功单播地址也分配了但模型消息一发就石沉大海。这个问题严格来说已经不属于Provision本身了但它恰恰是Provision“完成”之后的下一步所以我在排查链路里一起写出来。根源基本都是AppKey没有绑定到模型上。Mesh的访问控制是分层的NetKey管网络层AppKey管应用层。设备入网拿到的是NetKey可以收发全网广播但你要操作具体的OnOff Server模型必须由Provisioner通过AppKey Add和Model App Bind两条配置消息把AppKey绑上去。如果漏了这一步设备在网络里“活着”但你的控制指令它听不到。就像你进了公司大楼但门禁还没给你开通某个具体办公室的权限站在门口卡住。这类问题用ESP32的日志排查也很方便。正常绑定后设备端模型消息的处理回调会触发ONOFF_SET事件没绑定AppKey时消息根本到不了应用层回调自然不触发。我处理这种问题的方法比较土但很管用先在设备端把received appkey的状态打印出来再在App端重新发一次Bind观察打印值的变化。5. 配网之后还要做什么模型配置与多形态蓝牙产品的衔接Provision成功只是开始真正让Mesh网络“好用”的是入网之后那一套配置流程。前面提到的AppKey绑定只是配置模型Configuration Model能力的一小部分。完整流程还包括读取设备的Composition Data搞清楚它有哪几个Element、每个Element底下挂了哪些模型、模型对SIG标准还是厂商Vendor的然后按产品需求分配单播地址、组播地址设置订阅关系。抽象点说Provision解决的是“你是我的人”配置模型解决的是“你负责干什么活”。实际产品里最常见的配置错误发生在组播地址这一块。很多人把设备配好后App发单播消息能控制但一用群组控制就失灵。原因往往是Provisioner下发了AppKey却没给模型配置Subscribe Address或者配置了订阅0xFFFF这个全节点地址但设备端SDK在编译时没有开启对应的组播处理宏。还有一个容易被忽略的限制一个模型可以订阅多个地址但每个模型的Publication地址只有一个。你想让同一个灯同时响应“客厅灯”和“全屋灯”两个组注意不是靠Publication而是靠Subscribe两者概念完全不一样。我在MQTT里转过来的时候花了不少时间才转过这个弯。另外如果产品包含低功耗节点Provision之后还要处理Friend机制。低功耗节点LPN为了省电不会一直监听信道它需要和一个Friend节点建立友谊关系让Friend帮它缓存消息。这个关系建立属于Mesh配置的一部分用的是Config Friend Set命令。如果你开发的是电池供电的传感器节点入网只是第一步能不能把Friend关系稳定建立起来、缓存容量够不够才是决定产品体验的关键。这个方向坑很深我打算在后面专门写一篇这里先给个提醒不要在低功耗节点上直接发组播做控制否则你会被丢包率逼疯。再说说和Mesh没直接关系但经常一起出现的产品形态。现在很多智能设备是多模的既支持BLE Mesh又保留经典蓝牙音频或BLE HID功能。比如一个智能音箱平时用Wi-Fi联网本地调试用BLE Mesh节点耳机连接又走经典蓝牙A2DP电话场景还要切SCO模式。A2DP和SCO切换这个热搜词我印象很深它和Provision完全没有关系但如果你做的设备是“先配网再语音”那么配网完成后蓝牙音频链路的状态切换会成为新的噩梦。A2DP和SCO走的是不同的物理链路参数切换时基带要重新协商处理不好就是“一放歌就断开”。把这部分内容放到Provision之后提是想提醒大家配好网不代表产品没问题蓝牙产品的联调永远是多协议栈协同的问题。芯片选型方面我这两年接触过杰理、沁恒这些国产方案。老实说在经典蓝牙音频和低成本BLE场景里国产芯片的性价比确实很高比如沁恒CH579在低功耗蓝牙这块的TMOS调度就有自己的特色。但如果你想做完整的BLE Mesh节点尤其是需要跑Mesh Provisioner角色国产方案目前支持度参差不齐。有些芯片只支持PB-GATT有些压根没实现Provisioning状态机只做了BLE从机透传。所以选型之前一定先去芯片厂商的SDK里搜有没有mesh相关demo。没有的话大概率意味着你得从协议栈底层自己啃成本会直线上升。如果项目时间紧我的建议是Mesh优先选北欧nRF5、乐鑫ESP32这类被验证过的方案等产品跑通了再评估国产替代。最后分享一个我自己调试Provision时养成的习惯每次做配网实验之前先把设备的Flash区域、事件回调、空中抓包三个层面的状态都记录一遍。Flash干净与否决定设备是否处于未配网状态事件回调决定协议栈跑到哪一步抓包决定无线链路上实际传了什么。这三个维度只要对上了绝大多数Provision问题都能在十分钟内定位。别上来就改代码先确认状态再动逻辑。这是我做蓝牙开发几年下来性价比最高的排错方式。