<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="zh-CN">
	<id>http://doc.iotxx.com/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Jinx</id>
	<title>谷雨文档中心 - 用户贡献 [zh-cn]</title>
	<link rel="self" type="application/atom+xml" href="http://doc.iotxx.com/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Jinx"/>
	<link rel="alternate" type="text/html" href="http://doc.iotxx.com/%E7%89%B9%E6%AE%8A:%E7%94%A8%E6%88%B7%E8%B4%A1%E7%8C%AE/Jinx"/>
	<updated>2026-05-25T05:41:32Z</updated>
	<subtitle>用户贡献</subtitle>
	<generator>MediaWiki 1.31.1</generator>
	<entry>
		<id>http://doc.iotxx.com/index.php?title=NRF52832DK-Mesh%E7%BB%84%E7%BD%91%E5%AE%9E%E9%AA%8C&amp;diff=2829</id>
		<title>NRF52832DK-Mesh组网实验</title>
		<link rel="alternate" type="text/html" href="http://doc.iotxx.com/index.php?title=NRF52832DK-Mesh%E7%BB%84%E7%BD%91%E5%AE%9E%E9%AA%8C&amp;diff=2829"/>
		<updated>2020-04-02T09:04:37Z</updated>

		<summary type="html">&lt;p&gt;Jinx：/* 软件配置 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;蓝牙Mesh和BLE是并行发展的一个独立分支，虽然底层共用Physical Layer和Link Layer，但是上层协议并不相同。因此，nRF52832的Mesh例程，并不在BLE协议栈sdk中，而是安装一个扩展包。&lt;br /&gt;
&lt;br /&gt;
本文介绍蓝牙Mesh相关的环境搭建和实验介绍。&lt;br /&gt;
&lt;br /&gt;
== Mesh开发环境 ==&lt;br /&gt;
&lt;br /&gt;
=== 蓝牙Mesh SDK ===&lt;br /&gt;
SDK位置：&amp;lt;code&amp;gt;归档资料/2-协议栈SDK/nRF5_SDK_Mesh_v310.zip&amp;lt;/code&amp;gt;，注意文件名中的Mesh字样。&lt;br /&gt;
&lt;br /&gt;
和BLE协议栈一样，将该压缩包直接解压到BLE协议栈同级目录即可使用，解压后的SDK路径如下：&amp;lt;code&amp;gt;E:\project-nordic\nRF5_SDK_Mesh_v310&amp;lt;/code&amp;gt;&lt;br /&gt;
[[文件:NRF52832DK-MESH-SDK-INSTALL.gif|居中|无框|750x750像素]]&lt;br /&gt;
&lt;br /&gt;
=== 集成开发软件SES ===&lt;br /&gt;
SES时是Segger Embedded Studio编译器的缩写，用来编译蓝牙Mesh代码，SES功能与IAR或Keil类似。&lt;br /&gt;
&lt;br /&gt;
备注：Nordic为何使用SES而不是IAR，原因不得而知。&lt;br /&gt;
&lt;br /&gt;
==== SES安装 ====&lt;br /&gt;
SES软件位置：&amp;lt;code&amp;gt;归档资料/7-编译器SES/Setup_EmbeddedStudio_ARM_v410_win_x64.exe&amp;lt;/code&amp;gt;。&lt;br /&gt;
&lt;br /&gt;
我们双击打开安装界面，按照如下动图所示方式进行安装（可以全部默认安装）。[[文件:Ses install.gif|边框|居中|无框|556x556像素]]&lt;br /&gt;
&lt;br /&gt;
==== SES许可证激活 ====&lt;br /&gt;
我们双击打开安装好的SES，选择编译自带测试例程，此时弹出需要激活的界面。&lt;br /&gt;
[[文件:Licensed1.jpg|边框|居中|无框|1205x1205像素]]&lt;br /&gt;
由于我们准备使用SES编译nordic的NRF52832的SDK，所以这里我们选择Activate Your Free License。这里因为nordic公司已经帮客户想SEGGER公司买下了使用的版权，所以是免费的许可证。&lt;br /&gt;
[[文件:Licensed2.jpg|边框|居中|无框|736x736像素]]&lt;br /&gt;
我们按照要求填写好信息，主要是邮箱地址（用于接收SEGGER公司发给我们的许可证），填写完成之后点击Request License。下面是我们接收的邮件信息，我们将红色框内的内容复制下来。&lt;br /&gt;
[[文件:Ses install3.png|边框|居中|无框|1301x1301像素]]&lt;br /&gt;
打开SES，选择Tools\License Manager...，我们选择Activate Embedded Studio，打开如下界面，并将我们的许可证粘贴进去，然后点击Install License。&lt;br /&gt;
[[文件:Licensed4.png|边框|居中|无框|481x481像素]]&lt;br /&gt;
安装好许可证之后，跳转到如下的界面。&lt;br /&gt;
[[文件:Licensed5.png|边框|居中|无框|481x481像素]]&lt;br /&gt;
此时我们的许可证激活完毕（有时候需要等待一段时间，才会显示激活成功）。&lt;br /&gt;
[[文件:Licensed6.png|边框|居中|无框|515x515像素]]&lt;br /&gt;
&lt;br /&gt;
== mesh实验学习流程概论 ==&lt;br /&gt;
1、首先大家一定要先查看一下我们资料中的手册“BLE-Mesh技术揭秘”，这个是很重要的一个mesh基本概念的介绍，只有看完这个文档，我们才能明白mesh组网是什么，是怎么完成的，后续的我们实验内容，也会在这个文档中查到相应的对照。&lt;br /&gt;
&lt;br /&gt;
2、按照Light_switch测试实验的实验说明，跑通测试流程（可以固件清零重新烧写多测试几遍，有助于增强我们的流程熟练度，利于后面的开发）。light_switch实验是现在nordic提供给我们的唯一的真正意义上的mesh组网通信例程。&lt;br /&gt;
&lt;br /&gt;
3、根据我们下面拆分的组网流程，对照依次的实验说明，进行mesh组网流程的分步学习。前期实验部分我们主要讲解节点设备，配置者设备功能我们使用手机app完成，目的是减少大家的学习难度，配置者设备我们会放在最后一个实验给大家做介绍。&lt;br /&gt;
&lt;br /&gt;
①广播（通信TX）——了解节点设备的广播功能，分为三个重要部分：PB-ADV和Unprov Beacon，PB-GATT的广播，以及模型的Publish用于发送数据。&lt;br /&gt;
&lt;br /&gt;
②扫描（通信RX）——了解节点设备的扫描功能，分为两个重要部分：普通的BLE SCAN功能，以及模块的Subscription用于接收数据。&lt;br /&gt;
&lt;br /&gt;
③入网验证——了解节点设备接入网络的验证过程，实验中使用的是PB-GATT去配置的，包含了：交换公共密钥，完成验证（是否有带外OOB）。&lt;br /&gt;
&lt;br /&gt;
④设备信息分配——启动配置数据分配，给节点分配如下数据：网络key（Netkey）、应用key（Appkey）以及节点设备地址（nodeAddr）。&lt;br /&gt;
&lt;br /&gt;
⑤元素——了解节点设备的元素与模型定义。&lt;br /&gt;
&lt;br /&gt;
⑥模型——了解模型的功能和创建方法，模型的订阅subscription和发布publish功能。&lt;br /&gt;
&lt;br /&gt;
== Light_switch测试实验 ==&lt;br /&gt;
=== 实验简介 ===&lt;br /&gt;
此示例演示了包含充当两个角色的设备的网状生态系统：配置角色（Provisioner role）和节点角色（Node role、provisionee role）。 它还通过在应用程序中使用[Generic OnOff model]来演示了如何使用Mesh模型。&lt;br /&gt;
&lt;br /&gt;
该示例由三个较小的示例组成：&lt;br /&gt;
&lt;br /&gt;
Light switch server：一个实现了[Generic OnOff server model]的简约服务器，用于接收状态数据并控制板上LED 1的状态。&lt;br /&gt;
&lt;br /&gt;
Light switch client：一个实现了[Generic OnOff client model]的简约客户端。当用户按下任意按钮时，OnOff Set消息将发送到配置的目标地址。&lt;br /&gt;
&lt;br /&gt;
Mesh Provisioner：一个简单的静态配置设备应用，用于建立演示网络，该配置设备在一个网状网络中配置所有节点。 此外，配置设备还可以在这些节点上配置网格模型[Generic OnOff model]实例的绑定以及发布和订阅设置，以使它们能够相互通信。&lt;br /&gt;
&lt;br /&gt;
[Generic OnOff Client/Server]模型用于操纵打开/关闭状态。请注意，当服务器设置了发布地址（如本例所示）时，服务器会将其状态更改的任何操作披露到其发布地址。&lt;br /&gt;
&lt;br /&gt;
下图给出了将由静态供应商设置的网状网络的整体视图， 括号中的数字表示预配置程序分配给这些节点的地址。&lt;br /&gt;
[[文件:Mesh network.png|边框|居中|无框|727x727像素]]&lt;br /&gt;
灯开关服务器和灯开关客户端示例均具有预配方角色。它们支持通过广告承载（PB-ADV）和GATT承载（PB-GATT）进行配置，并且还支持网状代理服务器（Proxy Service），但是不支持代理客户端（Proxy Client）。&lt;br /&gt;
&lt;br /&gt;
=== 硬件说明 ===&lt;br /&gt;
完成这个实验，我们最少需要两个开发板硬件用做我们的节点设备（Node）：&lt;br /&gt;
* 一个开发板用做client&lt;br /&gt;
* 一个或者多个开发板用做server&lt;br /&gt;
此外，我们还需要以下之一作为我们的配置设备（Provisioner）：&lt;br /&gt;
* 如果您决定使用静态预配器示例，则多准备一个开发板&lt;br /&gt;
* 如果您决定使用app程序进行设置，则需要安装手机app@link_nrf_mesh_app（@link_nrf_mesh_app_ios或@link_nrf_mesh_app_android）。&lt;br /&gt;
{{Note|text=默认我们使用3个开发板完成我们的实验，3个开发板分别对应client、server以及provisioner三个类型。|type=warning}}&lt;br /&gt;
&lt;br /&gt;
=== 软件说明 ===&lt;br /&gt;
1、我们使用NrfGo上位机，依次对三个开发板进行擦除及softdevice的烧写&lt;br /&gt;
[[文件:Lightswitch nrfgo.png|边框|居中|无框|1013x1013像素]]&lt;br /&gt;
2、使用SEGGER Embedded Studio for ARM编译器分别打开我们`&amp;lt;InstallFolder&amp;gt;/examples/light_switch`路径下的3个实验，并完成编译&lt;br /&gt;
&lt;br /&gt;
●\client\light_switch_client_nrf52832_xxAA_s132_6_1_0.emProject&lt;br /&gt;
&lt;br /&gt;
●\server\light_switch_server_nrf52832_xxAA_s132_6_1_0.emProject&lt;br /&gt;
&lt;br /&gt;
●\provisioner\light_switch_provisioner_nrf52832_xxAA_s132_6_1_0.emProject&lt;br /&gt;
[[文件:Lightswitch SES build.png|边框|居中|无框|1013x1013像素]]&lt;br /&gt;
3、此时我们对3个开发板分别进行固件的烧写，分别烧写client、server、provisioner（建议大家给3个开发板贴上标签，方便后续区分）&lt;br /&gt;
[[文件:Lightswitch SES download.png.gif|边框|居中|无框|1009x1009像素]]&lt;br /&gt;
&lt;br /&gt;
=== 实验现象 ===&lt;br /&gt;
{{Note|text=注意：保证我们的3个设备都是刚按照软件说明部分的步骤配置完成，并且没有进行过任何硬件控制（按键操作）。目的是保证所有设备均未被我们人为配置过，没有进行过网络配置，否则可能有任意异常（不同网络配置，导致的不同现象）|type=danger}}&lt;br /&gt;
1、首先我们分别给client和provisioner供电，然后我们按下provisioner的按键S1：&lt;br /&gt;
&lt;br /&gt;
●provisoner：LED灯点亮，代表正在配置查找设备，配置组网&lt;br /&gt;
&lt;br /&gt;
●client：首先LED3和LED4闪烁，代表正在组网；LED1-LED4四个灯一起闪烁，代表组网完成&lt;br /&gt;
&lt;br /&gt;
我们如果打开RTT检测log打印，可以看到如下的信息，这个时候我们已经给client设备分配了Node Address为0x0100。provisioner信息（上），client信息（下）。&lt;br /&gt;
[[文件:Lightswitch rtt provisoner1.png|边框|无框|1080x1080像素|居中]]&lt;br /&gt;
[[文件:Lightswitch_rtt_node_client.png|边框|居中|无框|656x656像素]]&lt;br /&gt;
&lt;br /&gt;
2、使用provisioner配置好client之后，我们给server开发板上电，这个时候provisioner将会配置server入网：&lt;br /&gt;
&lt;br /&gt;
●provisoner：LED1点亮代表正在配置网络，LED2点亮代表网络配置完成&lt;br /&gt;
&lt;br /&gt;
●server：首先LED3和LED4闪烁，代表正在组网；LED1-LED4四个灯一起闪烁，代表组网完成&lt;br /&gt;
&lt;br /&gt;
我们打开RTT检测log打印，可以看到server已经配置入网，并且被分配了地址Node Address为0x0104。provisioner信息（上），server信息（下）。&lt;br /&gt;
[[文件:Lightswitch rtt provisoner2.png|边框|无框|710x710像素|居中]]&lt;br /&gt;
[[文件:Lightswitch rtt node server.png|边框|无框|656x656像素|居中]]&lt;br /&gt;
&lt;br /&gt;
3、此时整个网络的配置已经完成，这个时候我们可以按下client上的按键来控制server上的LED点亮或者熄灭。&lt;br /&gt;
&lt;br /&gt;
由于我们的server被provisioner分配的Node Address是0x0104（偶数），所以我们通过client的S3控制server的LED1点亮，通过client的S4控制server的LED1熄灭。&lt;br /&gt;
{{Note|text=client设备对于server设备的控制，通过Node Address的奇偶位不同，分成了两个组。&lt;br /&gt;
client的S1和S2分别控制Node Address为奇数的server设备的LED1点亮和熄灭。&lt;br /&gt;
&lt;br /&gt;
client的S3和S4分别控制Node Address为偶数的server设备的LED1点亮和熄灭。|type=warning}}&lt;br /&gt;
我们打开RTT检测log打印，可以看到client分别按下button2（S3）以及button3（S4），分别会设置server的GPIO输出高低电平。client信息（上），server信息（下）。&lt;br /&gt;
[[文件:Lightswitch rtt node client2.png|边框|无框|656x656像素|居中]]&lt;br /&gt;
[[文件:Lightswitch rtt node server2.png|边框|无框|656x656像素|居中]]&lt;br /&gt;
&lt;br /&gt;
4、至此我们的mesh组网的lightswitch实验测试完成。&lt;br /&gt;
&lt;br /&gt;
== ①广播（通信TX） ==&lt;br /&gt;
有关广播部分，我们需要给大家讲解三个方面，分别如下：&lt;br /&gt;
&lt;br /&gt;
1、PB-ADV，这个是mesh中的广播承载层，用于我们启动配置过程（暂时的例程和手机app都是用的PB-GATT来配置的，但是源码中PB-ADV的流程是包含的）。&lt;br /&gt;
&lt;br /&gt;
2、Unprov Beacon，这个是广播一个未被配置的beacon，就是一个特殊形式的广播数据包，用于声明自己是未经配置的设备。&lt;br /&gt;
&lt;br /&gt;
3、PB-GATT，这个是mesh中的GATT承载层，用于我们启动配置过程（当前手机app就是使用PB-GATT，也就是连接之后，通过GATT服务去发送配置信息）。&lt;br /&gt;
&lt;br /&gt;
4、Publish，这个是我们用于模型发布数据的部分，我们将需要发送的数据通过advData广播出去。&lt;br /&gt;
&lt;br /&gt;
=== PB-ADV ===&lt;br /&gt;
PB-ADV说明请查看“BLE-Mesh技术揭秘”第8.1章节。&lt;br /&gt;
&lt;br /&gt;
我们来看下PB-ADV整个的流程，首先在mian文件中，我们的start()函数下，我们调用了mesh_provisionee_prov_start()函数去启动节点设备的等待配置。&lt;br /&gt;
[[文件:PB-ADV-01.png|边框|居中|无框|1200x1200px]]我们继续追踪mesh_provisionee_prov_start()，可以看到在使能宏定义MESH_FEATURE_PB_ADV_ENABLED的情况下，可以追踪到nrf_mesh_prov_bearer_adv_interface_get()函数。&lt;br /&gt;
[[文件:PB-ADV-02.png|边框|居中|无框|1200x1200px]]在nrf_mesh_prov_bearer_adv_interface_get()函数中，我们可以看到prov_bearer_interface_t结构体，这个结构体下面提供了我们配置承载层需要的API函数。&lt;br /&gt;
[[文件:PB-ADV-03.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
=== Unprov Beacon ===&lt;br /&gt;
Unprov Beacon说明请查看“BLE-Mesh技术揭秘”第5.5及5.5.1章节。&lt;br /&gt;
&lt;br /&gt;
上面我们有说到PB-ADV是承载层用于配置节点的功能，而Unprov Beacon则是我们的节点设备用于声明自己未经配置，所以这两个其实是一脉相承的。&lt;br /&gt;
&lt;br /&gt;
紧接着上面的nrf_mesh_prov_bearer_adv_interface_get()函数，其中的prov_bearer_interface_t结构体包含的是处理承载层的API函数，在这个结构体当中有一个.listen_start = prov_bearer_adv_listen，是用于开始监听传入的配置链接的功能，在这个函数中我们可以看到send_unprov_beacon()函数。&lt;br /&gt;
[[文件:PB-ADV-04.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
send_unprov_beacon()函数是用于发送Unprov Beacon数据的函数，在这个函数中我们调用prov_beacon_unprov_build函数去创建用于广播的Unprov Beacon数据包，然后调用advertiser_interval_set函数去设置广播的间隔，最后调用advertiser_packet_send函数去将我们的数据包发送出去。&lt;br /&gt;
&lt;br /&gt;
默认的Unprov Beacon广播间隔是NRF_MESH_PROV_BEARER_ADV_UNPROV_BEACON_INTERVAL_MS 2000。&lt;br /&gt;
&lt;br /&gt;
最终调用的是advertiser_packet_send(0函数去将广播数据发送出去的。&lt;br /&gt;
[[文件:PB-ADV-05.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Publish-12.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
这里我们再最后看一下prov_beacon_unprov_build()函数，这个函数是生成我们的Unprov Beacon的数据包。&lt;br /&gt;
&lt;br /&gt;
其中标识位是0x2B，这个flag是beacon的意思。data的第一个字节0x00代表是我们的BEACON_TYPE_UNPROV（未配置的节点设备），接下来的16个字节是device uuid（这个是寄存器FICR中的device id生成的），接下来2字节是oob info（这里是00 00，代表没有使能OOB），最后的4个字节是URI通过AES-CMAC算法计算出来的值。&lt;br /&gt;
[[文件:PB-GATT-11.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:PB-GATT-12.png|边框|居中|无框]]&lt;br /&gt;
&lt;br /&gt;
=== PB-GATT ===&lt;br /&gt;
PB-GATT说明请查看“BLE-Mesh技术揭秘”第8.1章节。&lt;br /&gt;
&lt;br /&gt;
和PB-ADV一样的，我们从main文件中的start()函数，追踪到mesh_provisionee_prov_start()，可以看到在使能宏定义MESH_FEATURE_PB_GATT_ENABLED的情况下，可以追踪到nrf_mesh_prov_bearer_gatt_init()以及nrf_mesh_prov_bearer_gatt_interface_get()函数。&lt;br /&gt;
[[文件:PB-GATT-01.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
首先我们看一下nrf_mesh_prov_bearer_gatt_init()函数，这个是初始化并设置PB-GATT服务（通过Mesh GATT接口）的函数。对比我们使用nrf master control panel手机APP连接之后获取到的服务，可以看到其中的PB-GATT的服务以及特征值。&lt;br /&gt;
&lt;br /&gt;
再往下的mesh_gatt_init()函数，这个就不给大家分析了，是和我们普通的BLE一样的方式去注册一个服务。&lt;br /&gt;
[[文件:PB-GATT-04.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:PB-GATT-03.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:PB-GATT-02.png|边框|居中|无框]]&lt;br /&gt;
在nrf_mesh_prov_bearer_gatt_interface_get()函数中，我们可以看到prov_bearer_interface_t结构体，这个结构体下面提供了我们配置承载层需要的API函数（这个是和上方的PB-ADV是一样的）。&lt;br /&gt;
[[文件:PB-GATT-05.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
和Unprov Beacon类似的，当我们开始监听配置链接的时候，会触发.listen_start = listen_start_cb的回调，在这个回调中我们触发FSM（有限状态机）的事件E_LISTEN_START，然后调用link_evt_send()去post顺序承载事件。&lt;br /&gt;
[[文件:PB-GATT-06.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
然后我们来到状态机列表m_pb_gatt_fsm_transition_table[]，可以看到E_LISTEN_START事件ID代表的动作ID是A_LISTEN_START。而最终我们的A_LISTEN_START动作，执行的是a_listen_start函数功能（这其中涉及的代码逻辑，暂时先不说明，大家可以自己查看源码的引用）。&lt;br /&gt;
[[文件:PB-GATT-07.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:PB-GATT-08.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
然后我们继续追踪a_listen_start()，可以看到其中调用了mesh_adv_params_set函数设置了我们广播的超时时间MESH_ADV_TIMEOUT_INFINITE以及广播间隔NRF_MESH_PROV_BEARER_GATT_UNPROV_BEACON_INTERVAL_MS，调用mesh_adv_data_set函数设置了我们的广播数据内容，最后调用mesh_adv_start函数启动广播。&lt;br /&gt;
&lt;br /&gt;
默认的PB-GATT的广播数据的广播周期是NRF_MESH_PROV_BEARER_GATT_UNPROV_BEACON_INTERVAL_MS 200。&lt;br /&gt;
[[文件:PB-GATT-09.png|边框|居中|无框|1200x1200像素]]我们同样的看一下mesh_adv_data_set()函数最终配置生成的广播数据，可以看到广播数据的flag是0x06，服务的UUID就是我们上面提到的PB-GATT服务的UUID，接下来16字节UUID是和PB-ADV一样的FICR寄存器的device id值，最后的扫描回调数据携带的是设备的全名“nRF5x Mesh Light”。&lt;br /&gt;
[[文件:PB-GATT-13.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:PB-GATT-14.png|边框|居中|无框]]最后是我们的mesh_adv_start()函数去启动广播，可以看到调用是我们ble stack sotfdevice当中的API函数sd_ble_gap_adv_start()去启动的。&lt;br /&gt;
[[文件:PB-GATT-15.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
=== Publish（模型发布） ===&lt;br /&gt;
这一章节我们只给大家讲解模块publish的数据发布流程，暂时不说明模型的创建方法，以及如何使用，这个放在下面单独的模型章节进行说明。&lt;br /&gt;
&lt;br /&gt;
以generic_onoff_server模型为例，我们找到access_model_publish以及access_model_reply函数，这两个函数的功能类似的，都是用于我们用户的模型发送数据的。&lt;br /&gt;
&lt;br /&gt;
其中access_model_publish是用于模型主动发送，而access_model_reply是用于模型给对应的Opcode回应数据。&lt;br /&gt;
&lt;br /&gt;
这里我们以access_model_publish函数为主，来看下发布数据的代码流程。&lt;br /&gt;
[[文件:Publish-01.png|边框|居中|无框|1200x1200像素]]查看一下access_model_publish()函数，我们可以看到packet_alloc_and_tx()函数，这个是我们继续发送数据的函数。下面还有一个蓝牙方框中的内容，是我们定义的重传机制（mesh数据为了保证传输的成功率，默认都是会重传数据包的，默认重传次数是4次）。[[文件:Publish-02.png|边框|居中|无框|1200x1200像素]]然后我们继续看一下packet_alloc_and_tx()函数，在这个函数中，我们最终会调用packet_tx()函数将数据发送出去。[[文件:Publish-03.png|边框|居中|无框|1200x1200像素]]继续追踪packet_tx()函数，这个函数的内容比较多，我们只截取了其中数据发送的部分，也就是我们的nrf_mesh_packet_send()函数，我们在他的tx_params中赋值好我们需要发送出去的数据内容。[[文件:Publish-04.png|边框|居中|无框|1199x1199像素]]nrf_mesh_packet_send()函数最终调用的是transport_tx()函数发送数据。而transport_tx()函数，最终是调用upper_transport_tx()函数进行的数据发送。[[文件:Publish-05.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Publish-06.png|边框|居中|无框|1200x1200像素]]我们再继续追踪upper_transport_tx()函数，可以看到我们会根据p_metadata-&amp;gt;segmented来判断数据是否要分包发送。&lt;br /&gt;
[[文件:Publish-13.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
接下里我们以不分包发送函数unsegmented_packet_tx()为例，来继续说明数据传输流程。&lt;br /&gt;
&lt;br /&gt;
我们可以看到我们的unsegmented_packet_tx()函数中，最终是调用的network_packet_send()函数。（分包的segmented_packet_tx()函数最终也是调用的network_packet_send()，大家可以自己追踪代码看一下）&lt;br /&gt;
[[文件:Publish-14.png|边框|居中|无框|1200x1200像素]]继续追踪我们的network_packet_send()函数，可以看到我们的net_packet_encrypt()加密函数，这个是将我们的明文数据，做好加密工作。&lt;br /&gt;
&lt;br /&gt;
然后调用core_tx_packet_send()函数发送出去。[[文件:Publish-15.png|边框|居中|无框|1200x1200像素]]最后我们追踪到core_tx_packet_send()函数，可以看到调用的是p_bearer-&amp;gt;p_interface-&amp;gt;packet_send()函数去发送的。所以接下来我们要找到我们的packet_send()功能是在哪边实现的。[[文件:Publish-07.png|边框|居中|无框|1200x1200像素]]可以看到我们是在core_tx_bearer_add函数中去获得的p_interface这个功能。[[文件:Publish-08.png|边框|居中|无框|1200x1200像素]]我们追踪core_tx_bearer_add()函数的引用路径，可以看到是在core_tx_adv_init()当中。（除了此处，还有另一处地方也有引用，功能是类似的，大家可以自行了解）。&lt;br /&gt;
&lt;br /&gt;
我们看下参数m_interface，可以看到m_interface-&amp;gt;packet_send功能，就是对应我们在core_tx_packet_send()函数调用的p_bearer-&amp;gt;p_interface-&amp;gt;packet_send()。[[文件:Publish-09.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Publish-10.png|边框|居中|无框|1200x1200像素]]所以我们接下来来看下m_interface结构体当中的packet_send()函数实现的是什么功能，可以看到最终调用的是advertiser_packet_send()函数将数据发送出去。看到这边我们就很熟悉了，和上面的几个广播功能，最后调用的都是相同的广播包发送函数。[[文件:Publish-11.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Publish-12.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
== ②扫描（通信RX） ==&lt;br /&gt;
&lt;br /&gt;
=== 标准BLE SCAN ===&lt;br /&gt;
看过了我们的广播功能之后，我们来看下扫描的部分。在ble mesh中，我们的扫描功能scan，就是用于获取数据的，类似于数据通信的RX。&lt;br /&gt;
&lt;br /&gt;
我们从main文件中的initialize()函数开始，首先是我们mesh_init()函数，最终我们会调用mesh_stack_init()函数。&lt;br /&gt;
[[文件:SCAN-01.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
在mesh_satck_init函数中，我们可以看到调用了nrf_mesh_init函数去初始化core的参数。&lt;br /&gt;
[[文件:SCAN-02.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
这一段是nrf_mesh_init函数部分，由于这个初始化函数太长，所以我们只截取了scan相关的部分。&lt;br /&gt;
&lt;br /&gt;
首先是第一点scanner_init(scanner_packet_process_cb)函数，在这个函数中，我们回去初始化扫描的参数配置，并且会开启扫描功能。最终我们可以从scanner_packet_process_cb这个回调中得到我们扫描到的数据（获得所有的BLE广播数据）。&lt;br /&gt;
&lt;br /&gt;
然后是第二点ad_listener_subscribe(&amp;amp;m_nrf_mesh_listener)，这个是用来侦听订阅的消息的（从获得的所有BLE广播数据中，筛选出的订阅的消息）。&lt;br /&gt;
[[文件:SCAN-03.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
然后我们继续看一下scanner_init的扫描初始化函数，在这个函数中我们首先调用scanner_config_reset()函数去配置扫描的默认参数。其中包含了：&lt;br /&gt;
&lt;br /&gt;
1、扫描的通道SCANNER_CHANNELS_DEFAULT，也就是BLE的｛37，38，39｝三个channels都去扫描。&lt;br /&gt;
&lt;br /&gt;
2、规范定义的非连接状态访问地址BEARER_ACCESS_ADDR_DEFAULT。&lt;br /&gt;
&lt;br /&gt;
3、配置物理层协议是1Mbit模式。&lt;br /&gt;
&lt;br /&gt;
4、扫描的间隔时间BEARER_SCAN_INT_DEFAULT_MS，扫描的窗口时间BEARER_SCAN_WINDOW_DEFAULT_MS&lt;br /&gt;
&lt;br /&gt;
5、射频的发射功率RADIO_POWER_NRF_0DBM。&lt;br /&gt;
&lt;br /&gt;
6、BLE广播包最大长度RADIO_CONFIG_ADV_MAX_PAYLOAD_SIZE。&lt;br /&gt;
&lt;br /&gt;
最后我们会在nrf_mesh_enable()函数中调用scanner_enable()函数去启动扫描，这个大家可以自己查看一下代码，比较容易理解。&lt;br /&gt;
[[文件:SCAN-04.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:SCAN-05.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
最后我们来看一下scanner_packet_process_cb()回调函数，其中我们可以直接拿到的扫描的数据包是rx_data，包含了数据的类型、数据内容以及数据长度等等信息。到这边我们的扫描功能是已经完成的，但是我们还需要再看下下面一部分的ad_listener的流程。&lt;br /&gt;
&lt;br /&gt;
=== Subscription（模型订阅） ===&lt;br /&gt;
我们需要注意下ad_listener_process()函数，这个函数是将我们扫描到的订阅数据（不符合标准的BLE广播数据，则认为是侦听到的订阅数据）传递到订阅者进行处理（订阅者指的是带订阅功能的模型）。这个是和我们上方说的关注点二ad_listener_subscribe()函数是一脉相承的。&lt;br /&gt;
[[文件:SCAN-06.png|边框|居中|无框|1200x1200像素]]然后我们来对照着看一下ad_listener_subscribe()函数以及ad_listener_process()函数。这两个函数一个是定义了侦听订阅，一个是将扫描获取到的数据传递给订阅者。这两者间的运作关系，大家有兴趣的可以继续查阅他的底层代码逻辑处理，一个是向list中添加订阅者信息，一个是将数据通过p_listener-&amp;gt;handler()将数据传递给订阅者处理。&lt;br /&gt;
[[文件:SCAN-07.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:SCAN-08.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
最终当我们扫描到订阅消息，并调用ad_listener_process()函数传递时，会触发我们注册的回调nrf_mesh_listen（调用ad_listener_subscribe(&amp;amp;m_nrf_mesh_listener)时，在m_nrf_mesh_listener中）。我们来看下nrf_mesh_listen函数中的处理，其中我们先从数据包中解析了数据的类型type，然后根据不同类型的数据进行相应的处理。&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+&lt;br /&gt;
|AD_TYPE_MESH &lt;br /&gt;
|蓝牙网格物体的AD类型&lt;br /&gt;
|-&lt;br /&gt;
|AD_TYPE_BEACON &lt;br /&gt;
|蓝牙网状信标的AD类型&lt;br /&gt;
|-&lt;br /&gt;
|AD_TYPE_PB_ADV &lt;br /&gt;
|PB-ADV消息的AD类型&lt;br /&gt;
|-&lt;br /&gt;
|AD_TYPE_DFU &lt;br /&gt;
|nRF OpenMesh消息的AD类型&lt;br /&gt;
|}&lt;br /&gt;
[[文件:SCAN-09.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
== ③入网验证 ==&lt;br /&gt;
经过前两章的学习，我们对于BLE MESH的广播及扫描的代码流程已经有了一定的了解，也就是已经大概的明白了数据的发送和接收的流程。&lt;br /&gt;
&lt;br /&gt;
那么从这一章节开始，我们将会给大家展示，如何配置一个节点设备入网。配置者设备，我们使用的是手机APP：nRF Mesh。&lt;br /&gt;
&lt;br /&gt;
我们从main文件中的start()函数开始，来看一下入网的代码处理及其流程。在start()函数中我们最后是调用的mesh_provisionee_prov_start()函数来启动的节点配置功能，我们来查看一下它所携带的参数prov_start_params。这部分的参数都是比较重要的，有携带的是设备的信息的、有携带认证密码的，有回调返回的，所以我们会挨个介绍一下。&lt;br /&gt;
[[文件:Prov-01.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
首先携带的是static_auth_data，这个是我们带外OOB认证的密码。如果我们在使用nRF Mesh去进行身份验证的时候，选择使用Static OOB，那么当我们在进行PB-GATT的连接过程中，会弹出一个新的窗口，要求我们去输入这个静态的128bit的OOB密钥。&lt;br /&gt;
[[文件:Prov-02.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Prov-14.png|边框|居中|无框]]&lt;br /&gt;
[[文件:Prov-15.png|边框|居中|无框]]&lt;br /&gt;
接下来携带的是provisioning_complete_cb()回调函数，这个函数处理的是节点设备成功的被配置入网，在这里我们可以获取到我们的节点设备被分配的node_Address。&lt;br /&gt;
[[文件:Prov-03.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
然后携带是device_identification_start_cb()回调函数，这个回调函数是用于通知开发者，我们的节点设备当前已经开始配置。&lt;br /&gt;
[[文件:Prov-04.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
再然后携带的是URI【统一资源标识符（Uniform Resource Identifier，URI)】，这个是用于标识我们的设备类型的，这个数据会通过AES-CMAC算法加密后添加到我们的Unprov Beacon数据的末尾。&lt;br /&gt;
&lt;br /&gt;
这里我们是light switch的server设备，所以我们定义URI是EX_URI_LS_SERVER。&lt;br /&gt;
[[文件:Prov-05.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
分析完携带的参数之后，我们继续来查看mesh_provisionee_prov_start()函数。在这个函数中，我们首先是定义了一下prov_caps，也就是配置支持的OOB功能。然后我们调用nrf_mesh_generate_keys()函数去生成有效的密钥对等待使用。&lt;br /&gt;
&lt;br /&gt;
再然后是调用nrf_mesh_prov_init()函数去初始化prov配置信息的结构体参数，里面有结构体本身m_pro_ctx，需要配置的参数公钥m_public、私钥m_private，以及我们刚刚定义的oob功能prov_caps，最后还携带了一个事件回调函数prov_ect_handler。&lt;br /&gt;
&lt;br /&gt;
接下来是PB-ADV以及PB-GATT的处理过程，这个前面已经讲解过，这里不再赘述。&lt;br /&gt;
&lt;br /&gt;
最后我们调用provisionee_start()函数去开启配置。&lt;br /&gt;
[[文件:Prov-06.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
我们继续来追踪一下provisionee_start()函数，在这个函数中我们调用nrf_mesh_prov_listen()函数去开启侦听承载层的消息。这里我们需要侦听PB-ADV以及PB-GATT两个。&lt;br /&gt;
[[文件:Prov-07.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
然后在nrf_mesh_prov_listen()函数中，我们调用prov_provisionee_listen()函数携带我们刚刚传入的nrf_mesh_prov_ctx_t结构体，以及p_bearer、URI、oob_info_source等参数信息，继续向下传递。&lt;br /&gt;
[[文件:Prov-08.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
最终我们来查看下prov_provisionee_listen()函数，在这个函数中我们传递了p_bearer-&amp;gt;p_callbacks给m_prov_callbacks回调，然后调用p_bearer-&amp;gt;p_interface-&amp;gt;listen_start()函数去启动PB-ADV以及PB-GATT的侦听。&lt;br /&gt;
[[文件:Prov-09.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
当我们在侦听之后，接收到任何配置相关的消息，都将通过m_prov_callbacks()函数回调传递给我们处理，大家可以自行看下其中4个不同的功能函数，具体处理的都是什么消息。&lt;br /&gt;
[[文件:Prov-10.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
在prov_provisionee_listen()函数中，我们可以看到回调函数指针m_prov_callbacks指向的是p_bearer-&amp;gt;p_callbacks，我们一路向上查找，最终可以找到这个p_bearer-&amp;gt;p_callbacks函数就是指的prov_evt_handler()函数。所以其实我们用户可以在这一层去查看比较重要的配置事件返回，而不需要到上面的m_prov_callbacks中这样偏底层的地方去查看。&lt;br /&gt;
&lt;br /&gt;
在这个回调函数中，我们可以看到mian文件中注册的prov_device_identification_start_cb、prov_device_identification_stop_cb、provisioning_aborted_cb的返回。&lt;br /&gt;
[[文件:Prov-11.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
那么除了上方已经存在的几个回调，我们可以看到mian文件中的start()函数下的prov_start_params参数，还缺少一个配置完成的回调prov_complete_cb()。&lt;br /&gt;
&lt;br /&gt;
这个回调的返回方式比较特殊，和上方其他的都不同，是由SD（softdevice）直接从底层返回给我们的。注册这个回调的函数是NRF_SDH_STATE_OBSERVER，返回的回调函数是sd_state_evt_handler。其中触发的事件NRF_SDH_EVT_STATE_ENABLED，这个是在mian文件中的initialize()函数下的ble_stack_init()函数中就被触发的，这个大家可以自行查看一下，不太重要，因为都是nordic底层处理好的消息。&lt;br /&gt;
[[文件:Prov-13.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Prov-12.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
以上整个配置流程的处理，不太好分步展示相应的现象，这个大家可以自己使用SES在线仿真的方式去调试一下流程，这里我们直接给大家展示整个流程打印出的相应的信息，以及nRF Mesh中暂时出来的现象。&lt;br /&gt;
[[文件:Prov-16.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Prov-17.png|边框|居中|无框]]&lt;br /&gt;
&lt;br /&gt;
== ④设备信息分配 ==&lt;br /&gt;
当我们成功的配置节点设备入网之后，我们需要给节点设备主要分配如下几个信息，有关这几个要点的介绍，可以查看“BLE-Mesh技术揭秘”第3章节：&lt;br /&gt;
&lt;br /&gt;
1、节点在网络中的地址Node Address&lt;br /&gt;
&lt;br /&gt;
2、网络密钥Netkey&lt;br /&gt;
&lt;br /&gt;
3、应用密钥Appkey&lt;br /&gt;
&lt;br /&gt;
4、元素的地址Element Address&lt;br /&gt;
&lt;br /&gt;
5、绑定model的Appkey，分配Publish Address，Subscription Address&lt;br /&gt;
&lt;br /&gt;
=== Node Address ===&lt;br /&gt;
首先我们来看一下我们使用nRF Mesh成功配置我们的节点之后，打印出来的消息，开始的紫色字体显示了我们给当前节点设备分配的Node Address。&lt;br /&gt;
&lt;br /&gt;
后面紧接着还有四段黄色字体的内容（这个部分大家的工程是不会打印的，是我们修改例程打印出来给大家展示一下），这四个部分就是我们配置节点的时候，配置者设备（nRF Mesh）给节点设备的四个主要指令，这些指令的具体功能，大家可以查看mesh的协议手册。[[文件:Config-13.png|边框|居中|无框|1080x1080像素]]在代码中，我们会在mian文件的provisioning_complete_cb()回调函数中获取并打印我们的Node Address，这个回调触发的含义就是我们的节点配置已经完成了。&lt;br /&gt;
&lt;br /&gt;
具体这个回调在代码中的回调及触发流程，大家看可以查看“③入网验证”的说明。[[文件:Config-20.png|边框|居中|无框|1200x1200像素]]我们使用nRF Mesh配置好节点设备之后，app界面显示内容如下，包含了节点的信息（地址、名称、配置时间等等），元素和模型，以及Netkey和Appkey等几个重要的设备信息。[[文件:Config-01.jpg|边框|居中|无框]]&lt;br /&gt;
[[文件:Config-04.jpg|边框|居中|无框]]&lt;br /&gt;
&lt;br /&gt;
=== Netkey ===&lt;br /&gt;
我们点击查看一下Network key，可以看到里面有一个Network Key 1，这是一个16字节的网络密钥。我们可以在nRF Mesh的Settings界面去增加或者删除一个network key，用户可以自定义此密钥。[[文件:Config-02.jpg|边框|居中|无框]]在我们的代码中，我们是在pro_evt_handler回调的NRF_MESH_PROV_EVT_COMPLETE事件中获取这个netkey，这个回调函数的触发流程同样是在“③入网验证”已经有说明。[[文件:Prov-11.png|边框|居中|无框|1200x1200像素]]我们直接看下NRF_MESH_PROV_EVT_COMPLETE事件下的mesh_stack_provisioning_data_store()函数，在这个函数中我们主要的任务，就是将配置者分配给我们的node address、netkey、devkey等等信息，分别做一些存储以及功能绑定。&lt;br /&gt;
[[文件:Netkey-01.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
=== Appkey ===&lt;br /&gt;
同样的我们打开Application Key，可以看到其中包含的3个密钥，这里我们只选用了Application Key 1作为我们的密钥。大家同样可以在nRF Mesh的Settings界面去增加或者删除一个App key，用户可以自定义此密钥。[[文件:Config-03.jpg|边框|居中|无框]]appkey这里和netkey不同，netkey是我们网络中的节点设备以及我们配置者设备共有的一个密钥，只有拥有这个密钥，我们才能在当前网络中通信，所以当我们的节点设备配置入网后，会立刻得到netkey（必须要分配给我的节点设备才行）。&lt;br /&gt;
&lt;br /&gt;
而appkey只是我们的某个模型用于通信的密钥，所以我们称它为应用密钥，这个appkey的分配，是在我们的config server下。这个config_server_init的初始化流程，是在我们的mesh_stack_init()函数下，就去初始化配置好的。最终当我们有信息配置的时候，会触发opcode_handlers，我们可以从这个操作码列表中查找相应的信息处理。[[文件:Config-22.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Config-23.png|边框|居中|无框|1200x1200像素]]我们来看下config server的opcode_handlers[]操作码处理程序列表，使用黄色标记给大家圈了几个操作码指令，这几个是我们配置一个最简单的server节点设备，都需要使用到的，包含了：&lt;br /&gt;
&lt;br /&gt;
1、appkey的添加，CONFIG_OPCODE_APPKEY_ADD&lt;br /&gt;
&lt;br /&gt;
2、publish发布地址，CONFIG_OPCODE_MODEL_PUBLICATION_VIRTUAL_ADDRESS_SET&lt;br /&gt;
&lt;br /&gt;
3、subscription订阅地址，CONFIG_OPCODE_MODEL_SUBSCRIPTION_ADD&lt;br /&gt;
&lt;br /&gt;
4、模型appkey的绑定，CONFIG_OPCODE_MODEL_APP_BIND&lt;br /&gt;
&lt;br /&gt;
5、netkey的更新，CONFIG_OPCODE_NETKEY_ADD[[文件:Config-21.png|边框|居中|无框|1200x1200像素]]我们可以看到当接收到CONFIG_OPCODE_APPKEY_ADD操作码的数据，会调用handle_appkey_add()函数去处理，在这个函数中我们最终会调用dsm_appkey_add()函数去存储我们的appkey。[[文件:Appkey-01.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
== ⑤Element元素 ==&lt;br /&gt;
元素简单的说就是定义了节点的功能，一些具有复杂功能的节点设备，会包含多个元素。一个节点设备最少需要包含一个元素，我们称之为主元素。&lt;br /&gt;
&lt;br /&gt;
从主元素开始（如果有多个元素，则第一个元素就是主元素），我们会给它分配相应的元素地址。主元素的地址就是节点的地址，第二个元素的地址就是主元素的地址基础上加上1，依次类推。&lt;br /&gt;
&lt;br /&gt;
可以看到在我们的主元素Element：0x0002中包含了3个不同的服务，这3个服务就是我们的模型Model，其中Configuration Server与Health Server这两个服务，是每一个节点都必须携带的模型。Configuration Server（配置服务）是用于配置节点设备信息，Health Server（健康服务）是用于节点进行网络内的心跳联系。剩下的Generic On Off Server服务，这个就是我们用于控制一个开关量的模型（在例程中我们通过控制LED的IO口高低电平来展示）。[[文件:Config-05.jpg|边框|居中|无框]]在我们的代码中，我们首先需要在nrf_mesh_config_app.h中定义好我们节点设备的最大元素数量，可以看到我们定义#define ACCESS_ELEMENT_COUNT (1)，也就是说我们当前的例程，只含有一个主元素。&lt;br /&gt;
[[文件:Element-01.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
而这个ACCESS_ELEMENT_COUNT ，是在我们开启节点配置的地方被调用的，这个mesh_provsionionee_prov_start()函数在前面的内容中有多次讲到，这里就不再说明了。&lt;br /&gt;
[[文件:Element-02.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
== ⑥model模型 ==&lt;br /&gt;
如果说元素是一个功能集合，那么模型就是元素下的具体的某一个功能。&lt;br /&gt;
&lt;br /&gt;
我们以灯的控制为例，灯的常用控制有开关、亮度、色温几种，其中开关、亮度和色温就是具体的模块，而处理灯控的集合就是元素。&lt;br /&gt;
&lt;br /&gt;
=== Configure Server ===&lt;br /&gt;
配置服务模型，这个的大概代码处理流程在我们的“④设备信息分配-&amp;gt;Appkey”已经有了说明，它是我们节点设备必须包含的一个特殊功能的模型。&lt;br /&gt;
&lt;br /&gt;
它的实际功能就像它的名字一样，就是用于配置（configuration）我们的设备信息，比如appkey的添加删除，密钥的绑定删除，其他的模型的发布和订阅控制。具体包含的操作码的使用方法，我们会在generic on off server模型当中使用的时候来说明。&lt;br /&gt;
&lt;br /&gt;
=== Health Server ===&lt;br /&gt;
健康服务，顾名思义就是判断我们的设备是否在网络中保持健康（正常）工作，它同样是一个必须包含的特殊功能的模型。&lt;br /&gt;
&lt;br /&gt;
它的初始化和config server是一样的，都是放在了mesh_stack_init()函数下。&lt;br /&gt;
[[文件:Health-01.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
我们来看下health_server_init()函数，最终我们会调用access_model_add()函数去初始化模型，并将模型添加到对应的元素中。&lt;br /&gt;
&lt;br /&gt;
其中的access_model_add_params_t参数，包含的分别是元素的索引（将模型添加到第几个元素当中），模型的ID（由mesh协议手册确定好了），以及最最重要的操作码列表，这个列表中会包含我们的指令码，以及对应的函数功能处理。&lt;br /&gt;
[[文件:Health-02.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
最后我们看下具体包含了哪些健康模型操作码：&lt;br /&gt;
&lt;br /&gt;
1、“运行状况当前状态”消息的操作码，HEALTH_OPCODE_CURRENT_STATUS&lt;br /&gt;
&lt;br /&gt;
2、“ 健康故障状态”消息的操作码，HEALTH_OPCODE_FAULT_STATUS&lt;br /&gt;
&lt;br /&gt;
3、“  获取健康注意事项”消息的操作码，HEALTH_OPCODE_ATTENTION_GET&lt;br /&gt;
&lt;br /&gt;
4、“ 健康注意集”消息的操作码，HEALTH_OPCODE_ATTENTION_SET&lt;br /&gt;
&lt;br /&gt;
5、“未确认健康注意事项”消息的操作码，HEALTH_OPCODE_ATTENTION_SET_UNACKED&lt;br /&gt;
&lt;br /&gt;
6、“健康注意状态”消息的操作码，HEALTH_OPCODE_ATTENTION_STATUS&lt;br /&gt;
&lt;br /&gt;
7、“健康故障清除”消息的操作码，HEALTH_OPCODE_FAULT_CLEAR&lt;br /&gt;
&lt;br /&gt;
8、“健康故障清除未确认”消息的操作码，HEALTH_OPCODE_FAULT_CLEAR_UNACKED&lt;br /&gt;
&lt;br /&gt;
9、“健康故障获取”消息的操作码，HEALTH_OPCODE_FAULT_GET&lt;br /&gt;
&lt;br /&gt;
10、“健康故障测试”消息的操作码，HEALTH_OPCODE_FAULT_TEST&lt;br /&gt;
&lt;br /&gt;
11、“未确认健康故障测试”消息的操作码，HEALTH_OPCODE_FAULT_TEST_UNACKED&lt;br /&gt;
&lt;br /&gt;
12、“健康期获取”消息的操作码，HEALTH_OPCODE_PERIOD_GET&lt;br /&gt;
&lt;br /&gt;
13、“健康期设置”消息的操作码，HEALTH_OPCODE_PERIOD_SET&lt;br /&gt;
&lt;br /&gt;
14、“未确认健康期设置”消息的操作码，HEALTH_OPCODE_PERIOD_SET_UNACKED&lt;br /&gt;
&lt;br /&gt;
15、“健康期状态”消息的操作码，HEALTH_OPCODE_PERIOD_STATUS&lt;br /&gt;
[[文件:Health-03.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
在大家的前期使用中，一般也不会真的需要大家去处理health server下的指令，毕竟我们刚开始使用，也很难让设备在网络中出现各种运行问题（节点少功能少）。但是当大家自己开始开发实际功能时，就一定要注意分析health server下的指令码返回，用来快速解决我们网络中的问题。&lt;br /&gt;
&lt;br /&gt;
=== Generic On Off Server ===&lt;br /&gt;
接下来我们来看下Generic On Off Server服务，这个服务和上方的两个服务不太一样，他不是我们的特殊必要服务，是一个通用的模型（用于具体的功能数据的处理，这里是用于开关功能）。借用这个服务模型，我们给大家介绍一下如何创建一个符合BLE MESH协议的模型。&lt;br /&gt;
&lt;br /&gt;
我们看下模型的初始化方法，在generic_onoff_server_init()函数中，首先我们使用access_model_add_params_t定义一个模型分配参数的结构体init_params，在这个结构体当中，我们要给定：&lt;br /&gt;
&lt;br /&gt;
1、model_id（模型的ID），这个是mesh协议规定好的，我们可以查看“归档资料\8-蓝牙协议手册和芯片手册\蓝牙mesh协议手册\《MshMDLv1.0.1.pdf》”的第7.3章节有归纳&lt;br /&gt;
&lt;br /&gt;
2、element_index（元素索引），这个看我们准备将这个服务添加到哪一个元素下，这里我们因为只有一个主元素，所以我们的元素索引为0&lt;br /&gt;
&lt;br /&gt;
3、p_opcode_handlers（操作码列表，指令码列表），这个也是mesh协议规定好的，我们可以查看“归档资料\8-蓝牙协议手册和芯片手册\蓝牙mesh协议手册\《MshMDLv1.0.1.pdf》”的第7.1章节归纳，具体的操作码的功能及要求，大家需要对应这个pdf手册，自己查看一下，对于一个模型ID，我们不一定要注册所有的属于它的opcode，只要注册我们需要使用的即可&lt;br /&gt;
&lt;br /&gt;
4、opcode_count（操作码数量），准备注册的操作码的数量&lt;br /&gt;
&lt;br /&gt;
5、p_args（通用参数指针），用于参数的传递&lt;br /&gt;
&lt;br /&gt;
6、publish_timeout_cb（发布超时时间），发布的定时器到期，会进入此回调，我们可以做一些处理（例如重新发送数据等等）。如果我们的模型不支持定期发布，可以将此值设置为NULL。&lt;br /&gt;
&lt;br /&gt;
当我们配置好模型的参数结构体之后，会调用access_model_add()函数去初始化模型并添加到元素，同时会返回给我们用于存储此模型的句柄指针p_server-&amp;gt;model_handle。&lt;br /&gt;
&lt;br /&gt;
因为我们的onoff模型，需要订阅消息，所以我们需要调用access_model_subscription_list_alloc()函数，携带我们的模型句柄去分配模型的订阅列表。&lt;br /&gt;
[[文件:Onoff-01.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
看完模型的初始化注册之后，我们来看下模型包含的opcode操作码，可以看到在m_opcode_handlers[]操作码列表中包含了三个opcode。这三个opcode我们可以看下他的定义，以及对照一下《MshMDLv1.0.1.pdf》第7.1章节的归纳，可以看到是一一对应的关系。具体这三个opcode的功能，大家可以查看手册中的说明，或者直接看下他们对应的功能处理函数。&lt;br /&gt;
[[文件:Onoff-02.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Onoff-03.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Onoff-04.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
接下来我们看下GENERIC_ONOFF_OPCODE_SET与GENERIC_ONOFF_OPCODE_SET_UNACKNOWLEDGED对应的handle_set，可以看到在这个函数的最后，我们会去调用status_send()函数去发送数据。&lt;br /&gt;
[[文件:Onoff-05.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
然后我们看下status_send()函数，在这个函数中，我们最终会调用access_model_publish来发布我们的数据，或者使用accsee_model_reply来回复数据。&lt;br /&gt;
&lt;br /&gt;
这两个函数再往下就是我们之前在广播部分讲解的packet_alloc_and_tx函数去发送广播数据了，这个有不清楚的，大家可以到前面去查看一下。&lt;br /&gt;
[[文件:Onoff-06.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
==== model bind appkey ====&lt;br /&gt;
那么按照上方的方法注册好模型，然后我们的节点设备被成功的配置入网之后，我们如何让这个模型工作起来。&lt;br /&gt;
&lt;br /&gt;
首先第一步是需要将我们的模型绑定到相应的appkey，只有这样我们的模型才能拿到用于通信的密钥，例如如下的截图当中，我们利用手机app软件nRF Mesh去给我们的Generic On Off Server模型绑定Appkey1。[[文件:Config-14.png|边框|居中|无框|1040x1040像素]]&lt;br /&gt;
[[文件:Config-06.jpg|边框|居中|无框]]&lt;br /&gt;
[[文件:Config-07.jpg|边框|居中|无框]]&lt;br /&gt;
[[文件:Config-08.jpg|边框|居中|无框]]当我们在使用app去绑定appkey的时候，在我们的代码中的体现是在config server下的opcode_handlers[]操作码列表中的CONFIG_OPCODE_MODEL_APP_BIND。&lt;br /&gt;
[[文件:Onoff-07.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
我们追踪一下CONFIG_OPCODE_MODEL_APP_BIND对应的handle_model_app_bind_unbind()，这个函数比较长，这边只给大家截取了部分，在为截取到部分，还有获取元素索引的get_element_index，获取模型ID和模型handle的access_handle_get，然后才是我们的dsm_appkey_index_to_appkey_handle函数用于将appkey索引给到appkey句柄。然后调用access_model_application_bind函数去绑定模型和appkey，最终如果是成功绑定，则调用access_flash_config_store函数去存储修改的绑定信息。&lt;br /&gt;
[[文件:Onoff-08.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
==== model publish address ====&lt;br /&gt;
当我们完成了模型的appkey绑定之后，我们就可以开始设置puhlish的地址以及subscription的地址。&lt;br /&gt;
&lt;br /&gt;
这里我们先看下nRF Mesh配置publish Address，可以看到我们选择的定义的0xC000组地址。[[文件:Model-pub-01.jpg|边框|居中|无框]]&lt;br /&gt;
[[文件:Model-pub-02.jpg|边框|居中|无框]]&lt;br /&gt;
[[文件:Model-pub-03.jpg|边框|居中|无框]]&lt;br /&gt;
[[文件:Model-pub-04.jpg|边框|居中|无框]]我们可以看到，在我们配置publish地址的时候，RTTViewer中会打印黄色标注的操作码数据信息，可以看到他的OPCODE是0x03。[[文件:Model-pub-05.png|边框|居中|无框|996x996像素]]我们来看下这个操作码对应的功能，可以看到CONFIG_OPCODE_MODEL_PUBLICATION_SET操作码对应的功能函数是handle_config_model_publication_set，根据函数的名称我们就可以看出，是用于配置模型发布的。[[文件:Model-sub-04.jpg|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Model-pub-07.jpg|边框|居中|无框|1200x1200像素]]在handle_config_model_publication_set函数中，可以看到我们先使用dsm_appkey_index_to_appkey_handle函数获取了appkey的句柄。然后调用dsm_address_publish_add函数去添加发布的地址。[[文件:Model-pub-08.jpg|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
==== model subscription address ====&lt;br /&gt;
然后我们再看下订阅地址的添加，可以看到我们使用nRF Mesh配置subscription订阅了组地址0xC001。[[文件:Model-sub-02.jpg|边框|居中|无框]]&lt;br /&gt;
[[文件:Model-sub-01.jpg|边框|居中|无框]]可以看到，当我们分配subscription地址的时候，可以看到RTTViewer中打印黄色标注的信息。可以看到他的OPCODE是0x801B。[[文件:Model-sub-03.jpg|边框|居中|无框|982x982像素]]我们来看下这个操作码对应的功能，可以看到CONFIG_OPCODE_MODEL_SUBSCRIPTION_ADD操作码对应的功能函数是handle_config_model_subscription_add，根据函数的名称我们就可以看出，是用于配置模型订阅的。[[文件:Model-pub-06.jpg|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Model-sub-05.jpg|边框|居中|无框|1200x1200像素]]在handle_config_model_subscription_add函数中，我们会调用dsm_address_subscription_add函数去添加我们的订阅地址。[[文件:Model-sub-06.jpg|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
== Mesh天猫精灵实验 ==&lt;br /&gt;
&lt;br /&gt;
=== 天猫精灵介绍 ===&lt;br /&gt;
天猫精灵（TmallGenie）是阿里巴巴人工智能实验室（Alibaba A.I.Labs）于2017年7月5日发布的AI智能产品品牌，当天同步发布了天猫精灵首款硬件产品——AI智能语音终端设备天猫精灵，未来还将推出更多AI智能产品。&lt;br /&gt;
&lt;br /&gt;
天猫精灵内置AliGenie操作系统，AliGenie生活在云端，它能够听懂中文普通话语音指令，目前可实现智能家居控制、语音购物、手机充值、叫外卖、音频音乐播放等功能，带来人机交互新体验。依靠阿里云的机器学习技术和计算能力，AliGenie能够不断进化成长，了解使用者的喜好和习惯，成为人类智能助手。&lt;br /&gt;
&lt;br /&gt;
=== AliGenie平台介绍 ===&lt;br /&gt;
'''阿里精灵AliGenie平台地址：[//E%3A/NRF52832/www.aligenie.com www.aligenie.com]'''&lt;br /&gt;
&lt;br /&gt;
'''IOT接入平台控制中心：[//E%3A/NRF52832/iot.aligenie.com/home iot.aligenie.com/home]'''&lt;br /&gt;
&lt;br /&gt;
'''IOT平台接入规范手册：[/www.aligenie.com/doc/357554/tgllbp https://www.aligenie.com/doc/357554/tgllbp]'''&lt;br /&gt;
&lt;br /&gt;
天猫精灵IoT开放平台，是阿里巴巴人工智能实验室(Alibaba A.I.Labs)面向品牌商、方案商、模组商/芯片商以及个人开发者推出的，将IoT物联网技术（蓝牙Mesh协议、WiFi协议、天猫精灵IoT云服务）和AI（天猫精灵ASR语音识别、NLP自然语言处理、TTS语音合成）等对外输出的开放式平台。&lt;br /&gt;
&lt;br /&gt;
开发者可按直连接入（WiFi模组、蓝牙Mesh模组）、云云接入（OAuth2.0）2类方式，接入天猫精灵软硬件生态（天猫精灵音箱、天猫精灵App、AliGenie Inside智能设备）及阿里巴巴集团生态服务，实现语音和触屏交互，为用户提供天猫精灵IoT控制、查询、播报和主动服务。目前已支持40+个IoT类目，1000+型号。天猫精灵IoT开放平台会持续创新，不断为开发者带来新技术，同时降低平台开发者进驻门槛，让AI普惠大众！&lt;br /&gt;
&lt;br /&gt;
=== 硬件连接 ===&lt;br /&gt;
1、NRF52832DK连接Jlink-Lite仿真器，并连接到电脑USB&lt;br /&gt;
&lt;br /&gt;
2、天猫精灵（方糖R）接电源供电，根据天猫精灵使用手册，将其连接上家里的WIFI&lt;br /&gt;
&lt;br /&gt;
=== 平台设备创建 ===&lt;br /&gt;
1、  登录IOT接入平台控制中心：[//E%3A/NRF52832/iot.aligenie.com/home iot.aligenie.com/home]，大家先点击右上角注册一下，因为是阿里巴巴旗下的产品，所以我们只需要用我们的淘宝账号就可以完成注册。&lt;br /&gt;
[[文件:Tmjl-03.png|边框|居中|无框|1189x1189像素]]&lt;br /&gt;
&lt;br /&gt;
2、  注册完成之后，我们登入平台，并且创建一个蓝牙mesh设备，如下图所示：&lt;br /&gt;
[[文件:Tmjl-04.gif|边框|居中|无框|1200x1200像素]]3、  记录刚刚平台分配给我们的三元组，任意一个就行，不需要所有的，举例如下：&lt;br /&gt;
{| class=&amp;quot;wikitable sortable mw-collapsible&amp;quot;&lt;br /&gt;
|+&lt;br /&gt;
!Product ID&lt;br /&gt;
!Device Secret&lt;br /&gt;
!Mac 地址&lt;br /&gt;
|-&lt;br /&gt;
|9244&lt;br /&gt;
|0855f693ce1246f3e09653603283dc02&lt;br /&gt;
|f8a7635b7683&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== 软件配置 ===&lt;br /&gt;
将例程“ghostyu-tmjl”解压到mesh SDK的如下路径中，并且确认编译无误：\nrf5SDKforMeshv310src\examples\ghostyu-tmjl&lt;br /&gt;
[[文件:Tmjl-01.png|边框|居中|无框|450x450像素]]&lt;br /&gt;
[[文件:Tmjl-02.png|边框|居中|无框|650x650像素]]&lt;br /&gt;
&lt;br /&gt;
==== 三元组配置 ====&lt;br /&gt;
''三元组具体说明可以查看''：https://www.aligenie.com/doc/357554/gtgprq&lt;br /&gt;
&lt;br /&gt;
举例如平台设备创建第三步记录的三元组：&lt;br /&gt;
&lt;br /&gt;
'''Product ID'''：'''9244''' ，转成16进制就是'''0x0000241C''' ，所以最终数据是{'''1c 24 00 00'''}（4字节、低位在前）&lt;br /&gt;
&lt;br /&gt;
'''Mac地址'''：'''f8a7635b7683'''，所以最终数据是'''｛83 76 5b 63 a7 f8｝'''（6字节，低位在前）&lt;br /&gt;
&lt;br /&gt;
'''AUTH Data'''：利用'''三元组的数据'''以及'''SHA256计算器'''得到我们的加密数据，如下图所示，最终我们得到加密数据为：'''{ 0x73,0x76,0x09,0x93, 0x8d,0x6b,0x6a,0x52, 0x42,0x54,0xe5,0x41, 0x57,0xe4,0x75,0x15}'''&lt;br /&gt;
&lt;br /&gt;
''SHA256计算器''：https://www.jisuan.mobi/pmHbB1bmuum16Xxx.html&lt;br /&gt;
[[文件:Tmjl-05.png|边框|居中|无框|589x589像素]]&lt;br /&gt;
&lt;br /&gt;
==== 代码修改 ====&lt;br /&gt;
根据刚刚三元组配置产生的数据，修改mian文件中对应的值。&lt;br /&gt;
[[文件:Tmjl-06.png|边框|居中|无框|1292x1292像素]]&lt;br /&gt;
&lt;br /&gt;
==== 软件烧写 ====&lt;br /&gt;
修改好代码之后，我们依次进行如下步骤操作，注意'''不可跳跃任意步骤。'''&lt;br /&gt;
&lt;br /&gt;
1、利用nrfgo工具擦除芯片&lt;br /&gt;
&lt;br /&gt;
2、下载正确的softdevice&lt;br /&gt;
&lt;br /&gt;
3、编译天猫精灵例程（注意：已经完成“代码修改”步骤）&lt;br /&gt;
&lt;br /&gt;
4、将编译好的程序下载到开发板中&lt;br /&gt;
&lt;br /&gt;
=== 调试阶段 ===&lt;br /&gt;
&lt;br /&gt;
==== 确认设备状态 ====&lt;br /&gt;
在进入调试之前，我们确认一下设备状态，设备状态正确后，进入调试流程。&lt;br /&gt;
&lt;br /&gt;
1、  按下开发板复位按键，4个LED灯闪烁2次后熄灭&lt;br /&gt;
&lt;br /&gt;
2、  对天猫精灵说“天猫精灵，你联网了吗”，等待天猫精灵回复“我已联网”&lt;br /&gt;
&lt;br /&gt;
==== 调试流程 ====&lt;br /&gt;
1、  对天猫精灵说“天猫精灵，找队友”，天猫精灵回复“正在查找智能设备”&lt;br /&gt;
&lt;br /&gt;
2、  如果我们前面的软件配置是对的，过一会天猫精灵会回复“找到智能灯设备，是否连接”；如果配置有问题，天猫精灵找不到设备会回复“未找到智能设备”，这个时候我们需要检查软件配置流程中的product ID和Mac地址&lt;br /&gt;
&lt;br /&gt;
3、  若天猫精灵回复“找到智能灯设备，是否连接”，我们回答“连接”，天猫精灵会回复“正在连接智能设备，请稍等”&lt;br /&gt;
&lt;br /&gt;
4、  等待1段时间（1min以内），如果回复“智能设备连接成功”，则代表成功连接开发板；如果回复“连接失败”，则检查软件配置流程中的Data Auth密码&lt;br /&gt;
&lt;br /&gt;
5、  如果连接成功，我们按下开发板按键S2（'''注意：复位开发板后，都需要重新按下S2'''）&lt;br /&gt;
&lt;br /&gt;
6、  我们对天猫精灵说“天猫精灵，打开灯”，这个时候开发板上D1会点亮；如果如果说“天猫精灵，关闭灯”，这个时候开发板上D1会熄灭&lt;br /&gt;
&lt;br /&gt;
7、  到第6步结束，已经完成测试，接下来我们也可以到平台上调试一下。此时我们只能测试“打开灯”以及“关闭灯”功能。&lt;br /&gt;
[[文件:Tmjl-07.jpg|边框|居中|无框|1105x1105像素]]&lt;br /&gt;
[[文件:Tmjl-08.png|边框|居中|无框|1109x1109像素]]&lt;br /&gt;
&lt;br /&gt;
== Mesh串口透传实验 ==&lt;br /&gt;
[[分类:NRF52832DK]]&lt;br /&gt;
[[分类:实验手册]]&lt;/div&gt;</summary>
		<author><name>Jinx</name></author>
		
	</entry>
	<entry>
		<id>http://doc.iotxx.com/index.php?title=NRF52832DK-Mesh%E7%BB%84%E7%BD%91%E5%AE%9E%E9%AA%8C&amp;diff=2828</id>
		<title>NRF52832DK-Mesh组网实验</title>
		<link rel="alternate" type="text/html" href="http://doc.iotxx.com/index.php?title=NRF52832DK-Mesh%E7%BB%84%E7%BD%91%E5%AE%9E%E9%AA%8C&amp;diff=2828"/>
		<updated>2020-04-02T08:43:16Z</updated>

		<summary type="html">&lt;p&gt;Jinx：/* 软件配置 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;蓝牙Mesh和BLE是并行发展的一个独立分支，虽然底层共用Physical Layer和Link Layer，但是上层协议并不相同。因此，nRF52832的Mesh例程，并不在BLE协议栈sdk中，而是安装一个扩展包。&lt;br /&gt;
&lt;br /&gt;
本文介绍蓝牙Mesh相关的环境搭建和实验介绍。&lt;br /&gt;
&lt;br /&gt;
== Mesh开发环境 ==&lt;br /&gt;
&lt;br /&gt;
=== 蓝牙Mesh SDK ===&lt;br /&gt;
SDK位置：&amp;lt;code&amp;gt;归档资料/2-协议栈SDK/nRF5_SDK_Mesh_v310.zip&amp;lt;/code&amp;gt;，注意文件名中的Mesh字样。&lt;br /&gt;
&lt;br /&gt;
和BLE协议栈一样，将该压缩包直接解压到BLE协议栈同级目录即可使用，解压后的SDK路径如下：&amp;lt;code&amp;gt;E:\project-nordic\nRF5_SDK_Mesh_v310&amp;lt;/code&amp;gt;&lt;br /&gt;
[[文件:NRF52832DK-MESH-SDK-INSTALL.gif|居中|无框|750x750像素]]&lt;br /&gt;
&lt;br /&gt;
=== 集成开发软件SES ===&lt;br /&gt;
SES时是Segger Embedded Studio编译器的缩写，用来编译蓝牙Mesh代码，SES功能与IAR或Keil类似。&lt;br /&gt;
&lt;br /&gt;
备注：Nordic为何使用SES而不是IAR，原因不得而知。&lt;br /&gt;
&lt;br /&gt;
==== SES安装 ====&lt;br /&gt;
SES软件位置：&amp;lt;code&amp;gt;归档资料/7-编译器SES/Setup_EmbeddedStudio_ARM_v410_win_x64.exe&amp;lt;/code&amp;gt;。&lt;br /&gt;
&lt;br /&gt;
我们双击打开安装界面，按照如下动图所示方式进行安装（可以全部默认安装）。[[文件:Ses install.gif|边框|居中|无框|556x556像素]]&lt;br /&gt;
&lt;br /&gt;
==== SES许可证激活 ====&lt;br /&gt;
我们双击打开安装好的SES，选择编译自带测试例程，此时弹出需要激活的界面。&lt;br /&gt;
[[文件:Licensed1.jpg|边框|居中|无框|1205x1205像素]]&lt;br /&gt;
由于我们准备使用SES编译nordic的NRF52832的SDK，所以这里我们选择Activate Your Free License。这里因为nordic公司已经帮客户想SEGGER公司买下了使用的版权，所以是免费的许可证。&lt;br /&gt;
[[文件:Licensed2.jpg|边框|居中|无框|736x736像素]]&lt;br /&gt;
我们按照要求填写好信息，主要是邮箱地址（用于接收SEGGER公司发给我们的许可证），填写完成之后点击Request License。下面是我们接收的邮件信息，我们将红色框内的内容复制下来。&lt;br /&gt;
[[文件:Ses install3.png|边框|居中|无框|1301x1301像素]]&lt;br /&gt;
打开SES，选择Tools\License Manager...，我们选择Activate Embedded Studio，打开如下界面，并将我们的许可证粘贴进去，然后点击Install License。&lt;br /&gt;
[[文件:Licensed4.png|边框|居中|无框|481x481像素]]&lt;br /&gt;
安装好许可证之后，跳转到如下的界面。&lt;br /&gt;
[[文件:Licensed5.png|边框|居中|无框|481x481像素]]&lt;br /&gt;
此时我们的许可证激活完毕（有时候需要等待一段时间，才会显示激活成功）。&lt;br /&gt;
[[文件:Licensed6.png|边框|居中|无框|515x515像素]]&lt;br /&gt;
&lt;br /&gt;
== mesh实验学习流程概论 ==&lt;br /&gt;
1、首先大家一定要先查看一下我们资料中的手册“BLE-Mesh技术揭秘”，这个是很重要的一个mesh基本概念的介绍，只有看完这个文档，我们才能明白mesh组网是什么，是怎么完成的，后续的我们实验内容，也会在这个文档中查到相应的对照。&lt;br /&gt;
&lt;br /&gt;
2、按照Light_switch测试实验的实验说明，跑通测试流程（可以固件清零重新烧写多测试几遍，有助于增强我们的流程熟练度，利于后面的开发）。light_switch实验是现在nordic提供给我们的唯一的真正意义上的mesh组网通信例程。&lt;br /&gt;
&lt;br /&gt;
3、根据我们下面拆分的组网流程，对照依次的实验说明，进行mesh组网流程的分步学习。前期实验部分我们主要讲解节点设备，配置者设备功能我们使用手机app完成，目的是减少大家的学习难度，配置者设备我们会放在最后一个实验给大家做介绍。&lt;br /&gt;
&lt;br /&gt;
①广播（通信TX）——了解节点设备的广播功能，分为三个重要部分：PB-ADV和Unprov Beacon，PB-GATT的广播，以及模型的Publish用于发送数据。&lt;br /&gt;
&lt;br /&gt;
②扫描（通信RX）——了解节点设备的扫描功能，分为两个重要部分：普通的BLE SCAN功能，以及模块的Subscription用于接收数据。&lt;br /&gt;
&lt;br /&gt;
③入网验证——了解节点设备接入网络的验证过程，实验中使用的是PB-GATT去配置的，包含了：交换公共密钥，完成验证（是否有带外OOB）。&lt;br /&gt;
&lt;br /&gt;
④设备信息分配——启动配置数据分配，给节点分配如下数据：网络key（Netkey）、应用key（Appkey）以及节点设备地址（nodeAddr）。&lt;br /&gt;
&lt;br /&gt;
⑤元素——了解节点设备的元素与模型定义。&lt;br /&gt;
&lt;br /&gt;
⑥模型——了解模型的功能和创建方法，模型的订阅subscription和发布publish功能。&lt;br /&gt;
&lt;br /&gt;
== Light_switch测试实验 ==&lt;br /&gt;
=== 实验简介 ===&lt;br /&gt;
此示例演示了包含充当两个角色的设备的网状生态系统：配置角色（Provisioner role）和节点角色（Node role、provisionee role）。 它还通过在应用程序中使用[Generic OnOff model]来演示了如何使用Mesh模型。&lt;br /&gt;
&lt;br /&gt;
该示例由三个较小的示例组成：&lt;br /&gt;
&lt;br /&gt;
Light switch server：一个实现了[Generic OnOff server model]的简约服务器，用于接收状态数据并控制板上LED 1的状态。&lt;br /&gt;
&lt;br /&gt;
Light switch client：一个实现了[Generic OnOff client model]的简约客户端。当用户按下任意按钮时，OnOff Set消息将发送到配置的目标地址。&lt;br /&gt;
&lt;br /&gt;
Mesh Provisioner：一个简单的静态配置设备应用，用于建立演示网络，该配置设备在一个网状网络中配置所有节点。 此外，配置设备还可以在这些节点上配置网格模型[Generic OnOff model]实例的绑定以及发布和订阅设置，以使它们能够相互通信。&lt;br /&gt;
&lt;br /&gt;
[Generic OnOff Client/Server]模型用于操纵打开/关闭状态。请注意，当服务器设置了发布地址（如本例所示）时，服务器会将其状态更改的任何操作披露到其发布地址。&lt;br /&gt;
&lt;br /&gt;
下图给出了将由静态供应商设置的网状网络的整体视图， 括号中的数字表示预配置程序分配给这些节点的地址。&lt;br /&gt;
[[文件:Mesh network.png|边框|居中|无框|727x727像素]]&lt;br /&gt;
灯开关服务器和灯开关客户端示例均具有预配方角色。它们支持通过广告承载（PB-ADV）和GATT承载（PB-GATT）进行配置，并且还支持网状代理服务器（Proxy Service），但是不支持代理客户端（Proxy Client）。&lt;br /&gt;
&lt;br /&gt;
=== 硬件说明 ===&lt;br /&gt;
完成这个实验，我们最少需要两个开发板硬件用做我们的节点设备（Node）：&lt;br /&gt;
* 一个开发板用做client&lt;br /&gt;
* 一个或者多个开发板用做server&lt;br /&gt;
此外，我们还需要以下之一作为我们的配置设备（Provisioner）：&lt;br /&gt;
* 如果您决定使用静态预配器示例，则多准备一个开发板&lt;br /&gt;
* 如果您决定使用app程序进行设置，则需要安装手机app@link_nrf_mesh_app（@link_nrf_mesh_app_ios或@link_nrf_mesh_app_android）。&lt;br /&gt;
{{Note|text=默认我们使用3个开发板完成我们的实验，3个开发板分别对应client、server以及provisioner三个类型。|type=warning}}&lt;br /&gt;
&lt;br /&gt;
=== 软件说明 ===&lt;br /&gt;
1、我们使用NrfGo上位机，依次对三个开发板进行擦除及softdevice的烧写&lt;br /&gt;
[[文件:Lightswitch nrfgo.png|边框|居中|无框|1013x1013像素]]&lt;br /&gt;
2、使用SEGGER Embedded Studio for ARM编译器分别打开我们`&amp;lt;InstallFolder&amp;gt;/examples/light_switch`路径下的3个实验，并完成编译&lt;br /&gt;
&lt;br /&gt;
●\client\light_switch_client_nrf52832_xxAA_s132_6_1_0.emProject&lt;br /&gt;
&lt;br /&gt;
●\server\light_switch_server_nrf52832_xxAA_s132_6_1_0.emProject&lt;br /&gt;
&lt;br /&gt;
●\provisioner\light_switch_provisioner_nrf52832_xxAA_s132_6_1_0.emProject&lt;br /&gt;
[[文件:Lightswitch SES build.png|边框|居中|无框|1013x1013像素]]&lt;br /&gt;
3、此时我们对3个开发板分别进行固件的烧写，分别烧写client、server、provisioner（建议大家给3个开发板贴上标签，方便后续区分）&lt;br /&gt;
[[文件:Lightswitch SES download.png.gif|边框|居中|无框|1009x1009像素]]&lt;br /&gt;
&lt;br /&gt;
=== 实验现象 ===&lt;br /&gt;
{{Note|text=注意：保证我们的3个设备都是刚按照软件说明部分的步骤配置完成，并且没有进行过任何硬件控制（按键操作）。目的是保证所有设备均未被我们人为配置过，没有进行过网络配置，否则可能有任意异常（不同网络配置，导致的不同现象）|type=danger}}&lt;br /&gt;
1、首先我们分别给client和provisioner供电，然后我们按下provisioner的按键S1：&lt;br /&gt;
&lt;br /&gt;
●provisoner：LED灯点亮，代表正在配置查找设备，配置组网&lt;br /&gt;
&lt;br /&gt;
●client：首先LED3和LED4闪烁，代表正在组网；LED1-LED4四个灯一起闪烁，代表组网完成&lt;br /&gt;
&lt;br /&gt;
我们如果打开RTT检测log打印，可以看到如下的信息，这个时候我们已经给client设备分配了Node Address为0x0100。provisioner信息（上），client信息（下）。&lt;br /&gt;
[[文件:Lightswitch rtt provisoner1.png|边框|无框|1080x1080像素|居中]]&lt;br /&gt;
[[文件:Lightswitch_rtt_node_client.png|边框|居中|无框|656x656像素]]&lt;br /&gt;
&lt;br /&gt;
2、使用provisioner配置好client之后，我们给server开发板上电，这个时候provisioner将会配置server入网：&lt;br /&gt;
&lt;br /&gt;
●provisoner：LED1点亮代表正在配置网络，LED2点亮代表网络配置完成&lt;br /&gt;
&lt;br /&gt;
●server：首先LED3和LED4闪烁，代表正在组网；LED1-LED4四个灯一起闪烁，代表组网完成&lt;br /&gt;
&lt;br /&gt;
我们打开RTT检测log打印，可以看到server已经配置入网，并且被分配了地址Node Address为0x0104。provisioner信息（上），server信息（下）。&lt;br /&gt;
[[文件:Lightswitch rtt provisoner2.png|边框|无框|710x710像素|居中]]&lt;br /&gt;
[[文件:Lightswitch rtt node server.png|边框|无框|656x656像素|居中]]&lt;br /&gt;
&lt;br /&gt;
3、此时整个网络的配置已经完成，这个时候我们可以按下client上的按键来控制server上的LED点亮或者熄灭。&lt;br /&gt;
&lt;br /&gt;
由于我们的server被provisioner分配的Node Address是0x0104（偶数），所以我们通过client的S3控制server的LED1点亮，通过client的S4控制server的LED1熄灭。&lt;br /&gt;
{{Note|text=client设备对于server设备的控制，通过Node Address的奇偶位不同，分成了两个组。&lt;br /&gt;
client的S1和S2分别控制Node Address为奇数的server设备的LED1点亮和熄灭。&lt;br /&gt;
&lt;br /&gt;
client的S3和S4分别控制Node Address为偶数的server设备的LED1点亮和熄灭。|type=warning}}&lt;br /&gt;
我们打开RTT检测log打印，可以看到client分别按下button2（S3）以及button3（S4），分别会设置server的GPIO输出高低电平。client信息（上），server信息（下）。&lt;br /&gt;
[[文件:Lightswitch rtt node client2.png|边框|无框|656x656像素|居中]]&lt;br /&gt;
[[文件:Lightswitch rtt node server2.png|边框|无框|656x656像素|居中]]&lt;br /&gt;
&lt;br /&gt;
4、至此我们的mesh组网的lightswitch实验测试完成。&lt;br /&gt;
&lt;br /&gt;
== ①广播（通信TX） ==&lt;br /&gt;
有关广播部分，我们需要给大家讲解三个方面，分别如下：&lt;br /&gt;
&lt;br /&gt;
1、PB-ADV，这个是mesh中的广播承载层，用于我们启动配置过程（暂时的例程和手机app都是用的PB-GATT来配置的，但是源码中PB-ADV的流程是包含的）。&lt;br /&gt;
&lt;br /&gt;
2、Unprov Beacon，这个是广播一个未被配置的beacon，就是一个特殊形式的广播数据包，用于声明自己是未经配置的设备。&lt;br /&gt;
&lt;br /&gt;
3、PB-GATT，这个是mesh中的GATT承载层，用于我们启动配置过程（当前手机app就是使用PB-GATT，也就是连接之后，通过GATT服务去发送配置信息）。&lt;br /&gt;
&lt;br /&gt;
4、Publish，这个是我们用于模型发布数据的部分，我们将需要发送的数据通过advData广播出去。&lt;br /&gt;
&lt;br /&gt;
=== PB-ADV ===&lt;br /&gt;
PB-ADV说明请查看“BLE-Mesh技术揭秘”第8.1章节。&lt;br /&gt;
&lt;br /&gt;
我们来看下PB-ADV整个的流程，首先在mian文件中，我们的start()函数下，我们调用了mesh_provisionee_prov_start()函数去启动节点设备的等待配置。&lt;br /&gt;
[[文件:PB-ADV-01.png|边框|居中|无框|1200x1200px]]我们继续追踪mesh_provisionee_prov_start()，可以看到在使能宏定义MESH_FEATURE_PB_ADV_ENABLED的情况下，可以追踪到nrf_mesh_prov_bearer_adv_interface_get()函数。&lt;br /&gt;
[[文件:PB-ADV-02.png|边框|居中|无框|1200x1200px]]在nrf_mesh_prov_bearer_adv_interface_get()函数中，我们可以看到prov_bearer_interface_t结构体，这个结构体下面提供了我们配置承载层需要的API函数。&lt;br /&gt;
[[文件:PB-ADV-03.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
=== Unprov Beacon ===&lt;br /&gt;
Unprov Beacon说明请查看“BLE-Mesh技术揭秘”第5.5及5.5.1章节。&lt;br /&gt;
&lt;br /&gt;
上面我们有说到PB-ADV是承载层用于配置节点的功能，而Unprov Beacon则是我们的节点设备用于声明自己未经配置，所以这两个其实是一脉相承的。&lt;br /&gt;
&lt;br /&gt;
紧接着上面的nrf_mesh_prov_bearer_adv_interface_get()函数，其中的prov_bearer_interface_t结构体包含的是处理承载层的API函数，在这个结构体当中有一个.listen_start = prov_bearer_adv_listen，是用于开始监听传入的配置链接的功能，在这个函数中我们可以看到send_unprov_beacon()函数。&lt;br /&gt;
[[文件:PB-ADV-04.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
send_unprov_beacon()函数是用于发送Unprov Beacon数据的函数，在这个函数中我们调用prov_beacon_unprov_build函数去创建用于广播的Unprov Beacon数据包，然后调用advertiser_interval_set函数去设置广播的间隔，最后调用advertiser_packet_send函数去将我们的数据包发送出去。&lt;br /&gt;
&lt;br /&gt;
默认的Unprov Beacon广播间隔是NRF_MESH_PROV_BEARER_ADV_UNPROV_BEACON_INTERVAL_MS 2000。&lt;br /&gt;
&lt;br /&gt;
最终调用的是advertiser_packet_send(0函数去将广播数据发送出去的。&lt;br /&gt;
[[文件:PB-ADV-05.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Publish-12.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
这里我们再最后看一下prov_beacon_unprov_build()函数，这个函数是生成我们的Unprov Beacon的数据包。&lt;br /&gt;
&lt;br /&gt;
其中标识位是0x2B，这个flag是beacon的意思。data的第一个字节0x00代表是我们的BEACON_TYPE_UNPROV（未配置的节点设备），接下来的16个字节是device uuid（这个是寄存器FICR中的device id生成的），接下来2字节是oob info（这里是00 00，代表没有使能OOB），最后的4个字节是URI通过AES-CMAC算法计算出来的值。&lt;br /&gt;
[[文件:PB-GATT-11.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:PB-GATT-12.png|边框|居中|无框]]&lt;br /&gt;
&lt;br /&gt;
=== PB-GATT ===&lt;br /&gt;
PB-GATT说明请查看“BLE-Mesh技术揭秘”第8.1章节。&lt;br /&gt;
&lt;br /&gt;
和PB-ADV一样的，我们从main文件中的start()函数，追踪到mesh_provisionee_prov_start()，可以看到在使能宏定义MESH_FEATURE_PB_GATT_ENABLED的情况下，可以追踪到nrf_mesh_prov_bearer_gatt_init()以及nrf_mesh_prov_bearer_gatt_interface_get()函数。&lt;br /&gt;
[[文件:PB-GATT-01.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
首先我们看一下nrf_mesh_prov_bearer_gatt_init()函数，这个是初始化并设置PB-GATT服务（通过Mesh GATT接口）的函数。对比我们使用nrf master control panel手机APP连接之后获取到的服务，可以看到其中的PB-GATT的服务以及特征值。&lt;br /&gt;
&lt;br /&gt;
再往下的mesh_gatt_init()函数，这个就不给大家分析了，是和我们普通的BLE一样的方式去注册一个服务。&lt;br /&gt;
[[文件:PB-GATT-04.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:PB-GATT-03.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:PB-GATT-02.png|边框|居中|无框]]&lt;br /&gt;
在nrf_mesh_prov_bearer_gatt_interface_get()函数中，我们可以看到prov_bearer_interface_t结构体，这个结构体下面提供了我们配置承载层需要的API函数（这个是和上方的PB-ADV是一样的）。&lt;br /&gt;
[[文件:PB-GATT-05.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
和Unprov Beacon类似的，当我们开始监听配置链接的时候，会触发.listen_start = listen_start_cb的回调，在这个回调中我们触发FSM（有限状态机）的事件E_LISTEN_START，然后调用link_evt_send()去post顺序承载事件。&lt;br /&gt;
[[文件:PB-GATT-06.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
然后我们来到状态机列表m_pb_gatt_fsm_transition_table[]，可以看到E_LISTEN_START事件ID代表的动作ID是A_LISTEN_START。而最终我们的A_LISTEN_START动作，执行的是a_listen_start函数功能（这其中涉及的代码逻辑，暂时先不说明，大家可以自己查看源码的引用）。&lt;br /&gt;
[[文件:PB-GATT-07.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:PB-GATT-08.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
然后我们继续追踪a_listen_start()，可以看到其中调用了mesh_adv_params_set函数设置了我们广播的超时时间MESH_ADV_TIMEOUT_INFINITE以及广播间隔NRF_MESH_PROV_BEARER_GATT_UNPROV_BEACON_INTERVAL_MS，调用mesh_adv_data_set函数设置了我们的广播数据内容，最后调用mesh_adv_start函数启动广播。&lt;br /&gt;
&lt;br /&gt;
默认的PB-GATT的广播数据的广播周期是NRF_MESH_PROV_BEARER_GATT_UNPROV_BEACON_INTERVAL_MS 200。&lt;br /&gt;
[[文件:PB-GATT-09.png|边框|居中|无框|1200x1200像素]]我们同样的看一下mesh_adv_data_set()函数最终配置生成的广播数据，可以看到广播数据的flag是0x06，服务的UUID就是我们上面提到的PB-GATT服务的UUID，接下来16字节UUID是和PB-ADV一样的FICR寄存器的device id值，最后的扫描回调数据携带的是设备的全名“nRF5x Mesh Light”。&lt;br /&gt;
[[文件:PB-GATT-13.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:PB-GATT-14.png|边框|居中|无框]]最后是我们的mesh_adv_start()函数去启动广播，可以看到调用是我们ble stack sotfdevice当中的API函数sd_ble_gap_adv_start()去启动的。&lt;br /&gt;
[[文件:PB-GATT-15.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
=== Publish（模型发布） ===&lt;br /&gt;
这一章节我们只给大家讲解模块publish的数据发布流程，暂时不说明模型的创建方法，以及如何使用，这个放在下面单独的模型章节进行说明。&lt;br /&gt;
&lt;br /&gt;
以generic_onoff_server模型为例，我们找到access_model_publish以及access_model_reply函数，这两个函数的功能类似的，都是用于我们用户的模型发送数据的。&lt;br /&gt;
&lt;br /&gt;
其中access_model_publish是用于模型主动发送，而access_model_reply是用于模型给对应的Opcode回应数据。&lt;br /&gt;
&lt;br /&gt;
这里我们以access_model_publish函数为主，来看下发布数据的代码流程。&lt;br /&gt;
[[文件:Publish-01.png|边框|居中|无框|1200x1200像素]]查看一下access_model_publish()函数，我们可以看到packet_alloc_and_tx()函数，这个是我们继续发送数据的函数。下面还有一个蓝牙方框中的内容，是我们定义的重传机制（mesh数据为了保证传输的成功率，默认都是会重传数据包的，默认重传次数是4次）。[[文件:Publish-02.png|边框|居中|无框|1200x1200像素]]然后我们继续看一下packet_alloc_and_tx()函数，在这个函数中，我们最终会调用packet_tx()函数将数据发送出去。[[文件:Publish-03.png|边框|居中|无框|1200x1200像素]]继续追踪packet_tx()函数，这个函数的内容比较多，我们只截取了其中数据发送的部分，也就是我们的nrf_mesh_packet_send()函数，我们在他的tx_params中赋值好我们需要发送出去的数据内容。[[文件:Publish-04.png|边框|居中|无框|1199x1199像素]]nrf_mesh_packet_send()函数最终调用的是transport_tx()函数发送数据。而transport_tx()函数，最终是调用upper_transport_tx()函数进行的数据发送。[[文件:Publish-05.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Publish-06.png|边框|居中|无框|1200x1200像素]]我们再继续追踪upper_transport_tx()函数，可以看到我们会根据p_metadata-&amp;gt;segmented来判断数据是否要分包发送。&lt;br /&gt;
[[文件:Publish-13.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
接下里我们以不分包发送函数unsegmented_packet_tx()为例，来继续说明数据传输流程。&lt;br /&gt;
&lt;br /&gt;
我们可以看到我们的unsegmented_packet_tx()函数中，最终是调用的network_packet_send()函数。（分包的segmented_packet_tx()函数最终也是调用的network_packet_send()，大家可以自己追踪代码看一下）&lt;br /&gt;
[[文件:Publish-14.png|边框|居中|无框|1200x1200像素]]继续追踪我们的network_packet_send()函数，可以看到我们的net_packet_encrypt()加密函数，这个是将我们的明文数据，做好加密工作。&lt;br /&gt;
&lt;br /&gt;
然后调用core_tx_packet_send()函数发送出去。[[文件:Publish-15.png|边框|居中|无框|1200x1200像素]]最后我们追踪到core_tx_packet_send()函数，可以看到调用的是p_bearer-&amp;gt;p_interface-&amp;gt;packet_send()函数去发送的。所以接下来我们要找到我们的packet_send()功能是在哪边实现的。[[文件:Publish-07.png|边框|居中|无框|1200x1200像素]]可以看到我们是在core_tx_bearer_add函数中去获得的p_interface这个功能。[[文件:Publish-08.png|边框|居中|无框|1200x1200像素]]我们追踪core_tx_bearer_add()函数的引用路径，可以看到是在core_tx_adv_init()当中。（除了此处，还有另一处地方也有引用，功能是类似的，大家可以自行了解）。&lt;br /&gt;
&lt;br /&gt;
我们看下参数m_interface，可以看到m_interface-&amp;gt;packet_send功能，就是对应我们在core_tx_packet_send()函数调用的p_bearer-&amp;gt;p_interface-&amp;gt;packet_send()。[[文件:Publish-09.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Publish-10.png|边框|居中|无框|1200x1200像素]]所以我们接下来来看下m_interface结构体当中的packet_send()函数实现的是什么功能，可以看到最终调用的是advertiser_packet_send()函数将数据发送出去。看到这边我们就很熟悉了，和上面的几个广播功能，最后调用的都是相同的广播包发送函数。[[文件:Publish-11.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Publish-12.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
== ②扫描（通信RX） ==&lt;br /&gt;
&lt;br /&gt;
=== 标准BLE SCAN ===&lt;br /&gt;
看过了我们的广播功能之后，我们来看下扫描的部分。在ble mesh中，我们的扫描功能scan，就是用于获取数据的，类似于数据通信的RX。&lt;br /&gt;
&lt;br /&gt;
我们从main文件中的initialize()函数开始，首先是我们mesh_init()函数，最终我们会调用mesh_stack_init()函数。&lt;br /&gt;
[[文件:SCAN-01.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
在mesh_satck_init函数中，我们可以看到调用了nrf_mesh_init函数去初始化core的参数。&lt;br /&gt;
[[文件:SCAN-02.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
这一段是nrf_mesh_init函数部分，由于这个初始化函数太长，所以我们只截取了scan相关的部分。&lt;br /&gt;
&lt;br /&gt;
首先是第一点scanner_init(scanner_packet_process_cb)函数，在这个函数中，我们回去初始化扫描的参数配置，并且会开启扫描功能。最终我们可以从scanner_packet_process_cb这个回调中得到我们扫描到的数据（获得所有的BLE广播数据）。&lt;br /&gt;
&lt;br /&gt;
然后是第二点ad_listener_subscribe(&amp;amp;m_nrf_mesh_listener)，这个是用来侦听订阅的消息的（从获得的所有BLE广播数据中，筛选出的订阅的消息）。&lt;br /&gt;
[[文件:SCAN-03.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
然后我们继续看一下scanner_init的扫描初始化函数，在这个函数中我们首先调用scanner_config_reset()函数去配置扫描的默认参数。其中包含了：&lt;br /&gt;
&lt;br /&gt;
1、扫描的通道SCANNER_CHANNELS_DEFAULT，也就是BLE的｛37，38，39｝三个channels都去扫描。&lt;br /&gt;
&lt;br /&gt;
2、规范定义的非连接状态访问地址BEARER_ACCESS_ADDR_DEFAULT。&lt;br /&gt;
&lt;br /&gt;
3、配置物理层协议是1Mbit模式。&lt;br /&gt;
&lt;br /&gt;
4、扫描的间隔时间BEARER_SCAN_INT_DEFAULT_MS，扫描的窗口时间BEARER_SCAN_WINDOW_DEFAULT_MS&lt;br /&gt;
&lt;br /&gt;
5、射频的发射功率RADIO_POWER_NRF_0DBM。&lt;br /&gt;
&lt;br /&gt;
6、BLE广播包最大长度RADIO_CONFIG_ADV_MAX_PAYLOAD_SIZE。&lt;br /&gt;
&lt;br /&gt;
最后我们会在nrf_mesh_enable()函数中调用scanner_enable()函数去启动扫描，这个大家可以自己查看一下代码，比较容易理解。&lt;br /&gt;
[[文件:SCAN-04.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:SCAN-05.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
最后我们来看一下scanner_packet_process_cb()回调函数，其中我们可以直接拿到的扫描的数据包是rx_data，包含了数据的类型、数据内容以及数据长度等等信息。到这边我们的扫描功能是已经完成的，但是我们还需要再看下下面一部分的ad_listener的流程。&lt;br /&gt;
&lt;br /&gt;
=== Subscription（模型订阅） ===&lt;br /&gt;
我们需要注意下ad_listener_process()函数，这个函数是将我们扫描到的订阅数据（不符合标准的BLE广播数据，则认为是侦听到的订阅数据）传递到订阅者进行处理（订阅者指的是带订阅功能的模型）。这个是和我们上方说的关注点二ad_listener_subscribe()函数是一脉相承的。&lt;br /&gt;
[[文件:SCAN-06.png|边框|居中|无框|1200x1200像素]]然后我们来对照着看一下ad_listener_subscribe()函数以及ad_listener_process()函数。这两个函数一个是定义了侦听订阅，一个是将扫描获取到的数据传递给订阅者。这两者间的运作关系，大家有兴趣的可以继续查阅他的底层代码逻辑处理，一个是向list中添加订阅者信息，一个是将数据通过p_listener-&amp;gt;handler()将数据传递给订阅者处理。&lt;br /&gt;
[[文件:SCAN-07.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:SCAN-08.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
最终当我们扫描到订阅消息，并调用ad_listener_process()函数传递时，会触发我们注册的回调nrf_mesh_listen（调用ad_listener_subscribe(&amp;amp;m_nrf_mesh_listener)时，在m_nrf_mesh_listener中）。我们来看下nrf_mesh_listen函数中的处理，其中我们先从数据包中解析了数据的类型type，然后根据不同类型的数据进行相应的处理。&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+&lt;br /&gt;
|AD_TYPE_MESH &lt;br /&gt;
|蓝牙网格物体的AD类型&lt;br /&gt;
|-&lt;br /&gt;
|AD_TYPE_BEACON &lt;br /&gt;
|蓝牙网状信标的AD类型&lt;br /&gt;
|-&lt;br /&gt;
|AD_TYPE_PB_ADV &lt;br /&gt;
|PB-ADV消息的AD类型&lt;br /&gt;
|-&lt;br /&gt;
|AD_TYPE_DFU &lt;br /&gt;
|nRF OpenMesh消息的AD类型&lt;br /&gt;
|}&lt;br /&gt;
[[文件:SCAN-09.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
== ③入网验证 ==&lt;br /&gt;
经过前两章的学习，我们对于BLE MESH的广播及扫描的代码流程已经有了一定的了解，也就是已经大概的明白了数据的发送和接收的流程。&lt;br /&gt;
&lt;br /&gt;
那么从这一章节开始，我们将会给大家展示，如何配置一个节点设备入网。配置者设备，我们使用的是手机APP：nRF Mesh。&lt;br /&gt;
&lt;br /&gt;
我们从main文件中的start()函数开始，来看一下入网的代码处理及其流程。在start()函数中我们最后是调用的mesh_provisionee_prov_start()函数来启动的节点配置功能，我们来查看一下它所携带的参数prov_start_params。这部分的参数都是比较重要的，有携带的是设备的信息的、有携带认证密码的，有回调返回的，所以我们会挨个介绍一下。&lt;br /&gt;
[[文件:Prov-01.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
首先携带的是static_auth_data，这个是我们带外OOB认证的密码。如果我们在使用nRF Mesh去进行身份验证的时候，选择使用Static OOB，那么当我们在进行PB-GATT的连接过程中，会弹出一个新的窗口，要求我们去输入这个静态的128bit的OOB密钥。&lt;br /&gt;
[[文件:Prov-02.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Prov-14.png|边框|居中|无框]]&lt;br /&gt;
[[文件:Prov-15.png|边框|居中|无框]]&lt;br /&gt;
接下来携带的是provisioning_complete_cb()回调函数，这个函数处理的是节点设备成功的被配置入网，在这里我们可以获取到我们的节点设备被分配的node_Address。&lt;br /&gt;
[[文件:Prov-03.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
然后携带是device_identification_start_cb()回调函数，这个回调函数是用于通知开发者，我们的节点设备当前已经开始配置。&lt;br /&gt;
[[文件:Prov-04.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
再然后携带的是URI【统一资源标识符（Uniform Resource Identifier，URI)】，这个是用于标识我们的设备类型的，这个数据会通过AES-CMAC算法加密后添加到我们的Unprov Beacon数据的末尾。&lt;br /&gt;
&lt;br /&gt;
这里我们是light switch的server设备，所以我们定义URI是EX_URI_LS_SERVER。&lt;br /&gt;
[[文件:Prov-05.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
分析完携带的参数之后，我们继续来查看mesh_provisionee_prov_start()函数。在这个函数中，我们首先是定义了一下prov_caps，也就是配置支持的OOB功能。然后我们调用nrf_mesh_generate_keys()函数去生成有效的密钥对等待使用。&lt;br /&gt;
&lt;br /&gt;
再然后是调用nrf_mesh_prov_init()函数去初始化prov配置信息的结构体参数，里面有结构体本身m_pro_ctx，需要配置的参数公钥m_public、私钥m_private，以及我们刚刚定义的oob功能prov_caps，最后还携带了一个事件回调函数prov_ect_handler。&lt;br /&gt;
&lt;br /&gt;
接下来是PB-ADV以及PB-GATT的处理过程，这个前面已经讲解过，这里不再赘述。&lt;br /&gt;
&lt;br /&gt;
最后我们调用provisionee_start()函数去开启配置。&lt;br /&gt;
[[文件:Prov-06.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
我们继续来追踪一下provisionee_start()函数，在这个函数中我们调用nrf_mesh_prov_listen()函数去开启侦听承载层的消息。这里我们需要侦听PB-ADV以及PB-GATT两个。&lt;br /&gt;
[[文件:Prov-07.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
然后在nrf_mesh_prov_listen()函数中，我们调用prov_provisionee_listen()函数携带我们刚刚传入的nrf_mesh_prov_ctx_t结构体，以及p_bearer、URI、oob_info_source等参数信息，继续向下传递。&lt;br /&gt;
[[文件:Prov-08.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
最终我们来查看下prov_provisionee_listen()函数，在这个函数中我们传递了p_bearer-&amp;gt;p_callbacks给m_prov_callbacks回调，然后调用p_bearer-&amp;gt;p_interface-&amp;gt;listen_start()函数去启动PB-ADV以及PB-GATT的侦听。&lt;br /&gt;
[[文件:Prov-09.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
当我们在侦听之后，接收到任何配置相关的消息，都将通过m_prov_callbacks()函数回调传递给我们处理，大家可以自行看下其中4个不同的功能函数，具体处理的都是什么消息。&lt;br /&gt;
[[文件:Prov-10.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
在prov_provisionee_listen()函数中，我们可以看到回调函数指针m_prov_callbacks指向的是p_bearer-&amp;gt;p_callbacks，我们一路向上查找，最终可以找到这个p_bearer-&amp;gt;p_callbacks函数就是指的prov_evt_handler()函数。所以其实我们用户可以在这一层去查看比较重要的配置事件返回，而不需要到上面的m_prov_callbacks中这样偏底层的地方去查看。&lt;br /&gt;
&lt;br /&gt;
在这个回调函数中，我们可以看到mian文件中注册的prov_device_identification_start_cb、prov_device_identification_stop_cb、provisioning_aborted_cb的返回。&lt;br /&gt;
[[文件:Prov-11.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
那么除了上方已经存在的几个回调，我们可以看到mian文件中的start()函数下的prov_start_params参数，还缺少一个配置完成的回调prov_complete_cb()。&lt;br /&gt;
&lt;br /&gt;
这个回调的返回方式比较特殊，和上方其他的都不同，是由SD（softdevice）直接从底层返回给我们的。注册这个回调的函数是NRF_SDH_STATE_OBSERVER，返回的回调函数是sd_state_evt_handler。其中触发的事件NRF_SDH_EVT_STATE_ENABLED，这个是在mian文件中的initialize()函数下的ble_stack_init()函数中就被触发的，这个大家可以自行查看一下，不太重要，因为都是nordic底层处理好的消息。&lt;br /&gt;
[[文件:Prov-13.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Prov-12.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
以上整个配置流程的处理，不太好分步展示相应的现象，这个大家可以自己使用SES在线仿真的方式去调试一下流程，这里我们直接给大家展示整个流程打印出的相应的信息，以及nRF Mesh中暂时出来的现象。&lt;br /&gt;
[[文件:Prov-16.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Prov-17.png|边框|居中|无框]]&lt;br /&gt;
&lt;br /&gt;
== ④设备信息分配 ==&lt;br /&gt;
当我们成功的配置节点设备入网之后，我们需要给节点设备主要分配如下几个信息，有关这几个要点的介绍，可以查看“BLE-Mesh技术揭秘”第3章节：&lt;br /&gt;
&lt;br /&gt;
1、节点在网络中的地址Node Address&lt;br /&gt;
&lt;br /&gt;
2、网络密钥Netkey&lt;br /&gt;
&lt;br /&gt;
3、应用密钥Appkey&lt;br /&gt;
&lt;br /&gt;
4、元素的地址Element Address&lt;br /&gt;
&lt;br /&gt;
5、绑定model的Appkey，分配Publish Address，Subscription Address&lt;br /&gt;
&lt;br /&gt;
=== Node Address ===&lt;br /&gt;
首先我们来看一下我们使用nRF Mesh成功配置我们的节点之后，打印出来的消息，开始的紫色字体显示了我们给当前节点设备分配的Node Address。&lt;br /&gt;
&lt;br /&gt;
后面紧接着还有四段黄色字体的内容（这个部分大家的工程是不会打印的，是我们修改例程打印出来给大家展示一下），这四个部分就是我们配置节点的时候，配置者设备（nRF Mesh）给节点设备的四个主要指令，这些指令的具体功能，大家可以查看mesh的协议手册。[[文件:Config-13.png|边框|居中|无框|1080x1080像素]]在代码中，我们会在mian文件的provisioning_complete_cb()回调函数中获取并打印我们的Node Address，这个回调触发的含义就是我们的节点配置已经完成了。&lt;br /&gt;
&lt;br /&gt;
具体这个回调在代码中的回调及触发流程，大家看可以查看“③入网验证”的说明。[[文件:Config-20.png|边框|居中|无框|1200x1200像素]]我们使用nRF Mesh配置好节点设备之后，app界面显示内容如下，包含了节点的信息（地址、名称、配置时间等等），元素和模型，以及Netkey和Appkey等几个重要的设备信息。[[文件:Config-01.jpg|边框|居中|无框]]&lt;br /&gt;
[[文件:Config-04.jpg|边框|居中|无框]]&lt;br /&gt;
&lt;br /&gt;
=== Netkey ===&lt;br /&gt;
我们点击查看一下Network key，可以看到里面有一个Network Key 1，这是一个16字节的网络密钥。我们可以在nRF Mesh的Settings界面去增加或者删除一个network key，用户可以自定义此密钥。[[文件:Config-02.jpg|边框|居中|无框]]在我们的代码中，我们是在pro_evt_handler回调的NRF_MESH_PROV_EVT_COMPLETE事件中获取这个netkey，这个回调函数的触发流程同样是在“③入网验证”已经有说明。[[文件:Prov-11.png|边框|居中|无框|1200x1200像素]]我们直接看下NRF_MESH_PROV_EVT_COMPLETE事件下的mesh_stack_provisioning_data_store()函数，在这个函数中我们主要的任务，就是将配置者分配给我们的node address、netkey、devkey等等信息，分别做一些存储以及功能绑定。&lt;br /&gt;
[[文件:Netkey-01.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
=== Appkey ===&lt;br /&gt;
同样的我们打开Application Key，可以看到其中包含的3个密钥，这里我们只选用了Application Key 1作为我们的密钥。大家同样可以在nRF Mesh的Settings界面去增加或者删除一个App key，用户可以自定义此密钥。[[文件:Config-03.jpg|边框|居中|无框]]appkey这里和netkey不同，netkey是我们网络中的节点设备以及我们配置者设备共有的一个密钥，只有拥有这个密钥，我们才能在当前网络中通信，所以当我们的节点设备配置入网后，会立刻得到netkey（必须要分配给我的节点设备才行）。&lt;br /&gt;
&lt;br /&gt;
而appkey只是我们的某个模型用于通信的密钥，所以我们称它为应用密钥，这个appkey的分配，是在我们的config server下。这个config_server_init的初始化流程，是在我们的mesh_stack_init()函数下，就去初始化配置好的。最终当我们有信息配置的时候，会触发opcode_handlers，我们可以从这个操作码列表中查找相应的信息处理。[[文件:Config-22.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Config-23.png|边框|居中|无框|1200x1200像素]]我们来看下config server的opcode_handlers[]操作码处理程序列表，使用黄色标记给大家圈了几个操作码指令，这几个是我们配置一个最简单的server节点设备，都需要使用到的，包含了：&lt;br /&gt;
&lt;br /&gt;
1、appkey的添加，CONFIG_OPCODE_APPKEY_ADD&lt;br /&gt;
&lt;br /&gt;
2、publish发布地址，CONFIG_OPCODE_MODEL_PUBLICATION_VIRTUAL_ADDRESS_SET&lt;br /&gt;
&lt;br /&gt;
3、subscription订阅地址，CONFIG_OPCODE_MODEL_SUBSCRIPTION_ADD&lt;br /&gt;
&lt;br /&gt;
4、模型appkey的绑定，CONFIG_OPCODE_MODEL_APP_BIND&lt;br /&gt;
&lt;br /&gt;
5、netkey的更新，CONFIG_OPCODE_NETKEY_ADD[[文件:Config-21.png|边框|居中|无框|1200x1200像素]]我们可以看到当接收到CONFIG_OPCODE_APPKEY_ADD操作码的数据，会调用handle_appkey_add()函数去处理，在这个函数中我们最终会调用dsm_appkey_add()函数去存储我们的appkey。[[文件:Appkey-01.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
== ⑤Element元素 ==&lt;br /&gt;
元素简单的说就是定义了节点的功能，一些具有复杂功能的节点设备，会包含多个元素。一个节点设备最少需要包含一个元素，我们称之为主元素。&lt;br /&gt;
&lt;br /&gt;
从主元素开始（如果有多个元素，则第一个元素就是主元素），我们会给它分配相应的元素地址。主元素的地址就是节点的地址，第二个元素的地址就是主元素的地址基础上加上1，依次类推。&lt;br /&gt;
&lt;br /&gt;
可以看到在我们的主元素Element：0x0002中包含了3个不同的服务，这3个服务就是我们的模型Model，其中Configuration Server与Health Server这两个服务，是每一个节点都必须携带的模型。Configuration Server（配置服务）是用于配置节点设备信息，Health Server（健康服务）是用于节点进行网络内的心跳联系。剩下的Generic On Off Server服务，这个就是我们用于控制一个开关量的模型（在例程中我们通过控制LED的IO口高低电平来展示）。[[文件:Config-05.jpg|边框|居中|无框]]在我们的代码中，我们首先需要在nrf_mesh_config_app.h中定义好我们节点设备的最大元素数量，可以看到我们定义#define ACCESS_ELEMENT_COUNT (1)，也就是说我们当前的例程，只含有一个主元素。&lt;br /&gt;
[[文件:Element-01.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
而这个ACCESS_ELEMENT_COUNT ，是在我们开启节点配置的地方被调用的，这个mesh_provsionionee_prov_start()函数在前面的内容中有多次讲到，这里就不再说明了。&lt;br /&gt;
[[文件:Element-02.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
== ⑥model模型 ==&lt;br /&gt;
如果说元素是一个功能集合，那么模型就是元素下的具体的某一个功能。&lt;br /&gt;
&lt;br /&gt;
我们以灯的控制为例，灯的常用控制有开关、亮度、色温几种，其中开关、亮度和色温就是具体的模块，而处理灯控的集合就是元素。&lt;br /&gt;
&lt;br /&gt;
=== Configure Server ===&lt;br /&gt;
配置服务模型，这个的大概代码处理流程在我们的“④设备信息分配-&amp;gt;Appkey”已经有了说明，它是我们节点设备必须包含的一个特殊功能的模型。&lt;br /&gt;
&lt;br /&gt;
它的实际功能就像它的名字一样，就是用于配置（configuration）我们的设备信息，比如appkey的添加删除，密钥的绑定删除，其他的模型的发布和订阅控制。具体包含的操作码的使用方法，我们会在generic on off server模型当中使用的时候来说明。&lt;br /&gt;
&lt;br /&gt;
=== Health Server ===&lt;br /&gt;
健康服务，顾名思义就是判断我们的设备是否在网络中保持健康（正常）工作，它同样是一个必须包含的特殊功能的模型。&lt;br /&gt;
&lt;br /&gt;
它的初始化和config server是一样的，都是放在了mesh_stack_init()函数下。&lt;br /&gt;
[[文件:Health-01.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
我们来看下health_server_init()函数，最终我们会调用access_model_add()函数去初始化模型，并将模型添加到对应的元素中。&lt;br /&gt;
&lt;br /&gt;
其中的access_model_add_params_t参数，包含的分别是元素的索引（将模型添加到第几个元素当中），模型的ID（由mesh协议手册确定好了），以及最最重要的操作码列表，这个列表中会包含我们的指令码，以及对应的函数功能处理。&lt;br /&gt;
[[文件:Health-02.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
最后我们看下具体包含了哪些健康模型操作码：&lt;br /&gt;
&lt;br /&gt;
1、“运行状况当前状态”消息的操作码，HEALTH_OPCODE_CURRENT_STATUS&lt;br /&gt;
&lt;br /&gt;
2、“ 健康故障状态”消息的操作码，HEALTH_OPCODE_FAULT_STATUS&lt;br /&gt;
&lt;br /&gt;
3、“  获取健康注意事项”消息的操作码，HEALTH_OPCODE_ATTENTION_GET&lt;br /&gt;
&lt;br /&gt;
4、“ 健康注意集”消息的操作码，HEALTH_OPCODE_ATTENTION_SET&lt;br /&gt;
&lt;br /&gt;
5、“未确认健康注意事项”消息的操作码，HEALTH_OPCODE_ATTENTION_SET_UNACKED&lt;br /&gt;
&lt;br /&gt;
6、“健康注意状态”消息的操作码，HEALTH_OPCODE_ATTENTION_STATUS&lt;br /&gt;
&lt;br /&gt;
7、“健康故障清除”消息的操作码，HEALTH_OPCODE_FAULT_CLEAR&lt;br /&gt;
&lt;br /&gt;
8、“健康故障清除未确认”消息的操作码，HEALTH_OPCODE_FAULT_CLEAR_UNACKED&lt;br /&gt;
&lt;br /&gt;
9、“健康故障获取”消息的操作码，HEALTH_OPCODE_FAULT_GET&lt;br /&gt;
&lt;br /&gt;
10、“健康故障测试”消息的操作码，HEALTH_OPCODE_FAULT_TEST&lt;br /&gt;
&lt;br /&gt;
11、“未确认健康故障测试”消息的操作码，HEALTH_OPCODE_FAULT_TEST_UNACKED&lt;br /&gt;
&lt;br /&gt;
12、“健康期获取”消息的操作码，HEALTH_OPCODE_PERIOD_GET&lt;br /&gt;
&lt;br /&gt;
13、“健康期设置”消息的操作码，HEALTH_OPCODE_PERIOD_SET&lt;br /&gt;
&lt;br /&gt;
14、“未确认健康期设置”消息的操作码，HEALTH_OPCODE_PERIOD_SET_UNACKED&lt;br /&gt;
&lt;br /&gt;
15、“健康期状态”消息的操作码，HEALTH_OPCODE_PERIOD_STATUS&lt;br /&gt;
[[文件:Health-03.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
在大家的前期使用中，一般也不会真的需要大家去处理health server下的指令，毕竟我们刚开始使用，也很难让设备在网络中出现各种运行问题（节点少功能少）。但是当大家自己开始开发实际功能时，就一定要注意分析health server下的指令码返回，用来快速解决我们网络中的问题。&lt;br /&gt;
&lt;br /&gt;
=== Generic On Off Server ===&lt;br /&gt;
接下来我们来看下Generic On Off Server服务，这个服务和上方的两个服务不太一样，他不是我们的特殊必要服务，是一个通用的模型（用于具体的功能数据的处理，这里是用于开关功能）。借用这个服务模型，我们给大家介绍一下如何创建一个符合BLE MESH协议的模型。&lt;br /&gt;
&lt;br /&gt;
我们看下模型的初始化方法，在generic_onoff_server_init()函数中，首先我们使用access_model_add_params_t定义一个模型分配参数的结构体init_params，在这个结构体当中，我们要给定：&lt;br /&gt;
&lt;br /&gt;
1、model_id（模型的ID），这个是mesh协议规定好的，我们可以查看“归档资料\8-蓝牙协议手册和芯片手册\蓝牙mesh协议手册\《MshMDLv1.0.1.pdf》”的第7.3章节有归纳&lt;br /&gt;
&lt;br /&gt;
2、element_index（元素索引），这个看我们准备将这个服务添加到哪一个元素下，这里我们因为只有一个主元素，所以我们的元素索引为0&lt;br /&gt;
&lt;br /&gt;
3、p_opcode_handlers（操作码列表，指令码列表），这个也是mesh协议规定好的，我们可以查看“归档资料\8-蓝牙协议手册和芯片手册\蓝牙mesh协议手册\《MshMDLv1.0.1.pdf》”的第7.1章节归纳，具体的操作码的功能及要求，大家需要对应这个pdf手册，自己查看一下，对于一个模型ID，我们不一定要注册所有的属于它的opcode，只要注册我们需要使用的即可&lt;br /&gt;
&lt;br /&gt;
4、opcode_count（操作码数量），准备注册的操作码的数量&lt;br /&gt;
&lt;br /&gt;
5、p_args（通用参数指针），用于参数的传递&lt;br /&gt;
&lt;br /&gt;
6、publish_timeout_cb（发布超时时间），发布的定时器到期，会进入此回调，我们可以做一些处理（例如重新发送数据等等）。如果我们的模型不支持定期发布，可以将此值设置为NULL。&lt;br /&gt;
&lt;br /&gt;
当我们配置好模型的参数结构体之后，会调用access_model_add()函数去初始化模型并添加到元素，同时会返回给我们用于存储此模型的句柄指针p_server-&amp;gt;model_handle。&lt;br /&gt;
&lt;br /&gt;
因为我们的onoff模型，需要订阅消息，所以我们需要调用access_model_subscription_list_alloc()函数，携带我们的模型句柄去分配模型的订阅列表。&lt;br /&gt;
[[文件:Onoff-01.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
看完模型的初始化注册之后，我们来看下模型包含的opcode操作码，可以看到在m_opcode_handlers[]操作码列表中包含了三个opcode。这三个opcode我们可以看下他的定义，以及对照一下《MshMDLv1.0.1.pdf》第7.1章节的归纳，可以看到是一一对应的关系。具体这三个opcode的功能，大家可以查看手册中的说明，或者直接看下他们对应的功能处理函数。&lt;br /&gt;
[[文件:Onoff-02.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Onoff-03.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Onoff-04.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
接下来我们看下GENERIC_ONOFF_OPCODE_SET与GENERIC_ONOFF_OPCODE_SET_UNACKNOWLEDGED对应的handle_set，可以看到在这个函数的最后，我们会去调用status_send()函数去发送数据。&lt;br /&gt;
[[文件:Onoff-05.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
然后我们看下status_send()函数，在这个函数中，我们最终会调用access_model_publish来发布我们的数据，或者使用accsee_model_reply来回复数据。&lt;br /&gt;
&lt;br /&gt;
这两个函数再往下就是我们之前在广播部分讲解的packet_alloc_and_tx函数去发送广播数据了，这个有不清楚的，大家可以到前面去查看一下。&lt;br /&gt;
[[文件:Onoff-06.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
==== model bind appkey ====&lt;br /&gt;
那么按照上方的方法注册好模型，然后我们的节点设备被成功的配置入网之后，我们如何让这个模型工作起来。&lt;br /&gt;
&lt;br /&gt;
首先第一步是需要将我们的模型绑定到相应的appkey，只有这样我们的模型才能拿到用于通信的密钥，例如如下的截图当中，我们利用手机app软件nRF Mesh去给我们的Generic On Off Server模型绑定Appkey1。[[文件:Config-14.png|边框|居中|无框|1040x1040像素]]&lt;br /&gt;
[[文件:Config-06.jpg|边框|居中|无框]]&lt;br /&gt;
[[文件:Config-07.jpg|边框|居中|无框]]&lt;br /&gt;
[[文件:Config-08.jpg|边框|居中|无框]]当我们在使用app去绑定appkey的时候，在我们的代码中的体现是在config server下的opcode_handlers[]操作码列表中的CONFIG_OPCODE_MODEL_APP_BIND。&lt;br /&gt;
[[文件:Onoff-07.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
我们追踪一下CONFIG_OPCODE_MODEL_APP_BIND对应的handle_model_app_bind_unbind()，这个函数比较长，这边只给大家截取了部分，在为截取到部分，还有获取元素索引的get_element_index，获取模型ID和模型handle的access_handle_get，然后才是我们的dsm_appkey_index_to_appkey_handle函数用于将appkey索引给到appkey句柄。然后调用access_model_application_bind函数去绑定模型和appkey，最终如果是成功绑定，则调用access_flash_config_store函数去存储修改的绑定信息。&lt;br /&gt;
[[文件:Onoff-08.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
==== model publish address ====&lt;br /&gt;
当我们完成了模型的appkey绑定之后，我们就可以开始设置puhlish的地址以及subscription的地址。&lt;br /&gt;
&lt;br /&gt;
这里我们先看下nRF Mesh配置publish Address，可以看到我们选择的定义的0xC000组地址。[[文件:Model-pub-01.jpg|边框|居中|无框]]&lt;br /&gt;
[[文件:Model-pub-02.jpg|边框|居中|无框]]&lt;br /&gt;
[[文件:Model-pub-03.jpg|边框|居中|无框]]&lt;br /&gt;
[[文件:Model-pub-04.jpg|边框|居中|无框]]我们可以看到，在我们配置publish地址的时候，RTTViewer中会打印黄色标注的操作码数据信息，可以看到他的OPCODE是0x03。[[文件:Model-pub-05.png|边框|居中|无框|996x996像素]]我们来看下这个操作码对应的功能，可以看到CONFIG_OPCODE_MODEL_PUBLICATION_SET操作码对应的功能函数是handle_config_model_publication_set，根据函数的名称我们就可以看出，是用于配置模型发布的。[[文件:Model-sub-04.jpg|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Model-pub-07.jpg|边框|居中|无框|1200x1200像素]]在handle_config_model_publication_set函数中，可以看到我们先使用dsm_appkey_index_to_appkey_handle函数获取了appkey的句柄。然后调用dsm_address_publish_add函数去添加发布的地址。[[文件:Model-pub-08.jpg|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
==== model subscription address ====&lt;br /&gt;
然后我们再看下订阅地址的添加，可以看到我们使用nRF Mesh配置subscription订阅了组地址0xC001。[[文件:Model-sub-02.jpg|边框|居中|无框]]&lt;br /&gt;
[[文件:Model-sub-01.jpg|边框|居中|无框]]可以看到，当我们分配subscription地址的时候，可以看到RTTViewer中打印黄色标注的信息。可以看到他的OPCODE是0x801B。[[文件:Model-sub-03.jpg|边框|居中|无框|982x982像素]]我们来看下这个操作码对应的功能，可以看到CONFIG_OPCODE_MODEL_SUBSCRIPTION_ADD操作码对应的功能函数是handle_config_model_subscription_add，根据函数的名称我们就可以看出，是用于配置模型订阅的。[[文件:Model-pub-06.jpg|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Model-sub-05.jpg|边框|居中|无框|1200x1200像素]]在handle_config_model_subscription_add函数中，我们会调用dsm_address_subscription_add函数去添加我们的订阅地址。[[文件:Model-sub-06.jpg|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
== Mesh天猫精灵实验 ==&lt;br /&gt;
&lt;br /&gt;
=== 天猫精灵介绍 ===&lt;br /&gt;
天猫精灵（TmallGenie）是阿里巴巴人工智能实验室（Alibaba A.I.Labs）于2017年7月5日发布的AI智能产品品牌，当天同步发布了天猫精灵首款硬件产品——AI智能语音终端设备天猫精灵，未来还将推出更多AI智能产品。&lt;br /&gt;
&lt;br /&gt;
天猫精灵内置AliGenie操作系统，AliGenie生活在云端，它能够听懂中文普通话语音指令，目前可实现智能家居控制、语音购物、手机充值、叫外卖、音频音乐播放等功能，带来人机交互新体验。依靠阿里云的机器学习技术和计算能力，AliGenie能够不断进化成长，了解使用者的喜好和习惯，成为人类智能助手。&lt;br /&gt;
&lt;br /&gt;
=== AliGenie平台介绍 ===&lt;br /&gt;
'''阿里精灵AliGenie平台地址：[//E%3A/NRF52832/www.aligenie.com www.aligenie.com]'''&lt;br /&gt;
&lt;br /&gt;
'''IOT接入平台控制中心：[//E%3A/NRF52832/iot.aligenie.com/home iot.aligenie.com/home]'''&lt;br /&gt;
&lt;br /&gt;
'''IOT平台接入规范手册：[/www.aligenie.com/doc/357554/tgllbp https://www.aligenie.com/doc/357554/tgllbp]'''&lt;br /&gt;
&lt;br /&gt;
天猫精灵IoT开放平台，是阿里巴巴人工智能实验室(Alibaba A.I.Labs)面向品牌商、方案商、模组商/芯片商以及个人开发者推出的，将IoT物联网技术（蓝牙Mesh协议、WiFi协议、天猫精灵IoT云服务）和AI（天猫精灵ASR语音识别、NLP自然语言处理、TTS语音合成）等对外输出的开放式平台。&lt;br /&gt;
&lt;br /&gt;
开发者可按直连接入（WiFi模组、蓝牙Mesh模组）、云云接入（OAuth2.0）2类方式，接入天猫精灵软硬件生态（天猫精灵音箱、天猫精灵App、AliGenie Inside智能设备）及阿里巴巴集团生态服务，实现语音和触屏交互，为用户提供天猫精灵IoT控制、查询、播报和主动服务。目前已支持40+个IoT类目，1000+型号。天猫精灵IoT开放平台会持续创新，不断为开发者带来新技术，同时降低平台开发者进驻门槛，让AI普惠大众！&lt;br /&gt;
&lt;br /&gt;
=== 硬件连接 ===&lt;br /&gt;
1、NRF52832DK连接Jlink-Lite仿真器，并连接到电脑USB&lt;br /&gt;
&lt;br /&gt;
2、天猫精灵（方糖R）接电源供电，根据天猫精灵使用手册，将其连接上家里的WIFI&lt;br /&gt;
&lt;br /&gt;
=== 平台设备创建 ===&lt;br /&gt;
1、  登录IOT接入平台控制中心：[//E%3A/NRF52832/iot.aligenie.com/home iot.aligenie.com/home]，大家先点击右上角注册一下，因为是阿里巴巴旗下的产品，所以我们只需要用我们的淘宝账号就可以完成注册。&lt;br /&gt;
[[文件:Tmjl-03.png|边框|居中|无框|1189x1189像素]]&lt;br /&gt;
&lt;br /&gt;
2、  注册完成之后，我们登入平台，并且创建一个蓝牙mesh设备，如下图所示：&lt;br /&gt;
[[文件:Tmjl-04.gif|边框|居中|无框|1200x1200像素]]3、  记录刚刚平台分配给我们的三元组，任意一个就行，不需要所有的，举例如下：&lt;br /&gt;
{| class=&amp;quot;wikitable sortable mw-collapsible mw-collapsed&amp;quot;&lt;br /&gt;
|+&lt;br /&gt;
!Product ID&lt;br /&gt;
!Device Secret&lt;br /&gt;
!Mac 地址&lt;br /&gt;
|-&lt;br /&gt;
|9244&lt;br /&gt;
|0855f693ce1246f3e09653603283dc02&lt;br /&gt;
|f8a7635b7683&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== 软件配置 ===&lt;br /&gt;
将例程“ghostyu-tmjl”解压到mesh SDK的如下路径中，并且确认编译无误：\nrf5SDKforMeshv310src\examples\ghostyu-tmjl&lt;br /&gt;
[[文件:Tmjl-01.png|边框|居中|无框|450x450像素]]&lt;br /&gt;
[[文件:Tmjl-02.png|边框|居中|无框|650x650像素]]&lt;br /&gt;
&lt;br /&gt;
==== 三元组配置 ====&lt;br /&gt;
''三元组具体说明可以查看''：https://www.aligenie.com/doc/357554/gtgprq&lt;br /&gt;
&lt;br /&gt;
举例如平台设备创建第三步记录的三元组：&lt;br /&gt;
&lt;br /&gt;
'''Product ID'''：'''9244''' ，转成16进制就是'''0x0000241C''' ，所以最终数据是{'''1c 24 00 00'''}（4字节、低位在前）&lt;br /&gt;
&lt;br /&gt;
'''Mac地址'''：'''f8a7635b7683'''，所以最终数据是'''｛83 76 5b 63 a7 f8｝'''（6字节，低位在前）&lt;br /&gt;
&lt;br /&gt;
'''AUTH Data'''：利用'''三元组的数据'''以及'''SHA256计算器'''得到我们的加密数据，如下图所示，最终我们得到加密数据为：'''{ 0x73,0x76,0x09,0x93, 0x8d,0x6b,0x6a,0x52, 0x42,0x54,0xe5,0x41, 0x57,0xe4,0x75,0x15}'''&lt;br /&gt;
&lt;br /&gt;
''SHA256计算器''：https://www.jisuan.mobi/pmHbB1bmuum16Xxx.html&lt;br /&gt;
[[文件:Tmjl-05.png|边框|居中|无框|589x589像素]]&lt;br /&gt;
&lt;br /&gt;
==== 代码修改 ====&lt;br /&gt;
根据刚刚三元组配置产生的数据，修改mian文件中对应的值。&lt;br /&gt;
[[文件:Tmjl-06.png|边框|居中|无框|1292x1292像素]]&lt;br /&gt;
&lt;br /&gt;
==== 软件烧写 ====&lt;br /&gt;
修改好代码之后，我们依次进行如下步骤操作，注意'''不可跳跃任意步骤。'''&lt;br /&gt;
&lt;br /&gt;
1、利用nrfgo工具擦除芯片&lt;br /&gt;
&lt;br /&gt;
2、下载正确的softdevice&lt;br /&gt;
&lt;br /&gt;
3、编译天猫精灵例程（注意：已经完成“代码修改”步骤）&lt;br /&gt;
&lt;br /&gt;
4、将编译好的程序下载到开发板中&lt;br /&gt;
&lt;br /&gt;
=== 调试阶段 ===&lt;br /&gt;
&lt;br /&gt;
==== 确认设备状态 ====&lt;br /&gt;
在进入调试之前，我们确认一下设备状态，设备状态正确后，进入调试流程。&lt;br /&gt;
&lt;br /&gt;
1、  按下开发板复位按键，4个LED灯闪烁2次后熄灭&lt;br /&gt;
&lt;br /&gt;
2、  对天猫精灵说“天猫精灵，你联网了吗”，等待天猫精灵回复“我已联网”&lt;br /&gt;
&lt;br /&gt;
==== 调试流程 ====&lt;br /&gt;
1、  对天猫精灵说“天猫精灵，找队友”，天猫精灵回复“正在查找智能设备”&lt;br /&gt;
&lt;br /&gt;
2、  如果我们前面的软件配置是对的，过一会天猫精灵会回复“找到智能灯设备，是否连接”；如果配置有问题，天猫精灵找不到设备会回复“未找到智能设备”，这个时候我们需要检查软件配置流程中的product ID和Mac地址&lt;br /&gt;
&lt;br /&gt;
3、  若天猫精灵回复“找到智能灯设备，是否连接”，我们回答“连接”，天猫精灵会回复“正在连接智能设备，请稍等”&lt;br /&gt;
&lt;br /&gt;
4、  等待1段时间（1min以内），如果回复“智能设备连接成功”，则代表成功连接开发板；如果回复“连接失败”，则检查软件配置流程中的Data Auth密码&lt;br /&gt;
&lt;br /&gt;
5、  如果连接成功，我们按下开发板按键S2（'''注意：复位开发板后，都需要重新按下S2'''）&lt;br /&gt;
&lt;br /&gt;
6、  我们对天猫精灵说“天猫精灵，打开灯”，这个时候开发板上D1会点亮；如果如果说“天猫精灵，关闭灯”，这个时候开发板上D1会熄灭&lt;br /&gt;
&lt;br /&gt;
7、  到第6步结束，已经完成测试，接下来我们也可以到平台上调试一下。此时我们只能测试“打开灯”以及“关闭灯”功能。&lt;br /&gt;
[[文件:Tmjl-07.jpg|边框|居中|无框|1105x1105像素]]&lt;br /&gt;
[[文件:Tmjl-08.png|边框|居中|无框|1109x1109像素]]&lt;br /&gt;
&lt;br /&gt;
== Mesh串口透传实验 ==&lt;br /&gt;
[[分类:NRF52832DK]]&lt;br /&gt;
[[分类:实验手册]]&lt;/div&gt;</summary>
		<author><name>Jinx</name></author>
		
	</entry>
	<entry>
		<id>http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:Tmjl-08.png&amp;diff=2827</id>
		<title>文件:Tmjl-08.png</title>
		<link rel="alternate" type="text/html" href="http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:Tmjl-08.png&amp;diff=2827"/>
		<updated>2020-04-02T08:42:51Z</updated>

		<summary type="html">&lt;p&gt;Jinx：&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Tmjl-08&lt;/div&gt;</summary>
		<author><name>Jinx</name></author>
		
	</entry>
	<entry>
		<id>http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:Tmjl-07.jpg&amp;diff=2826</id>
		<title>文件:Tmjl-07.jpg</title>
		<link rel="alternate" type="text/html" href="http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:Tmjl-07.jpg&amp;diff=2826"/>
		<updated>2020-04-02T08:42:34Z</updated>

		<summary type="html">&lt;p&gt;Jinx：&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Tmjl-07&lt;/div&gt;</summary>
		<author><name>Jinx</name></author>
		
	</entry>
	<entry>
		<id>http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:Tmjl-06.png&amp;diff=2825</id>
		<title>文件:Tmjl-06.png</title>
		<link rel="alternate" type="text/html" href="http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:Tmjl-06.png&amp;diff=2825"/>
		<updated>2020-04-02T08:39:15Z</updated>

		<summary type="html">&lt;p&gt;Jinx：&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Tmjl-06&lt;/div&gt;</summary>
		<author><name>Jinx</name></author>
		
	</entry>
	<entry>
		<id>http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:Tmjl-05.png&amp;diff=2824</id>
		<title>文件:Tmjl-05.png</title>
		<link rel="alternate" type="text/html" href="http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:Tmjl-05.png&amp;diff=2824"/>
		<updated>2020-04-02T08:38:11Z</updated>

		<summary type="html">&lt;p&gt;Jinx：&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Tmjl-05&lt;/div&gt;</summary>
		<author><name>Jinx</name></author>
		
	</entry>
	<entry>
		<id>http://doc.iotxx.com/index.php?title=NRF52832DK-Mesh%E7%BB%84%E7%BD%91%E5%AE%9E%E9%AA%8C&amp;diff=2823</id>
		<title>NRF52832DK-Mesh组网实验</title>
		<link rel="alternate" type="text/html" href="http://doc.iotxx.com/index.php?title=NRF52832DK-Mesh%E7%BB%84%E7%BD%91%E5%AE%9E%E9%AA%8C&amp;diff=2823"/>
		<updated>2020-04-02T08:24:24Z</updated>

		<summary type="html">&lt;p&gt;Jinx：/* Mesh天猫精灵实验 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;蓝牙Mesh和BLE是并行发展的一个独立分支，虽然底层共用Physical Layer和Link Layer，但是上层协议并不相同。因此，nRF52832的Mesh例程，并不在BLE协议栈sdk中，而是安装一个扩展包。&lt;br /&gt;
&lt;br /&gt;
本文介绍蓝牙Mesh相关的环境搭建和实验介绍。&lt;br /&gt;
&lt;br /&gt;
== Mesh开发环境 ==&lt;br /&gt;
&lt;br /&gt;
=== 蓝牙Mesh SDK ===&lt;br /&gt;
SDK位置：&amp;lt;code&amp;gt;归档资料/2-协议栈SDK/nRF5_SDK_Mesh_v310.zip&amp;lt;/code&amp;gt;，注意文件名中的Mesh字样。&lt;br /&gt;
&lt;br /&gt;
和BLE协议栈一样，将该压缩包直接解压到BLE协议栈同级目录即可使用，解压后的SDK路径如下：&amp;lt;code&amp;gt;E:\project-nordic\nRF5_SDK_Mesh_v310&amp;lt;/code&amp;gt;&lt;br /&gt;
[[文件:NRF52832DK-MESH-SDK-INSTALL.gif|居中|无框|750x750像素]]&lt;br /&gt;
&lt;br /&gt;
=== 集成开发软件SES ===&lt;br /&gt;
SES时是Segger Embedded Studio编译器的缩写，用来编译蓝牙Mesh代码，SES功能与IAR或Keil类似。&lt;br /&gt;
&lt;br /&gt;
备注：Nordic为何使用SES而不是IAR，原因不得而知。&lt;br /&gt;
&lt;br /&gt;
==== SES安装 ====&lt;br /&gt;
SES软件位置：&amp;lt;code&amp;gt;归档资料/7-编译器SES/Setup_EmbeddedStudio_ARM_v410_win_x64.exe&amp;lt;/code&amp;gt;。&lt;br /&gt;
&lt;br /&gt;
我们双击打开安装界面，按照如下动图所示方式进行安装（可以全部默认安装）。[[文件:Ses install.gif|边框|居中|无框|556x556像素]]&lt;br /&gt;
&lt;br /&gt;
==== SES许可证激活 ====&lt;br /&gt;
我们双击打开安装好的SES，选择编译自带测试例程，此时弹出需要激活的界面。&lt;br /&gt;
[[文件:Licensed1.jpg|边框|居中|无框|1205x1205像素]]&lt;br /&gt;
由于我们准备使用SES编译nordic的NRF52832的SDK，所以这里我们选择Activate Your Free License。这里因为nordic公司已经帮客户想SEGGER公司买下了使用的版权，所以是免费的许可证。&lt;br /&gt;
[[文件:Licensed2.jpg|边框|居中|无框|736x736像素]]&lt;br /&gt;
我们按照要求填写好信息，主要是邮箱地址（用于接收SEGGER公司发给我们的许可证），填写完成之后点击Request License。下面是我们接收的邮件信息，我们将红色框内的内容复制下来。&lt;br /&gt;
[[文件:Ses install3.png|边框|居中|无框|1301x1301像素]]&lt;br /&gt;
打开SES，选择Tools\License Manager...，我们选择Activate Embedded Studio，打开如下界面，并将我们的许可证粘贴进去，然后点击Install License。&lt;br /&gt;
[[文件:Licensed4.png|边框|居中|无框|481x481像素]]&lt;br /&gt;
安装好许可证之后，跳转到如下的界面。&lt;br /&gt;
[[文件:Licensed5.png|边框|居中|无框|481x481像素]]&lt;br /&gt;
此时我们的许可证激活完毕（有时候需要等待一段时间，才会显示激活成功）。&lt;br /&gt;
[[文件:Licensed6.png|边框|居中|无框|515x515像素]]&lt;br /&gt;
&lt;br /&gt;
== mesh实验学习流程概论 ==&lt;br /&gt;
1、首先大家一定要先查看一下我们资料中的手册“BLE-Mesh技术揭秘”，这个是很重要的一个mesh基本概念的介绍，只有看完这个文档，我们才能明白mesh组网是什么，是怎么完成的，后续的我们实验内容，也会在这个文档中查到相应的对照。&lt;br /&gt;
&lt;br /&gt;
2、按照Light_switch测试实验的实验说明，跑通测试流程（可以固件清零重新烧写多测试几遍，有助于增强我们的流程熟练度，利于后面的开发）。light_switch实验是现在nordic提供给我们的唯一的真正意义上的mesh组网通信例程。&lt;br /&gt;
&lt;br /&gt;
3、根据我们下面拆分的组网流程，对照依次的实验说明，进行mesh组网流程的分步学习。前期实验部分我们主要讲解节点设备，配置者设备功能我们使用手机app完成，目的是减少大家的学习难度，配置者设备我们会放在最后一个实验给大家做介绍。&lt;br /&gt;
&lt;br /&gt;
①广播（通信TX）——了解节点设备的广播功能，分为三个重要部分：PB-ADV和Unprov Beacon，PB-GATT的广播，以及模型的Publish用于发送数据。&lt;br /&gt;
&lt;br /&gt;
②扫描（通信RX）——了解节点设备的扫描功能，分为两个重要部分：普通的BLE SCAN功能，以及模块的Subscription用于接收数据。&lt;br /&gt;
&lt;br /&gt;
③入网验证——了解节点设备接入网络的验证过程，实验中使用的是PB-GATT去配置的，包含了：交换公共密钥，完成验证（是否有带外OOB）。&lt;br /&gt;
&lt;br /&gt;
④设备信息分配——启动配置数据分配，给节点分配如下数据：网络key（Netkey）、应用key（Appkey）以及节点设备地址（nodeAddr）。&lt;br /&gt;
&lt;br /&gt;
⑤元素——了解节点设备的元素与模型定义。&lt;br /&gt;
&lt;br /&gt;
⑥模型——了解模型的功能和创建方法，模型的订阅subscription和发布publish功能。&lt;br /&gt;
&lt;br /&gt;
== Light_switch测试实验 ==&lt;br /&gt;
=== 实验简介 ===&lt;br /&gt;
此示例演示了包含充当两个角色的设备的网状生态系统：配置角色（Provisioner role）和节点角色（Node role、provisionee role）。 它还通过在应用程序中使用[Generic OnOff model]来演示了如何使用Mesh模型。&lt;br /&gt;
&lt;br /&gt;
该示例由三个较小的示例组成：&lt;br /&gt;
&lt;br /&gt;
Light switch server：一个实现了[Generic OnOff server model]的简约服务器，用于接收状态数据并控制板上LED 1的状态。&lt;br /&gt;
&lt;br /&gt;
Light switch client：一个实现了[Generic OnOff client model]的简约客户端。当用户按下任意按钮时，OnOff Set消息将发送到配置的目标地址。&lt;br /&gt;
&lt;br /&gt;
Mesh Provisioner：一个简单的静态配置设备应用，用于建立演示网络，该配置设备在一个网状网络中配置所有节点。 此外，配置设备还可以在这些节点上配置网格模型[Generic OnOff model]实例的绑定以及发布和订阅设置，以使它们能够相互通信。&lt;br /&gt;
&lt;br /&gt;
[Generic OnOff Client/Server]模型用于操纵打开/关闭状态。请注意，当服务器设置了发布地址（如本例所示）时，服务器会将其状态更改的任何操作披露到其发布地址。&lt;br /&gt;
&lt;br /&gt;
下图给出了将由静态供应商设置的网状网络的整体视图， 括号中的数字表示预配置程序分配给这些节点的地址。&lt;br /&gt;
[[文件:Mesh network.png|边框|居中|无框|727x727像素]]&lt;br /&gt;
灯开关服务器和灯开关客户端示例均具有预配方角色。它们支持通过广告承载（PB-ADV）和GATT承载（PB-GATT）进行配置，并且还支持网状代理服务器（Proxy Service），但是不支持代理客户端（Proxy Client）。&lt;br /&gt;
&lt;br /&gt;
=== 硬件说明 ===&lt;br /&gt;
完成这个实验，我们最少需要两个开发板硬件用做我们的节点设备（Node）：&lt;br /&gt;
* 一个开发板用做client&lt;br /&gt;
* 一个或者多个开发板用做server&lt;br /&gt;
此外，我们还需要以下之一作为我们的配置设备（Provisioner）：&lt;br /&gt;
* 如果您决定使用静态预配器示例，则多准备一个开发板&lt;br /&gt;
* 如果您决定使用app程序进行设置，则需要安装手机app@link_nrf_mesh_app（@link_nrf_mesh_app_ios或@link_nrf_mesh_app_android）。&lt;br /&gt;
{{Note|text=默认我们使用3个开发板完成我们的实验，3个开发板分别对应client、server以及provisioner三个类型。|type=warning}}&lt;br /&gt;
&lt;br /&gt;
=== 软件说明 ===&lt;br /&gt;
1、我们使用NrfGo上位机，依次对三个开发板进行擦除及softdevice的烧写&lt;br /&gt;
[[文件:Lightswitch nrfgo.png|边框|居中|无框|1013x1013像素]]&lt;br /&gt;
2、使用SEGGER Embedded Studio for ARM编译器分别打开我们`&amp;lt;InstallFolder&amp;gt;/examples/light_switch`路径下的3个实验，并完成编译&lt;br /&gt;
&lt;br /&gt;
●\client\light_switch_client_nrf52832_xxAA_s132_6_1_0.emProject&lt;br /&gt;
&lt;br /&gt;
●\server\light_switch_server_nrf52832_xxAA_s132_6_1_0.emProject&lt;br /&gt;
&lt;br /&gt;
●\provisioner\light_switch_provisioner_nrf52832_xxAA_s132_6_1_0.emProject&lt;br /&gt;
[[文件:Lightswitch SES build.png|边框|居中|无框|1013x1013像素]]&lt;br /&gt;
3、此时我们对3个开发板分别进行固件的烧写，分别烧写client、server、provisioner（建议大家给3个开发板贴上标签，方便后续区分）&lt;br /&gt;
[[文件:Lightswitch SES download.png.gif|边框|居中|无框|1009x1009像素]]&lt;br /&gt;
&lt;br /&gt;
=== 实验现象 ===&lt;br /&gt;
{{Note|text=注意：保证我们的3个设备都是刚按照软件说明部分的步骤配置完成，并且没有进行过任何硬件控制（按键操作）。目的是保证所有设备均未被我们人为配置过，没有进行过网络配置，否则可能有任意异常（不同网络配置，导致的不同现象）|type=danger}}&lt;br /&gt;
1、首先我们分别给client和provisioner供电，然后我们按下provisioner的按键S1：&lt;br /&gt;
&lt;br /&gt;
●provisoner：LED灯点亮，代表正在配置查找设备，配置组网&lt;br /&gt;
&lt;br /&gt;
●client：首先LED3和LED4闪烁，代表正在组网；LED1-LED4四个灯一起闪烁，代表组网完成&lt;br /&gt;
&lt;br /&gt;
我们如果打开RTT检测log打印，可以看到如下的信息，这个时候我们已经给client设备分配了Node Address为0x0100。provisioner信息（上），client信息（下）。&lt;br /&gt;
[[文件:Lightswitch rtt provisoner1.png|边框|无框|1080x1080像素|居中]]&lt;br /&gt;
[[文件:Lightswitch_rtt_node_client.png|边框|居中|无框|656x656像素]]&lt;br /&gt;
&lt;br /&gt;
2、使用provisioner配置好client之后，我们给server开发板上电，这个时候provisioner将会配置server入网：&lt;br /&gt;
&lt;br /&gt;
●provisoner：LED1点亮代表正在配置网络，LED2点亮代表网络配置完成&lt;br /&gt;
&lt;br /&gt;
●server：首先LED3和LED4闪烁，代表正在组网；LED1-LED4四个灯一起闪烁，代表组网完成&lt;br /&gt;
&lt;br /&gt;
我们打开RTT检测log打印，可以看到server已经配置入网，并且被分配了地址Node Address为0x0104。provisioner信息（上），server信息（下）。&lt;br /&gt;
[[文件:Lightswitch rtt provisoner2.png|边框|无框|710x710像素|居中]]&lt;br /&gt;
[[文件:Lightswitch rtt node server.png|边框|无框|656x656像素|居中]]&lt;br /&gt;
&lt;br /&gt;
3、此时整个网络的配置已经完成，这个时候我们可以按下client上的按键来控制server上的LED点亮或者熄灭。&lt;br /&gt;
&lt;br /&gt;
由于我们的server被provisioner分配的Node Address是0x0104（偶数），所以我们通过client的S3控制server的LED1点亮，通过client的S4控制server的LED1熄灭。&lt;br /&gt;
{{Note|text=client设备对于server设备的控制，通过Node Address的奇偶位不同，分成了两个组。&lt;br /&gt;
client的S1和S2分别控制Node Address为奇数的server设备的LED1点亮和熄灭。&lt;br /&gt;
&lt;br /&gt;
client的S3和S4分别控制Node Address为偶数的server设备的LED1点亮和熄灭。|type=warning}}&lt;br /&gt;
我们打开RTT检测log打印，可以看到client分别按下button2（S3）以及button3（S4），分别会设置server的GPIO输出高低电平。client信息（上），server信息（下）。&lt;br /&gt;
[[文件:Lightswitch rtt node client2.png|边框|无框|656x656像素|居中]]&lt;br /&gt;
[[文件:Lightswitch rtt node server2.png|边框|无框|656x656像素|居中]]&lt;br /&gt;
&lt;br /&gt;
4、至此我们的mesh组网的lightswitch实验测试完成。&lt;br /&gt;
&lt;br /&gt;
== ①广播（通信TX） ==&lt;br /&gt;
有关广播部分，我们需要给大家讲解三个方面，分别如下：&lt;br /&gt;
&lt;br /&gt;
1、PB-ADV，这个是mesh中的广播承载层，用于我们启动配置过程（暂时的例程和手机app都是用的PB-GATT来配置的，但是源码中PB-ADV的流程是包含的）。&lt;br /&gt;
&lt;br /&gt;
2、Unprov Beacon，这个是广播一个未被配置的beacon，就是一个特殊形式的广播数据包，用于声明自己是未经配置的设备。&lt;br /&gt;
&lt;br /&gt;
3、PB-GATT，这个是mesh中的GATT承载层，用于我们启动配置过程（当前手机app就是使用PB-GATT，也就是连接之后，通过GATT服务去发送配置信息）。&lt;br /&gt;
&lt;br /&gt;
4、Publish，这个是我们用于模型发布数据的部分，我们将需要发送的数据通过advData广播出去。&lt;br /&gt;
&lt;br /&gt;
=== PB-ADV ===&lt;br /&gt;
PB-ADV说明请查看“BLE-Mesh技术揭秘”第8.1章节。&lt;br /&gt;
&lt;br /&gt;
我们来看下PB-ADV整个的流程，首先在mian文件中，我们的start()函数下，我们调用了mesh_provisionee_prov_start()函数去启动节点设备的等待配置。&lt;br /&gt;
[[文件:PB-ADV-01.png|边框|居中|无框|1200x1200px]]我们继续追踪mesh_provisionee_prov_start()，可以看到在使能宏定义MESH_FEATURE_PB_ADV_ENABLED的情况下，可以追踪到nrf_mesh_prov_bearer_adv_interface_get()函数。&lt;br /&gt;
[[文件:PB-ADV-02.png|边框|居中|无框|1200x1200px]]在nrf_mesh_prov_bearer_adv_interface_get()函数中，我们可以看到prov_bearer_interface_t结构体，这个结构体下面提供了我们配置承载层需要的API函数。&lt;br /&gt;
[[文件:PB-ADV-03.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
=== Unprov Beacon ===&lt;br /&gt;
Unprov Beacon说明请查看“BLE-Mesh技术揭秘”第5.5及5.5.1章节。&lt;br /&gt;
&lt;br /&gt;
上面我们有说到PB-ADV是承载层用于配置节点的功能，而Unprov Beacon则是我们的节点设备用于声明自己未经配置，所以这两个其实是一脉相承的。&lt;br /&gt;
&lt;br /&gt;
紧接着上面的nrf_mesh_prov_bearer_adv_interface_get()函数，其中的prov_bearer_interface_t结构体包含的是处理承载层的API函数，在这个结构体当中有一个.listen_start = prov_bearer_adv_listen，是用于开始监听传入的配置链接的功能，在这个函数中我们可以看到send_unprov_beacon()函数。&lt;br /&gt;
[[文件:PB-ADV-04.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
send_unprov_beacon()函数是用于发送Unprov Beacon数据的函数，在这个函数中我们调用prov_beacon_unprov_build函数去创建用于广播的Unprov Beacon数据包，然后调用advertiser_interval_set函数去设置广播的间隔，最后调用advertiser_packet_send函数去将我们的数据包发送出去。&lt;br /&gt;
&lt;br /&gt;
默认的Unprov Beacon广播间隔是NRF_MESH_PROV_BEARER_ADV_UNPROV_BEACON_INTERVAL_MS 2000。&lt;br /&gt;
&lt;br /&gt;
最终调用的是advertiser_packet_send(0函数去将广播数据发送出去的。&lt;br /&gt;
[[文件:PB-ADV-05.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Publish-12.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
这里我们再最后看一下prov_beacon_unprov_build()函数，这个函数是生成我们的Unprov Beacon的数据包。&lt;br /&gt;
&lt;br /&gt;
其中标识位是0x2B，这个flag是beacon的意思。data的第一个字节0x00代表是我们的BEACON_TYPE_UNPROV（未配置的节点设备），接下来的16个字节是device uuid（这个是寄存器FICR中的device id生成的），接下来2字节是oob info（这里是00 00，代表没有使能OOB），最后的4个字节是URI通过AES-CMAC算法计算出来的值。&lt;br /&gt;
[[文件:PB-GATT-11.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:PB-GATT-12.png|边框|居中|无框]]&lt;br /&gt;
&lt;br /&gt;
=== PB-GATT ===&lt;br /&gt;
PB-GATT说明请查看“BLE-Mesh技术揭秘”第8.1章节。&lt;br /&gt;
&lt;br /&gt;
和PB-ADV一样的，我们从main文件中的start()函数，追踪到mesh_provisionee_prov_start()，可以看到在使能宏定义MESH_FEATURE_PB_GATT_ENABLED的情况下，可以追踪到nrf_mesh_prov_bearer_gatt_init()以及nrf_mesh_prov_bearer_gatt_interface_get()函数。&lt;br /&gt;
[[文件:PB-GATT-01.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
首先我们看一下nrf_mesh_prov_bearer_gatt_init()函数，这个是初始化并设置PB-GATT服务（通过Mesh GATT接口）的函数。对比我们使用nrf master control panel手机APP连接之后获取到的服务，可以看到其中的PB-GATT的服务以及特征值。&lt;br /&gt;
&lt;br /&gt;
再往下的mesh_gatt_init()函数，这个就不给大家分析了，是和我们普通的BLE一样的方式去注册一个服务。&lt;br /&gt;
[[文件:PB-GATT-04.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:PB-GATT-03.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:PB-GATT-02.png|边框|居中|无框]]&lt;br /&gt;
在nrf_mesh_prov_bearer_gatt_interface_get()函数中，我们可以看到prov_bearer_interface_t结构体，这个结构体下面提供了我们配置承载层需要的API函数（这个是和上方的PB-ADV是一样的）。&lt;br /&gt;
[[文件:PB-GATT-05.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
和Unprov Beacon类似的，当我们开始监听配置链接的时候，会触发.listen_start = listen_start_cb的回调，在这个回调中我们触发FSM（有限状态机）的事件E_LISTEN_START，然后调用link_evt_send()去post顺序承载事件。&lt;br /&gt;
[[文件:PB-GATT-06.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
然后我们来到状态机列表m_pb_gatt_fsm_transition_table[]，可以看到E_LISTEN_START事件ID代表的动作ID是A_LISTEN_START。而最终我们的A_LISTEN_START动作，执行的是a_listen_start函数功能（这其中涉及的代码逻辑，暂时先不说明，大家可以自己查看源码的引用）。&lt;br /&gt;
[[文件:PB-GATT-07.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:PB-GATT-08.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
然后我们继续追踪a_listen_start()，可以看到其中调用了mesh_adv_params_set函数设置了我们广播的超时时间MESH_ADV_TIMEOUT_INFINITE以及广播间隔NRF_MESH_PROV_BEARER_GATT_UNPROV_BEACON_INTERVAL_MS，调用mesh_adv_data_set函数设置了我们的广播数据内容，最后调用mesh_adv_start函数启动广播。&lt;br /&gt;
&lt;br /&gt;
默认的PB-GATT的广播数据的广播周期是NRF_MESH_PROV_BEARER_GATT_UNPROV_BEACON_INTERVAL_MS 200。&lt;br /&gt;
[[文件:PB-GATT-09.png|边框|居中|无框|1200x1200像素]]我们同样的看一下mesh_adv_data_set()函数最终配置生成的广播数据，可以看到广播数据的flag是0x06，服务的UUID就是我们上面提到的PB-GATT服务的UUID，接下来16字节UUID是和PB-ADV一样的FICR寄存器的device id值，最后的扫描回调数据携带的是设备的全名“nRF5x Mesh Light”。&lt;br /&gt;
[[文件:PB-GATT-13.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:PB-GATT-14.png|边框|居中|无框]]最后是我们的mesh_adv_start()函数去启动广播，可以看到调用是我们ble stack sotfdevice当中的API函数sd_ble_gap_adv_start()去启动的。&lt;br /&gt;
[[文件:PB-GATT-15.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
=== Publish（模型发布） ===&lt;br /&gt;
这一章节我们只给大家讲解模块publish的数据发布流程，暂时不说明模型的创建方法，以及如何使用，这个放在下面单独的模型章节进行说明。&lt;br /&gt;
&lt;br /&gt;
以generic_onoff_server模型为例，我们找到access_model_publish以及access_model_reply函数，这两个函数的功能类似的，都是用于我们用户的模型发送数据的。&lt;br /&gt;
&lt;br /&gt;
其中access_model_publish是用于模型主动发送，而access_model_reply是用于模型给对应的Opcode回应数据。&lt;br /&gt;
&lt;br /&gt;
这里我们以access_model_publish函数为主，来看下发布数据的代码流程。&lt;br /&gt;
[[文件:Publish-01.png|边框|居中|无框|1200x1200像素]]查看一下access_model_publish()函数，我们可以看到packet_alloc_and_tx()函数，这个是我们继续发送数据的函数。下面还有一个蓝牙方框中的内容，是我们定义的重传机制（mesh数据为了保证传输的成功率，默认都是会重传数据包的，默认重传次数是4次）。[[文件:Publish-02.png|边框|居中|无框|1200x1200像素]]然后我们继续看一下packet_alloc_and_tx()函数，在这个函数中，我们最终会调用packet_tx()函数将数据发送出去。[[文件:Publish-03.png|边框|居中|无框|1200x1200像素]]继续追踪packet_tx()函数，这个函数的内容比较多，我们只截取了其中数据发送的部分，也就是我们的nrf_mesh_packet_send()函数，我们在他的tx_params中赋值好我们需要发送出去的数据内容。[[文件:Publish-04.png|边框|居中|无框|1199x1199像素]]nrf_mesh_packet_send()函数最终调用的是transport_tx()函数发送数据。而transport_tx()函数，最终是调用upper_transport_tx()函数进行的数据发送。[[文件:Publish-05.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Publish-06.png|边框|居中|无框|1200x1200像素]]我们再继续追踪upper_transport_tx()函数，可以看到我们会根据p_metadata-&amp;gt;segmented来判断数据是否要分包发送。&lt;br /&gt;
[[文件:Publish-13.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
接下里我们以不分包发送函数unsegmented_packet_tx()为例，来继续说明数据传输流程。&lt;br /&gt;
&lt;br /&gt;
我们可以看到我们的unsegmented_packet_tx()函数中，最终是调用的network_packet_send()函数。（分包的segmented_packet_tx()函数最终也是调用的network_packet_send()，大家可以自己追踪代码看一下）&lt;br /&gt;
[[文件:Publish-14.png|边框|居中|无框|1200x1200像素]]继续追踪我们的network_packet_send()函数，可以看到我们的net_packet_encrypt()加密函数，这个是将我们的明文数据，做好加密工作。&lt;br /&gt;
&lt;br /&gt;
然后调用core_tx_packet_send()函数发送出去。[[文件:Publish-15.png|边框|居中|无框|1200x1200像素]]最后我们追踪到core_tx_packet_send()函数，可以看到调用的是p_bearer-&amp;gt;p_interface-&amp;gt;packet_send()函数去发送的。所以接下来我们要找到我们的packet_send()功能是在哪边实现的。[[文件:Publish-07.png|边框|居中|无框|1200x1200像素]]可以看到我们是在core_tx_bearer_add函数中去获得的p_interface这个功能。[[文件:Publish-08.png|边框|居中|无框|1200x1200像素]]我们追踪core_tx_bearer_add()函数的引用路径，可以看到是在core_tx_adv_init()当中。（除了此处，还有另一处地方也有引用，功能是类似的，大家可以自行了解）。&lt;br /&gt;
&lt;br /&gt;
我们看下参数m_interface，可以看到m_interface-&amp;gt;packet_send功能，就是对应我们在core_tx_packet_send()函数调用的p_bearer-&amp;gt;p_interface-&amp;gt;packet_send()。[[文件:Publish-09.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Publish-10.png|边框|居中|无框|1200x1200像素]]所以我们接下来来看下m_interface结构体当中的packet_send()函数实现的是什么功能，可以看到最终调用的是advertiser_packet_send()函数将数据发送出去。看到这边我们就很熟悉了，和上面的几个广播功能，最后调用的都是相同的广播包发送函数。[[文件:Publish-11.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Publish-12.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
== ②扫描（通信RX） ==&lt;br /&gt;
&lt;br /&gt;
=== 标准BLE SCAN ===&lt;br /&gt;
看过了我们的广播功能之后，我们来看下扫描的部分。在ble mesh中，我们的扫描功能scan，就是用于获取数据的，类似于数据通信的RX。&lt;br /&gt;
&lt;br /&gt;
我们从main文件中的initialize()函数开始，首先是我们mesh_init()函数，最终我们会调用mesh_stack_init()函数。&lt;br /&gt;
[[文件:SCAN-01.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
在mesh_satck_init函数中，我们可以看到调用了nrf_mesh_init函数去初始化core的参数。&lt;br /&gt;
[[文件:SCAN-02.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
这一段是nrf_mesh_init函数部分，由于这个初始化函数太长，所以我们只截取了scan相关的部分。&lt;br /&gt;
&lt;br /&gt;
首先是第一点scanner_init(scanner_packet_process_cb)函数，在这个函数中，我们回去初始化扫描的参数配置，并且会开启扫描功能。最终我们可以从scanner_packet_process_cb这个回调中得到我们扫描到的数据（获得所有的BLE广播数据）。&lt;br /&gt;
&lt;br /&gt;
然后是第二点ad_listener_subscribe(&amp;amp;m_nrf_mesh_listener)，这个是用来侦听订阅的消息的（从获得的所有BLE广播数据中，筛选出的订阅的消息）。&lt;br /&gt;
[[文件:SCAN-03.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
然后我们继续看一下scanner_init的扫描初始化函数，在这个函数中我们首先调用scanner_config_reset()函数去配置扫描的默认参数。其中包含了：&lt;br /&gt;
&lt;br /&gt;
1、扫描的通道SCANNER_CHANNELS_DEFAULT，也就是BLE的｛37，38，39｝三个channels都去扫描。&lt;br /&gt;
&lt;br /&gt;
2、规范定义的非连接状态访问地址BEARER_ACCESS_ADDR_DEFAULT。&lt;br /&gt;
&lt;br /&gt;
3、配置物理层协议是1Mbit模式。&lt;br /&gt;
&lt;br /&gt;
4、扫描的间隔时间BEARER_SCAN_INT_DEFAULT_MS，扫描的窗口时间BEARER_SCAN_WINDOW_DEFAULT_MS&lt;br /&gt;
&lt;br /&gt;
5、射频的发射功率RADIO_POWER_NRF_0DBM。&lt;br /&gt;
&lt;br /&gt;
6、BLE广播包最大长度RADIO_CONFIG_ADV_MAX_PAYLOAD_SIZE。&lt;br /&gt;
&lt;br /&gt;
最后我们会在nrf_mesh_enable()函数中调用scanner_enable()函数去启动扫描，这个大家可以自己查看一下代码，比较容易理解。&lt;br /&gt;
[[文件:SCAN-04.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:SCAN-05.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
最后我们来看一下scanner_packet_process_cb()回调函数，其中我们可以直接拿到的扫描的数据包是rx_data，包含了数据的类型、数据内容以及数据长度等等信息。到这边我们的扫描功能是已经完成的，但是我们还需要再看下下面一部分的ad_listener的流程。&lt;br /&gt;
&lt;br /&gt;
=== Subscription（模型订阅） ===&lt;br /&gt;
我们需要注意下ad_listener_process()函数，这个函数是将我们扫描到的订阅数据（不符合标准的BLE广播数据，则认为是侦听到的订阅数据）传递到订阅者进行处理（订阅者指的是带订阅功能的模型）。这个是和我们上方说的关注点二ad_listener_subscribe()函数是一脉相承的。&lt;br /&gt;
[[文件:SCAN-06.png|边框|居中|无框|1200x1200像素]]然后我们来对照着看一下ad_listener_subscribe()函数以及ad_listener_process()函数。这两个函数一个是定义了侦听订阅，一个是将扫描获取到的数据传递给订阅者。这两者间的运作关系，大家有兴趣的可以继续查阅他的底层代码逻辑处理，一个是向list中添加订阅者信息，一个是将数据通过p_listener-&amp;gt;handler()将数据传递给订阅者处理。&lt;br /&gt;
[[文件:SCAN-07.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:SCAN-08.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
最终当我们扫描到订阅消息，并调用ad_listener_process()函数传递时，会触发我们注册的回调nrf_mesh_listen（调用ad_listener_subscribe(&amp;amp;m_nrf_mesh_listener)时，在m_nrf_mesh_listener中）。我们来看下nrf_mesh_listen函数中的处理，其中我们先从数据包中解析了数据的类型type，然后根据不同类型的数据进行相应的处理。&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+&lt;br /&gt;
|AD_TYPE_MESH &lt;br /&gt;
|蓝牙网格物体的AD类型&lt;br /&gt;
|-&lt;br /&gt;
|AD_TYPE_BEACON &lt;br /&gt;
|蓝牙网状信标的AD类型&lt;br /&gt;
|-&lt;br /&gt;
|AD_TYPE_PB_ADV &lt;br /&gt;
|PB-ADV消息的AD类型&lt;br /&gt;
|-&lt;br /&gt;
|AD_TYPE_DFU &lt;br /&gt;
|nRF OpenMesh消息的AD类型&lt;br /&gt;
|}&lt;br /&gt;
[[文件:SCAN-09.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
== ③入网验证 ==&lt;br /&gt;
经过前两章的学习，我们对于BLE MESH的广播及扫描的代码流程已经有了一定的了解，也就是已经大概的明白了数据的发送和接收的流程。&lt;br /&gt;
&lt;br /&gt;
那么从这一章节开始，我们将会给大家展示，如何配置一个节点设备入网。配置者设备，我们使用的是手机APP：nRF Mesh。&lt;br /&gt;
&lt;br /&gt;
我们从main文件中的start()函数开始，来看一下入网的代码处理及其流程。在start()函数中我们最后是调用的mesh_provisionee_prov_start()函数来启动的节点配置功能，我们来查看一下它所携带的参数prov_start_params。这部分的参数都是比较重要的，有携带的是设备的信息的、有携带认证密码的，有回调返回的，所以我们会挨个介绍一下。&lt;br /&gt;
[[文件:Prov-01.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
首先携带的是static_auth_data，这个是我们带外OOB认证的密码。如果我们在使用nRF Mesh去进行身份验证的时候，选择使用Static OOB，那么当我们在进行PB-GATT的连接过程中，会弹出一个新的窗口，要求我们去输入这个静态的128bit的OOB密钥。&lt;br /&gt;
[[文件:Prov-02.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Prov-14.png|边框|居中|无框]]&lt;br /&gt;
[[文件:Prov-15.png|边框|居中|无框]]&lt;br /&gt;
接下来携带的是provisioning_complete_cb()回调函数，这个函数处理的是节点设备成功的被配置入网，在这里我们可以获取到我们的节点设备被分配的node_Address。&lt;br /&gt;
[[文件:Prov-03.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
然后携带是device_identification_start_cb()回调函数，这个回调函数是用于通知开发者，我们的节点设备当前已经开始配置。&lt;br /&gt;
[[文件:Prov-04.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
再然后携带的是URI【统一资源标识符（Uniform Resource Identifier，URI)】，这个是用于标识我们的设备类型的，这个数据会通过AES-CMAC算法加密后添加到我们的Unprov Beacon数据的末尾。&lt;br /&gt;
&lt;br /&gt;
这里我们是light switch的server设备，所以我们定义URI是EX_URI_LS_SERVER。&lt;br /&gt;
[[文件:Prov-05.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
分析完携带的参数之后，我们继续来查看mesh_provisionee_prov_start()函数。在这个函数中，我们首先是定义了一下prov_caps，也就是配置支持的OOB功能。然后我们调用nrf_mesh_generate_keys()函数去生成有效的密钥对等待使用。&lt;br /&gt;
&lt;br /&gt;
再然后是调用nrf_mesh_prov_init()函数去初始化prov配置信息的结构体参数，里面有结构体本身m_pro_ctx，需要配置的参数公钥m_public、私钥m_private，以及我们刚刚定义的oob功能prov_caps，最后还携带了一个事件回调函数prov_ect_handler。&lt;br /&gt;
&lt;br /&gt;
接下来是PB-ADV以及PB-GATT的处理过程，这个前面已经讲解过，这里不再赘述。&lt;br /&gt;
&lt;br /&gt;
最后我们调用provisionee_start()函数去开启配置。&lt;br /&gt;
[[文件:Prov-06.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
我们继续来追踪一下provisionee_start()函数，在这个函数中我们调用nrf_mesh_prov_listen()函数去开启侦听承载层的消息。这里我们需要侦听PB-ADV以及PB-GATT两个。&lt;br /&gt;
[[文件:Prov-07.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
然后在nrf_mesh_prov_listen()函数中，我们调用prov_provisionee_listen()函数携带我们刚刚传入的nrf_mesh_prov_ctx_t结构体，以及p_bearer、URI、oob_info_source等参数信息，继续向下传递。&lt;br /&gt;
[[文件:Prov-08.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
最终我们来查看下prov_provisionee_listen()函数，在这个函数中我们传递了p_bearer-&amp;gt;p_callbacks给m_prov_callbacks回调，然后调用p_bearer-&amp;gt;p_interface-&amp;gt;listen_start()函数去启动PB-ADV以及PB-GATT的侦听。&lt;br /&gt;
[[文件:Prov-09.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
当我们在侦听之后，接收到任何配置相关的消息，都将通过m_prov_callbacks()函数回调传递给我们处理，大家可以自行看下其中4个不同的功能函数，具体处理的都是什么消息。&lt;br /&gt;
[[文件:Prov-10.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
在prov_provisionee_listen()函数中，我们可以看到回调函数指针m_prov_callbacks指向的是p_bearer-&amp;gt;p_callbacks，我们一路向上查找，最终可以找到这个p_bearer-&amp;gt;p_callbacks函数就是指的prov_evt_handler()函数。所以其实我们用户可以在这一层去查看比较重要的配置事件返回，而不需要到上面的m_prov_callbacks中这样偏底层的地方去查看。&lt;br /&gt;
&lt;br /&gt;
在这个回调函数中，我们可以看到mian文件中注册的prov_device_identification_start_cb、prov_device_identification_stop_cb、provisioning_aborted_cb的返回。&lt;br /&gt;
[[文件:Prov-11.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
那么除了上方已经存在的几个回调，我们可以看到mian文件中的start()函数下的prov_start_params参数，还缺少一个配置完成的回调prov_complete_cb()。&lt;br /&gt;
&lt;br /&gt;
这个回调的返回方式比较特殊，和上方其他的都不同，是由SD（softdevice）直接从底层返回给我们的。注册这个回调的函数是NRF_SDH_STATE_OBSERVER，返回的回调函数是sd_state_evt_handler。其中触发的事件NRF_SDH_EVT_STATE_ENABLED，这个是在mian文件中的initialize()函数下的ble_stack_init()函数中就被触发的，这个大家可以自行查看一下，不太重要，因为都是nordic底层处理好的消息。&lt;br /&gt;
[[文件:Prov-13.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Prov-12.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
以上整个配置流程的处理，不太好分步展示相应的现象，这个大家可以自己使用SES在线仿真的方式去调试一下流程，这里我们直接给大家展示整个流程打印出的相应的信息，以及nRF Mesh中暂时出来的现象。&lt;br /&gt;
[[文件:Prov-16.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Prov-17.png|边框|居中|无框]]&lt;br /&gt;
&lt;br /&gt;
== ④设备信息分配 ==&lt;br /&gt;
当我们成功的配置节点设备入网之后，我们需要给节点设备主要分配如下几个信息，有关这几个要点的介绍，可以查看“BLE-Mesh技术揭秘”第3章节：&lt;br /&gt;
&lt;br /&gt;
1、节点在网络中的地址Node Address&lt;br /&gt;
&lt;br /&gt;
2、网络密钥Netkey&lt;br /&gt;
&lt;br /&gt;
3、应用密钥Appkey&lt;br /&gt;
&lt;br /&gt;
4、元素的地址Element Address&lt;br /&gt;
&lt;br /&gt;
5、绑定model的Appkey，分配Publish Address，Subscription Address&lt;br /&gt;
&lt;br /&gt;
=== Node Address ===&lt;br /&gt;
首先我们来看一下我们使用nRF Mesh成功配置我们的节点之后，打印出来的消息，开始的紫色字体显示了我们给当前节点设备分配的Node Address。&lt;br /&gt;
&lt;br /&gt;
后面紧接着还有四段黄色字体的内容（这个部分大家的工程是不会打印的，是我们修改例程打印出来给大家展示一下），这四个部分就是我们配置节点的时候，配置者设备（nRF Mesh）给节点设备的四个主要指令，这些指令的具体功能，大家可以查看mesh的协议手册。[[文件:Config-13.png|边框|居中|无框|1080x1080像素]]在代码中，我们会在mian文件的provisioning_complete_cb()回调函数中获取并打印我们的Node Address，这个回调触发的含义就是我们的节点配置已经完成了。&lt;br /&gt;
&lt;br /&gt;
具体这个回调在代码中的回调及触发流程，大家看可以查看“③入网验证”的说明。[[文件:Config-20.png|边框|居中|无框|1200x1200像素]]我们使用nRF Mesh配置好节点设备之后，app界面显示内容如下，包含了节点的信息（地址、名称、配置时间等等），元素和模型，以及Netkey和Appkey等几个重要的设备信息。[[文件:Config-01.jpg|边框|居中|无框]]&lt;br /&gt;
[[文件:Config-04.jpg|边框|居中|无框]]&lt;br /&gt;
&lt;br /&gt;
=== Netkey ===&lt;br /&gt;
我们点击查看一下Network key，可以看到里面有一个Network Key 1，这是一个16字节的网络密钥。我们可以在nRF Mesh的Settings界面去增加或者删除一个network key，用户可以自定义此密钥。[[文件:Config-02.jpg|边框|居中|无框]]在我们的代码中，我们是在pro_evt_handler回调的NRF_MESH_PROV_EVT_COMPLETE事件中获取这个netkey，这个回调函数的触发流程同样是在“③入网验证”已经有说明。[[文件:Prov-11.png|边框|居中|无框|1200x1200像素]]我们直接看下NRF_MESH_PROV_EVT_COMPLETE事件下的mesh_stack_provisioning_data_store()函数，在这个函数中我们主要的任务，就是将配置者分配给我们的node address、netkey、devkey等等信息，分别做一些存储以及功能绑定。&lt;br /&gt;
[[文件:Netkey-01.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
=== Appkey ===&lt;br /&gt;
同样的我们打开Application Key，可以看到其中包含的3个密钥，这里我们只选用了Application Key 1作为我们的密钥。大家同样可以在nRF Mesh的Settings界面去增加或者删除一个App key，用户可以自定义此密钥。[[文件:Config-03.jpg|边框|居中|无框]]appkey这里和netkey不同，netkey是我们网络中的节点设备以及我们配置者设备共有的一个密钥，只有拥有这个密钥，我们才能在当前网络中通信，所以当我们的节点设备配置入网后，会立刻得到netkey（必须要分配给我的节点设备才行）。&lt;br /&gt;
&lt;br /&gt;
而appkey只是我们的某个模型用于通信的密钥，所以我们称它为应用密钥，这个appkey的分配，是在我们的config server下。这个config_server_init的初始化流程，是在我们的mesh_stack_init()函数下，就去初始化配置好的。最终当我们有信息配置的时候，会触发opcode_handlers，我们可以从这个操作码列表中查找相应的信息处理。[[文件:Config-22.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Config-23.png|边框|居中|无框|1200x1200像素]]我们来看下config server的opcode_handlers[]操作码处理程序列表，使用黄色标记给大家圈了几个操作码指令，这几个是我们配置一个最简单的server节点设备，都需要使用到的，包含了：&lt;br /&gt;
&lt;br /&gt;
1、appkey的添加，CONFIG_OPCODE_APPKEY_ADD&lt;br /&gt;
&lt;br /&gt;
2、publish发布地址，CONFIG_OPCODE_MODEL_PUBLICATION_VIRTUAL_ADDRESS_SET&lt;br /&gt;
&lt;br /&gt;
3、subscription订阅地址，CONFIG_OPCODE_MODEL_SUBSCRIPTION_ADD&lt;br /&gt;
&lt;br /&gt;
4、模型appkey的绑定，CONFIG_OPCODE_MODEL_APP_BIND&lt;br /&gt;
&lt;br /&gt;
5、netkey的更新，CONFIG_OPCODE_NETKEY_ADD[[文件:Config-21.png|边框|居中|无框|1200x1200像素]]我们可以看到当接收到CONFIG_OPCODE_APPKEY_ADD操作码的数据，会调用handle_appkey_add()函数去处理，在这个函数中我们最终会调用dsm_appkey_add()函数去存储我们的appkey。[[文件:Appkey-01.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
== ⑤Element元素 ==&lt;br /&gt;
元素简单的说就是定义了节点的功能，一些具有复杂功能的节点设备，会包含多个元素。一个节点设备最少需要包含一个元素，我们称之为主元素。&lt;br /&gt;
&lt;br /&gt;
从主元素开始（如果有多个元素，则第一个元素就是主元素），我们会给它分配相应的元素地址。主元素的地址就是节点的地址，第二个元素的地址就是主元素的地址基础上加上1，依次类推。&lt;br /&gt;
&lt;br /&gt;
可以看到在我们的主元素Element：0x0002中包含了3个不同的服务，这3个服务就是我们的模型Model，其中Configuration Server与Health Server这两个服务，是每一个节点都必须携带的模型。Configuration Server（配置服务）是用于配置节点设备信息，Health Server（健康服务）是用于节点进行网络内的心跳联系。剩下的Generic On Off Server服务，这个就是我们用于控制一个开关量的模型（在例程中我们通过控制LED的IO口高低电平来展示）。[[文件:Config-05.jpg|边框|居中|无框]]在我们的代码中，我们首先需要在nrf_mesh_config_app.h中定义好我们节点设备的最大元素数量，可以看到我们定义#define ACCESS_ELEMENT_COUNT (1)，也就是说我们当前的例程，只含有一个主元素。&lt;br /&gt;
[[文件:Element-01.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
而这个ACCESS_ELEMENT_COUNT ，是在我们开启节点配置的地方被调用的，这个mesh_provsionionee_prov_start()函数在前面的内容中有多次讲到，这里就不再说明了。&lt;br /&gt;
[[文件:Element-02.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
== ⑥model模型 ==&lt;br /&gt;
如果说元素是一个功能集合，那么模型就是元素下的具体的某一个功能。&lt;br /&gt;
&lt;br /&gt;
我们以灯的控制为例，灯的常用控制有开关、亮度、色温几种，其中开关、亮度和色温就是具体的模块，而处理灯控的集合就是元素。&lt;br /&gt;
&lt;br /&gt;
=== Configure Server ===&lt;br /&gt;
配置服务模型，这个的大概代码处理流程在我们的“④设备信息分配-&amp;gt;Appkey”已经有了说明，它是我们节点设备必须包含的一个特殊功能的模型。&lt;br /&gt;
&lt;br /&gt;
它的实际功能就像它的名字一样，就是用于配置（configuration）我们的设备信息，比如appkey的添加删除，密钥的绑定删除，其他的模型的发布和订阅控制。具体包含的操作码的使用方法，我们会在generic on off server模型当中使用的时候来说明。&lt;br /&gt;
&lt;br /&gt;
=== Health Server ===&lt;br /&gt;
健康服务，顾名思义就是判断我们的设备是否在网络中保持健康（正常）工作，它同样是一个必须包含的特殊功能的模型。&lt;br /&gt;
&lt;br /&gt;
它的初始化和config server是一样的，都是放在了mesh_stack_init()函数下。&lt;br /&gt;
[[文件:Health-01.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
我们来看下health_server_init()函数，最终我们会调用access_model_add()函数去初始化模型，并将模型添加到对应的元素中。&lt;br /&gt;
&lt;br /&gt;
其中的access_model_add_params_t参数，包含的分别是元素的索引（将模型添加到第几个元素当中），模型的ID（由mesh协议手册确定好了），以及最最重要的操作码列表，这个列表中会包含我们的指令码，以及对应的函数功能处理。&lt;br /&gt;
[[文件:Health-02.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
最后我们看下具体包含了哪些健康模型操作码：&lt;br /&gt;
&lt;br /&gt;
1、“运行状况当前状态”消息的操作码，HEALTH_OPCODE_CURRENT_STATUS&lt;br /&gt;
&lt;br /&gt;
2、“ 健康故障状态”消息的操作码，HEALTH_OPCODE_FAULT_STATUS&lt;br /&gt;
&lt;br /&gt;
3、“  获取健康注意事项”消息的操作码，HEALTH_OPCODE_ATTENTION_GET&lt;br /&gt;
&lt;br /&gt;
4、“ 健康注意集”消息的操作码，HEALTH_OPCODE_ATTENTION_SET&lt;br /&gt;
&lt;br /&gt;
5、“未确认健康注意事项”消息的操作码，HEALTH_OPCODE_ATTENTION_SET_UNACKED&lt;br /&gt;
&lt;br /&gt;
6、“健康注意状态”消息的操作码，HEALTH_OPCODE_ATTENTION_STATUS&lt;br /&gt;
&lt;br /&gt;
7、“健康故障清除”消息的操作码，HEALTH_OPCODE_FAULT_CLEAR&lt;br /&gt;
&lt;br /&gt;
8、“健康故障清除未确认”消息的操作码，HEALTH_OPCODE_FAULT_CLEAR_UNACKED&lt;br /&gt;
&lt;br /&gt;
9、“健康故障获取”消息的操作码，HEALTH_OPCODE_FAULT_GET&lt;br /&gt;
&lt;br /&gt;
10、“健康故障测试”消息的操作码，HEALTH_OPCODE_FAULT_TEST&lt;br /&gt;
&lt;br /&gt;
11、“未确认健康故障测试”消息的操作码，HEALTH_OPCODE_FAULT_TEST_UNACKED&lt;br /&gt;
&lt;br /&gt;
12、“健康期获取”消息的操作码，HEALTH_OPCODE_PERIOD_GET&lt;br /&gt;
&lt;br /&gt;
13、“健康期设置”消息的操作码，HEALTH_OPCODE_PERIOD_SET&lt;br /&gt;
&lt;br /&gt;
14、“未确认健康期设置”消息的操作码，HEALTH_OPCODE_PERIOD_SET_UNACKED&lt;br /&gt;
&lt;br /&gt;
15、“健康期状态”消息的操作码，HEALTH_OPCODE_PERIOD_STATUS&lt;br /&gt;
[[文件:Health-03.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
在大家的前期使用中，一般也不会真的需要大家去处理health server下的指令，毕竟我们刚开始使用，也很难让设备在网络中出现各种运行问题（节点少功能少）。但是当大家自己开始开发实际功能时，就一定要注意分析health server下的指令码返回，用来快速解决我们网络中的问题。&lt;br /&gt;
&lt;br /&gt;
=== Generic On Off Server ===&lt;br /&gt;
接下来我们来看下Generic On Off Server服务，这个服务和上方的两个服务不太一样，他不是我们的特殊必要服务，是一个通用的模型（用于具体的功能数据的处理，这里是用于开关功能）。借用这个服务模型，我们给大家介绍一下如何创建一个符合BLE MESH协议的模型。&lt;br /&gt;
&lt;br /&gt;
我们看下模型的初始化方法，在generic_onoff_server_init()函数中，首先我们使用access_model_add_params_t定义一个模型分配参数的结构体init_params，在这个结构体当中，我们要给定：&lt;br /&gt;
&lt;br /&gt;
1、model_id（模型的ID），这个是mesh协议规定好的，我们可以查看“归档资料\8-蓝牙协议手册和芯片手册\蓝牙mesh协议手册\《MshMDLv1.0.1.pdf》”的第7.3章节有归纳&lt;br /&gt;
&lt;br /&gt;
2、element_index（元素索引），这个看我们准备将这个服务添加到哪一个元素下，这里我们因为只有一个主元素，所以我们的元素索引为0&lt;br /&gt;
&lt;br /&gt;
3、p_opcode_handlers（操作码列表，指令码列表），这个也是mesh协议规定好的，我们可以查看“归档资料\8-蓝牙协议手册和芯片手册\蓝牙mesh协议手册\《MshMDLv1.0.1.pdf》”的第7.1章节归纳，具体的操作码的功能及要求，大家需要对应这个pdf手册，自己查看一下，对于一个模型ID，我们不一定要注册所有的属于它的opcode，只要注册我们需要使用的即可&lt;br /&gt;
&lt;br /&gt;
4、opcode_count（操作码数量），准备注册的操作码的数量&lt;br /&gt;
&lt;br /&gt;
5、p_args（通用参数指针），用于参数的传递&lt;br /&gt;
&lt;br /&gt;
6、publish_timeout_cb（发布超时时间），发布的定时器到期，会进入此回调，我们可以做一些处理（例如重新发送数据等等）。如果我们的模型不支持定期发布，可以将此值设置为NULL。&lt;br /&gt;
&lt;br /&gt;
当我们配置好模型的参数结构体之后，会调用access_model_add()函数去初始化模型并添加到元素，同时会返回给我们用于存储此模型的句柄指针p_server-&amp;gt;model_handle。&lt;br /&gt;
&lt;br /&gt;
因为我们的onoff模型，需要订阅消息，所以我们需要调用access_model_subscription_list_alloc()函数，携带我们的模型句柄去分配模型的订阅列表。&lt;br /&gt;
[[文件:Onoff-01.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
看完模型的初始化注册之后，我们来看下模型包含的opcode操作码，可以看到在m_opcode_handlers[]操作码列表中包含了三个opcode。这三个opcode我们可以看下他的定义，以及对照一下《MshMDLv1.0.1.pdf》第7.1章节的归纳，可以看到是一一对应的关系。具体这三个opcode的功能，大家可以查看手册中的说明，或者直接看下他们对应的功能处理函数。&lt;br /&gt;
[[文件:Onoff-02.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Onoff-03.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Onoff-04.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
接下来我们看下GENERIC_ONOFF_OPCODE_SET与GENERIC_ONOFF_OPCODE_SET_UNACKNOWLEDGED对应的handle_set，可以看到在这个函数的最后，我们会去调用status_send()函数去发送数据。&lt;br /&gt;
[[文件:Onoff-05.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
然后我们看下status_send()函数，在这个函数中，我们最终会调用access_model_publish来发布我们的数据，或者使用accsee_model_reply来回复数据。&lt;br /&gt;
&lt;br /&gt;
这两个函数再往下就是我们之前在广播部分讲解的packet_alloc_and_tx函数去发送广播数据了，这个有不清楚的，大家可以到前面去查看一下。&lt;br /&gt;
[[文件:Onoff-06.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
==== model bind appkey ====&lt;br /&gt;
那么按照上方的方法注册好模型，然后我们的节点设备被成功的配置入网之后，我们如何让这个模型工作起来。&lt;br /&gt;
&lt;br /&gt;
首先第一步是需要将我们的模型绑定到相应的appkey，只有这样我们的模型才能拿到用于通信的密钥，例如如下的截图当中，我们利用手机app软件nRF Mesh去给我们的Generic On Off Server模型绑定Appkey1。[[文件:Config-14.png|边框|居中|无框|1040x1040像素]]&lt;br /&gt;
[[文件:Config-06.jpg|边框|居中|无框]]&lt;br /&gt;
[[文件:Config-07.jpg|边框|居中|无框]]&lt;br /&gt;
[[文件:Config-08.jpg|边框|居中|无框]]当我们在使用app去绑定appkey的时候，在我们的代码中的体现是在config server下的opcode_handlers[]操作码列表中的CONFIG_OPCODE_MODEL_APP_BIND。&lt;br /&gt;
[[文件:Onoff-07.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
我们追踪一下CONFIG_OPCODE_MODEL_APP_BIND对应的handle_model_app_bind_unbind()，这个函数比较长，这边只给大家截取了部分，在为截取到部分，还有获取元素索引的get_element_index，获取模型ID和模型handle的access_handle_get，然后才是我们的dsm_appkey_index_to_appkey_handle函数用于将appkey索引给到appkey句柄。然后调用access_model_application_bind函数去绑定模型和appkey，最终如果是成功绑定，则调用access_flash_config_store函数去存储修改的绑定信息。&lt;br /&gt;
[[文件:Onoff-08.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
==== model publish address ====&lt;br /&gt;
当我们完成了模型的appkey绑定之后，我们就可以开始设置puhlish的地址以及subscription的地址。&lt;br /&gt;
&lt;br /&gt;
这里我们先看下nRF Mesh配置publish Address，可以看到我们选择的定义的0xC000组地址。[[文件:Model-pub-01.jpg|边框|居中|无框]]&lt;br /&gt;
[[文件:Model-pub-02.jpg|边框|居中|无框]]&lt;br /&gt;
[[文件:Model-pub-03.jpg|边框|居中|无框]]&lt;br /&gt;
[[文件:Model-pub-04.jpg|边框|居中|无框]]我们可以看到，在我们配置publish地址的时候，RTTViewer中会打印黄色标注的操作码数据信息，可以看到他的OPCODE是0x03。[[文件:Model-pub-05.png|边框|居中|无框|996x996像素]]我们来看下这个操作码对应的功能，可以看到CONFIG_OPCODE_MODEL_PUBLICATION_SET操作码对应的功能函数是handle_config_model_publication_set，根据函数的名称我们就可以看出，是用于配置模型发布的。[[文件:Model-sub-04.jpg|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Model-pub-07.jpg|边框|居中|无框|1200x1200像素]]在handle_config_model_publication_set函数中，可以看到我们先使用dsm_appkey_index_to_appkey_handle函数获取了appkey的句柄。然后调用dsm_address_publish_add函数去添加发布的地址。[[文件:Model-pub-08.jpg|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
==== model subscription address ====&lt;br /&gt;
然后我们再看下订阅地址的添加，可以看到我们使用nRF Mesh配置subscription订阅了组地址0xC001。[[文件:Model-sub-02.jpg|边框|居中|无框]]&lt;br /&gt;
[[文件:Model-sub-01.jpg|边框|居中|无框]]可以看到，当我们分配subscription地址的时候，可以看到RTTViewer中打印黄色标注的信息。可以看到他的OPCODE是0x801B。[[文件:Model-sub-03.jpg|边框|居中|无框|982x982像素]]我们来看下这个操作码对应的功能，可以看到CONFIG_OPCODE_MODEL_SUBSCRIPTION_ADD操作码对应的功能函数是handle_config_model_subscription_add，根据函数的名称我们就可以看出，是用于配置模型订阅的。[[文件:Model-pub-06.jpg|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Model-sub-05.jpg|边框|居中|无框|1200x1200像素]]在handle_config_model_subscription_add函数中，我们会调用dsm_address_subscription_add函数去添加我们的订阅地址。[[文件:Model-sub-06.jpg|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
== Mesh串口透传实验 ==&lt;br /&gt;
&lt;br /&gt;
== Mesh天猫精灵实验 ==&lt;br /&gt;
&lt;br /&gt;
=== 天猫精灵介绍 ===&lt;br /&gt;
天猫精灵（TmallGenie）是阿里巴巴人工智能实验室（Alibaba A.I.Labs）于2017年7月5日发布的AI智能产品品牌，当天同步发布了天猫精灵首款硬件产品——AI智能语音终端设备天猫精灵，未来还将推出更多AI智能产品。&lt;br /&gt;
&lt;br /&gt;
天猫精灵内置AliGenie操作系统，AliGenie生活在云端，它能够听懂中文普通话语音指令，目前可实现智能家居控制、语音购物、手机充值、叫外卖、音频音乐播放等功能，带来人机交互新体验。依靠阿里云的机器学习技术和计算能力，AliGenie能够不断进化成长，了解使用者的喜好和习惯，成为人类智能助手。&lt;br /&gt;
&lt;br /&gt;
=== AliGenie平台介绍 ===&lt;br /&gt;
'''阿里精灵AliGenie平台地址：[//E%3A/NRF52832/www.aligenie.com www.aligenie.com]'''&lt;br /&gt;
&lt;br /&gt;
'''IOT接入平台控制中心：[//E%3A/NRF52832/iot.aligenie.com/home iot.aligenie.com/home]'''&lt;br /&gt;
&lt;br /&gt;
'''IOT平台接入规范手册：[/www.aligenie.com/doc/357554/tgllbp https://www.aligenie.com/doc/357554/tgllbp]'''&lt;br /&gt;
&lt;br /&gt;
天猫精灵IoT开放平台，是阿里巴巴人工智能实验室(Alibaba A.I.Labs)面向品牌商、方案商、模组商/芯片商以及个人开发者推出的，将IoT物联网技术（蓝牙Mesh协议、WiFi协议、天猫精灵IoT云服务）和AI（天猫精灵ASR语音识别、NLP自然语言处理、TTS语音合成）等对外输出的开放式平台。&lt;br /&gt;
&lt;br /&gt;
开发者可按直连接入（WiFi模组、蓝牙Mesh模组）、云云接入（OAuth2.0）2类方式，接入天猫精灵软硬件生态（天猫精灵音箱、天猫精灵App、AliGenie Inside智能设备）及阿里巴巴集团生态服务，实现语音和触屏交互，为用户提供天猫精灵IoT控制、查询、播报和主动服务。目前已支持40+个IoT类目，1000+型号。天猫精灵IoT开放平台会持续创新，不断为开发者带来新技术，同时降低平台开发者进驻门槛，让AI普惠大众！&lt;br /&gt;
&lt;br /&gt;
=== 操作流程 ===&lt;br /&gt;
&lt;br /&gt;
==== 硬件连接 ====&lt;br /&gt;
1、NRF52832DK连接Jlink-Lite仿真器，并连接到电脑USB&lt;br /&gt;
&lt;br /&gt;
2、天猫精灵（方糖R）接电源供电，根据天猫精灵使用手册，将其连接上家里的WIFI&lt;br /&gt;
&lt;br /&gt;
==== 平台设备创建 ====&lt;br /&gt;
1、  登录IOT接入平台控制中心：[//E%3A/NRF52832/iot.aligenie.com/home iot.aligenie.com/home]，大家先点击右上角注册一下，因为是阿里巴巴旗下的产品，所以我们只需要用我们的淘宝账号就可以完成注册。&lt;br /&gt;
[[文件:Tmjl-03.png|边框|居中|无框|1189x1189像素]]&lt;br /&gt;
&lt;br /&gt;
2、  注册完成之后，我们登入平台，并且创建一个蓝牙mesh设备，如下图所示：&lt;br /&gt;
[[文件:Tmjl-04.gif|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
==== 软件配置 ====&lt;br /&gt;
将例程&amp;quot;ghostyu-tmjl&amp;quot;解压到mesh SDK的如下路径中：\个人协议栈路径\nrf5SDKforMeshv310src\examples\ghostyu-tmjl&lt;br /&gt;
[[文件:Tmjl-01.png|边框|居中|无框|450x450像素]]&lt;br /&gt;
[[文件:Tmjl-02.png|边框|居中|无框|650x650像素]]&lt;br /&gt;
[[分类:NRF52832DK]]&lt;br /&gt;
[[分类:实验手册]]&lt;/div&gt;</summary>
		<author><name>Jinx</name></author>
		
	</entry>
	<entry>
		<id>http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:Tmjl-03.png&amp;diff=2822</id>
		<title>文件:Tmjl-03.png</title>
		<link rel="alternate" type="text/html" href="http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:Tmjl-03.png&amp;diff=2822"/>
		<updated>2020-04-02T08:22:23Z</updated>

		<summary type="html">&lt;p&gt;Jinx：&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Tmjl-03&lt;/div&gt;</summary>
		<author><name>Jinx</name></author>
		
	</entry>
	<entry>
		<id>http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:Tmjl-04.gif&amp;diff=2821</id>
		<title>文件:Tmjl-04.gif</title>
		<link rel="alternate" type="text/html" href="http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:Tmjl-04.gif&amp;diff=2821"/>
		<updated>2020-04-02T08:21:38Z</updated>

		<summary type="html">&lt;p&gt;Jinx：&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;tmjl-04&lt;/div&gt;</summary>
		<author><name>Jinx</name></author>
		
	</entry>
	<entry>
		<id>http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:Tmjl-02.png&amp;diff=2820</id>
		<title>文件:Tmjl-02.png</title>
		<link rel="alternate" type="text/html" href="http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:Tmjl-02.png&amp;diff=2820"/>
		<updated>2020-04-02T08:13:02Z</updated>

		<summary type="html">&lt;p&gt;Jinx：&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Tmjl-02&lt;/div&gt;</summary>
		<author><name>Jinx</name></author>
		
	</entry>
	<entry>
		<id>http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:Tmjl-01.png&amp;diff=2819</id>
		<title>文件:Tmjl-01.png</title>
		<link rel="alternate" type="text/html" href="http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:Tmjl-01.png&amp;diff=2819"/>
		<updated>2020-04-02T08:11:30Z</updated>

		<summary type="html">&lt;p&gt;Jinx：&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Tmjl-01&lt;/div&gt;</summary>
		<author><name>Jinx</name></author>
		
	</entry>
	<entry>
		<id>http://doc.iotxx.com/index.php?title=NRF52832DK-Mesh%E7%BB%84%E7%BD%91%E5%AE%9E%E9%AA%8C&amp;diff=2818</id>
		<title>NRF52832DK-Mesh组网实验</title>
		<link rel="alternate" type="text/html" href="http://doc.iotxx.com/index.php?title=NRF52832DK-Mesh%E7%BB%84%E7%BD%91%E5%AE%9E%E9%AA%8C&amp;diff=2818"/>
		<updated>2020-04-02T03:46:53Z</updated>

		<summary type="html">&lt;p&gt;Jinx：/* model subscription address */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;蓝牙Mesh和BLE是并行发展的一个独立分支，虽然底层共用Physical Layer和Link Layer，但是上层协议并不相同。因此，nRF52832的Mesh例程，并不在BLE协议栈sdk中，而是安装一个扩展包。&lt;br /&gt;
&lt;br /&gt;
本文介绍蓝牙Mesh相关的环境搭建和实验介绍。&lt;br /&gt;
&lt;br /&gt;
== Mesh开发环境 ==&lt;br /&gt;
&lt;br /&gt;
=== 蓝牙Mesh SDK ===&lt;br /&gt;
SDK位置：&amp;lt;code&amp;gt;归档资料/2-协议栈SDK/nRF5_SDK_Mesh_v310.zip&amp;lt;/code&amp;gt;，注意文件名中的Mesh字样。&lt;br /&gt;
&lt;br /&gt;
和BLE协议栈一样，将该压缩包直接解压到BLE协议栈同级目录即可使用，解压后的SDK路径如下：&amp;lt;code&amp;gt;E:\project-nordic\nRF5_SDK_Mesh_v310&amp;lt;/code&amp;gt;&lt;br /&gt;
[[文件:NRF52832DK-MESH-SDK-INSTALL.gif|居中|无框|750x750像素]]&lt;br /&gt;
&lt;br /&gt;
=== 集成开发软件SES ===&lt;br /&gt;
SES时是Segger Embedded Studio编译器的缩写，用来编译蓝牙Mesh代码，SES功能与IAR或Keil类似。&lt;br /&gt;
&lt;br /&gt;
备注：Nordic为何使用SES而不是IAR，原因不得而知。&lt;br /&gt;
&lt;br /&gt;
==== SES安装 ====&lt;br /&gt;
SES软件位置：&amp;lt;code&amp;gt;归档资料/7-编译器SES/Setup_EmbeddedStudio_ARM_v410_win_x64.exe&amp;lt;/code&amp;gt;。&lt;br /&gt;
&lt;br /&gt;
我们双击打开安装界面，按照如下动图所示方式进行安装（可以全部默认安装）。[[文件:Ses install.gif|边框|居中|无框|556x556像素]]&lt;br /&gt;
&lt;br /&gt;
==== SES许可证激活 ====&lt;br /&gt;
我们双击打开安装好的SES，选择编译自带测试例程，此时弹出需要激活的界面。&lt;br /&gt;
[[文件:Licensed1.jpg|边框|居中|无框|1205x1205像素]]&lt;br /&gt;
由于我们准备使用SES编译nordic的NRF52832的SDK，所以这里我们选择Activate Your Free License。这里因为nordic公司已经帮客户想SEGGER公司买下了使用的版权，所以是免费的许可证。&lt;br /&gt;
[[文件:Licensed2.jpg|边框|居中|无框|736x736像素]]&lt;br /&gt;
我们按照要求填写好信息，主要是邮箱地址（用于接收SEGGER公司发给我们的许可证），填写完成之后点击Request License。下面是我们接收的邮件信息，我们将红色框内的内容复制下来。&lt;br /&gt;
[[文件:Ses install3.png|边框|居中|无框|1301x1301像素]]&lt;br /&gt;
打开SES，选择Tools\License Manager...，我们选择Activate Embedded Studio，打开如下界面，并将我们的许可证粘贴进去，然后点击Install License。&lt;br /&gt;
[[文件:Licensed4.png|边框|居中|无框|481x481像素]]&lt;br /&gt;
安装好许可证之后，跳转到如下的界面。&lt;br /&gt;
[[文件:Licensed5.png|边框|居中|无框|481x481像素]]&lt;br /&gt;
此时我们的许可证激活完毕（有时候需要等待一段时间，才会显示激活成功）。&lt;br /&gt;
[[文件:Licensed6.png|边框|居中|无框|515x515像素]]&lt;br /&gt;
&lt;br /&gt;
== mesh实验学习流程概论 ==&lt;br /&gt;
1、首先大家一定要先查看一下我们资料中的手册“BLE-Mesh技术揭秘”，这个是很重要的一个mesh基本概念的介绍，只有看完这个文档，我们才能明白mesh组网是什么，是怎么完成的，后续的我们实验内容，也会在这个文档中查到相应的对照。&lt;br /&gt;
&lt;br /&gt;
2、按照Light_switch测试实验的实验说明，跑通测试流程（可以固件清零重新烧写多测试几遍，有助于增强我们的流程熟练度，利于后面的开发）。light_switch实验是现在nordic提供给我们的唯一的真正意义上的mesh组网通信例程。&lt;br /&gt;
&lt;br /&gt;
3、根据我们下面拆分的组网流程，对照依次的实验说明，进行mesh组网流程的分步学习。前期实验部分我们主要讲解节点设备，配置者设备功能我们使用手机app完成，目的是减少大家的学习难度，配置者设备我们会放在最后一个实验给大家做介绍。&lt;br /&gt;
&lt;br /&gt;
①广播（通信TX）——了解节点设备的广播功能，分为三个重要部分：PB-ADV和Unprov Beacon，PB-GATT的广播，以及模型的Publish用于发送数据。&lt;br /&gt;
&lt;br /&gt;
②扫描（通信RX）——了解节点设备的扫描功能，分为两个重要部分：普通的BLE SCAN功能，以及模块的Subscription用于接收数据。&lt;br /&gt;
&lt;br /&gt;
③入网验证——了解节点设备接入网络的验证过程，实验中使用的是PB-GATT去配置的，包含了：交换公共密钥，完成验证（是否有带外OOB）。&lt;br /&gt;
&lt;br /&gt;
④设备信息分配——启动配置数据分配，给节点分配如下数据：网络key（Netkey）、应用key（Appkey）以及节点设备地址（nodeAddr）。&lt;br /&gt;
&lt;br /&gt;
⑤元素——了解节点设备的元素与模型定义。&lt;br /&gt;
&lt;br /&gt;
⑥模型——了解模型的功能和创建方法，模型的订阅subscription和发布publish功能。&lt;br /&gt;
&lt;br /&gt;
== Light_switch测试实验 ==&lt;br /&gt;
=== 实验简介 ===&lt;br /&gt;
此示例演示了包含充当两个角色的设备的网状生态系统：配置角色（Provisioner role）和节点角色（Node role、provisionee role）。 它还通过在应用程序中使用[Generic OnOff model]来演示了如何使用Mesh模型。&lt;br /&gt;
&lt;br /&gt;
该示例由三个较小的示例组成：&lt;br /&gt;
&lt;br /&gt;
Light switch server：一个实现了[Generic OnOff server model]的简约服务器，用于接收状态数据并控制板上LED 1的状态。&lt;br /&gt;
&lt;br /&gt;
Light switch client：一个实现了[Generic OnOff client model]的简约客户端。当用户按下任意按钮时，OnOff Set消息将发送到配置的目标地址。&lt;br /&gt;
&lt;br /&gt;
Mesh Provisioner：一个简单的静态配置设备应用，用于建立演示网络，该配置设备在一个网状网络中配置所有节点。 此外，配置设备还可以在这些节点上配置网格模型[Generic OnOff model]实例的绑定以及发布和订阅设置，以使它们能够相互通信。&lt;br /&gt;
&lt;br /&gt;
[Generic OnOff Client/Server]模型用于操纵打开/关闭状态。请注意，当服务器设置了发布地址（如本例所示）时，服务器会将其状态更改的任何操作披露到其发布地址。&lt;br /&gt;
&lt;br /&gt;
下图给出了将由静态供应商设置的网状网络的整体视图， 括号中的数字表示预配置程序分配给这些节点的地址。&lt;br /&gt;
[[文件:Mesh network.png|边框|居中|无框|727x727像素]]&lt;br /&gt;
灯开关服务器和灯开关客户端示例均具有预配方角色。它们支持通过广告承载（PB-ADV）和GATT承载（PB-GATT）进行配置，并且还支持网状代理服务器（Proxy Service），但是不支持代理客户端（Proxy Client）。&lt;br /&gt;
&lt;br /&gt;
=== 硬件说明 ===&lt;br /&gt;
完成这个实验，我们最少需要两个开发板硬件用做我们的节点设备（Node）：&lt;br /&gt;
* 一个开发板用做client&lt;br /&gt;
* 一个或者多个开发板用做server&lt;br /&gt;
此外，我们还需要以下之一作为我们的配置设备（Provisioner）：&lt;br /&gt;
* 如果您决定使用静态预配器示例，则多准备一个开发板&lt;br /&gt;
* 如果您决定使用app程序进行设置，则需要安装手机app@link_nrf_mesh_app（@link_nrf_mesh_app_ios或@link_nrf_mesh_app_android）。&lt;br /&gt;
{{Note|text=默认我们使用3个开发板完成我们的实验，3个开发板分别对应client、server以及provisioner三个类型。|type=warning}}&lt;br /&gt;
&lt;br /&gt;
=== 软件说明 ===&lt;br /&gt;
1、我们使用NrfGo上位机，依次对三个开发板进行擦除及softdevice的烧写&lt;br /&gt;
[[文件:Lightswitch nrfgo.png|边框|居中|无框|1013x1013像素]]&lt;br /&gt;
2、使用SEGGER Embedded Studio for ARM编译器分别打开我们`&amp;lt;InstallFolder&amp;gt;/examples/light_switch`路径下的3个实验，并完成编译&lt;br /&gt;
&lt;br /&gt;
●\client\light_switch_client_nrf52832_xxAA_s132_6_1_0.emProject&lt;br /&gt;
&lt;br /&gt;
●\server\light_switch_server_nrf52832_xxAA_s132_6_1_0.emProject&lt;br /&gt;
&lt;br /&gt;
●\provisioner\light_switch_provisioner_nrf52832_xxAA_s132_6_1_0.emProject&lt;br /&gt;
[[文件:Lightswitch SES build.png|边框|居中|无框|1013x1013像素]]&lt;br /&gt;
3、此时我们对3个开发板分别进行固件的烧写，分别烧写client、server、provisioner（建议大家给3个开发板贴上标签，方便后续区分）&lt;br /&gt;
[[文件:Lightswitch SES download.png.gif|边框|居中|无框|1009x1009像素]]&lt;br /&gt;
&lt;br /&gt;
=== 实验现象 ===&lt;br /&gt;
{{Note|text=注意：保证我们的3个设备都是刚按照软件说明部分的步骤配置完成，并且没有进行过任何硬件控制（按键操作）。目的是保证所有设备均未被我们人为配置过，没有进行过网络配置，否则可能有任意异常（不同网络配置，导致的不同现象）|type=danger}}&lt;br /&gt;
1、首先我们分别给client和provisioner供电，然后我们按下provisioner的按键S1：&lt;br /&gt;
&lt;br /&gt;
●provisoner：LED灯点亮，代表正在配置查找设备，配置组网&lt;br /&gt;
&lt;br /&gt;
●client：首先LED3和LED4闪烁，代表正在组网；LED1-LED4四个灯一起闪烁，代表组网完成&lt;br /&gt;
&lt;br /&gt;
我们如果打开RTT检测log打印，可以看到如下的信息，这个时候我们已经给client设备分配了Node Address为0x0100。provisioner信息（上），client信息（下）。&lt;br /&gt;
[[文件:Lightswitch rtt provisoner1.png|边框|无框|1080x1080像素|居中]]&lt;br /&gt;
[[文件:Lightswitch_rtt_node_client.png|边框|居中|无框|656x656像素]]&lt;br /&gt;
&lt;br /&gt;
2、使用provisioner配置好client之后，我们给server开发板上电，这个时候provisioner将会配置server入网：&lt;br /&gt;
&lt;br /&gt;
●provisoner：LED1点亮代表正在配置网络，LED2点亮代表网络配置完成&lt;br /&gt;
&lt;br /&gt;
●server：首先LED3和LED4闪烁，代表正在组网；LED1-LED4四个灯一起闪烁，代表组网完成&lt;br /&gt;
&lt;br /&gt;
我们打开RTT检测log打印，可以看到server已经配置入网，并且被分配了地址Node Address为0x0104。provisioner信息（上），server信息（下）。&lt;br /&gt;
[[文件:Lightswitch rtt provisoner2.png|边框|无框|710x710像素|居中]]&lt;br /&gt;
[[文件:Lightswitch rtt node server.png|边框|无框|656x656像素|居中]]&lt;br /&gt;
&lt;br /&gt;
3、此时整个网络的配置已经完成，这个时候我们可以按下client上的按键来控制server上的LED点亮或者熄灭。&lt;br /&gt;
&lt;br /&gt;
由于我们的server被provisioner分配的Node Address是0x0104（偶数），所以我们通过client的S3控制server的LED1点亮，通过client的S4控制server的LED1熄灭。&lt;br /&gt;
{{Note|text=client设备对于server设备的控制，通过Node Address的奇偶位不同，分成了两个组。&lt;br /&gt;
client的S1和S2分别控制Node Address为奇数的server设备的LED1点亮和熄灭。&lt;br /&gt;
&lt;br /&gt;
client的S3和S4分别控制Node Address为偶数的server设备的LED1点亮和熄灭。|type=warning}}&lt;br /&gt;
我们打开RTT检测log打印，可以看到client分别按下button2（S3）以及button3（S4），分别会设置server的GPIO输出高低电平。client信息（上），server信息（下）。&lt;br /&gt;
[[文件:Lightswitch rtt node client2.png|边框|无框|656x656像素|居中]]&lt;br /&gt;
[[文件:Lightswitch rtt node server2.png|边框|无框|656x656像素|居中]]&lt;br /&gt;
&lt;br /&gt;
4、至此我们的mesh组网的lightswitch实验测试完成。&lt;br /&gt;
&lt;br /&gt;
== ①广播（通信TX） ==&lt;br /&gt;
有关广播部分，我们需要给大家讲解三个方面，分别如下：&lt;br /&gt;
&lt;br /&gt;
1、PB-ADV，这个是mesh中的广播承载层，用于我们启动配置过程（暂时的例程和手机app都是用的PB-GATT来配置的，但是源码中PB-ADV的流程是包含的）。&lt;br /&gt;
&lt;br /&gt;
2、Unprov Beacon，这个是广播一个未被配置的beacon，就是一个特殊形式的广播数据包，用于声明自己是未经配置的设备。&lt;br /&gt;
&lt;br /&gt;
3、PB-GATT，这个是mesh中的GATT承载层，用于我们启动配置过程（当前手机app就是使用PB-GATT，也就是连接之后，通过GATT服务去发送配置信息）。&lt;br /&gt;
&lt;br /&gt;
4、Publish，这个是我们用于模型发布数据的部分，我们将需要发送的数据通过advData广播出去。&lt;br /&gt;
&lt;br /&gt;
=== PB-ADV ===&lt;br /&gt;
PB-ADV说明请查看“BLE-Mesh技术揭秘”第8.1章节。&lt;br /&gt;
&lt;br /&gt;
我们来看下PB-ADV整个的流程，首先在mian文件中，我们的start()函数下，我们调用了mesh_provisionee_prov_start()函数去启动节点设备的等待配置。&lt;br /&gt;
[[文件:PB-ADV-01.png|边框|居中|无框|1200x1200px]]我们继续追踪mesh_provisionee_prov_start()，可以看到在使能宏定义MESH_FEATURE_PB_ADV_ENABLED的情况下，可以追踪到nrf_mesh_prov_bearer_adv_interface_get()函数。&lt;br /&gt;
[[文件:PB-ADV-02.png|边框|居中|无框|1200x1200px]]在nrf_mesh_prov_bearer_adv_interface_get()函数中，我们可以看到prov_bearer_interface_t结构体，这个结构体下面提供了我们配置承载层需要的API函数。&lt;br /&gt;
[[文件:PB-ADV-03.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
=== Unprov Beacon ===&lt;br /&gt;
Unprov Beacon说明请查看“BLE-Mesh技术揭秘”第5.5及5.5.1章节。&lt;br /&gt;
&lt;br /&gt;
上面我们有说到PB-ADV是承载层用于配置节点的功能，而Unprov Beacon则是我们的节点设备用于声明自己未经配置，所以这两个其实是一脉相承的。&lt;br /&gt;
&lt;br /&gt;
紧接着上面的nrf_mesh_prov_bearer_adv_interface_get()函数，其中的prov_bearer_interface_t结构体包含的是处理承载层的API函数，在这个结构体当中有一个.listen_start = prov_bearer_adv_listen，是用于开始监听传入的配置链接的功能，在这个函数中我们可以看到send_unprov_beacon()函数。&lt;br /&gt;
[[文件:PB-ADV-04.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
send_unprov_beacon()函数是用于发送Unprov Beacon数据的函数，在这个函数中我们调用prov_beacon_unprov_build函数去创建用于广播的Unprov Beacon数据包，然后调用advertiser_interval_set函数去设置广播的间隔，最后调用advertiser_packet_send函数去将我们的数据包发送出去。&lt;br /&gt;
&lt;br /&gt;
默认的Unprov Beacon广播间隔是NRF_MESH_PROV_BEARER_ADV_UNPROV_BEACON_INTERVAL_MS 2000。&lt;br /&gt;
&lt;br /&gt;
最终调用的是advertiser_packet_send(0函数去将广播数据发送出去的。&lt;br /&gt;
[[文件:PB-ADV-05.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Publish-12.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
这里我们再最后看一下prov_beacon_unprov_build()函数，这个函数是生成我们的Unprov Beacon的数据包。&lt;br /&gt;
&lt;br /&gt;
其中标识位是0x2B，这个flag是beacon的意思。data的第一个字节0x00代表是我们的BEACON_TYPE_UNPROV（未配置的节点设备），接下来的16个字节是device uuid（这个是寄存器FICR中的device id生成的），接下来2字节是oob info（这里是00 00，代表没有使能OOB），最后的4个字节是URI通过AES-CMAC算法计算出来的值。&lt;br /&gt;
[[文件:PB-GATT-11.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:PB-GATT-12.png|边框|居中|无框]]&lt;br /&gt;
&lt;br /&gt;
=== PB-GATT ===&lt;br /&gt;
PB-GATT说明请查看“BLE-Mesh技术揭秘”第8.1章节。&lt;br /&gt;
&lt;br /&gt;
和PB-ADV一样的，我们从main文件中的start()函数，追踪到mesh_provisionee_prov_start()，可以看到在使能宏定义MESH_FEATURE_PB_GATT_ENABLED的情况下，可以追踪到nrf_mesh_prov_bearer_gatt_init()以及nrf_mesh_prov_bearer_gatt_interface_get()函数。&lt;br /&gt;
[[文件:PB-GATT-01.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
首先我们看一下nrf_mesh_prov_bearer_gatt_init()函数，这个是初始化并设置PB-GATT服务（通过Mesh GATT接口）的函数。对比我们使用nrf master control panel手机APP连接之后获取到的服务，可以看到其中的PB-GATT的服务以及特征值。&lt;br /&gt;
&lt;br /&gt;
再往下的mesh_gatt_init()函数，这个就不给大家分析了，是和我们普通的BLE一样的方式去注册一个服务。&lt;br /&gt;
[[文件:PB-GATT-04.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:PB-GATT-03.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:PB-GATT-02.png|边框|居中|无框]]&lt;br /&gt;
在nrf_mesh_prov_bearer_gatt_interface_get()函数中，我们可以看到prov_bearer_interface_t结构体，这个结构体下面提供了我们配置承载层需要的API函数（这个是和上方的PB-ADV是一样的）。&lt;br /&gt;
[[文件:PB-GATT-05.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
和Unprov Beacon类似的，当我们开始监听配置链接的时候，会触发.listen_start = listen_start_cb的回调，在这个回调中我们触发FSM（有限状态机）的事件E_LISTEN_START，然后调用link_evt_send()去post顺序承载事件。&lt;br /&gt;
[[文件:PB-GATT-06.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
然后我们来到状态机列表m_pb_gatt_fsm_transition_table[]，可以看到E_LISTEN_START事件ID代表的动作ID是A_LISTEN_START。而最终我们的A_LISTEN_START动作，执行的是a_listen_start函数功能（这其中涉及的代码逻辑，暂时先不说明，大家可以自己查看源码的引用）。&lt;br /&gt;
[[文件:PB-GATT-07.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:PB-GATT-08.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
然后我们继续追踪a_listen_start()，可以看到其中调用了mesh_adv_params_set函数设置了我们广播的超时时间MESH_ADV_TIMEOUT_INFINITE以及广播间隔NRF_MESH_PROV_BEARER_GATT_UNPROV_BEACON_INTERVAL_MS，调用mesh_adv_data_set函数设置了我们的广播数据内容，最后调用mesh_adv_start函数启动广播。&lt;br /&gt;
&lt;br /&gt;
默认的PB-GATT的广播数据的广播周期是NRF_MESH_PROV_BEARER_GATT_UNPROV_BEACON_INTERVAL_MS 200。&lt;br /&gt;
[[文件:PB-GATT-09.png|边框|居中|无框|1200x1200像素]]我们同样的看一下mesh_adv_data_set()函数最终配置生成的广播数据，可以看到广播数据的flag是0x06，服务的UUID就是我们上面提到的PB-GATT服务的UUID，接下来16字节UUID是和PB-ADV一样的FICR寄存器的device id值，最后的扫描回调数据携带的是设备的全名“nRF5x Mesh Light”。&lt;br /&gt;
[[文件:PB-GATT-13.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:PB-GATT-14.png|边框|居中|无框]]最后是我们的mesh_adv_start()函数去启动广播，可以看到调用是我们ble stack sotfdevice当中的API函数sd_ble_gap_adv_start()去启动的。&lt;br /&gt;
[[文件:PB-GATT-15.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
=== Publish（模型发布） ===&lt;br /&gt;
这一章节我们只给大家讲解模块publish的数据发布流程，暂时不说明模型的创建方法，以及如何使用，这个放在下面单独的模型章节进行说明。&lt;br /&gt;
&lt;br /&gt;
以generic_onoff_server模型为例，我们找到access_model_publish以及access_model_reply函数，这两个函数的功能类似的，都是用于我们用户的模型发送数据的。&lt;br /&gt;
&lt;br /&gt;
其中access_model_publish是用于模型主动发送，而access_model_reply是用于模型给对应的Opcode回应数据。&lt;br /&gt;
&lt;br /&gt;
这里我们以access_model_publish函数为主，来看下发布数据的代码流程。&lt;br /&gt;
[[文件:Publish-01.png|边框|居中|无框|1200x1200像素]]查看一下access_model_publish()函数，我们可以看到packet_alloc_and_tx()函数，这个是我们继续发送数据的函数。下面还有一个蓝牙方框中的内容，是我们定义的重传机制（mesh数据为了保证传输的成功率，默认都是会重传数据包的，默认重传次数是4次）。[[文件:Publish-02.png|边框|居中|无框|1200x1200像素]]然后我们继续看一下packet_alloc_and_tx()函数，在这个函数中，我们最终会调用packet_tx()函数将数据发送出去。[[文件:Publish-03.png|边框|居中|无框|1200x1200像素]]继续追踪packet_tx()函数，这个函数的内容比较多，我们只截取了其中数据发送的部分，也就是我们的nrf_mesh_packet_send()函数，我们在他的tx_params中赋值好我们需要发送出去的数据内容。[[文件:Publish-04.png|边框|居中|无框|1199x1199像素]]nrf_mesh_packet_send()函数最终调用的是transport_tx()函数发送数据。而transport_tx()函数，最终是调用upper_transport_tx()函数进行的数据发送。[[文件:Publish-05.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Publish-06.png|边框|居中|无框|1200x1200像素]]我们再继续追踪upper_transport_tx()函数，可以看到我们会根据p_metadata-&amp;gt;segmented来判断数据是否要分包发送。&lt;br /&gt;
[[文件:Publish-13.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
接下里我们以不分包发送函数unsegmented_packet_tx()为例，来继续说明数据传输流程。&lt;br /&gt;
&lt;br /&gt;
我们可以看到我们的unsegmented_packet_tx()函数中，最终是调用的network_packet_send()函数。（分包的segmented_packet_tx()函数最终也是调用的network_packet_send()，大家可以自己追踪代码看一下）&lt;br /&gt;
[[文件:Publish-14.png|边框|居中|无框|1200x1200像素]]继续追踪我们的network_packet_send()函数，可以看到我们的net_packet_encrypt()加密函数，这个是将我们的明文数据，做好加密工作。&lt;br /&gt;
&lt;br /&gt;
然后调用core_tx_packet_send()函数发送出去。[[文件:Publish-15.png|边框|居中|无框|1200x1200像素]]最后我们追踪到core_tx_packet_send()函数，可以看到调用的是p_bearer-&amp;gt;p_interface-&amp;gt;packet_send()函数去发送的。所以接下来我们要找到我们的packet_send()功能是在哪边实现的。[[文件:Publish-07.png|边框|居中|无框|1200x1200像素]]可以看到我们是在core_tx_bearer_add函数中去获得的p_interface这个功能。[[文件:Publish-08.png|边框|居中|无框|1200x1200像素]]我们追踪core_tx_bearer_add()函数的引用路径，可以看到是在core_tx_adv_init()当中。（除了此处，还有另一处地方也有引用，功能是类似的，大家可以自行了解）。&lt;br /&gt;
&lt;br /&gt;
我们看下参数m_interface，可以看到m_interface-&amp;gt;packet_send功能，就是对应我们在core_tx_packet_send()函数调用的p_bearer-&amp;gt;p_interface-&amp;gt;packet_send()。[[文件:Publish-09.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Publish-10.png|边框|居中|无框|1200x1200像素]]所以我们接下来来看下m_interface结构体当中的packet_send()函数实现的是什么功能，可以看到最终调用的是advertiser_packet_send()函数将数据发送出去。看到这边我们就很熟悉了，和上面的几个广播功能，最后调用的都是相同的广播包发送函数。[[文件:Publish-11.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Publish-12.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
== ②扫描（通信RX） ==&lt;br /&gt;
&lt;br /&gt;
=== 标准BLE SCAN ===&lt;br /&gt;
看过了我们的广播功能之后，我们来看下扫描的部分。在ble mesh中，我们的扫描功能scan，就是用于获取数据的，类似于数据通信的RX。&lt;br /&gt;
&lt;br /&gt;
我们从main文件中的initialize()函数开始，首先是我们mesh_init()函数，最终我们会调用mesh_stack_init()函数。&lt;br /&gt;
[[文件:SCAN-01.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
在mesh_satck_init函数中，我们可以看到调用了nrf_mesh_init函数去初始化core的参数。&lt;br /&gt;
[[文件:SCAN-02.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
这一段是nrf_mesh_init函数部分，由于这个初始化函数太长，所以我们只截取了scan相关的部分。&lt;br /&gt;
&lt;br /&gt;
首先是第一点scanner_init(scanner_packet_process_cb)函数，在这个函数中，我们回去初始化扫描的参数配置，并且会开启扫描功能。最终我们可以从scanner_packet_process_cb这个回调中得到我们扫描到的数据（获得所有的BLE广播数据）。&lt;br /&gt;
&lt;br /&gt;
然后是第二点ad_listener_subscribe(&amp;amp;m_nrf_mesh_listener)，这个是用来侦听订阅的消息的（从获得的所有BLE广播数据中，筛选出的订阅的消息）。&lt;br /&gt;
[[文件:SCAN-03.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
然后我们继续看一下scanner_init的扫描初始化函数，在这个函数中我们首先调用scanner_config_reset()函数去配置扫描的默认参数。其中包含了：&lt;br /&gt;
&lt;br /&gt;
1、扫描的通道SCANNER_CHANNELS_DEFAULT，也就是BLE的｛37，38，39｝三个channels都去扫描。&lt;br /&gt;
&lt;br /&gt;
2、规范定义的非连接状态访问地址BEARER_ACCESS_ADDR_DEFAULT。&lt;br /&gt;
&lt;br /&gt;
3、配置物理层协议是1Mbit模式。&lt;br /&gt;
&lt;br /&gt;
4、扫描的间隔时间BEARER_SCAN_INT_DEFAULT_MS，扫描的窗口时间BEARER_SCAN_WINDOW_DEFAULT_MS&lt;br /&gt;
&lt;br /&gt;
5、射频的发射功率RADIO_POWER_NRF_0DBM。&lt;br /&gt;
&lt;br /&gt;
6、BLE广播包最大长度RADIO_CONFIG_ADV_MAX_PAYLOAD_SIZE。&lt;br /&gt;
&lt;br /&gt;
最后我们会在nrf_mesh_enable()函数中调用scanner_enable()函数去启动扫描，这个大家可以自己查看一下代码，比较容易理解。&lt;br /&gt;
[[文件:SCAN-04.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:SCAN-05.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
最后我们来看一下scanner_packet_process_cb()回调函数，其中我们可以直接拿到的扫描的数据包是rx_data，包含了数据的类型、数据内容以及数据长度等等信息。到这边我们的扫描功能是已经完成的，但是我们还需要再看下下面一部分的ad_listener的流程。&lt;br /&gt;
&lt;br /&gt;
=== Subscription（模型订阅） ===&lt;br /&gt;
我们需要注意下ad_listener_process()函数，这个函数是将我们扫描到的订阅数据（不符合标准的BLE广播数据，则认为是侦听到的订阅数据）传递到订阅者进行处理（订阅者指的是带订阅功能的模型）。这个是和我们上方说的关注点二ad_listener_subscribe()函数是一脉相承的。&lt;br /&gt;
[[文件:SCAN-06.png|边框|居中|无框|1200x1200像素]]然后我们来对照着看一下ad_listener_subscribe()函数以及ad_listener_process()函数。这两个函数一个是定义了侦听订阅，一个是将扫描获取到的数据传递给订阅者。这两者间的运作关系，大家有兴趣的可以继续查阅他的底层代码逻辑处理，一个是向list中添加订阅者信息，一个是将数据通过p_listener-&amp;gt;handler()将数据传递给订阅者处理。&lt;br /&gt;
[[文件:SCAN-07.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:SCAN-08.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
最终当我们扫描到订阅消息，并调用ad_listener_process()函数传递时，会触发我们注册的回调nrf_mesh_listen（调用ad_listener_subscribe(&amp;amp;m_nrf_mesh_listener)时，在m_nrf_mesh_listener中）。我们来看下nrf_mesh_listen函数中的处理，其中我们先从数据包中解析了数据的类型type，然后根据不同类型的数据进行相应的处理。&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+&lt;br /&gt;
|AD_TYPE_MESH &lt;br /&gt;
|蓝牙网格物体的AD类型&lt;br /&gt;
|-&lt;br /&gt;
|AD_TYPE_BEACON &lt;br /&gt;
|蓝牙网状信标的AD类型&lt;br /&gt;
|-&lt;br /&gt;
|AD_TYPE_PB_ADV &lt;br /&gt;
|PB-ADV消息的AD类型&lt;br /&gt;
|-&lt;br /&gt;
|AD_TYPE_DFU &lt;br /&gt;
|nRF OpenMesh消息的AD类型&lt;br /&gt;
|}&lt;br /&gt;
[[文件:SCAN-09.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
== ③入网验证 ==&lt;br /&gt;
经过前两章的学习，我们对于BLE MESH的广播及扫描的代码流程已经有了一定的了解，也就是已经大概的明白了数据的发送和接收的流程。&lt;br /&gt;
&lt;br /&gt;
那么从这一章节开始，我们将会给大家展示，如何配置一个节点设备入网。配置者设备，我们使用的是手机APP：nRF Mesh。&lt;br /&gt;
&lt;br /&gt;
我们从main文件中的start()函数开始，来看一下入网的代码处理及其流程。在start()函数中我们最后是调用的mesh_provisionee_prov_start()函数来启动的节点配置功能，我们来查看一下它所携带的参数prov_start_params。这部分的参数都是比较重要的，有携带的是设备的信息的、有携带认证密码的，有回调返回的，所以我们会挨个介绍一下。&lt;br /&gt;
[[文件:Prov-01.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
首先携带的是static_auth_data，这个是我们带外OOB认证的密码。如果我们在使用nRF Mesh去进行身份验证的时候，选择使用Static OOB，那么当我们在进行PB-GATT的连接过程中，会弹出一个新的窗口，要求我们去输入这个静态的128bit的OOB密钥。&lt;br /&gt;
[[文件:Prov-02.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Prov-14.png|边框|居中|无框]]&lt;br /&gt;
[[文件:Prov-15.png|边框|居中|无框]]&lt;br /&gt;
接下来携带的是provisioning_complete_cb()回调函数，这个函数处理的是节点设备成功的被配置入网，在这里我们可以获取到我们的节点设备被分配的node_Address。&lt;br /&gt;
[[文件:Prov-03.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
然后携带是device_identification_start_cb()回调函数，这个回调函数是用于通知开发者，我们的节点设备当前已经开始配置。&lt;br /&gt;
[[文件:Prov-04.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
再然后携带的是URI【统一资源标识符（Uniform Resource Identifier，URI)】，这个是用于标识我们的设备类型的，这个数据会通过AES-CMAC算法加密后添加到我们的Unprov Beacon数据的末尾。&lt;br /&gt;
&lt;br /&gt;
这里我们是light switch的server设备，所以我们定义URI是EX_URI_LS_SERVER。&lt;br /&gt;
[[文件:Prov-05.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
分析完携带的参数之后，我们继续来查看mesh_provisionee_prov_start()函数。在这个函数中，我们首先是定义了一下prov_caps，也就是配置支持的OOB功能。然后我们调用nrf_mesh_generate_keys()函数去生成有效的密钥对等待使用。&lt;br /&gt;
&lt;br /&gt;
再然后是调用nrf_mesh_prov_init()函数去初始化prov配置信息的结构体参数，里面有结构体本身m_pro_ctx，需要配置的参数公钥m_public、私钥m_private，以及我们刚刚定义的oob功能prov_caps，最后还携带了一个事件回调函数prov_ect_handler。&lt;br /&gt;
&lt;br /&gt;
接下来是PB-ADV以及PB-GATT的处理过程，这个前面已经讲解过，这里不再赘述。&lt;br /&gt;
&lt;br /&gt;
最后我们调用provisionee_start()函数去开启配置。&lt;br /&gt;
[[文件:Prov-06.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
我们继续来追踪一下provisionee_start()函数，在这个函数中我们调用nrf_mesh_prov_listen()函数去开启侦听承载层的消息。这里我们需要侦听PB-ADV以及PB-GATT两个。&lt;br /&gt;
[[文件:Prov-07.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
然后在nrf_mesh_prov_listen()函数中，我们调用prov_provisionee_listen()函数携带我们刚刚传入的nrf_mesh_prov_ctx_t结构体，以及p_bearer、URI、oob_info_source等参数信息，继续向下传递。&lt;br /&gt;
[[文件:Prov-08.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
最终我们来查看下prov_provisionee_listen()函数，在这个函数中我们传递了p_bearer-&amp;gt;p_callbacks给m_prov_callbacks回调，然后调用p_bearer-&amp;gt;p_interface-&amp;gt;listen_start()函数去启动PB-ADV以及PB-GATT的侦听。&lt;br /&gt;
[[文件:Prov-09.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
当我们在侦听之后，接收到任何配置相关的消息，都将通过m_prov_callbacks()函数回调传递给我们处理，大家可以自行看下其中4个不同的功能函数，具体处理的都是什么消息。&lt;br /&gt;
[[文件:Prov-10.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
在prov_provisionee_listen()函数中，我们可以看到回调函数指针m_prov_callbacks指向的是p_bearer-&amp;gt;p_callbacks，我们一路向上查找，最终可以找到这个p_bearer-&amp;gt;p_callbacks函数就是指的prov_evt_handler()函数。所以其实我们用户可以在这一层去查看比较重要的配置事件返回，而不需要到上面的m_prov_callbacks中这样偏底层的地方去查看。&lt;br /&gt;
&lt;br /&gt;
在这个回调函数中，我们可以看到mian文件中注册的prov_device_identification_start_cb、prov_device_identification_stop_cb、provisioning_aborted_cb的返回。&lt;br /&gt;
[[文件:Prov-11.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
那么除了上方已经存在的几个回调，我们可以看到mian文件中的start()函数下的prov_start_params参数，还缺少一个配置完成的回调prov_complete_cb()。&lt;br /&gt;
&lt;br /&gt;
这个回调的返回方式比较特殊，和上方其他的都不同，是由SD（softdevice）直接从底层返回给我们的。注册这个回调的函数是NRF_SDH_STATE_OBSERVER，返回的回调函数是sd_state_evt_handler。其中触发的事件NRF_SDH_EVT_STATE_ENABLED，这个是在mian文件中的initialize()函数下的ble_stack_init()函数中就被触发的，这个大家可以自行查看一下，不太重要，因为都是nordic底层处理好的消息。&lt;br /&gt;
[[文件:Prov-13.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Prov-12.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
以上整个配置流程的处理，不太好分步展示相应的现象，这个大家可以自己使用SES在线仿真的方式去调试一下流程，这里我们直接给大家展示整个流程打印出的相应的信息，以及nRF Mesh中暂时出来的现象。&lt;br /&gt;
[[文件:Prov-16.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Prov-17.png|边框|居中|无框]]&lt;br /&gt;
&lt;br /&gt;
== ④设备信息分配 ==&lt;br /&gt;
当我们成功的配置节点设备入网之后，我们需要给节点设备主要分配如下几个信息，有关这几个要点的介绍，可以查看“BLE-Mesh技术揭秘”第3章节：&lt;br /&gt;
&lt;br /&gt;
1、节点在网络中的地址Node Address&lt;br /&gt;
&lt;br /&gt;
2、网络密钥Netkey&lt;br /&gt;
&lt;br /&gt;
3、应用密钥Appkey&lt;br /&gt;
&lt;br /&gt;
4、元素的地址Element Address&lt;br /&gt;
&lt;br /&gt;
5、绑定model的Appkey，分配Publish Address，Subscription Address&lt;br /&gt;
&lt;br /&gt;
=== Node Address ===&lt;br /&gt;
首先我们来看一下我们使用nRF Mesh成功配置我们的节点之后，打印出来的消息，开始的紫色字体显示了我们给当前节点设备分配的Node Address。&lt;br /&gt;
&lt;br /&gt;
后面紧接着还有四段黄色字体的内容（这个部分大家的工程是不会打印的，是我们修改例程打印出来给大家展示一下），这四个部分就是我们配置节点的时候，配置者设备（nRF Mesh）给节点设备的四个主要指令，这些指令的具体功能，大家可以查看mesh的协议手册。[[文件:Config-13.png|边框|居中|无框|1080x1080像素]]在代码中，我们会在mian文件的provisioning_complete_cb()回调函数中获取并打印我们的Node Address，这个回调触发的含义就是我们的节点配置已经完成了。&lt;br /&gt;
&lt;br /&gt;
具体这个回调在代码中的回调及触发流程，大家看可以查看“③入网验证”的说明。[[文件:Config-20.png|边框|居中|无框|1200x1200像素]]我们使用nRF Mesh配置好节点设备之后，app界面显示内容如下，包含了节点的信息（地址、名称、配置时间等等），元素和模型，以及Netkey和Appkey等几个重要的设备信息。[[文件:Config-01.jpg|边框|居中|无框]]&lt;br /&gt;
[[文件:Config-04.jpg|边框|居中|无框]]&lt;br /&gt;
&lt;br /&gt;
=== Netkey ===&lt;br /&gt;
我们点击查看一下Network key，可以看到里面有一个Network Key 1，这是一个16字节的网络密钥。我们可以在nRF Mesh的Settings界面去增加或者删除一个network key，用户可以自定义此密钥。[[文件:Config-02.jpg|边框|居中|无框]]在我们的代码中，我们是在pro_evt_handler回调的NRF_MESH_PROV_EVT_COMPLETE事件中获取这个netkey，这个回调函数的触发流程同样是在“③入网验证”已经有说明。[[文件:Prov-11.png|边框|居中|无框|1200x1200像素]]我们直接看下NRF_MESH_PROV_EVT_COMPLETE事件下的mesh_stack_provisioning_data_store()函数，在这个函数中我们主要的任务，就是将配置者分配给我们的node address、netkey、devkey等等信息，分别做一些存储以及功能绑定。&lt;br /&gt;
[[文件:Netkey-01.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
=== Appkey ===&lt;br /&gt;
同样的我们打开Application Key，可以看到其中包含的3个密钥，这里我们只选用了Application Key 1作为我们的密钥。大家同样可以在nRF Mesh的Settings界面去增加或者删除一个App key，用户可以自定义此密钥。[[文件:Config-03.jpg|边框|居中|无框]]appkey这里和netkey不同，netkey是我们网络中的节点设备以及我们配置者设备共有的一个密钥，只有拥有这个密钥，我们才能在当前网络中通信，所以当我们的节点设备配置入网后，会立刻得到netkey（必须要分配给我的节点设备才行）。&lt;br /&gt;
&lt;br /&gt;
而appkey只是我们的某个模型用于通信的密钥，所以我们称它为应用密钥，这个appkey的分配，是在我们的config server下。这个config_server_init的初始化流程，是在我们的mesh_stack_init()函数下，就去初始化配置好的。最终当我们有信息配置的时候，会触发opcode_handlers，我们可以从这个操作码列表中查找相应的信息处理。[[文件:Config-22.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Config-23.png|边框|居中|无框|1200x1200像素]]我们来看下config server的opcode_handlers[]操作码处理程序列表，使用黄色标记给大家圈了几个操作码指令，这几个是我们配置一个最简单的server节点设备，都需要使用到的，包含了：&lt;br /&gt;
&lt;br /&gt;
1、appkey的添加，CONFIG_OPCODE_APPKEY_ADD&lt;br /&gt;
&lt;br /&gt;
2、publish发布地址，CONFIG_OPCODE_MODEL_PUBLICATION_VIRTUAL_ADDRESS_SET&lt;br /&gt;
&lt;br /&gt;
3、subscription订阅地址，CONFIG_OPCODE_MODEL_SUBSCRIPTION_ADD&lt;br /&gt;
&lt;br /&gt;
4、模型appkey的绑定，CONFIG_OPCODE_MODEL_APP_BIND&lt;br /&gt;
&lt;br /&gt;
5、netkey的更新，CONFIG_OPCODE_NETKEY_ADD[[文件:Config-21.png|边框|居中|无框|1200x1200像素]]我们可以看到当接收到CONFIG_OPCODE_APPKEY_ADD操作码的数据，会调用handle_appkey_add()函数去处理，在这个函数中我们最终会调用dsm_appkey_add()函数去存储我们的appkey。[[文件:Appkey-01.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
== ⑤Element元素 ==&lt;br /&gt;
元素简单的说就是定义了节点的功能，一些具有复杂功能的节点设备，会包含多个元素。一个节点设备最少需要包含一个元素，我们称之为主元素。&lt;br /&gt;
&lt;br /&gt;
从主元素开始（如果有多个元素，则第一个元素就是主元素），我们会给它分配相应的元素地址。主元素的地址就是节点的地址，第二个元素的地址就是主元素的地址基础上加上1，依次类推。&lt;br /&gt;
&lt;br /&gt;
可以看到在我们的主元素Element：0x0002中包含了3个不同的服务，这3个服务就是我们的模型Model，其中Configuration Server与Health Server这两个服务，是每一个节点都必须携带的模型。Configuration Server（配置服务）是用于配置节点设备信息，Health Server（健康服务）是用于节点进行网络内的心跳联系。剩下的Generic On Off Server服务，这个就是我们用于控制一个开关量的模型（在例程中我们通过控制LED的IO口高低电平来展示）。[[文件:Config-05.jpg|边框|居中|无框]]在我们的代码中，我们首先需要在nrf_mesh_config_app.h中定义好我们节点设备的最大元素数量，可以看到我们定义#define ACCESS_ELEMENT_COUNT (1)，也就是说我们当前的例程，只含有一个主元素。&lt;br /&gt;
[[文件:Element-01.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
而这个ACCESS_ELEMENT_COUNT ，是在我们开启节点配置的地方被调用的，这个mesh_provsionionee_prov_start()函数在前面的内容中有多次讲到，这里就不再说明了。&lt;br /&gt;
[[文件:Element-02.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
== ⑥model模型 ==&lt;br /&gt;
如果说元素是一个功能集合，那么模型就是元素下的具体的某一个功能。&lt;br /&gt;
&lt;br /&gt;
我们以灯的控制为例，灯的常用控制有开关、亮度、色温几种，其中开关、亮度和色温就是具体的模块，而处理灯控的集合就是元素。&lt;br /&gt;
&lt;br /&gt;
=== Configure Server ===&lt;br /&gt;
配置服务模型，这个的大概代码处理流程在我们的“④设备信息分配-&amp;gt;Appkey”已经有了说明，它是我们节点设备必须包含的一个特殊功能的模型。&lt;br /&gt;
&lt;br /&gt;
它的实际功能就像它的名字一样，就是用于配置（configuration）我们的设备信息，比如appkey的添加删除，密钥的绑定删除，其他的模型的发布和订阅控制。具体包含的操作码的使用方法，我们会在generic on off server模型当中使用的时候来说明。&lt;br /&gt;
&lt;br /&gt;
=== Health Server ===&lt;br /&gt;
健康服务，顾名思义就是判断我们的设备是否在网络中保持健康（正常）工作，它同样是一个必须包含的特殊功能的模型。&lt;br /&gt;
&lt;br /&gt;
它的初始化和config server是一样的，都是放在了mesh_stack_init()函数下。&lt;br /&gt;
[[文件:Health-01.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
我们来看下health_server_init()函数，最终我们会调用access_model_add()函数去初始化模型，并将模型添加到对应的元素中。&lt;br /&gt;
&lt;br /&gt;
其中的access_model_add_params_t参数，包含的分别是元素的索引（将模型添加到第几个元素当中），模型的ID（由mesh协议手册确定好了），以及最最重要的操作码列表，这个列表中会包含我们的指令码，以及对应的函数功能处理。&lt;br /&gt;
[[文件:Health-02.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
最后我们看下具体包含了哪些健康模型操作码：&lt;br /&gt;
&lt;br /&gt;
1、“运行状况当前状态”消息的操作码，HEALTH_OPCODE_CURRENT_STATUS&lt;br /&gt;
&lt;br /&gt;
2、“ 健康故障状态”消息的操作码，HEALTH_OPCODE_FAULT_STATUS&lt;br /&gt;
&lt;br /&gt;
3、“  获取健康注意事项”消息的操作码，HEALTH_OPCODE_ATTENTION_GET&lt;br /&gt;
&lt;br /&gt;
4、“ 健康注意集”消息的操作码，HEALTH_OPCODE_ATTENTION_SET&lt;br /&gt;
&lt;br /&gt;
5、“未确认健康注意事项”消息的操作码，HEALTH_OPCODE_ATTENTION_SET_UNACKED&lt;br /&gt;
&lt;br /&gt;
6、“健康注意状态”消息的操作码，HEALTH_OPCODE_ATTENTION_STATUS&lt;br /&gt;
&lt;br /&gt;
7、“健康故障清除”消息的操作码，HEALTH_OPCODE_FAULT_CLEAR&lt;br /&gt;
&lt;br /&gt;
8、“健康故障清除未确认”消息的操作码，HEALTH_OPCODE_FAULT_CLEAR_UNACKED&lt;br /&gt;
&lt;br /&gt;
9、“健康故障获取”消息的操作码，HEALTH_OPCODE_FAULT_GET&lt;br /&gt;
&lt;br /&gt;
10、“健康故障测试”消息的操作码，HEALTH_OPCODE_FAULT_TEST&lt;br /&gt;
&lt;br /&gt;
11、“未确认健康故障测试”消息的操作码，HEALTH_OPCODE_FAULT_TEST_UNACKED&lt;br /&gt;
&lt;br /&gt;
12、“健康期获取”消息的操作码，HEALTH_OPCODE_PERIOD_GET&lt;br /&gt;
&lt;br /&gt;
13、“健康期设置”消息的操作码，HEALTH_OPCODE_PERIOD_SET&lt;br /&gt;
&lt;br /&gt;
14、“未确认健康期设置”消息的操作码，HEALTH_OPCODE_PERIOD_SET_UNACKED&lt;br /&gt;
&lt;br /&gt;
15、“健康期状态”消息的操作码，HEALTH_OPCODE_PERIOD_STATUS&lt;br /&gt;
[[文件:Health-03.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
在大家的前期使用中，一般也不会真的需要大家去处理health server下的指令，毕竟我们刚开始使用，也很难让设备在网络中出现各种运行问题（节点少功能少）。但是当大家自己开始开发实际功能时，就一定要注意分析health server下的指令码返回，用来快速解决我们网络中的问题。&lt;br /&gt;
&lt;br /&gt;
=== Generic On Off Server ===&lt;br /&gt;
接下来我们来看下Generic On Off Server服务，这个服务和上方的两个服务不太一样，他不是我们的特殊必要服务，是一个通用的模型（用于具体的功能数据的处理，这里是用于开关功能）。借用这个服务模型，我们给大家介绍一下如何创建一个符合BLE MESH协议的模型。&lt;br /&gt;
&lt;br /&gt;
我们看下模型的初始化方法，在generic_onoff_server_init()函数中，首先我们使用access_model_add_params_t定义一个模型分配参数的结构体init_params，在这个结构体当中，我们要给定：&lt;br /&gt;
&lt;br /&gt;
1、model_id（模型的ID），这个是mesh协议规定好的，我们可以查看“归档资料\8-蓝牙协议手册和芯片手册\蓝牙mesh协议手册\《MshMDLv1.0.1.pdf》”的第7.3章节有归纳&lt;br /&gt;
&lt;br /&gt;
2、element_index（元素索引），这个看我们准备将这个服务添加到哪一个元素下，这里我们因为只有一个主元素，所以我们的元素索引为0&lt;br /&gt;
&lt;br /&gt;
3、p_opcode_handlers（操作码列表，指令码列表），这个也是mesh协议规定好的，我们可以查看“归档资料\8-蓝牙协议手册和芯片手册\蓝牙mesh协议手册\《MshMDLv1.0.1.pdf》”的第7.1章节归纳，具体的操作码的功能及要求，大家需要对应这个pdf手册，自己查看一下，对于一个模型ID，我们不一定要注册所有的属于它的opcode，只要注册我们需要使用的即可&lt;br /&gt;
&lt;br /&gt;
4、opcode_count（操作码数量），准备注册的操作码的数量&lt;br /&gt;
&lt;br /&gt;
5、p_args（通用参数指针），用于参数的传递&lt;br /&gt;
&lt;br /&gt;
6、publish_timeout_cb（发布超时时间），发布的定时器到期，会进入此回调，我们可以做一些处理（例如重新发送数据等等）。如果我们的模型不支持定期发布，可以将此值设置为NULL。&lt;br /&gt;
&lt;br /&gt;
当我们配置好模型的参数结构体之后，会调用access_model_add()函数去初始化模型并添加到元素，同时会返回给我们用于存储此模型的句柄指针p_server-&amp;gt;model_handle。&lt;br /&gt;
&lt;br /&gt;
因为我们的onoff模型，需要订阅消息，所以我们需要调用access_model_subscription_list_alloc()函数，携带我们的模型句柄去分配模型的订阅列表。&lt;br /&gt;
[[文件:Onoff-01.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
看完模型的初始化注册之后，我们来看下模型包含的opcode操作码，可以看到在m_opcode_handlers[]操作码列表中包含了三个opcode。这三个opcode我们可以看下他的定义，以及对照一下《MshMDLv1.0.1.pdf》第7.1章节的归纳，可以看到是一一对应的关系。具体这三个opcode的功能，大家可以查看手册中的说明，或者直接看下他们对应的功能处理函数。&lt;br /&gt;
[[文件:Onoff-02.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Onoff-03.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Onoff-04.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
接下来我们看下GENERIC_ONOFF_OPCODE_SET与GENERIC_ONOFF_OPCODE_SET_UNACKNOWLEDGED对应的handle_set，可以看到在这个函数的最后，我们会去调用status_send()函数去发送数据。&lt;br /&gt;
[[文件:Onoff-05.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
然后我们看下status_send()函数，在这个函数中，我们最终会调用access_model_publish来发布我们的数据，或者使用accsee_model_reply来回复数据。&lt;br /&gt;
&lt;br /&gt;
这两个函数再往下就是我们之前在广播部分讲解的packet_alloc_and_tx函数去发送广播数据了，这个有不清楚的，大家可以到前面去查看一下。&lt;br /&gt;
[[文件:Onoff-06.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
==== model bind appkey ====&lt;br /&gt;
那么按照上方的方法注册好模型，然后我们的节点设备被成功的配置入网之后，我们如何让这个模型工作起来。&lt;br /&gt;
&lt;br /&gt;
首先第一步是需要将我们的模型绑定到相应的appkey，只有这样我们的模型才能拿到用于通信的密钥，例如如下的截图当中，我们利用手机app软件nRF Mesh去给我们的Generic On Off Server模型绑定Appkey1。[[文件:Config-14.png|边框|居中|无框|1040x1040像素]]&lt;br /&gt;
[[文件:Config-06.jpg|边框|居中|无框]]&lt;br /&gt;
[[文件:Config-07.jpg|边框|居中|无框]]&lt;br /&gt;
[[文件:Config-08.jpg|边框|居中|无框]]当我们在使用app去绑定appkey的时候，在我们的代码中的体现是在config server下的opcode_handlers[]操作码列表中的CONFIG_OPCODE_MODEL_APP_BIND。&lt;br /&gt;
[[文件:Onoff-07.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
我们追踪一下CONFIG_OPCODE_MODEL_APP_BIND对应的handle_model_app_bind_unbind()，这个函数比较长，这边只给大家截取了部分，在为截取到部分，还有获取元素索引的get_element_index，获取模型ID和模型handle的access_handle_get，然后才是我们的dsm_appkey_index_to_appkey_handle函数用于将appkey索引给到appkey句柄。然后调用access_model_application_bind函数去绑定模型和appkey，最终如果是成功绑定，则调用access_flash_config_store函数去存储修改的绑定信息。&lt;br /&gt;
[[文件:Onoff-08.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
==== model publish address ====&lt;br /&gt;
当我们完成了模型的appkey绑定之后，我们就可以开始设置puhlish的地址以及subscription的地址。&lt;br /&gt;
&lt;br /&gt;
这里我们先看下nRF Mesh配置publish Address，可以看到我们选择的定义的0xC000组地址。[[文件:Model-pub-01.jpg|边框|居中|无框]]&lt;br /&gt;
[[文件:Model-pub-02.jpg|边框|居中|无框]]&lt;br /&gt;
[[文件:Model-pub-03.jpg|边框|居中|无框]]&lt;br /&gt;
[[文件:Model-pub-04.jpg|边框|居中|无框]]我们可以看到，在我们配置publish地址的时候，RTTViewer中会打印黄色标注的操作码数据信息，可以看到他的OPCODE是0x03。[[文件:Model-pub-05.png|边框|居中|无框|996x996像素]]我们来看下这个操作码对应的功能，可以看到CONFIG_OPCODE_MODEL_PUBLICATION_SET操作码对应的功能函数是handle_config_model_publication_set，根据函数的名称我们就可以看出，是用于配置模型发布的。[[文件:Model-sub-04.jpg|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Model-pub-07.jpg|边框|居中|无框|1200x1200像素]]在handle_config_model_publication_set函数中，可以看到我们先使用dsm_appkey_index_to_appkey_handle函数获取了appkey的句柄。然后调用dsm_address_publish_add函数去添加发布的地址。[[文件:Model-pub-08.jpg|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
==== model subscription address ====&lt;br /&gt;
然后我们再看下订阅地址的添加，可以看到我们使用nRF Mesh配置subscription订阅了组地址0xC001。[[文件:Model-sub-02.jpg|边框|居中|无框]]&lt;br /&gt;
[[文件:Model-sub-01.jpg|边框|居中|无框]]可以看到，当我们分配subscription地址的时候，可以看到RTTViewer中打印黄色标注的信息。可以看到他的OPCODE是0x801B。[[文件:Model-sub-03.jpg|边框|居中|无框|982x982像素]]我们来看下这个操作码对应的功能，可以看到CONFIG_OPCODE_MODEL_SUBSCRIPTION_ADD操作码对应的功能函数是handle_config_model_subscription_add，根据函数的名称我们就可以看出，是用于配置模型订阅的。[[文件:Model-pub-06.jpg|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Model-sub-05.jpg|边框|居中|无框|1200x1200像素]]在handle_config_model_subscription_add函数中，我们会调用dsm_address_subscription_add函数去添加我们的订阅地址。[[文件:Model-sub-06.jpg|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
== Mesh串口透传实验 ==&lt;br /&gt;
&lt;br /&gt;
== Mesh天猫精灵实验 ==&lt;br /&gt;
[[分类:NRF52832DK]]&lt;br /&gt;
[[分类:实验手册]]&lt;/div&gt;</summary>
		<author><name>Jinx</name></author>
		
	</entry>
	<entry>
		<id>http://doc.iotxx.com/index.php?title=NRF52832DK-Mesh%E7%BB%84%E7%BD%91%E5%AE%9E%E9%AA%8C&amp;diff=2817</id>
		<title>NRF52832DK-Mesh组网实验</title>
		<link rel="alternate" type="text/html" href="http://doc.iotxx.com/index.php?title=NRF52832DK-Mesh%E7%BB%84%E7%BD%91%E5%AE%9E%E9%AA%8C&amp;diff=2817"/>
		<updated>2020-04-02T03:44:27Z</updated>

		<summary type="html">&lt;p&gt;Jinx：/* model publish address */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;蓝牙Mesh和BLE是并行发展的一个独立分支，虽然底层共用Physical Layer和Link Layer，但是上层协议并不相同。因此，nRF52832的Mesh例程，并不在BLE协议栈sdk中，而是安装一个扩展包。&lt;br /&gt;
&lt;br /&gt;
本文介绍蓝牙Mesh相关的环境搭建和实验介绍。&lt;br /&gt;
&lt;br /&gt;
== Mesh开发环境 ==&lt;br /&gt;
&lt;br /&gt;
=== 蓝牙Mesh SDK ===&lt;br /&gt;
SDK位置：&amp;lt;code&amp;gt;归档资料/2-协议栈SDK/nRF5_SDK_Mesh_v310.zip&amp;lt;/code&amp;gt;，注意文件名中的Mesh字样。&lt;br /&gt;
&lt;br /&gt;
和BLE协议栈一样，将该压缩包直接解压到BLE协议栈同级目录即可使用，解压后的SDK路径如下：&amp;lt;code&amp;gt;E:\project-nordic\nRF5_SDK_Mesh_v310&amp;lt;/code&amp;gt;&lt;br /&gt;
[[文件:NRF52832DK-MESH-SDK-INSTALL.gif|居中|无框|750x750像素]]&lt;br /&gt;
&lt;br /&gt;
=== 集成开发软件SES ===&lt;br /&gt;
SES时是Segger Embedded Studio编译器的缩写，用来编译蓝牙Mesh代码，SES功能与IAR或Keil类似。&lt;br /&gt;
&lt;br /&gt;
备注：Nordic为何使用SES而不是IAR，原因不得而知。&lt;br /&gt;
&lt;br /&gt;
==== SES安装 ====&lt;br /&gt;
SES软件位置：&amp;lt;code&amp;gt;归档资料/7-编译器SES/Setup_EmbeddedStudio_ARM_v410_win_x64.exe&amp;lt;/code&amp;gt;。&lt;br /&gt;
&lt;br /&gt;
我们双击打开安装界面，按照如下动图所示方式进行安装（可以全部默认安装）。[[文件:Ses install.gif|边框|居中|无框|556x556像素]]&lt;br /&gt;
&lt;br /&gt;
==== SES许可证激活 ====&lt;br /&gt;
我们双击打开安装好的SES，选择编译自带测试例程，此时弹出需要激活的界面。&lt;br /&gt;
[[文件:Licensed1.jpg|边框|居中|无框|1205x1205像素]]&lt;br /&gt;
由于我们准备使用SES编译nordic的NRF52832的SDK，所以这里我们选择Activate Your Free License。这里因为nordic公司已经帮客户想SEGGER公司买下了使用的版权，所以是免费的许可证。&lt;br /&gt;
[[文件:Licensed2.jpg|边框|居中|无框|736x736像素]]&lt;br /&gt;
我们按照要求填写好信息，主要是邮箱地址（用于接收SEGGER公司发给我们的许可证），填写完成之后点击Request License。下面是我们接收的邮件信息，我们将红色框内的内容复制下来。&lt;br /&gt;
[[文件:Ses install3.png|边框|居中|无框|1301x1301像素]]&lt;br /&gt;
打开SES，选择Tools\License Manager...，我们选择Activate Embedded Studio，打开如下界面，并将我们的许可证粘贴进去，然后点击Install License。&lt;br /&gt;
[[文件:Licensed4.png|边框|居中|无框|481x481像素]]&lt;br /&gt;
安装好许可证之后，跳转到如下的界面。&lt;br /&gt;
[[文件:Licensed5.png|边框|居中|无框|481x481像素]]&lt;br /&gt;
此时我们的许可证激活完毕（有时候需要等待一段时间，才会显示激活成功）。&lt;br /&gt;
[[文件:Licensed6.png|边框|居中|无框|515x515像素]]&lt;br /&gt;
&lt;br /&gt;
== mesh实验学习流程概论 ==&lt;br /&gt;
1、首先大家一定要先查看一下我们资料中的手册“BLE-Mesh技术揭秘”，这个是很重要的一个mesh基本概念的介绍，只有看完这个文档，我们才能明白mesh组网是什么，是怎么完成的，后续的我们实验内容，也会在这个文档中查到相应的对照。&lt;br /&gt;
&lt;br /&gt;
2、按照Light_switch测试实验的实验说明，跑通测试流程（可以固件清零重新烧写多测试几遍，有助于增强我们的流程熟练度，利于后面的开发）。light_switch实验是现在nordic提供给我们的唯一的真正意义上的mesh组网通信例程。&lt;br /&gt;
&lt;br /&gt;
3、根据我们下面拆分的组网流程，对照依次的实验说明，进行mesh组网流程的分步学习。前期实验部分我们主要讲解节点设备，配置者设备功能我们使用手机app完成，目的是减少大家的学习难度，配置者设备我们会放在最后一个实验给大家做介绍。&lt;br /&gt;
&lt;br /&gt;
①广播（通信TX）——了解节点设备的广播功能，分为三个重要部分：PB-ADV和Unprov Beacon，PB-GATT的广播，以及模型的Publish用于发送数据。&lt;br /&gt;
&lt;br /&gt;
②扫描（通信RX）——了解节点设备的扫描功能，分为两个重要部分：普通的BLE SCAN功能，以及模块的Subscription用于接收数据。&lt;br /&gt;
&lt;br /&gt;
③入网验证——了解节点设备接入网络的验证过程，实验中使用的是PB-GATT去配置的，包含了：交换公共密钥，完成验证（是否有带外OOB）。&lt;br /&gt;
&lt;br /&gt;
④设备信息分配——启动配置数据分配，给节点分配如下数据：网络key（Netkey）、应用key（Appkey）以及节点设备地址（nodeAddr）。&lt;br /&gt;
&lt;br /&gt;
⑤元素——了解节点设备的元素与模型定义。&lt;br /&gt;
&lt;br /&gt;
⑥模型——了解模型的功能和创建方法，模型的订阅subscription和发布publish功能。&lt;br /&gt;
&lt;br /&gt;
== Light_switch测试实验 ==&lt;br /&gt;
=== 实验简介 ===&lt;br /&gt;
此示例演示了包含充当两个角色的设备的网状生态系统：配置角色（Provisioner role）和节点角色（Node role、provisionee role）。 它还通过在应用程序中使用[Generic OnOff model]来演示了如何使用Mesh模型。&lt;br /&gt;
&lt;br /&gt;
该示例由三个较小的示例组成：&lt;br /&gt;
&lt;br /&gt;
Light switch server：一个实现了[Generic OnOff server model]的简约服务器，用于接收状态数据并控制板上LED 1的状态。&lt;br /&gt;
&lt;br /&gt;
Light switch client：一个实现了[Generic OnOff client model]的简约客户端。当用户按下任意按钮时，OnOff Set消息将发送到配置的目标地址。&lt;br /&gt;
&lt;br /&gt;
Mesh Provisioner：一个简单的静态配置设备应用，用于建立演示网络，该配置设备在一个网状网络中配置所有节点。 此外，配置设备还可以在这些节点上配置网格模型[Generic OnOff model]实例的绑定以及发布和订阅设置，以使它们能够相互通信。&lt;br /&gt;
&lt;br /&gt;
[Generic OnOff Client/Server]模型用于操纵打开/关闭状态。请注意，当服务器设置了发布地址（如本例所示）时，服务器会将其状态更改的任何操作披露到其发布地址。&lt;br /&gt;
&lt;br /&gt;
下图给出了将由静态供应商设置的网状网络的整体视图， 括号中的数字表示预配置程序分配给这些节点的地址。&lt;br /&gt;
[[文件:Mesh network.png|边框|居中|无框|727x727像素]]&lt;br /&gt;
灯开关服务器和灯开关客户端示例均具有预配方角色。它们支持通过广告承载（PB-ADV）和GATT承载（PB-GATT）进行配置，并且还支持网状代理服务器（Proxy Service），但是不支持代理客户端（Proxy Client）。&lt;br /&gt;
&lt;br /&gt;
=== 硬件说明 ===&lt;br /&gt;
完成这个实验，我们最少需要两个开发板硬件用做我们的节点设备（Node）：&lt;br /&gt;
* 一个开发板用做client&lt;br /&gt;
* 一个或者多个开发板用做server&lt;br /&gt;
此外，我们还需要以下之一作为我们的配置设备（Provisioner）：&lt;br /&gt;
* 如果您决定使用静态预配器示例，则多准备一个开发板&lt;br /&gt;
* 如果您决定使用app程序进行设置，则需要安装手机app@link_nrf_mesh_app（@link_nrf_mesh_app_ios或@link_nrf_mesh_app_android）。&lt;br /&gt;
{{Note|text=默认我们使用3个开发板完成我们的实验，3个开发板分别对应client、server以及provisioner三个类型。|type=warning}}&lt;br /&gt;
&lt;br /&gt;
=== 软件说明 ===&lt;br /&gt;
1、我们使用NrfGo上位机，依次对三个开发板进行擦除及softdevice的烧写&lt;br /&gt;
[[文件:Lightswitch nrfgo.png|边框|居中|无框|1013x1013像素]]&lt;br /&gt;
2、使用SEGGER Embedded Studio for ARM编译器分别打开我们`&amp;lt;InstallFolder&amp;gt;/examples/light_switch`路径下的3个实验，并完成编译&lt;br /&gt;
&lt;br /&gt;
●\client\light_switch_client_nrf52832_xxAA_s132_6_1_0.emProject&lt;br /&gt;
&lt;br /&gt;
●\server\light_switch_server_nrf52832_xxAA_s132_6_1_0.emProject&lt;br /&gt;
&lt;br /&gt;
●\provisioner\light_switch_provisioner_nrf52832_xxAA_s132_6_1_0.emProject&lt;br /&gt;
[[文件:Lightswitch SES build.png|边框|居中|无框|1013x1013像素]]&lt;br /&gt;
3、此时我们对3个开发板分别进行固件的烧写，分别烧写client、server、provisioner（建议大家给3个开发板贴上标签，方便后续区分）&lt;br /&gt;
[[文件:Lightswitch SES download.png.gif|边框|居中|无框|1009x1009像素]]&lt;br /&gt;
&lt;br /&gt;
=== 实验现象 ===&lt;br /&gt;
{{Note|text=注意：保证我们的3个设备都是刚按照软件说明部分的步骤配置完成，并且没有进行过任何硬件控制（按键操作）。目的是保证所有设备均未被我们人为配置过，没有进行过网络配置，否则可能有任意异常（不同网络配置，导致的不同现象）|type=danger}}&lt;br /&gt;
1、首先我们分别给client和provisioner供电，然后我们按下provisioner的按键S1：&lt;br /&gt;
&lt;br /&gt;
●provisoner：LED灯点亮，代表正在配置查找设备，配置组网&lt;br /&gt;
&lt;br /&gt;
●client：首先LED3和LED4闪烁，代表正在组网；LED1-LED4四个灯一起闪烁，代表组网完成&lt;br /&gt;
&lt;br /&gt;
我们如果打开RTT检测log打印，可以看到如下的信息，这个时候我们已经给client设备分配了Node Address为0x0100。provisioner信息（上），client信息（下）。&lt;br /&gt;
[[文件:Lightswitch rtt provisoner1.png|边框|无框|1080x1080像素|居中]]&lt;br /&gt;
[[文件:Lightswitch_rtt_node_client.png|边框|居中|无框|656x656像素]]&lt;br /&gt;
&lt;br /&gt;
2、使用provisioner配置好client之后，我们给server开发板上电，这个时候provisioner将会配置server入网：&lt;br /&gt;
&lt;br /&gt;
●provisoner：LED1点亮代表正在配置网络，LED2点亮代表网络配置完成&lt;br /&gt;
&lt;br /&gt;
●server：首先LED3和LED4闪烁，代表正在组网；LED1-LED4四个灯一起闪烁，代表组网完成&lt;br /&gt;
&lt;br /&gt;
我们打开RTT检测log打印，可以看到server已经配置入网，并且被分配了地址Node Address为0x0104。provisioner信息（上），server信息（下）。&lt;br /&gt;
[[文件:Lightswitch rtt provisoner2.png|边框|无框|710x710像素|居中]]&lt;br /&gt;
[[文件:Lightswitch rtt node server.png|边框|无框|656x656像素|居中]]&lt;br /&gt;
&lt;br /&gt;
3、此时整个网络的配置已经完成，这个时候我们可以按下client上的按键来控制server上的LED点亮或者熄灭。&lt;br /&gt;
&lt;br /&gt;
由于我们的server被provisioner分配的Node Address是0x0104（偶数），所以我们通过client的S3控制server的LED1点亮，通过client的S4控制server的LED1熄灭。&lt;br /&gt;
{{Note|text=client设备对于server设备的控制，通过Node Address的奇偶位不同，分成了两个组。&lt;br /&gt;
client的S1和S2分别控制Node Address为奇数的server设备的LED1点亮和熄灭。&lt;br /&gt;
&lt;br /&gt;
client的S3和S4分别控制Node Address为偶数的server设备的LED1点亮和熄灭。|type=warning}}&lt;br /&gt;
我们打开RTT检测log打印，可以看到client分别按下button2（S3）以及button3（S4），分别会设置server的GPIO输出高低电平。client信息（上），server信息（下）。&lt;br /&gt;
[[文件:Lightswitch rtt node client2.png|边框|无框|656x656像素|居中]]&lt;br /&gt;
[[文件:Lightswitch rtt node server2.png|边框|无框|656x656像素|居中]]&lt;br /&gt;
&lt;br /&gt;
4、至此我们的mesh组网的lightswitch实验测试完成。&lt;br /&gt;
&lt;br /&gt;
== ①广播（通信TX） ==&lt;br /&gt;
有关广播部分，我们需要给大家讲解三个方面，分别如下：&lt;br /&gt;
&lt;br /&gt;
1、PB-ADV，这个是mesh中的广播承载层，用于我们启动配置过程（暂时的例程和手机app都是用的PB-GATT来配置的，但是源码中PB-ADV的流程是包含的）。&lt;br /&gt;
&lt;br /&gt;
2、Unprov Beacon，这个是广播一个未被配置的beacon，就是一个特殊形式的广播数据包，用于声明自己是未经配置的设备。&lt;br /&gt;
&lt;br /&gt;
3、PB-GATT，这个是mesh中的GATT承载层，用于我们启动配置过程（当前手机app就是使用PB-GATT，也就是连接之后，通过GATT服务去发送配置信息）。&lt;br /&gt;
&lt;br /&gt;
4、Publish，这个是我们用于模型发布数据的部分，我们将需要发送的数据通过advData广播出去。&lt;br /&gt;
&lt;br /&gt;
=== PB-ADV ===&lt;br /&gt;
PB-ADV说明请查看“BLE-Mesh技术揭秘”第8.1章节。&lt;br /&gt;
&lt;br /&gt;
我们来看下PB-ADV整个的流程，首先在mian文件中，我们的start()函数下，我们调用了mesh_provisionee_prov_start()函数去启动节点设备的等待配置。&lt;br /&gt;
[[文件:PB-ADV-01.png|边框|居中|无框|1200x1200px]]我们继续追踪mesh_provisionee_prov_start()，可以看到在使能宏定义MESH_FEATURE_PB_ADV_ENABLED的情况下，可以追踪到nrf_mesh_prov_bearer_adv_interface_get()函数。&lt;br /&gt;
[[文件:PB-ADV-02.png|边框|居中|无框|1200x1200px]]在nrf_mesh_prov_bearer_adv_interface_get()函数中，我们可以看到prov_bearer_interface_t结构体，这个结构体下面提供了我们配置承载层需要的API函数。&lt;br /&gt;
[[文件:PB-ADV-03.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
=== Unprov Beacon ===&lt;br /&gt;
Unprov Beacon说明请查看“BLE-Mesh技术揭秘”第5.5及5.5.1章节。&lt;br /&gt;
&lt;br /&gt;
上面我们有说到PB-ADV是承载层用于配置节点的功能，而Unprov Beacon则是我们的节点设备用于声明自己未经配置，所以这两个其实是一脉相承的。&lt;br /&gt;
&lt;br /&gt;
紧接着上面的nrf_mesh_prov_bearer_adv_interface_get()函数，其中的prov_bearer_interface_t结构体包含的是处理承载层的API函数，在这个结构体当中有一个.listen_start = prov_bearer_adv_listen，是用于开始监听传入的配置链接的功能，在这个函数中我们可以看到send_unprov_beacon()函数。&lt;br /&gt;
[[文件:PB-ADV-04.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
send_unprov_beacon()函数是用于发送Unprov Beacon数据的函数，在这个函数中我们调用prov_beacon_unprov_build函数去创建用于广播的Unprov Beacon数据包，然后调用advertiser_interval_set函数去设置广播的间隔，最后调用advertiser_packet_send函数去将我们的数据包发送出去。&lt;br /&gt;
&lt;br /&gt;
默认的Unprov Beacon广播间隔是NRF_MESH_PROV_BEARER_ADV_UNPROV_BEACON_INTERVAL_MS 2000。&lt;br /&gt;
&lt;br /&gt;
最终调用的是advertiser_packet_send(0函数去将广播数据发送出去的。&lt;br /&gt;
[[文件:PB-ADV-05.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Publish-12.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
这里我们再最后看一下prov_beacon_unprov_build()函数，这个函数是生成我们的Unprov Beacon的数据包。&lt;br /&gt;
&lt;br /&gt;
其中标识位是0x2B，这个flag是beacon的意思。data的第一个字节0x00代表是我们的BEACON_TYPE_UNPROV（未配置的节点设备），接下来的16个字节是device uuid（这个是寄存器FICR中的device id生成的），接下来2字节是oob info（这里是00 00，代表没有使能OOB），最后的4个字节是URI通过AES-CMAC算法计算出来的值。&lt;br /&gt;
[[文件:PB-GATT-11.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:PB-GATT-12.png|边框|居中|无框]]&lt;br /&gt;
&lt;br /&gt;
=== PB-GATT ===&lt;br /&gt;
PB-GATT说明请查看“BLE-Mesh技术揭秘”第8.1章节。&lt;br /&gt;
&lt;br /&gt;
和PB-ADV一样的，我们从main文件中的start()函数，追踪到mesh_provisionee_prov_start()，可以看到在使能宏定义MESH_FEATURE_PB_GATT_ENABLED的情况下，可以追踪到nrf_mesh_prov_bearer_gatt_init()以及nrf_mesh_prov_bearer_gatt_interface_get()函数。&lt;br /&gt;
[[文件:PB-GATT-01.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
首先我们看一下nrf_mesh_prov_bearer_gatt_init()函数，这个是初始化并设置PB-GATT服务（通过Mesh GATT接口）的函数。对比我们使用nrf master control panel手机APP连接之后获取到的服务，可以看到其中的PB-GATT的服务以及特征值。&lt;br /&gt;
&lt;br /&gt;
再往下的mesh_gatt_init()函数，这个就不给大家分析了，是和我们普通的BLE一样的方式去注册一个服务。&lt;br /&gt;
[[文件:PB-GATT-04.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:PB-GATT-03.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:PB-GATT-02.png|边框|居中|无框]]&lt;br /&gt;
在nrf_mesh_prov_bearer_gatt_interface_get()函数中，我们可以看到prov_bearer_interface_t结构体，这个结构体下面提供了我们配置承载层需要的API函数（这个是和上方的PB-ADV是一样的）。&lt;br /&gt;
[[文件:PB-GATT-05.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
和Unprov Beacon类似的，当我们开始监听配置链接的时候，会触发.listen_start = listen_start_cb的回调，在这个回调中我们触发FSM（有限状态机）的事件E_LISTEN_START，然后调用link_evt_send()去post顺序承载事件。&lt;br /&gt;
[[文件:PB-GATT-06.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
然后我们来到状态机列表m_pb_gatt_fsm_transition_table[]，可以看到E_LISTEN_START事件ID代表的动作ID是A_LISTEN_START。而最终我们的A_LISTEN_START动作，执行的是a_listen_start函数功能（这其中涉及的代码逻辑，暂时先不说明，大家可以自己查看源码的引用）。&lt;br /&gt;
[[文件:PB-GATT-07.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:PB-GATT-08.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
然后我们继续追踪a_listen_start()，可以看到其中调用了mesh_adv_params_set函数设置了我们广播的超时时间MESH_ADV_TIMEOUT_INFINITE以及广播间隔NRF_MESH_PROV_BEARER_GATT_UNPROV_BEACON_INTERVAL_MS，调用mesh_adv_data_set函数设置了我们的广播数据内容，最后调用mesh_adv_start函数启动广播。&lt;br /&gt;
&lt;br /&gt;
默认的PB-GATT的广播数据的广播周期是NRF_MESH_PROV_BEARER_GATT_UNPROV_BEACON_INTERVAL_MS 200。&lt;br /&gt;
[[文件:PB-GATT-09.png|边框|居中|无框|1200x1200像素]]我们同样的看一下mesh_adv_data_set()函数最终配置生成的广播数据，可以看到广播数据的flag是0x06，服务的UUID就是我们上面提到的PB-GATT服务的UUID，接下来16字节UUID是和PB-ADV一样的FICR寄存器的device id值，最后的扫描回调数据携带的是设备的全名“nRF5x Mesh Light”。&lt;br /&gt;
[[文件:PB-GATT-13.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:PB-GATT-14.png|边框|居中|无框]]最后是我们的mesh_adv_start()函数去启动广播，可以看到调用是我们ble stack sotfdevice当中的API函数sd_ble_gap_adv_start()去启动的。&lt;br /&gt;
[[文件:PB-GATT-15.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
=== Publish（模型发布） ===&lt;br /&gt;
这一章节我们只给大家讲解模块publish的数据发布流程，暂时不说明模型的创建方法，以及如何使用，这个放在下面单独的模型章节进行说明。&lt;br /&gt;
&lt;br /&gt;
以generic_onoff_server模型为例，我们找到access_model_publish以及access_model_reply函数，这两个函数的功能类似的，都是用于我们用户的模型发送数据的。&lt;br /&gt;
&lt;br /&gt;
其中access_model_publish是用于模型主动发送，而access_model_reply是用于模型给对应的Opcode回应数据。&lt;br /&gt;
&lt;br /&gt;
这里我们以access_model_publish函数为主，来看下发布数据的代码流程。&lt;br /&gt;
[[文件:Publish-01.png|边框|居中|无框|1200x1200像素]]查看一下access_model_publish()函数，我们可以看到packet_alloc_and_tx()函数，这个是我们继续发送数据的函数。下面还有一个蓝牙方框中的内容，是我们定义的重传机制（mesh数据为了保证传输的成功率，默认都是会重传数据包的，默认重传次数是4次）。[[文件:Publish-02.png|边框|居中|无框|1200x1200像素]]然后我们继续看一下packet_alloc_and_tx()函数，在这个函数中，我们最终会调用packet_tx()函数将数据发送出去。[[文件:Publish-03.png|边框|居中|无框|1200x1200像素]]继续追踪packet_tx()函数，这个函数的内容比较多，我们只截取了其中数据发送的部分，也就是我们的nrf_mesh_packet_send()函数，我们在他的tx_params中赋值好我们需要发送出去的数据内容。[[文件:Publish-04.png|边框|居中|无框|1199x1199像素]]nrf_mesh_packet_send()函数最终调用的是transport_tx()函数发送数据。而transport_tx()函数，最终是调用upper_transport_tx()函数进行的数据发送。[[文件:Publish-05.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Publish-06.png|边框|居中|无框|1200x1200像素]]我们再继续追踪upper_transport_tx()函数，可以看到我们会根据p_metadata-&amp;gt;segmented来判断数据是否要分包发送。&lt;br /&gt;
[[文件:Publish-13.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
接下里我们以不分包发送函数unsegmented_packet_tx()为例，来继续说明数据传输流程。&lt;br /&gt;
&lt;br /&gt;
我们可以看到我们的unsegmented_packet_tx()函数中，最终是调用的network_packet_send()函数。（分包的segmented_packet_tx()函数最终也是调用的network_packet_send()，大家可以自己追踪代码看一下）&lt;br /&gt;
[[文件:Publish-14.png|边框|居中|无框|1200x1200像素]]继续追踪我们的network_packet_send()函数，可以看到我们的net_packet_encrypt()加密函数，这个是将我们的明文数据，做好加密工作。&lt;br /&gt;
&lt;br /&gt;
然后调用core_tx_packet_send()函数发送出去。[[文件:Publish-15.png|边框|居中|无框|1200x1200像素]]最后我们追踪到core_tx_packet_send()函数，可以看到调用的是p_bearer-&amp;gt;p_interface-&amp;gt;packet_send()函数去发送的。所以接下来我们要找到我们的packet_send()功能是在哪边实现的。[[文件:Publish-07.png|边框|居中|无框|1200x1200像素]]可以看到我们是在core_tx_bearer_add函数中去获得的p_interface这个功能。[[文件:Publish-08.png|边框|居中|无框|1200x1200像素]]我们追踪core_tx_bearer_add()函数的引用路径，可以看到是在core_tx_adv_init()当中。（除了此处，还有另一处地方也有引用，功能是类似的，大家可以自行了解）。&lt;br /&gt;
&lt;br /&gt;
我们看下参数m_interface，可以看到m_interface-&amp;gt;packet_send功能，就是对应我们在core_tx_packet_send()函数调用的p_bearer-&amp;gt;p_interface-&amp;gt;packet_send()。[[文件:Publish-09.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Publish-10.png|边框|居中|无框|1200x1200像素]]所以我们接下来来看下m_interface结构体当中的packet_send()函数实现的是什么功能，可以看到最终调用的是advertiser_packet_send()函数将数据发送出去。看到这边我们就很熟悉了，和上面的几个广播功能，最后调用的都是相同的广播包发送函数。[[文件:Publish-11.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Publish-12.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
== ②扫描（通信RX） ==&lt;br /&gt;
&lt;br /&gt;
=== 标准BLE SCAN ===&lt;br /&gt;
看过了我们的广播功能之后，我们来看下扫描的部分。在ble mesh中，我们的扫描功能scan，就是用于获取数据的，类似于数据通信的RX。&lt;br /&gt;
&lt;br /&gt;
我们从main文件中的initialize()函数开始，首先是我们mesh_init()函数，最终我们会调用mesh_stack_init()函数。&lt;br /&gt;
[[文件:SCAN-01.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
在mesh_satck_init函数中，我们可以看到调用了nrf_mesh_init函数去初始化core的参数。&lt;br /&gt;
[[文件:SCAN-02.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
这一段是nrf_mesh_init函数部分，由于这个初始化函数太长，所以我们只截取了scan相关的部分。&lt;br /&gt;
&lt;br /&gt;
首先是第一点scanner_init(scanner_packet_process_cb)函数，在这个函数中，我们回去初始化扫描的参数配置，并且会开启扫描功能。最终我们可以从scanner_packet_process_cb这个回调中得到我们扫描到的数据（获得所有的BLE广播数据）。&lt;br /&gt;
&lt;br /&gt;
然后是第二点ad_listener_subscribe(&amp;amp;m_nrf_mesh_listener)，这个是用来侦听订阅的消息的（从获得的所有BLE广播数据中，筛选出的订阅的消息）。&lt;br /&gt;
[[文件:SCAN-03.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
然后我们继续看一下scanner_init的扫描初始化函数，在这个函数中我们首先调用scanner_config_reset()函数去配置扫描的默认参数。其中包含了：&lt;br /&gt;
&lt;br /&gt;
1、扫描的通道SCANNER_CHANNELS_DEFAULT，也就是BLE的｛37，38，39｝三个channels都去扫描。&lt;br /&gt;
&lt;br /&gt;
2、规范定义的非连接状态访问地址BEARER_ACCESS_ADDR_DEFAULT。&lt;br /&gt;
&lt;br /&gt;
3、配置物理层协议是1Mbit模式。&lt;br /&gt;
&lt;br /&gt;
4、扫描的间隔时间BEARER_SCAN_INT_DEFAULT_MS，扫描的窗口时间BEARER_SCAN_WINDOW_DEFAULT_MS&lt;br /&gt;
&lt;br /&gt;
5、射频的发射功率RADIO_POWER_NRF_0DBM。&lt;br /&gt;
&lt;br /&gt;
6、BLE广播包最大长度RADIO_CONFIG_ADV_MAX_PAYLOAD_SIZE。&lt;br /&gt;
&lt;br /&gt;
最后我们会在nrf_mesh_enable()函数中调用scanner_enable()函数去启动扫描，这个大家可以自己查看一下代码，比较容易理解。&lt;br /&gt;
[[文件:SCAN-04.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:SCAN-05.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
最后我们来看一下scanner_packet_process_cb()回调函数，其中我们可以直接拿到的扫描的数据包是rx_data，包含了数据的类型、数据内容以及数据长度等等信息。到这边我们的扫描功能是已经完成的，但是我们还需要再看下下面一部分的ad_listener的流程。&lt;br /&gt;
&lt;br /&gt;
=== Subscription（模型订阅） ===&lt;br /&gt;
我们需要注意下ad_listener_process()函数，这个函数是将我们扫描到的订阅数据（不符合标准的BLE广播数据，则认为是侦听到的订阅数据）传递到订阅者进行处理（订阅者指的是带订阅功能的模型）。这个是和我们上方说的关注点二ad_listener_subscribe()函数是一脉相承的。&lt;br /&gt;
[[文件:SCAN-06.png|边框|居中|无框|1200x1200像素]]然后我们来对照着看一下ad_listener_subscribe()函数以及ad_listener_process()函数。这两个函数一个是定义了侦听订阅，一个是将扫描获取到的数据传递给订阅者。这两者间的运作关系，大家有兴趣的可以继续查阅他的底层代码逻辑处理，一个是向list中添加订阅者信息，一个是将数据通过p_listener-&amp;gt;handler()将数据传递给订阅者处理。&lt;br /&gt;
[[文件:SCAN-07.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:SCAN-08.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
最终当我们扫描到订阅消息，并调用ad_listener_process()函数传递时，会触发我们注册的回调nrf_mesh_listen（调用ad_listener_subscribe(&amp;amp;m_nrf_mesh_listener)时，在m_nrf_mesh_listener中）。我们来看下nrf_mesh_listen函数中的处理，其中我们先从数据包中解析了数据的类型type，然后根据不同类型的数据进行相应的处理。&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+&lt;br /&gt;
|AD_TYPE_MESH &lt;br /&gt;
|蓝牙网格物体的AD类型&lt;br /&gt;
|-&lt;br /&gt;
|AD_TYPE_BEACON &lt;br /&gt;
|蓝牙网状信标的AD类型&lt;br /&gt;
|-&lt;br /&gt;
|AD_TYPE_PB_ADV &lt;br /&gt;
|PB-ADV消息的AD类型&lt;br /&gt;
|-&lt;br /&gt;
|AD_TYPE_DFU &lt;br /&gt;
|nRF OpenMesh消息的AD类型&lt;br /&gt;
|}&lt;br /&gt;
[[文件:SCAN-09.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
== ③入网验证 ==&lt;br /&gt;
经过前两章的学习，我们对于BLE MESH的广播及扫描的代码流程已经有了一定的了解，也就是已经大概的明白了数据的发送和接收的流程。&lt;br /&gt;
&lt;br /&gt;
那么从这一章节开始，我们将会给大家展示，如何配置一个节点设备入网。配置者设备，我们使用的是手机APP：nRF Mesh。&lt;br /&gt;
&lt;br /&gt;
我们从main文件中的start()函数开始，来看一下入网的代码处理及其流程。在start()函数中我们最后是调用的mesh_provisionee_prov_start()函数来启动的节点配置功能，我们来查看一下它所携带的参数prov_start_params。这部分的参数都是比较重要的，有携带的是设备的信息的、有携带认证密码的，有回调返回的，所以我们会挨个介绍一下。&lt;br /&gt;
[[文件:Prov-01.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
首先携带的是static_auth_data，这个是我们带外OOB认证的密码。如果我们在使用nRF Mesh去进行身份验证的时候，选择使用Static OOB，那么当我们在进行PB-GATT的连接过程中，会弹出一个新的窗口，要求我们去输入这个静态的128bit的OOB密钥。&lt;br /&gt;
[[文件:Prov-02.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Prov-14.png|边框|居中|无框]]&lt;br /&gt;
[[文件:Prov-15.png|边框|居中|无框]]&lt;br /&gt;
接下来携带的是provisioning_complete_cb()回调函数，这个函数处理的是节点设备成功的被配置入网，在这里我们可以获取到我们的节点设备被分配的node_Address。&lt;br /&gt;
[[文件:Prov-03.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
然后携带是device_identification_start_cb()回调函数，这个回调函数是用于通知开发者，我们的节点设备当前已经开始配置。&lt;br /&gt;
[[文件:Prov-04.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
再然后携带的是URI【统一资源标识符（Uniform Resource Identifier，URI)】，这个是用于标识我们的设备类型的，这个数据会通过AES-CMAC算法加密后添加到我们的Unprov Beacon数据的末尾。&lt;br /&gt;
&lt;br /&gt;
这里我们是light switch的server设备，所以我们定义URI是EX_URI_LS_SERVER。&lt;br /&gt;
[[文件:Prov-05.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
分析完携带的参数之后，我们继续来查看mesh_provisionee_prov_start()函数。在这个函数中，我们首先是定义了一下prov_caps，也就是配置支持的OOB功能。然后我们调用nrf_mesh_generate_keys()函数去生成有效的密钥对等待使用。&lt;br /&gt;
&lt;br /&gt;
再然后是调用nrf_mesh_prov_init()函数去初始化prov配置信息的结构体参数，里面有结构体本身m_pro_ctx，需要配置的参数公钥m_public、私钥m_private，以及我们刚刚定义的oob功能prov_caps，最后还携带了一个事件回调函数prov_ect_handler。&lt;br /&gt;
&lt;br /&gt;
接下来是PB-ADV以及PB-GATT的处理过程，这个前面已经讲解过，这里不再赘述。&lt;br /&gt;
&lt;br /&gt;
最后我们调用provisionee_start()函数去开启配置。&lt;br /&gt;
[[文件:Prov-06.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
我们继续来追踪一下provisionee_start()函数，在这个函数中我们调用nrf_mesh_prov_listen()函数去开启侦听承载层的消息。这里我们需要侦听PB-ADV以及PB-GATT两个。&lt;br /&gt;
[[文件:Prov-07.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
然后在nrf_mesh_prov_listen()函数中，我们调用prov_provisionee_listen()函数携带我们刚刚传入的nrf_mesh_prov_ctx_t结构体，以及p_bearer、URI、oob_info_source等参数信息，继续向下传递。&lt;br /&gt;
[[文件:Prov-08.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
最终我们来查看下prov_provisionee_listen()函数，在这个函数中我们传递了p_bearer-&amp;gt;p_callbacks给m_prov_callbacks回调，然后调用p_bearer-&amp;gt;p_interface-&amp;gt;listen_start()函数去启动PB-ADV以及PB-GATT的侦听。&lt;br /&gt;
[[文件:Prov-09.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
当我们在侦听之后，接收到任何配置相关的消息，都将通过m_prov_callbacks()函数回调传递给我们处理，大家可以自行看下其中4个不同的功能函数，具体处理的都是什么消息。&lt;br /&gt;
[[文件:Prov-10.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
在prov_provisionee_listen()函数中，我们可以看到回调函数指针m_prov_callbacks指向的是p_bearer-&amp;gt;p_callbacks，我们一路向上查找，最终可以找到这个p_bearer-&amp;gt;p_callbacks函数就是指的prov_evt_handler()函数。所以其实我们用户可以在这一层去查看比较重要的配置事件返回，而不需要到上面的m_prov_callbacks中这样偏底层的地方去查看。&lt;br /&gt;
&lt;br /&gt;
在这个回调函数中，我们可以看到mian文件中注册的prov_device_identification_start_cb、prov_device_identification_stop_cb、provisioning_aborted_cb的返回。&lt;br /&gt;
[[文件:Prov-11.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
那么除了上方已经存在的几个回调，我们可以看到mian文件中的start()函数下的prov_start_params参数，还缺少一个配置完成的回调prov_complete_cb()。&lt;br /&gt;
&lt;br /&gt;
这个回调的返回方式比较特殊，和上方其他的都不同，是由SD（softdevice）直接从底层返回给我们的。注册这个回调的函数是NRF_SDH_STATE_OBSERVER，返回的回调函数是sd_state_evt_handler。其中触发的事件NRF_SDH_EVT_STATE_ENABLED，这个是在mian文件中的initialize()函数下的ble_stack_init()函数中就被触发的，这个大家可以自行查看一下，不太重要，因为都是nordic底层处理好的消息。&lt;br /&gt;
[[文件:Prov-13.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Prov-12.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
以上整个配置流程的处理，不太好分步展示相应的现象，这个大家可以自己使用SES在线仿真的方式去调试一下流程，这里我们直接给大家展示整个流程打印出的相应的信息，以及nRF Mesh中暂时出来的现象。&lt;br /&gt;
[[文件:Prov-16.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Prov-17.png|边框|居中|无框]]&lt;br /&gt;
&lt;br /&gt;
== ④设备信息分配 ==&lt;br /&gt;
当我们成功的配置节点设备入网之后，我们需要给节点设备主要分配如下几个信息，有关这几个要点的介绍，可以查看“BLE-Mesh技术揭秘”第3章节：&lt;br /&gt;
&lt;br /&gt;
1、节点在网络中的地址Node Address&lt;br /&gt;
&lt;br /&gt;
2、网络密钥Netkey&lt;br /&gt;
&lt;br /&gt;
3、应用密钥Appkey&lt;br /&gt;
&lt;br /&gt;
4、元素的地址Element Address&lt;br /&gt;
&lt;br /&gt;
5、绑定model的Appkey，分配Publish Address，Subscription Address&lt;br /&gt;
&lt;br /&gt;
=== Node Address ===&lt;br /&gt;
首先我们来看一下我们使用nRF Mesh成功配置我们的节点之后，打印出来的消息，开始的紫色字体显示了我们给当前节点设备分配的Node Address。&lt;br /&gt;
&lt;br /&gt;
后面紧接着还有四段黄色字体的内容（这个部分大家的工程是不会打印的，是我们修改例程打印出来给大家展示一下），这四个部分就是我们配置节点的时候，配置者设备（nRF Mesh）给节点设备的四个主要指令，这些指令的具体功能，大家可以查看mesh的协议手册。[[文件:Config-13.png|边框|居中|无框|1080x1080像素]]在代码中，我们会在mian文件的provisioning_complete_cb()回调函数中获取并打印我们的Node Address，这个回调触发的含义就是我们的节点配置已经完成了。&lt;br /&gt;
&lt;br /&gt;
具体这个回调在代码中的回调及触发流程，大家看可以查看“③入网验证”的说明。[[文件:Config-20.png|边框|居中|无框|1200x1200像素]]我们使用nRF Mesh配置好节点设备之后，app界面显示内容如下，包含了节点的信息（地址、名称、配置时间等等），元素和模型，以及Netkey和Appkey等几个重要的设备信息。[[文件:Config-01.jpg|边框|居中|无框]]&lt;br /&gt;
[[文件:Config-04.jpg|边框|居中|无框]]&lt;br /&gt;
&lt;br /&gt;
=== Netkey ===&lt;br /&gt;
我们点击查看一下Network key，可以看到里面有一个Network Key 1，这是一个16字节的网络密钥。我们可以在nRF Mesh的Settings界面去增加或者删除一个network key，用户可以自定义此密钥。[[文件:Config-02.jpg|边框|居中|无框]]在我们的代码中，我们是在pro_evt_handler回调的NRF_MESH_PROV_EVT_COMPLETE事件中获取这个netkey，这个回调函数的触发流程同样是在“③入网验证”已经有说明。[[文件:Prov-11.png|边框|居中|无框|1200x1200像素]]我们直接看下NRF_MESH_PROV_EVT_COMPLETE事件下的mesh_stack_provisioning_data_store()函数，在这个函数中我们主要的任务，就是将配置者分配给我们的node address、netkey、devkey等等信息，分别做一些存储以及功能绑定。&lt;br /&gt;
[[文件:Netkey-01.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
=== Appkey ===&lt;br /&gt;
同样的我们打开Application Key，可以看到其中包含的3个密钥，这里我们只选用了Application Key 1作为我们的密钥。大家同样可以在nRF Mesh的Settings界面去增加或者删除一个App key，用户可以自定义此密钥。[[文件:Config-03.jpg|边框|居中|无框]]appkey这里和netkey不同，netkey是我们网络中的节点设备以及我们配置者设备共有的一个密钥，只有拥有这个密钥，我们才能在当前网络中通信，所以当我们的节点设备配置入网后，会立刻得到netkey（必须要分配给我的节点设备才行）。&lt;br /&gt;
&lt;br /&gt;
而appkey只是我们的某个模型用于通信的密钥，所以我们称它为应用密钥，这个appkey的分配，是在我们的config server下。这个config_server_init的初始化流程，是在我们的mesh_stack_init()函数下，就去初始化配置好的。最终当我们有信息配置的时候，会触发opcode_handlers，我们可以从这个操作码列表中查找相应的信息处理。[[文件:Config-22.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Config-23.png|边框|居中|无框|1200x1200像素]]我们来看下config server的opcode_handlers[]操作码处理程序列表，使用黄色标记给大家圈了几个操作码指令，这几个是我们配置一个最简单的server节点设备，都需要使用到的，包含了：&lt;br /&gt;
&lt;br /&gt;
1、appkey的添加，CONFIG_OPCODE_APPKEY_ADD&lt;br /&gt;
&lt;br /&gt;
2、publish发布地址，CONFIG_OPCODE_MODEL_PUBLICATION_VIRTUAL_ADDRESS_SET&lt;br /&gt;
&lt;br /&gt;
3、subscription订阅地址，CONFIG_OPCODE_MODEL_SUBSCRIPTION_ADD&lt;br /&gt;
&lt;br /&gt;
4、模型appkey的绑定，CONFIG_OPCODE_MODEL_APP_BIND&lt;br /&gt;
&lt;br /&gt;
5、netkey的更新，CONFIG_OPCODE_NETKEY_ADD[[文件:Config-21.png|边框|居中|无框|1200x1200像素]]我们可以看到当接收到CONFIG_OPCODE_APPKEY_ADD操作码的数据，会调用handle_appkey_add()函数去处理，在这个函数中我们最终会调用dsm_appkey_add()函数去存储我们的appkey。[[文件:Appkey-01.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
== ⑤Element元素 ==&lt;br /&gt;
元素简单的说就是定义了节点的功能，一些具有复杂功能的节点设备，会包含多个元素。一个节点设备最少需要包含一个元素，我们称之为主元素。&lt;br /&gt;
&lt;br /&gt;
从主元素开始（如果有多个元素，则第一个元素就是主元素），我们会给它分配相应的元素地址。主元素的地址就是节点的地址，第二个元素的地址就是主元素的地址基础上加上1，依次类推。&lt;br /&gt;
&lt;br /&gt;
可以看到在我们的主元素Element：0x0002中包含了3个不同的服务，这3个服务就是我们的模型Model，其中Configuration Server与Health Server这两个服务，是每一个节点都必须携带的模型。Configuration Server（配置服务）是用于配置节点设备信息，Health Server（健康服务）是用于节点进行网络内的心跳联系。剩下的Generic On Off Server服务，这个就是我们用于控制一个开关量的模型（在例程中我们通过控制LED的IO口高低电平来展示）。[[文件:Config-05.jpg|边框|居中|无框]]在我们的代码中，我们首先需要在nrf_mesh_config_app.h中定义好我们节点设备的最大元素数量，可以看到我们定义#define ACCESS_ELEMENT_COUNT (1)，也就是说我们当前的例程，只含有一个主元素。&lt;br /&gt;
[[文件:Element-01.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
而这个ACCESS_ELEMENT_COUNT ，是在我们开启节点配置的地方被调用的，这个mesh_provsionionee_prov_start()函数在前面的内容中有多次讲到，这里就不再说明了。&lt;br /&gt;
[[文件:Element-02.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
== ⑥model模型 ==&lt;br /&gt;
如果说元素是一个功能集合，那么模型就是元素下的具体的某一个功能。&lt;br /&gt;
&lt;br /&gt;
我们以灯的控制为例，灯的常用控制有开关、亮度、色温几种，其中开关、亮度和色温就是具体的模块，而处理灯控的集合就是元素。&lt;br /&gt;
&lt;br /&gt;
=== Configure Server ===&lt;br /&gt;
配置服务模型，这个的大概代码处理流程在我们的“④设备信息分配-&amp;gt;Appkey”已经有了说明，它是我们节点设备必须包含的一个特殊功能的模型。&lt;br /&gt;
&lt;br /&gt;
它的实际功能就像它的名字一样，就是用于配置（configuration）我们的设备信息，比如appkey的添加删除，密钥的绑定删除，其他的模型的发布和订阅控制。具体包含的操作码的使用方法，我们会在generic on off server模型当中使用的时候来说明。&lt;br /&gt;
&lt;br /&gt;
=== Health Server ===&lt;br /&gt;
健康服务，顾名思义就是判断我们的设备是否在网络中保持健康（正常）工作，它同样是一个必须包含的特殊功能的模型。&lt;br /&gt;
&lt;br /&gt;
它的初始化和config server是一样的，都是放在了mesh_stack_init()函数下。&lt;br /&gt;
[[文件:Health-01.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
我们来看下health_server_init()函数，最终我们会调用access_model_add()函数去初始化模型，并将模型添加到对应的元素中。&lt;br /&gt;
&lt;br /&gt;
其中的access_model_add_params_t参数，包含的分别是元素的索引（将模型添加到第几个元素当中），模型的ID（由mesh协议手册确定好了），以及最最重要的操作码列表，这个列表中会包含我们的指令码，以及对应的函数功能处理。&lt;br /&gt;
[[文件:Health-02.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
最后我们看下具体包含了哪些健康模型操作码：&lt;br /&gt;
&lt;br /&gt;
1、“运行状况当前状态”消息的操作码，HEALTH_OPCODE_CURRENT_STATUS&lt;br /&gt;
&lt;br /&gt;
2、“ 健康故障状态”消息的操作码，HEALTH_OPCODE_FAULT_STATUS&lt;br /&gt;
&lt;br /&gt;
3、“  获取健康注意事项”消息的操作码，HEALTH_OPCODE_ATTENTION_GET&lt;br /&gt;
&lt;br /&gt;
4、“ 健康注意集”消息的操作码，HEALTH_OPCODE_ATTENTION_SET&lt;br /&gt;
&lt;br /&gt;
5、“未确认健康注意事项”消息的操作码，HEALTH_OPCODE_ATTENTION_SET_UNACKED&lt;br /&gt;
&lt;br /&gt;
6、“健康注意状态”消息的操作码，HEALTH_OPCODE_ATTENTION_STATUS&lt;br /&gt;
&lt;br /&gt;
7、“健康故障清除”消息的操作码，HEALTH_OPCODE_FAULT_CLEAR&lt;br /&gt;
&lt;br /&gt;
8、“健康故障清除未确认”消息的操作码，HEALTH_OPCODE_FAULT_CLEAR_UNACKED&lt;br /&gt;
&lt;br /&gt;
9、“健康故障获取”消息的操作码，HEALTH_OPCODE_FAULT_GET&lt;br /&gt;
&lt;br /&gt;
10、“健康故障测试”消息的操作码，HEALTH_OPCODE_FAULT_TEST&lt;br /&gt;
&lt;br /&gt;
11、“未确认健康故障测试”消息的操作码，HEALTH_OPCODE_FAULT_TEST_UNACKED&lt;br /&gt;
&lt;br /&gt;
12、“健康期获取”消息的操作码，HEALTH_OPCODE_PERIOD_GET&lt;br /&gt;
&lt;br /&gt;
13、“健康期设置”消息的操作码，HEALTH_OPCODE_PERIOD_SET&lt;br /&gt;
&lt;br /&gt;
14、“未确认健康期设置”消息的操作码，HEALTH_OPCODE_PERIOD_SET_UNACKED&lt;br /&gt;
&lt;br /&gt;
15、“健康期状态”消息的操作码，HEALTH_OPCODE_PERIOD_STATUS&lt;br /&gt;
[[文件:Health-03.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
在大家的前期使用中，一般也不会真的需要大家去处理health server下的指令，毕竟我们刚开始使用，也很难让设备在网络中出现各种运行问题（节点少功能少）。但是当大家自己开始开发实际功能时，就一定要注意分析health server下的指令码返回，用来快速解决我们网络中的问题。&lt;br /&gt;
&lt;br /&gt;
=== Generic On Off Server ===&lt;br /&gt;
接下来我们来看下Generic On Off Server服务，这个服务和上方的两个服务不太一样，他不是我们的特殊必要服务，是一个通用的模型（用于具体的功能数据的处理，这里是用于开关功能）。借用这个服务模型，我们给大家介绍一下如何创建一个符合BLE MESH协议的模型。&lt;br /&gt;
&lt;br /&gt;
我们看下模型的初始化方法，在generic_onoff_server_init()函数中，首先我们使用access_model_add_params_t定义一个模型分配参数的结构体init_params，在这个结构体当中，我们要给定：&lt;br /&gt;
&lt;br /&gt;
1、model_id（模型的ID），这个是mesh协议规定好的，我们可以查看“归档资料\8-蓝牙协议手册和芯片手册\蓝牙mesh协议手册\《MshMDLv1.0.1.pdf》”的第7.3章节有归纳&lt;br /&gt;
&lt;br /&gt;
2、element_index（元素索引），这个看我们准备将这个服务添加到哪一个元素下，这里我们因为只有一个主元素，所以我们的元素索引为0&lt;br /&gt;
&lt;br /&gt;
3、p_opcode_handlers（操作码列表，指令码列表），这个也是mesh协议规定好的，我们可以查看“归档资料\8-蓝牙协议手册和芯片手册\蓝牙mesh协议手册\《MshMDLv1.0.1.pdf》”的第7.1章节归纳，具体的操作码的功能及要求，大家需要对应这个pdf手册，自己查看一下，对于一个模型ID，我们不一定要注册所有的属于它的opcode，只要注册我们需要使用的即可&lt;br /&gt;
&lt;br /&gt;
4、opcode_count（操作码数量），准备注册的操作码的数量&lt;br /&gt;
&lt;br /&gt;
5、p_args（通用参数指针），用于参数的传递&lt;br /&gt;
&lt;br /&gt;
6、publish_timeout_cb（发布超时时间），发布的定时器到期，会进入此回调，我们可以做一些处理（例如重新发送数据等等）。如果我们的模型不支持定期发布，可以将此值设置为NULL。&lt;br /&gt;
&lt;br /&gt;
当我们配置好模型的参数结构体之后，会调用access_model_add()函数去初始化模型并添加到元素，同时会返回给我们用于存储此模型的句柄指针p_server-&amp;gt;model_handle。&lt;br /&gt;
&lt;br /&gt;
因为我们的onoff模型，需要订阅消息，所以我们需要调用access_model_subscription_list_alloc()函数，携带我们的模型句柄去分配模型的订阅列表。&lt;br /&gt;
[[文件:Onoff-01.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
看完模型的初始化注册之后，我们来看下模型包含的opcode操作码，可以看到在m_opcode_handlers[]操作码列表中包含了三个opcode。这三个opcode我们可以看下他的定义，以及对照一下《MshMDLv1.0.1.pdf》第7.1章节的归纳，可以看到是一一对应的关系。具体这三个opcode的功能，大家可以查看手册中的说明，或者直接看下他们对应的功能处理函数。&lt;br /&gt;
[[文件:Onoff-02.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Onoff-03.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Onoff-04.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
接下来我们看下GENERIC_ONOFF_OPCODE_SET与GENERIC_ONOFF_OPCODE_SET_UNACKNOWLEDGED对应的handle_set，可以看到在这个函数的最后，我们会去调用status_send()函数去发送数据。&lt;br /&gt;
[[文件:Onoff-05.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
然后我们看下status_send()函数，在这个函数中，我们最终会调用access_model_publish来发布我们的数据，或者使用accsee_model_reply来回复数据。&lt;br /&gt;
&lt;br /&gt;
这两个函数再往下就是我们之前在广播部分讲解的packet_alloc_and_tx函数去发送广播数据了，这个有不清楚的，大家可以到前面去查看一下。&lt;br /&gt;
[[文件:Onoff-06.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
==== model bind appkey ====&lt;br /&gt;
那么按照上方的方法注册好模型，然后我们的节点设备被成功的配置入网之后，我们如何让这个模型工作起来。&lt;br /&gt;
&lt;br /&gt;
首先第一步是需要将我们的模型绑定到相应的appkey，只有这样我们的模型才能拿到用于通信的密钥，例如如下的截图当中，我们利用手机app软件nRF Mesh去给我们的Generic On Off Server模型绑定Appkey1。[[文件:Config-14.png|边框|居中|无框|1040x1040像素]]&lt;br /&gt;
[[文件:Config-06.jpg|边框|居中|无框]]&lt;br /&gt;
[[文件:Config-07.jpg|边框|居中|无框]]&lt;br /&gt;
[[文件:Config-08.jpg|边框|居中|无框]]当我们在使用app去绑定appkey的时候，在我们的代码中的体现是在config server下的opcode_handlers[]操作码列表中的CONFIG_OPCODE_MODEL_APP_BIND。&lt;br /&gt;
[[文件:Onoff-07.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
我们追踪一下CONFIG_OPCODE_MODEL_APP_BIND对应的handle_model_app_bind_unbind()，这个函数比较长，这边只给大家截取了部分，在为截取到部分，还有获取元素索引的get_element_index，获取模型ID和模型handle的access_handle_get，然后才是我们的dsm_appkey_index_to_appkey_handle函数用于将appkey索引给到appkey句柄。然后调用access_model_application_bind函数去绑定模型和appkey，最终如果是成功绑定，则调用access_flash_config_store函数去存储修改的绑定信息。&lt;br /&gt;
[[文件:Onoff-08.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
==== model publish address ====&lt;br /&gt;
当我们完成了模型的appkey绑定之后，我们就可以开始设置puhlish的地址以及subscription的地址。&lt;br /&gt;
&lt;br /&gt;
这里我们先看下nRF Mesh配置publish Address，可以看到我们选择的定义的0xC000组地址。[[文件:Model-pub-01.jpg|边框|居中|无框]]&lt;br /&gt;
[[文件:Model-pub-02.jpg|边框|居中|无框]]&lt;br /&gt;
[[文件:Model-pub-03.jpg|边框|居中|无框]]&lt;br /&gt;
[[文件:Model-pub-04.jpg|边框|居中|无框]]我们可以看到，在我们配置publish地址的时候，RTTViewer中会打印黄色标注的操作码数据信息，可以看到他的OPCODE是0x03。[[文件:Model-pub-05.png|边框|居中|无框|996x996像素]]我们来看下这个操作码对应的功能，可以看到CONFIG_OPCODE_MODEL_PUBLICATION_SET操作码对应的功能函数是handle_config_model_publication_set，根据函数的名称我们就可以看出，是用于配置模型发布的。[[文件:Model-sub-04.jpg|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Model-pub-07.jpg|边框|居中|无框|1200x1200像素]]在handle_config_model_publication_set函数中，可以看到我们先使用dsm_appkey_index_to_appkey_handle函数获取了appkey的句柄。然后调用dsm_address_publish_add函数去添加发布的地址。[[文件:Model-pub-08.jpg|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
==== model subscription address ====&lt;br /&gt;
然后我们再看下订阅地址的添加，可以看到我们使用nRF Mesh配置subscription订阅了组地址0xC001。[[文件:Model-sub-02.jpg|边框|居中|无框]]&lt;br /&gt;
[[文件:Model-sub-01.jpg|边框|居中|无框]]可以看到，当我们分配subscription地址的时候，可以看到RTTViewer中打印黄色标注的信息。可以看到他的OPCODE是0x801B。[[文件:Model-sub-03.jpg|边框|居中|无框|982x982像素]]我们来看下这个操作码对应的功能，可以看到CONFIG_OPCODE_MODEL_SUBSCRIPTION_ADD操作码对应的功能函数是handle_config_model_subscription_add，根据函数的名称我们就可以看出，是用于配置模型订阅的。[[文件:Model-pub-06.jpg|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Model-sub-05.jpg|边框|居中|无框|1200x1200像素]]在handle_config_model_subscription_add函数中，我们会调用dsm_address_subscription_add函数去添加我们的订阅地址。[[文件:Model-sub-06.jpg|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[分类:NRF52832DK]]&lt;br /&gt;
[[分类:实验手册]]&lt;/div&gt;</summary>
		<author><name>Jinx</name></author>
		
	</entry>
	<entry>
		<id>http://doc.iotxx.com/index.php?title=NRF52832DK-Mesh%E7%BB%84%E7%BD%91%E5%AE%9E%E9%AA%8C&amp;diff=2816</id>
		<title>NRF52832DK-Mesh组网实验</title>
		<link rel="alternate" type="text/html" href="http://doc.iotxx.com/index.php?title=NRF52832DK-Mesh%E7%BB%84%E7%BD%91%E5%AE%9E%E9%AA%8C&amp;diff=2816"/>
		<updated>2020-04-02T03:12:13Z</updated>

		<summary type="html">&lt;p&gt;Jinx：/* model publish address */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;蓝牙Mesh和BLE是并行发展的一个独立分支，虽然底层共用Physical Layer和Link Layer，但是上层协议并不相同。因此，nRF52832的Mesh例程，并不在BLE协议栈sdk中，而是安装一个扩展包。&lt;br /&gt;
&lt;br /&gt;
本文介绍蓝牙Mesh相关的环境搭建和实验介绍。&lt;br /&gt;
&lt;br /&gt;
== Mesh开发环境 ==&lt;br /&gt;
&lt;br /&gt;
=== 蓝牙Mesh SDK ===&lt;br /&gt;
SDK位置：&amp;lt;code&amp;gt;归档资料/2-协议栈SDK/nRF5_SDK_Mesh_v310.zip&amp;lt;/code&amp;gt;，注意文件名中的Mesh字样。&lt;br /&gt;
&lt;br /&gt;
和BLE协议栈一样，将该压缩包直接解压到BLE协议栈同级目录即可使用，解压后的SDK路径如下：&amp;lt;code&amp;gt;E:\project-nordic\nRF5_SDK_Mesh_v310&amp;lt;/code&amp;gt;&lt;br /&gt;
[[文件:NRF52832DK-MESH-SDK-INSTALL.gif|居中|无框|750x750像素]]&lt;br /&gt;
&lt;br /&gt;
=== 集成开发软件SES ===&lt;br /&gt;
SES时是Segger Embedded Studio编译器的缩写，用来编译蓝牙Mesh代码，SES功能与IAR或Keil类似。&lt;br /&gt;
&lt;br /&gt;
备注：Nordic为何使用SES而不是IAR，原因不得而知。&lt;br /&gt;
&lt;br /&gt;
==== SES安装 ====&lt;br /&gt;
SES软件位置：&amp;lt;code&amp;gt;归档资料/7-编译器SES/Setup_EmbeddedStudio_ARM_v410_win_x64.exe&amp;lt;/code&amp;gt;。&lt;br /&gt;
&lt;br /&gt;
我们双击打开安装界面，按照如下动图所示方式进行安装（可以全部默认安装）。[[文件:Ses install.gif|边框|居中|无框|556x556像素]]&lt;br /&gt;
&lt;br /&gt;
==== SES许可证激活 ====&lt;br /&gt;
我们双击打开安装好的SES，选择编译自带测试例程，此时弹出需要激活的界面。&lt;br /&gt;
[[文件:Licensed1.jpg|边框|居中|无框|1205x1205像素]]&lt;br /&gt;
由于我们准备使用SES编译nordic的NRF52832的SDK，所以这里我们选择Activate Your Free License。这里因为nordic公司已经帮客户想SEGGER公司买下了使用的版权，所以是免费的许可证。&lt;br /&gt;
[[文件:Licensed2.jpg|边框|居中|无框|736x736像素]]&lt;br /&gt;
我们按照要求填写好信息，主要是邮箱地址（用于接收SEGGER公司发给我们的许可证），填写完成之后点击Request License。下面是我们接收的邮件信息，我们将红色框内的内容复制下来。&lt;br /&gt;
[[文件:Ses install3.png|边框|居中|无框|1301x1301像素]]&lt;br /&gt;
打开SES，选择Tools\License Manager...，我们选择Activate Embedded Studio，打开如下界面，并将我们的许可证粘贴进去，然后点击Install License。&lt;br /&gt;
[[文件:Licensed4.png|边框|居中|无框|481x481像素]]&lt;br /&gt;
安装好许可证之后，跳转到如下的界面。&lt;br /&gt;
[[文件:Licensed5.png|边框|居中|无框|481x481像素]]&lt;br /&gt;
此时我们的许可证激活完毕（有时候需要等待一段时间，才会显示激活成功）。&lt;br /&gt;
[[文件:Licensed6.png|边框|居中|无框|515x515像素]]&lt;br /&gt;
&lt;br /&gt;
== mesh实验学习流程概论 ==&lt;br /&gt;
1、首先大家一定要先查看一下我们资料中的手册“BLE-Mesh技术揭秘”，这个是很重要的一个mesh基本概念的介绍，只有看完这个文档，我们才能明白mesh组网是什么，是怎么完成的，后续的我们实验内容，也会在这个文档中查到相应的对照。&lt;br /&gt;
&lt;br /&gt;
2、按照Light_switch测试实验的实验说明，跑通测试流程（可以固件清零重新烧写多测试几遍，有助于增强我们的流程熟练度，利于后面的开发）。light_switch实验是现在nordic提供给我们的唯一的真正意义上的mesh组网通信例程。&lt;br /&gt;
&lt;br /&gt;
3、根据我们下面拆分的组网流程，对照依次的实验说明，进行mesh组网流程的分步学习。前期实验部分我们主要讲解节点设备，配置者设备功能我们使用手机app完成，目的是减少大家的学习难度，配置者设备我们会放在最后一个实验给大家做介绍。&lt;br /&gt;
&lt;br /&gt;
①广播（通信TX）——了解节点设备的广播功能，分为三个重要部分：PB-ADV和Unprov Beacon，PB-GATT的广播，以及模型的Publish用于发送数据。&lt;br /&gt;
&lt;br /&gt;
②扫描（通信RX）——了解节点设备的扫描功能，分为两个重要部分：普通的BLE SCAN功能，以及模块的Subscription用于接收数据。&lt;br /&gt;
&lt;br /&gt;
③入网验证——了解节点设备接入网络的验证过程，实验中使用的是PB-GATT去配置的，包含了：交换公共密钥，完成验证（是否有带外OOB）。&lt;br /&gt;
&lt;br /&gt;
④设备信息分配——启动配置数据分配，给节点分配如下数据：网络key（Netkey）、应用key（Appkey）以及节点设备地址（nodeAddr）。&lt;br /&gt;
&lt;br /&gt;
⑤元素——了解节点设备的元素与模型定义。&lt;br /&gt;
&lt;br /&gt;
⑥模型——了解模型的功能和创建方法，模型的订阅subscription和发布publish功能。&lt;br /&gt;
&lt;br /&gt;
== Light_switch测试实验 ==&lt;br /&gt;
=== 实验简介 ===&lt;br /&gt;
此示例演示了包含充当两个角色的设备的网状生态系统：配置角色（Provisioner role）和节点角色（Node role、provisionee role）。 它还通过在应用程序中使用[Generic OnOff model]来演示了如何使用Mesh模型。&lt;br /&gt;
&lt;br /&gt;
该示例由三个较小的示例组成：&lt;br /&gt;
&lt;br /&gt;
Light switch server：一个实现了[Generic OnOff server model]的简约服务器，用于接收状态数据并控制板上LED 1的状态。&lt;br /&gt;
&lt;br /&gt;
Light switch client：一个实现了[Generic OnOff client model]的简约客户端。当用户按下任意按钮时，OnOff Set消息将发送到配置的目标地址。&lt;br /&gt;
&lt;br /&gt;
Mesh Provisioner：一个简单的静态配置设备应用，用于建立演示网络，该配置设备在一个网状网络中配置所有节点。 此外，配置设备还可以在这些节点上配置网格模型[Generic OnOff model]实例的绑定以及发布和订阅设置，以使它们能够相互通信。&lt;br /&gt;
&lt;br /&gt;
[Generic OnOff Client/Server]模型用于操纵打开/关闭状态。请注意，当服务器设置了发布地址（如本例所示）时，服务器会将其状态更改的任何操作披露到其发布地址。&lt;br /&gt;
&lt;br /&gt;
下图给出了将由静态供应商设置的网状网络的整体视图， 括号中的数字表示预配置程序分配给这些节点的地址。&lt;br /&gt;
[[文件:Mesh network.png|边框|居中|无框|727x727像素]]&lt;br /&gt;
灯开关服务器和灯开关客户端示例均具有预配方角色。它们支持通过广告承载（PB-ADV）和GATT承载（PB-GATT）进行配置，并且还支持网状代理服务器（Proxy Service），但是不支持代理客户端（Proxy Client）。&lt;br /&gt;
&lt;br /&gt;
=== 硬件说明 ===&lt;br /&gt;
完成这个实验，我们最少需要两个开发板硬件用做我们的节点设备（Node）：&lt;br /&gt;
* 一个开发板用做client&lt;br /&gt;
* 一个或者多个开发板用做server&lt;br /&gt;
此外，我们还需要以下之一作为我们的配置设备（Provisioner）：&lt;br /&gt;
* 如果您决定使用静态预配器示例，则多准备一个开发板&lt;br /&gt;
* 如果您决定使用app程序进行设置，则需要安装手机app@link_nrf_mesh_app（@link_nrf_mesh_app_ios或@link_nrf_mesh_app_android）。&lt;br /&gt;
{{Note|text=默认我们使用3个开发板完成我们的实验，3个开发板分别对应client、server以及provisioner三个类型。|type=warning}}&lt;br /&gt;
&lt;br /&gt;
=== 软件说明 ===&lt;br /&gt;
1、我们使用NrfGo上位机，依次对三个开发板进行擦除及softdevice的烧写&lt;br /&gt;
[[文件:Lightswitch nrfgo.png|边框|居中|无框|1013x1013像素]]&lt;br /&gt;
2、使用SEGGER Embedded Studio for ARM编译器分别打开我们`&amp;lt;InstallFolder&amp;gt;/examples/light_switch`路径下的3个实验，并完成编译&lt;br /&gt;
&lt;br /&gt;
●\client\light_switch_client_nrf52832_xxAA_s132_6_1_0.emProject&lt;br /&gt;
&lt;br /&gt;
●\server\light_switch_server_nrf52832_xxAA_s132_6_1_0.emProject&lt;br /&gt;
&lt;br /&gt;
●\provisioner\light_switch_provisioner_nrf52832_xxAA_s132_6_1_0.emProject&lt;br /&gt;
[[文件:Lightswitch SES build.png|边框|居中|无框|1013x1013像素]]&lt;br /&gt;
3、此时我们对3个开发板分别进行固件的烧写，分别烧写client、server、provisioner（建议大家给3个开发板贴上标签，方便后续区分）&lt;br /&gt;
[[文件:Lightswitch SES download.png.gif|边框|居中|无框|1009x1009像素]]&lt;br /&gt;
&lt;br /&gt;
=== 实验现象 ===&lt;br /&gt;
{{Note|text=注意：保证我们的3个设备都是刚按照软件说明部分的步骤配置完成，并且没有进行过任何硬件控制（按键操作）。目的是保证所有设备均未被我们人为配置过，没有进行过网络配置，否则可能有任意异常（不同网络配置，导致的不同现象）|type=danger}}&lt;br /&gt;
1、首先我们分别给client和provisioner供电，然后我们按下provisioner的按键S1：&lt;br /&gt;
&lt;br /&gt;
●provisoner：LED灯点亮，代表正在配置查找设备，配置组网&lt;br /&gt;
&lt;br /&gt;
●client：首先LED3和LED4闪烁，代表正在组网；LED1-LED4四个灯一起闪烁，代表组网完成&lt;br /&gt;
&lt;br /&gt;
我们如果打开RTT检测log打印，可以看到如下的信息，这个时候我们已经给client设备分配了Node Address为0x0100。provisioner信息（上），client信息（下）。&lt;br /&gt;
[[文件:Lightswitch rtt provisoner1.png|边框|无框|1080x1080像素|居中]]&lt;br /&gt;
[[文件:Lightswitch_rtt_node_client.png|边框|居中|无框|656x656像素]]&lt;br /&gt;
&lt;br /&gt;
2、使用provisioner配置好client之后，我们给server开发板上电，这个时候provisioner将会配置server入网：&lt;br /&gt;
&lt;br /&gt;
●provisoner：LED1点亮代表正在配置网络，LED2点亮代表网络配置完成&lt;br /&gt;
&lt;br /&gt;
●server：首先LED3和LED4闪烁，代表正在组网；LED1-LED4四个灯一起闪烁，代表组网完成&lt;br /&gt;
&lt;br /&gt;
我们打开RTT检测log打印，可以看到server已经配置入网，并且被分配了地址Node Address为0x0104。provisioner信息（上），server信息（下）。&lt;br /&gt;
[[文件:Lightswitch rtt provisoner2.png|边框|无框|710x710像素|居中]]&lt;br /&gt;
[[文件:Lightswitch rtt node server.png|边框|无框|656x656像素|居中]]&lt;br /&gt;
&lt;br /&gt;
3、此时整个网络的配置已经完成，这个时候我们可以按下client上的按键来控制server上的LED点亮或者熄灭。&lt;br /&gt;
&lt;br /&gt;
由于我们的server被provisioner分配的Node Address是0x0104（偶数），所以我们通过client的S3控制server的LED1点亮，通过client的S4控制server的LED1熄灭。&lt;br /&gt;
{{Note|text=client设备对于server设备的控制，通过Node Address的奇偶位不同，分成了两个组。&lt;br /&gt;
client的S1和S2分别控制Node Address为奇数的server设备的LED1点亮和熄灭。&lt;br /&gt;
&lt;br /&gt;
client的S3和S4分别控制Node Address为偶数的server设备的LED1点亮和熄灭。|type=warning}}&lt;br /&gt;
我们打开RTT检测log打印，可以看到client分别按下button2（S3）以及button3（S4），分别会设置server的GPIO输出高低电平。client信息（上），server信息（下）。&lt;br /&gt;
[[文件:Lightswitch rtt node client2.png|边框|无框|656x656像素|居中]]&lt;br /&gt;
[[文件:Lightswitch rtt node server2.png|边框|无框|656x656像素|居中]]&lt;br /&gt;
&lt;br /&gt;
4、至此我们的mesh组网的lightswitch实验测试完成。&lt;br /&gt;
&lt;br /&gt;
== ①广播（通信TX） ==&lt;br /&gt;
有关广播部分，我们需要给大家讲解三个方面，分别如下：&lt;br /&gt;
&lt;br /&gt;
1、PB-ADV，这个是mesh中的广播承载层，用于我们启动配置过程（暂时的例程和手机app都是用的PB-GATT来配置的，但是源码中PB-ADV的流程是包含的）。&lt;br /&gt;
&lt;br /&gt;
2、Unprov Beacon，这个是广播一个未被配置的beacon，就是一个特殊形式的广播数据包，用于声明自己是未经配置的设备。&lt;br /&gt;
&lt;br /&gt;
3、PB-GATT，这个是mesh中的GATT承载层，用于我们启动配置过程（当前手机app就是使用PB-GATT，也就是连接之后，通过GATT服务去发送配置信息）。&lt;br /&gt;
&lt;br /&gt;
4、Publish，这个是我们用于模型发布数据的部分，我们将需要发送的数据通过advData广播出去。&lt;br /&gt;
&lt;br /&gt;
=== PB-ADV ===&lt;br /&gt;
PB-ADV说明请查看“BLE-Mesh技术揭秘”第8.1章节。&lt;br /&gt;
&lt;br /&gt;
我们来看下PB-ADV整个的流程，首先在mian文件中，我们的start()函数下，我们调用了mesh_provisionee_prov_start()函数去启动节点设备的等待配置。&lt;br /&gt;
[[文件:PB-ADV-01.png|边框|居中|无框|1200x1200px]]我们继续追踪mesh_provisionee_prov_start()，可以看到在使能宏定义MESH_FEATURE_PB_ADV_ENABLED的情况下，可以追踪到nrf_mesh_prov_bearer_adv_interface_get()函数。&lt;br /&gt;
[[文件:PB-ADV-02.png|边框|居中|无框|1200x1200px]]在nrf_mesh_prov_bearer_adv_interface_get()函数中，我们可以看到prov_bearer_interface_t结构体，这个结构体下面提供了我们配置承载层需要的API函数。&lt;br /&gt;
[[文件:PB-ADV-03.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
=== Unprov Beacon ===&lt;br /&gt;
Unprov Beacon说明请查看“BLE-Mesh技术揭秘”第5.5及5.5.1章节。&lt;br /&gt;
&lt;br /&gt;
上面我们有说到PB-ADV是承载层用于配置节点的功能，而Unprov Beacon则是我们的节点设备用于声明自己未经配置，所以这两个其实是一脉相承的。&lt;br /&gt;
&lt;br /&gt;
紧接着上面的nrf_mesh_prov_bearer_adv_interface_get()函数，其中的prov_bearer_interface_t结构体包含的是处理承载层的API函数，在这个结构体当中有一个.listen_start = prov_bearer_adv_listen，是用于开始监听传入的配置链接的功能，在这个函数中我们可以看到send_unprov_beacon()函数。&lt;br /&gt;
[[文件:PB-ADV-04.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
send_unprov_beacon()函数是用于发送Unprov Beacon数据的函数，在这个函数中我们调用prov_beacon_unprov_build函数去创建用于广播的Unprov Beacon数据包，然后调用advertiser_interval_set函数去设置广播的间隔，最后调用advertiser_packet_send函数去将我们的数据包发送出去。&lt;br /&gt;
&lt;br /&gt;
默认的Unprov Beacon广播间隔是NRF_MESH_PROV_BEARER_ADV_UNPROV_BEACON_INTERVAL_MS 2000。&lt;br /&gt;
&lt;br /&gt;
最终调用的是advertiser_packet_send(0函数去将广播数据发送出去的。&lt;br /&gt;
[[文件:PB-ADV-05.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Publish-12.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
这里我们再最后看一下prov_beacon_unprov_build()函数，这个函数是生成我们的Unprov Beacon的数据包。&lt;br /&gt;
&lt;br /&gt;
其中标识位是0x2B，这个flag是beacon的意思。data的第一个字节0x00代表是我们的BEACON_TYPE_UNPROV（未配置的节点设备），接下来的16个字节是device uuid（这个是寄存器FICR中的device id生成的），接下来2字节是oob info（这里是00 00，代表没有使能OOB），最后的4个字节是URI通过AES-CMAC算法计算出来的值。&lt;br /&gt;
[[文件:PB-GATT-11.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:PB-GATT-12.png|边框|居中|无框]]&lt;br /&gt;
&lt;br /&gt;
=== PB-GATT ===&lt;br /&gt;
PB-GATT说明请查看“BLE-Mesh技术揭秘”第8.1章节。&lt;br /&gt;
&lt;br /&gt;
和PB-ADV一样的，我们从main文件中的start()函数，追踪到mesh_provisionee_prov_start()，可以看到在使能宏定义MESH_FEATURE_PB_GATT_ENABLED的情况下，可以追踪到nrf_mesh_prov_bearer_gatt_init()以及nrf_mesh_prov_bearer_gatt_interface_get()函数。&lt;br /&gt;
[[文件:PB-GATT-01.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
首先我们看一下nrf_mesh_prov_bearer_gatt_init()函数，这个是初始化并设置PB-GATT服务（通过Mesh GATT接口）的函数。对比我们使用nrf master control panel手机APP连接之后获取到的服务，可以看到其中的PB-GATT的服务以及特征值。&lt;br /&gt;
&lt;br /&gt;
再往下的mesh_gatt_init()函数，这个就不给大家分析了，是和我们普通的BLE一样的方式去注册一个服务。&lt;br /&gt;
[[文件:PB-GATT-04.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:PB-GATT-03.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:PB-GATT-02.png|边框|居中|无框]]&lt;br /&gt;
在nrf_mesh_prov_bearer_gatt_interface_get()函数中，我们可以看到prov_bearer_interface_t结构体，这个结构体下面提供了我们配置承载层需要的API函数（这个是和上方的PB-ADV是一样的）。&lt;br /&gt;
[[文件:PB-GATT-05.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
和Unprov Beacon类似的，当我们开始监听配置链接的时候，会触发.listen_start = listen_start_cb的回调，在这个回调中我们触发FSM（有限状态机）的事件E_LISTEN_START，然后调用link_evt_send()去post顺序承载事件。&lt;br /&gt;
[[文件:PB-GATT-06.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
然后我们来到状态机列表m_pb_gatt_fsm_transition_table[]，可以看到E_LISTEN_START事件ID代表的动作ID是A_LISTEN_START。而最终我们的A_LISTEN_START动作，执行的是a_listen_start函数功能（这其中涉及的代码逻辑，暂时先不说明，大家可以自己查看源码的引用）。&lt;br /&gt;
[[文件:PB-GATT-07.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:PB-GATT-08.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
然后我们继续追踪a_listen_start()，可以看到其中调用了mesh_adv_params_set函数设置了我们广播的超时时间MESH_ADV_TIMEOUT_INFINITE以及广播间隔NRF_MESH_PROV_BEARER_GATT_UNPROV_BEACON_INTERVAL_MS，调用mesh_adv_data_set函数设置了我们的广播数据内容，最后调用mesh_adv_start函数启动广播。&lt;br /&gt;
&lt;br /&gt;
默认的PB-GATT的广播数据的广播周期是NRF_MESH_PROV_BEARER_GATT_UNPROV_BEACON_INTERVAL_MS 200。&lt;br /&gt;
[[文件:PB-GATT-09.png|边框|居中|无框|1200x1200像素]]我们同样的看一下mesh_adv_data_set()函数最终配置生成的广播数据，可以看到广播数据的flag是0x06，服务的UUID就是我们上面提到的PB-GATT服务的UUID，接下来16字节UUID是和PB-ADV一样的FICR寄存器的device id值，最后的扫描回调数据携带的是设备的全名“nRF5x Mesh Light”。&lt;br /&gt;
[[文件:PB-GATT-13.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:PB-GATT-14.png|边框|居中|无框]]最后是我们的mesh_adv_start()函数去启动广播，可以看到调用是我们ble stack sotfdevice当中的API函数sd_ble_gap_adv_start()去启动的。&lt;br /&gt;
[[文件:PB-GATT-15.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
=== Publish（模型发布） ===&lt;br /&gt;
这一章节我们只给大家讲解模块publish的数据发布流程，暂时不说明模型的创建方法，以及如何使用，这个放在下面单独的模型章节进行说明。&lt;br /&gt;
&lt;br /&gt;
以generic_onoff_server模型为例，我们找到access_model_publish以及access_model_reply函数，这两个函数的功能类似的，都是用于我们用户的模型发送数据的。&lt;br /&gt;
&lt;br /&gt;
其中access_model_publish是用于模型主动发送，而access_model_reply是用于模型给对应的Opcode回应数据。&lt;br /&gt;
&lt;br /&gt;
这里我们以access_model_publish函数为主，来看下发布数据的代码流程。&lt;br /&gt;
[[文件:Publish-01.png|边框|居中|无框|1200x1200像素]]查看一下access_model_publish()函数，我们可以看到packet_alloc_and_tx()函数，这个是我们继续发送数据的函数。下面还有一个蓝牙方框中的内容，是我们定义的重传机制（mesh数据为了保证传输的成功率，默认都是会重传数据包的，默认重传次数是4次）。[[文件:Publish-02.png|边框|居中|无框|1200x1200像素]]然后我们继续看一下packet_alloc_and_tx()函数，在这个函数中，我们最终会调用packet_tx()函数将数据发送出去。[[文件:Publish-03.png|边框|居中|无框|1200x1200像素]]继续追踪packet_tx()函数，这个函数的内容比较多，我们只截取了其中数据发送的部分，也就是我们的nrf_mesh_packet_send()函数，我们在他的tx_params中赋值好我们需要发送出去的数据内容。[[文件:Publish-04.png|边框|居中|无框|1199x1199像素]]nrf_mesh_packet_send()函数最终调用的是transport_tx()函数发送数据。而transport_tx()函数，最终是调用upper_transport_tx()函数进行的数据发送。[[文件:Publish-05.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Publish-06.png|边框|居中|无框|1200x1200像素]]我们再继续追踪upper_transport_tx()函数，可以看到我们会根据p_metadata-&amp;gt;segmented来判断数据是否要分包发送。&lt;br /&gt;
[[文件:Publish-13.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
接下里我们以不分包发送函数unsegmented_packet_tx()为例，来继续说明数据传输流程。&lt;br /&gt;
&lt;br /&gt;
我们可以看到我们的unsegmented_packet_tx()函数中，最终是调用的network_packet_send()函数。（分包的segmented_packet_tx()函数最终也是调用的network_packet_send()，大家可以自己追踪代码看一下）&lt;br /&gt;
[[文件:Publish-14.png|边框|居中|无框|1200x1200像素]]继续追踪我们的network_packet_send()函数，可以看到我们的net_packet_encrypt()加密函数，这个是将我们的明文数据，做好加密工作。&lt;br /&gt;
&lt;br /&gt;
然后调用core_tx_packet_send()函数发送出去。[[文件:Publish-15.png|边框|居中|无框|1200x1200像素]]最后我们追踪到core_tx_packet_send()函数，可以看到调用的是p_bearer-&amp;gt;p_interface-&amp;gt;packet_send()函数去发送的。所以接下来我们要找到我们的packet_send()功能是在哪边实现的。[[文件:Publish-07.png|边框|居中|无框|1200x1200像素]]可以看到我们是在core_tx_bearer_add函数中去获得的p_interface这个功能。[[文件:Publish-08.png|边框|居中|无框|1200x1200像素]]我们追踪core_tx_bearer_add()函数的引用路径，可以看到是在core_tx_adv_init()当中。（除了此处，还有另一处地方也有引用，功能是类似的，大家可以自行了解）。&lt;br /&gt;
&lt;br /&gt;
我们看下参数m_interface，可以看到m_interface-&amp;gt;packet_send功能，就是对应我们在core_tx_packet_send()函数调用的p_bearer-&amp;gt;p_interface-&amp;gt;packet_send()。[[文件:Publish-09.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Publish-10.png|边框|居中|无框|1200x1200像素]]所以我们接下来来看下m_interface结构体当中的packet_send()函数实现的是什么功能，可以看到最终调用的是advertiser_packet_send()函数将数据发送出去。看到这边我们就很熟悉了，和上面的几个广播功能，最后调用的都是相同的广播包发送函数。[[文件:Publish-11.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Publish-12.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
== ②扫描（通信RX） ==&lt;br /&gt;
&lt;br /&gt;
=== 标准BLE SCAN ===&lt;br /&gt;
看过了我们的广播功能之后，我们来看下扫描的部分。在ble mesh中，我们的扫描功能scan，就是用于获取数据的，类似于数据通信的RX。&lt;br /&gt;
&lt;br /&gt;
我们从main文件中的initialize()函数开始，首先是我们mesh_init()函数，最终我们会调用mesh_stack_init()函数。&lt;br /&gt;
[[文件:SCAN-01.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
在mesh_satck_init函数中，我们可以看到调用了nrf_mesh_init函数去初始化core的参数。&lt;br /&gt;
[[文件:SCAN-02.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
这一段是nrf_mesh_init函数部分，由于这个初始化函数太长，所以我们只截取了scan相关的部分。&lt;br /&gt;
&lt;br /&gt;
首先是第一点scanner_init(scanner_packet_process_cb)函数，在这个函数中，我们回去初始化扫描的参数配置，并且会开启扫描功能。最终我们可以从scanner_packet_process_cb这个回调中得到我们扫描到的数据（获得所有的BLE广播数据）。&lt;br /&gt;
&lt;br /&gt;
然后是第二点ad_listener_subscribe(&amp;amp;m_nrf_mesh_listener)，这个是用来侦听订阅的消息的（从获得的所有BLE广播数据中，筛选出的订阅的消息）。&lt;br /&gt;
[[文件:SCAN-03.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
然后我们继续看一下scanner_init的扫描初始化函数，在这个函数中我们首先调用scanner_config_reset()函数去配置扫描的默认参数。其中包含了：&lt;br /&gt;
&lt;br /&gt;
1、扫描的通道SCANNER_CHANNELS_DEFAULT，也就是BLE的｛37，38，39｝三个channels都去扫描。&lt;br /&gt;
&lt;br /&gt;
2、规范定义的非连接状态访问地址BEARER_ACCESS_ADDR_DEFAULT。&lt;br /&gt;
&lt;br /&gt;
3、配置物理层协议是1Mbit模式。&lt;br /&gt;
&lt;br /&gt;
4、扫描的间隔时间BEARER_SCAN_INT_DEFAULT_MS，扫描的窗口时间BEARER_SCAN_WINDOW_DEFAULT_MS&lt;br /&gt;
&lt;br /&gt;
5、射频的发射功率RADIO_POWER_NRF_0DBM。&lt;br /&gt;
&lt;br /&gt;
6、BLE广播包最大长度RADIO_CONFIG_ADV_MAX_PAYLOAD_SIZE。&lt;br /&gt;
&lt;br /&gt;
最后我们会在nrf_mesh_enable()函数中调用scanner_enable()函数去启动扫描，这个大家可以自己查看一下代码，比较容易理解。&lt;br /&gt;
[[文件:SCAN-04.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:SCAN-05.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
最后我们来看一下scanner_packet_process_cb()回调函数，其中我们可以直接拿到的扫描的数据包是rx_data，包含了数据的类型、数据内容以及数据长度等等信息。到这边我们的扫描功能是已经完成的，但是我们还需要再看下下面一部分的ad_listener的流程。&lt;br /&gt;
&lt;br /&gt;
=== Subscription（模型订阅） ===&lt;br /&gt;
我们需要注意下ad_listener_process()函数，这个函数是将我们扫描到的订阅数据（不符合标准的BLE广播数据，则认为是侦听到的订阅数据）传递到订阅者进行处理（订阅者指的是带订阅功能的模型）。这个是和我们上方说的关注点二ad_listener_subscribe()函数是一脉相承的。&lt;br /&gt;
[[文件:SCAN-06.png|边框|居中|无框|1200x1200像素]]然后我们来对照着看一下ad_listener_subscribe()函数以及ad_listener_process()函数。这两个函数一个是定义了侦听订阅，一个是将扫描获取到的数据传递给订阅者。这两者间的运作关系，大家有兴趣的可以继续查阅他的底层代码逻辑处理，一个是向list中添加订阅者信息，一个是将数据通过p_listener-&amp;gt;handler()将数据传递给订阅者处理。&lt;br /&gt;
[[文件:SCAN-07.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:SCAN-08.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
最终当我们扫描到订阅消息，并调用ad_listener_process()函数传递时，会触发我们注册的回调nrf_mesh_listen（调用ad_listener_subscribe(&amp;amp;m_nrf_mesh_listener)时，在m_nrf_mesh_listener中）。我们来看下nrf_mesh_listen函数中的处理，其中我们先从数据包中解析了数据的类型type，然后根据不同类型的数据进行相应的处理。&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+&lt;br /&gt;
|AD_TYPE_MESH &lt;br /&gt;
|蓝牙网格物体的AD类型&lt;br /&gt;
|-&lt;br /&gt;
|AD_TYPE_BEACON &lt;br /&gt;
|蓝牙网状信标的AD类型&lt;br /&gt;
|-&lt;br /&gt;
|AD_TYPE_PB_ADV &lt;br /&gt;
|PB-ADV消息的AD类型&lt;br /&gt;
|-&lt;br /&gt;
|AD_TYPE_DFU &lt;br /&gt;
|nRF OpenMesh消息的AD类型&lt;br /&gt;
|}&lt;br /&gt;
[[文件:SCAN-09.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
== ③入网验证 ==&lt;br /&gt;
经过前两章的学习，我们对于BLE MESH的广播及扫描的代码流程已经有了一定的了解，也就是已经大概的明白了数据的发送和接收的流程。&lt;br /&gt;
&lt;br /&gt;
那么从这一章节开始，我们将会给大家展示，如何配置一个节点设备入网。配置者设备，我们使用的是手机APP：nRF Mesh。&lt;br /&gt;
&lt;br /&gt;
我们从main文件中的start()函数开始，来看一下入网的代码处理及其流程。在start()函数中我们最后是调用的mesh_provisionee_prov_start()函数来启动的节点配置功能，我们来查看一下它所携带的参数prov_start_params。这部分的参数都是比较重要的，有携带的是设备的信息的、有携带认证密码的，有回调返回的，所以我们会挨个介绍一下。&lt;br /&gt;
[[文件:Prov-01.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
首先携带的是static_auth_data，这个是我们带外OOB认证的密码。如果我们在使用nRF Mesh去进行身份验证的时候，选择使用Static OOB，那么当我们在进行PB-GATT的连接过程中，会弹出一个新的窗口，要求我们去输入这个静态的128bit的OOB密钥。&lt;br /&gt;
[[文件:Prov-02.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Prov-14.png|边框|居中|无框]]&lt;br /&gt;
[[文件:Prov-15.png|边框|居中|无框]]&lt;br /&gt;
接下来携带的是provisioning_complete_cb()回调函数，这个函数处理的是节点设备成功的被配置入网，在这里我们可以获取到我们的节点设备被分配的node_Address。&lt;br /&gt;
[[文件:Prov-03.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
然后携带是device_identification_start_cb()回调函数，这个回调函数是用于通知开发者，我们的节点设备当前已经开始配置。&lt;br /&gt;
[[文件:Prov-04.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
再然后携带的是URI【统一资源标识符（Uniform Resource Identifier，URI)】，这个是用于标识我们的设备类型的，这个数据会通过AES-CMAC算法加密后添加到我们的Unprov Beacon数据的末尾。&lt;br /&gt;
&lt;br /&gt;
这里我们是light switch的server设备，所以我们定义URI是EX_URI_LS_SERVER。&lt;br /&gt;
[[文件:Prov-05.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
分析完携带的参数之后，我们继续来查看mesh_provisionee_prov_start()函数。在这个函数中，我们首先是定义了一下prov_caps，也就是配置支持的OOB功能。然后我们调用nrf_mesh_generate_keys()函数去生成有效的密钥对等待使用。&lt;br /&gt;
&lt;br /&gt;
再然后是调用nrf_mesh_prov_init()函数去初始化prov配置信息的结构体参数，里面有结构体本身m_pro_ctx，需要配置的参数公钥m_public、私钥m_private，以及我们刚刚定义的oob功能prov_caps，最后还携带了一个事件回调函数prov_ect_handler。&lt;br /&gt;
&lt;br /&gt;
接下来是PB-ADV以及PB-GATT的处理过程，这个前面已经讲解过，这里不再赘述。&lt;br /&gt;
&lt;br /&gt;
最后我们调用provisionee_start()函数去开启配置。&lt;br /&gt;
[[文件:Prov-06.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
我们继续来追踪一下provisionee_start()函数，在这个函数中我们调用nrf_mesh_prov_listen()函数去开启侦听承载层的消息。这里我们需要侦听PB-ADV以及PB-GATT两个。&lt;br /&gt;
[[文件:Prov-07.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
然后在nrf_mesh_prov_listen()函数中，我们调用prov_provisionee_listen()函数携带我们刚刚传入的nrf_mesh_prov_ctx_t结构体，以及p_bearer、URI、oob_info_source等参数信息，继续向下传递。&lt;br /&gt;
[[文件:Prov-08.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
最终我们来查看下prov_provisionee_listen()函数，在这个函数中我们传递了p_bearer-&amp;gt;p_callbacks给m_prov_callbacks回调，然后调用p_bearer-&amp;gt;p_interface-&amp;gt;listen_start()函数去启动PB-ADV以及PB-GATT的侦听。&lt;br /&gt;
[[文件:Prov-09.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
当我们在侦听之后，接收到任何配置相关的消息，都将通过m_prov_callbacks()函数回调传递给我们处理，大家可以自行看下其中4个不同的功能函数，具体处理的都是什么消息。&lt;br /&gt;
[[文件:Prov-10.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
在prov_provisionee_listen()函数中，我们可以看到回调函数指针m_prov_callbacks指向的是p_bearer-&amp;gt;p_callbacks，我们一路向上查找，最终可以找到这个p_bearer-&amp;gt;p_callbacks函数就是指的prov_evt_handler()函数。所以其实我们用户可以在这一层去查看比较重要的配置事件返回，而不需要到上面的m_prov_callbacks中这样偏底层的地方去查看。&lt;br /&gt;
&lt;br /&gt;
在这个回调函数中，我们可以看到mian文件中注册的prov_device_identification_start_cb、prov_device_identification_stop_cb、provisioning_aborted_cb的返回。&lt;br /&gt;
[[文件:Prov-11.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
那么除了上方已经存在的几个回调，我们可以看到mian文件中的start()函数下的prov_start_params参数，还缺少一个配置完成的回调prov_complete_cb()。&lt;br /&gt;
&lt;br /&gt;
这个回调的返回方式比较特殊，和上方其他的都不同，是由SD（softdevice）直接从底层返回给我们的。注册这个回调的函数是NRF_SDH_STATE_OBSERVER，返回的回调函数是sd_state_evt_handler。其中触发的事件NRF_SDH_EVT_STATE_ENABLED，这个是在mian文件中的initialize()函数下的ble_stack_init()函数中就被触发的，这个大家可以自行查看一下，不太重要，因为都是nordic底层处理好的消息。&lt;br /&gt;
[[文件:Prov-13.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Prov-12.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
以上整个配置流程的处理，不太好分步展示相应的现象，这个大家可以自己使用SES在线仿真的方式去调试一下流程，这里我们直接给大家展示整个流程打印出的相应的信息，以及nRF Mesh中暂时出来的现象。&lt;br /&gt;
[[文件:Prov-16.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Prov-17.png|边框|居中|无框]]&lt;br /&gt;
&lt;br /&gt;
== ④设备信息分配 ==&lt;br /&gt;
当我们成功的配置节点设备入网之后，我们需要给节点设备主要分配如下几个信息，有关这几个要点的介绍，可以查看“BLE-Mesh技术揭秘”第3章节：&lt;br /&gt;
&lt;br /&gt;
1、节点在网络中的地址Node Address&lt;br /&gt;
&lt;br /&gt;
2、网络密钥Netkey&lt;br /&gt;
&lt;br /&gt;
3、应用密钥Appkey&lt;br /&gt;
&lt;br /&gt;
4、元素的地址Element Address&lt;br /&gt;
&lt;br /&gt;
5、绑定model的Appkey，分配Publish Address，Subscription Address&lt;br /&gt;
&lt;br /&gt;
=== Node Address ===&lt;br /&gt;
首先我们来看一下我们使用nRF Mesh成功配置我们的节点之后，打印出来的消息，开始的紫色字体显示了我们给当前节点设备分配的Node Address。&lt;br /&gt;
&lt;br /&gt;
后面紧接着还有四段黄色字体的内容（这个部分大家的工程是不会打印的，是我们修改例程打印出来给大家展示一下），这四个部分就是我们配置节点的时候，配置者设备（nRF Mesh）给节点设备的四个主要指令，这些指令的具体功能，大家可以查看mesh的协议手册。[[文件:Config-13.png|边框|居中|无框|1080x1080像素]]在代码中，我们会在mian文件的provisioning_complete_cb()回调函数中获取并打印我们的Node Address，这个回调触发的含义就是我们的节点配置已经完成了。&lt;br /&gt;
&lt;br /&gt;
具体这个回调在代码中的回调及触发流程，大家看可以查看“③入网验证”的说明。[[文件:Config-20.png|边框|居中|无框|1200x1200像素]]我们使用nRF Mesh配置好节点设备之后，app界面显示内容如下，包含了节点的信息（地址、名称、配置时间等等），元素和模型，以及Netkey和Appkey等几个重要的设备信息。[[文件:Config-01.jpg|边框|居中|无框]]&lt;br /&gt;
[[文件:Config-04.jpg|边框|居中|无框]]&lt;br /&gt;
&lt;br /&gt;
=== Netkey ===&lt;br /&gt;
我们点击查看一下Network key，可以看到里面有一个Network Key 1，这是一个16字节的网络密钥。我们可以在nRF Mesh的Settings界面去增加或者删除一个network key，用户可以自定义此密钥。[[文件:Config-02.jpg|边框|居中|无框]]在我们的代码中，我们是在pro_evt_handler回调的NRF_MESH_PROV_EVT_COMPLETE事件中获取这个netkey，这个回调函数的触发流程同样是在“③入网验证”已经有说明。[[文件:Prov-11.png|边框|居中|无框|1200x1200像素]]我们直接看下NRF_MESH_PROV_EVT_COMPLETE事件下的mesh_stack_provisioning_data_store()函数，在这个函数中我们主要的任务，就是将配置者分配给我们的node address、netkey、devkey等等信息，分别做一些存储以及功能绑定。&lt;br /&gt;
[[文件:Netkey-01.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
=== Appkey ===&lt;br /&gt;
同样的我们打开Application Key，可以看到其中包含的3个密钥，这里我们只选用了Application Key 1作为我们的密钥。大家同样可以在nRF Mesh的Settings界面去增加或者删除一个App key，用户可以自定义此密钥。[[文件:Config-03.jpg|边框|居中|无框]]appkey这里和netkey不同，netkey是我们网络中的节点设备以及我们配置者设备共有的一个密钥，只有拥有这个密钥，我们才能在当前网络中通信，所以当我们的节点设备配置入网后，会立刻得到netkey（必须要分配给我的节点设备才行）。&lt;br /&gt;
&lt;br /&gt;
而appkey只是我们的某个模型用于通信的密钥，所以我们称它为应用密钥，这个appkey的分配，是在我们的config server下。这个config_server_init的初始化流程，是在我们的mesh_stack_init()函数下，就去初始化配置好的。最终当我们有信息配置的时候，会触发opcode_handlers，我们可以从这个操作码列表中查找相应的信息处理。[[文件:Config-22.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Config-23.png|边框|居中|无框|1200x1200像素]]我们来看下config server的opcode_handlers[]操作码处理程序列表，使用黄色标记给大家圈了几个操作码指令，这几个是我们配置一个最简单的server节点设备，都需要使用到的，包含了：&lt;br /&gt;
&lt;br /&gt;
1、appkey的添加，CONFIG_OPCODE_APPKEY_ADD&lt;br /&gt;
&lt;br /&gt;
2、publish发布地址，CONFIG_OPCODE_MODEL_PUBLICATION_VIRTUAL_ADDRESS_SET&lt;br /&gt;
&lt;br /&gt;
3、subscription订阅地址，CONFIG_OPCODE_MODEL_SUBSCRIPTION_ADD&lt;br /&gt;
&lt;br /&gt;
4、模型appkey的绑定，CONFIG_OPCODE_MODEL_APP_BIND&lt;br /&gt;
&lt;br /&gt;
5、netkey的更新，CONFIG_OPCODE_NETKEY_ADD[[文件:Config-21.png|边框|居中|无框|1200x1200像素]]我们可以看到当接收到CONFIG_OPCODE_APPKEY_ADD操作码的数据，会调用handle_appkey_add()函数去处理，在这个函数中我们最终会调用dsm_appkey_add()函数去存储我们的appkey。[[文件:Appkey-01.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
== ⑤Element元素 ==&lt;br /&gt;
元素简单的说就是定义了节点的功能，一些具有复杂功能的节点设备，会包含多个元素。一个节点设备最少需要包含一个元素，我们称之为主元素。&lt;br /&gt;
&lt;br /&gt;
从主元素开始（如果有多个元素，则第一个元素就是主元素），我们会给它分配相应的元素地址。主元素的地址就是节点的地址，第二个元素的地址就是主元素的地址基础上加上1，依次类推。&lt;br /&gt;
&lt;br /&gt;
可以看到在我们的主元素Element：0x0002中包含了3个不同的服务，这3个服务就是我们的模型Model，其中Configuration Server与Health Server这两个服务，是每一个节点都必须携带的模型。Configuration Server（配置服务）是用于配置节点设备信息，Health Server（健康服务）是用于节点进行网络内的心跳联系。剩下的Generic On Off Server服务，这个就是我们用于控制一个开关量的模型（在例程中我们通过控制LED的IO口高低电平来展示）。[[文件:Config-05.jpg|边框|居中|无框]]在我们的代码中，我们首先需要在nrf_mesh_config_app.h中定义好我们节点设备的最大元素数量，可以看到我们定义#define ACCESS_ELEMENT_COUNT (1)，也就是说我们当前的例程，只含有一个主元素。&lt;br /&gt;
[[文件:Element-01.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
而这个ACCESS_ELEMENT_COUNT ，是在我们开启节点配置的地方被调用的，这个mesh_provsionionee_prov_start()函数在前面的内容中有多次讲到，这里就不再说明了。&lt;br /&gt;
[[文件:Element-02.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
== ⑥model模型 ==&lt;br /&gt;
如果说元素是一个功能集合，那么模型就是元素下的具体的某一个功能。&lt;br /&gt;
&lt;br /&gt;
我们以灯的控制为例，灯的常用控制有开关、亮度、色温几种，其中开关、亮度和色温就是具体的模块，而处理灯控的集合就是元素。&lt;br /&gt;
&lt;br /&gt;
=== Configure Server ===&lt;br /&gt;
配置服务模型，这个的大概代码处理流程在我们的“④设备信息分配-&amp;gt;Appkey”已经有了说明，它是我们节点设备必须包含的一个特殊功能的模型。&lt;br /&gt;
&lt;br /&gt;
它的实际功能就像它的名字一样，就是用于配置（configuration）我们的设备信息，比如appkey的添加删除，密钥的绑定删除，其他的模型的发布和订阅控制。具体包含的操作码的使用方法，我们会在generic on off server模型当中使用的时候来说明。&lt;br /&gt;
&lt;br /&gt;
=== Health Server ===&lt;br /&gt;
健康服务，顾名思义就是判断我们的设备是否在网络中保持健康（正常）工作，它同样是一个必须包含的特殊功能的模型。&lt;br /&gt;
&lt;br /&gt;
它的初始化和config server是一样的，都是放在了mesh_stack_init()函数下。&lt;br /&gt;
[[文件:Health-01.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
我们来看下health_server_init()函数，最终我们会调用access_model_add()函数去初始化模型，并将模型添加到对应的元素中。&lt;br /&gt;
&lt;br /&gt;
其中的access_model_add_params_t参数，包含的分别是元素的索引（将模型添加到第几个元素当中），模型的ID（由mesh协议手册确定好了），以及最最重要的操作码列表，这个列表中会包含我们的指令码，以及对应的函数功能处理。&lt;br /&gt;
[[文件:Health-02.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
最后我们看下具体包含了哪些健康模型操作码：&lt;br /&gt;
&lt;br /&gt;
1、“运行状况当前状态”消息的操作码，HEALTH_OPCODE_CURRENT_STATUS&lt;br /&gt;
&lt;br /&gt;
2、“ 健康故障状态”消息的操作码，HEALTH_OPCODE_FAULT_STATUS&lt;br /&gt;
&lt;br /&gt;
3、“  获取健康注意事项”消息的操作码，HEALTH_OPCODE_ATTENTION_GET&lt;br /&gt;
&lt;br /&gt;
4、“ 健康注意集”消息的操作码，HEALTH_OPCODE_ATTENTION_SET&lt;br /&gt;
&lt;br /&gt;
5、“未确认健康注意事项”消息的操作码，HEALTH_OPCODE_ATTENTION_SET_UNACKED&lt;br /&gt;
&lt;br /&gt;
6、“健康注意状态”消息的操作码，HEALTH_OPCODE_ATTENTION_STATUS&lt;br /&gt;
&lt;br /&gt;
7、“健康故障清除”消息的操作码，HEALTH_OPCODE_FAULT_CLEAR&lt;br /&gt;
&lt;br /&gt;
8、“健康故障清除未确认”消息的操作码，HEALTH_OPCODE_FAULT_CLEAR_UNACKED&lt;br /&gt;
&lt;br /&gt;
9、“健康故障获取”消息的操作码，HEALTH_OPCODE_FAULT_GET&lt;br /&gt;
&lt;br /&gt;
10、“健康故障测试”消息的操作码，HEALTH_OPCODE_FAULT_TEST&lt;br /&gt;
&lt;br /&gt;
11、“未确认健康故障测试”消息的操作码，HEALTH_OPCODE_FAULT_TEST_UNACKED&lt;br /&gt;
&lt;br /&gt;
12、“健康期获取”消息的操作码，HEALTH_OPCODE_PERIOD_GET&lt;br /&gt;
&lt;br /&gt;
13、“健康期设置”消息的操作码，HEALTH_OPCODE_PERIOD_SET&lt;br /&gt;
&lt;br /&gt;
14、“未确认健康期设置”消息的操作码，HEALTH_OPCODE_PERIOD_SET_UNACKED&lt;br /&gt;
&lt;br /&gt;
15、“健康期状态”消息的操作码，HEALTH_OPCODE_PERIOD_STATUS&lt;br /&gt;
[[文件:Health-03.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
在大家的前期使用中，一般也不会真的需要大家去处理health server下的指令，毕竟我们刚开始使用，也很难让设备在网络中出现各种运行问题（节点少功能少）。但是当大家自己开始开发实际功能时，就一定要注意分析health server下的指令码返回，用来快速解决我们网络中的问题。&lt;br /&gt;
&lt;br /&gt;
=== Generic On Off Server ===&lt;br /&gt;
接下来我们来看下Generic On Off Server服务，这个服务和上方的两个服务不太一样，他不是我们的特殊必要服务，是一个通用的模型（用于具体的功能数据的处理，这里是用于开关功能）。借用这个服务模型，我们给大家介绍一下如何创建一个符合BLE MESH协议的模型。&lt;br /&gt;
&lt;br /&gt;
我们看下模型的初始化方法，在generic_onoff_server_init()函数中，首先我们使用access_model_add_params_t定义一个模型分配参数的结构体init_params，在这个结构体当中，我们要给定：&lt;br /&gt;
&lt;br /&gt;
1、model_id（模型的ID），这个是mesh协议规定好的，我们可以查看“归档资料\8-蓝牙协议手册和芯片手册\蓝牙mesh协议手册\《MshMDLv1.0.1.pdf》”的第7.3章节有归纳&lt;br /&gt;
&lt;br /&gt;
2、element_index（元素索引），这个看我们准备将这个服务添加到哪一个元素下，这里我们因为只有一个主元素，所以我们的元素索引为0&lt;br /&gt;
&lt;br /&gt;
3、p_opcode_handlers（操作码列表，指令码列表），这个也是mesh协议规定好的，我们可以查看“归档资料\8-蓝牙协议手册和芯片手册\蓝牙mesh协议手册\《MshMDLv1.0.1.pdf》”的第7.1章节归纳，具体的操作码的功能及要求，大家需要对应这个pdf手册，自己查看一下，对于一个模型ID，我们不一定要注册所有的属于它的opcode，只要注册我们需要使用的即可&lt;br /&gt;
&lt;br /&gt;
4、opcode_count（操作码数量），准备注册的操作码的数量&lt;br /&gt;
&lt;br /&gt;
5、p_args（通用参数指针），用于参数的传递&lt;br /&gt;
&lt;br /&gt;
6、publish_timeout_cb（发布超时时间），发布的定时器到期，会进入此回调，我们可以做一些处理（例如重新发送数据等等）。如果我们的模型不支持定期发布，可以将此值设置为NULL。&lt;br /&gt;
&lt;br /&gt;
当我们配置好模型的参数结构体之后，会调用access_model_add()函数去初始化模型并添加到元素，同时会返回给我们用于存储此模型的句柄指针p_server-&amp;gt;model_handle。&lt;br /&gt;
&lt;br /&gt;
因为我们的onoff模型，需要订阅消息，所以我们需要调用access_model_subscription_list_alloc()函数，携带我们的模型句柄去分配模型的订阅列表。&lt;br /&gt;
[[文件:Onoff-01.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
看完模型的初始化注册之后，我们来看下模型包含的opcode操作码，可以看到在m_opcode_handlers[]操作码列表中包含了三个opcode。这三个opcode我们可以看下他的定义，以及对照一下《MshMDLv1.0.1.pdf》第7.1章节的归纳，可以看到是一一对应的关系。具体这三个opcode的功能，大家可以查看手册中的说明，或者直接看下他们对应的功能处理函数。&lt;br /&gt;
[[文件:Onoff-02.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Onoff-03.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Onoff-04.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
接下来我们看下GENERIC_ONOFF_OPCODE_SET与GENERIC_ONOFF_OPCODE_SET_UNACKNOWLEDGED对应的handle_set，可以看到在这个函数的最后，我们会去调用status_send()函数去发送数据。&lt;br /&gt;
[[文件:Onoff-05.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
然后我们看下status_send()函数，在这个函数中，我们最终会调用access_model_publish来发布我们的数据，或者使用accsee_model_reply来回复数据。&lt;br /&gt;
&lt;br /&gt;
这两个函数再往下就是我们之前在广播部分讲解的packet_alloc_and_tx函数去发送广播数据了，这个有不清楚的，大家可以到前面去查看一下。&lt;br /&gt;
[[文件:Onoff-06.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
==== model bind appkey ====&lt;br /&gt;
那么按照上方的方法注册好模型，然后我们的节点设备被成功的配置入网之后，我们如何让这个模型工作起来。&lt;br /&gt;
&lt;br /&gt;
首先第一步是需要将我们的模型绑定到相应的appkey，只有这样我们的模型才能拿到用于通信的密钥，例如如下的截图当中，我们利用手机app软件nRF Mesh去给我们的Generic On Off Server模型绑定Appkey1。[[文件:Config-14.png|边框|居中|无框|1040x1040像素]]&lt;br /&gt;
[[文件:Config-06.jpg|边框|居中|无框]]&lt;br /&gt;
[[文件:Config-07.jpg|边框|居中|无框]]&lt;br /&gt;
[[文件:Config-08.jpg|边框|居中|无框]]当我们在使用app去绑定appkey的时候，在我们的代码中的体现是在config server下的opcode_handlers[]操作码列表中的CONFIG_OPCODE_MODEL_APP_BIND。&lt;br /&gt;
[[文件:Onoff-07.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
我们追踪一下CONFIG_OPCODE_MODEL_APP_BIND对应的handle_model_app_bind_unbind()，这个函数比较长，这边只给大家截取了部分，在为截取到部分，还有获取元素索引的get_element_index，获取模型ID和模型handle的access_handle_get，然后才是我们的dsm_appkey_index_to_appkey_handle函数用于将appkey索引给到appkey句柄。然后调用access_model_application_bind函数去绑定模型和appkey，最终如果是成功绑定，则调用access_flash_config_store函数去存储修改的绑定信息。&lt;br /&gt;
[[文件:Onoff-08.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
==== model publish address ====&lt;br /&gt;
[[文件:Model-pub-01.jpg|边框|居中|无框]]&lt;br /&gt;
[[文件:Model-pub-02.jpg|边框|居中|无框]]&lt;br /&gt;
[[文件:Model-pub-03.jpg|边框|居中|无框]]&lt;br /&gt;
[[文件:Model-pub-04.jpg|边框|居中|无框]]&lt;br /&gt;
[[文件:Model-sub-03.jpg|边框|居中|无框|982x982像素]]&lt;br /&gt;
[[文件:Model-sub-04.jpg|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Model-pub-07.jpg|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Model-pub-08.jpg|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
==== model subscription address ====&lt;br /&gt;
[[文件:Model-sub-02.jpg|边框|居中|无框]]&lt;br /&gt;
[[文件:Model-sub-01.jpg|边框|居中|无框]]&lt;br /&gt;
[[文件:Model-pub-06.jpg|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Model-pub-05.png|边框|居中|无框|996x996像素]]&lt;br /&gt;
[[文件:Model-sub-05.jpg|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Model-sub-06.jpg|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[分类:NRF52832DK]]&lt;br /&gt;
[[分类:实验手册]]&lt;/div&gt;</summary>
		<author><name>Jinx</name></author>
		
	</entry>
	<entry>
		<id>http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:Model-pub-08.jpg&amp;diff=2815</id>
		<title>文件:Model-pub-08.jpg</title>
		<link rel="alternate" type="text/html" href="http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:Model-pub-08.jpg&amp;diff=2815"/>
		<updated>2020-04-02T03:11:31Z</updated>

		<summary type="html">&lt;p&gt;Jinx：&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Model-pub-08&lt;/div&gt;</summary>
		<author><name>Jinx</name></author>
		
	</entry>
	<entry>
		<id>http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:Model-pub-07.jpg&amp;diff=2814</id>
		<title>文件:Model-pub-07.jpg</title>
		<link rel="alternate" type="text/html" href="http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:Model-pub-07.jpg&amp;diff=2814"/>
		<updated>2020-04-02T03:11:09Z</updated>

		<summary type="html">&lt;p&gt;Jinx：&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Model-pub-07&lt;/div&gt;</summary>
		<author><name>Jinx</name></author>
		
	</entry>
	<entry>
		<id>http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:Model-pub-05.png&amp;diff=2813</id>
		<title>文件:Model-pub-05.png</title>
		<link rel="alternate" type="text/html" href="http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:Model-pub-05.png&amp;diff=2813"/>
		<updated>2020-04-02T03:10:44Z</updated>

		<summary type="html">&lt;p&gt;Jinx：&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Model-pub-05&lt;/div&gt;</summary>
		<author><name>Jinx</name></author>
		
	</entry>
	<entry>
		<id>http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:Model-pub-06.jpg&amp;diff=2812</id>
		<title>文件:Model-pub-06.jpg</title>
		<link rel="alternate" type="text/html" href="http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:Model-pub-06.jpg&amp;diff=2812"/>
		<updated>2020-04-02T03:10:13Z</updated>

		<summary type="html">&lt;p&gt;Jinx：&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Model-pub-06&lt;/div&gt;</summary>
		<author><name>Jinx</name></author>
		
	</entry>
	<entry>
		<id>http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:Model-sub-06.jpg&amp;diff=2811</id>
		<title>文件:Model-sub-06.jpg</title>
		<link rel="alternate" type="text/html" href="http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:Model-sub-06.jpg&amp;diff=2811"/>
		<updated>2020-04-02T03:08:48Z</updated>

		<summary type="html">&lt;p&gt;Jinx：&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Model-sub-06&lt;/div&gt;</summary>
		<author><name>Jinx</name></author>
		
	</entry>
	<entry>
		<id>http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:Model-sub-05.jpg&amp;diff=2810</id>
		<title>文件:Model-sub-05.jpg</title>
		<link rel="alternate" type="text/html" href="http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:Model-sub-05.jpg&amp;diff=2810"/>
		<updated>2020-04-02T03:08:05Z</updated>

		<summary type="html">&lt;p&gt;Jinx：&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Model-sub-05&lt;/div&gt;</summary>
		<author><name>Jinx</name></author>
		
	</entry>
	<entry>
		<id>http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:Model-sub-04.jpg&amp;diff=2809</id>
		<title>文件:Model-sub-04.jpg</title>
		<link rel="alternate" type="text/html" href="http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:Model-sub-04.jpg&amp;diff=2809"/>
		<updated>2020-04-02T03:07:43Z</updated>

		<summary type="html">&lt;p&gt;Jinx：&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Model-sub-04&lt;/div&gt;</summary>
		<author><name>Jinx</name></author>
		
	</entry>
	<entry>
		<id>http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:Model-sub-03.jpg&amp;diff=2808</id>
		<title>文件:Model-sub-03.jpg</title>
		<link rel="alternate" type="text/html" href="http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:Model-sub-03.jpg&amp;diff=2808"/>
		<updated>2020-04-02T03:07:22Z</updated>

		<summary type="html">&lt;p&gt;Jinx：&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Model-sub-03&lt;/div&gt;</summary>
		<author><name>Jinx</name></author>
		
	</entry>
	<entry>
		<id>http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:Model-sub-02.jpg&amp;diff=2807</id>
		<title>文件:Model-sub-02.jpg</title>
		<link rel="alternate" type="text/html" href="http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:Model-sub-02.jpg&amp;diff=2807"/>
		<updated>2020-04-02T02:51:01Z</updated>

		<summary type="html">&lt;p&gt;Jinx：&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Model-sub-02&lt;/div&gt;</summary>
		<author><name>Jinx</name></author>
		
	</entry>
	<entry>
		<id>http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:Model-sub-01.jpg&amp;diff=2806</id>
		<title>文件:Model-sub-01.jpg</title>
		<link rel="alternate" type="text/html" href="http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:Model-sub-01.jpg&amp;diff=2806"/>
		<updated>2020-04-02T02:50:45Z</updated>

		<summary type="html">&lt;p&gt;Jinx：&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Model-sub-01&lt;/div&gt;</summary>
		<author><name>Jinx</name></author>
		
	</entry>
	<entry>
		<id>http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:Model-pub-04.jpg&amp;diff=2805</id>
		<title>文件:Model-pub-04.jpg</title>
		<link rel="alternate" type="text/html" href="http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:Model-pub-04.jpg&amp;diff=2805"/>
		<updated>2020-04-02T02:50:29Z</updated>

		<summary type="html">&lt;p&gt;Jinx：&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Model-pub-04&lt;/div&gt;</summary>
		<author><name>Jinx</name></author>
		
	</entry>
	<entry>
		<id>http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:Model-pub-03.jpg&amp;diff=2804</id>
		<title>文件:Model-pub-03.jpg</title>
		<link rel="alternate" type="text/html" href="http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:Model-pub-03.jpg&amp;diff=2804"/>
		<updated>2020-04-02T02:50:14Z</updated>

		<summary type="html">&lt;p&gt;Jinx：&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Model-pub-03&lt;/div&gt;</summary>
		<author><name>Jinx</name></author>
		
	</entry>
	<entry>
		<id>http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:Model-pub-02.jpg&amp;diff=2803</id>
		<title>文件:Model-pub-02.jpg</title>
		<link rel="alternate" type="text/html" href="http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:Model-pub-02.jpg&amp;diff=2803"/>
		<updated>2020-04-02T02:49:58Z</updated>

		<summary type="html">&lt;p&gt;Jinx：&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Model-pub-02&lt;/div&gt;</summary>
		<author><name>Jinx</name></author>
		
	</entry>
	<entry>
		<id>http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:Model-pub-01.jpg&amp;diff=2802</id>
		<title>文件:Model-pub-01.jpg</title>
		<link rel="alternate" type="text/html" href="http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:Model-pub-01.jpg&amp;diff=2802"/>
		<updated>2020-04-02T02:49:34Z</updated>

		<summary type="html">&lt;p&gt;Jinx：&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Model-pub-01&lt;/div&gt;</summary>
		<author><name>Jinx</name></author>
		
	</entry>
	<entry>
		<id>http://doc.iotxx.com/index.php?title=NRF52832DK-Mesh%E7%BB%84%E7%BD%91%E5%AE%9E%E9%AA%8C&amp;diff=2801</id>
		<title>NRF52832DK-Mesh组网实验</title>
		<link rel="alternate" type="text/html" href="http://doc.iotxx.com/index.php?title=NRF52832DK-Mesh%E7%BB%84%E7%BD%91%E5%AE%9E%E9%AA%8C&amp;diff=2801"/>
		<updated>2020-04-01T09:29:24Z</updated>

		<summary type="html">&lt;p&gt;Jinx：/* Generic On Off Server */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;蓝牙Mesh和BLE是并行发展的一个独立分支，虽然底层共用Physical Layer和Link Layer，但是上层协议并不相同。因此，nRF52832的Mesh例程，并不在BLE协议栈sdk中，而是安装一个扩展包。&lt;br /&gt;
&lt;br /&gt;
本文介绍蓝牙Mesh相关的环境搭建和实验介绍。&lt;br /&gt;
&lt;br /&gt;
== Mesh开发环境 ==&lt;br /&gt;
&lt;br /&gt;
=== 蓝牙Mesh SDK ===&lt;br /&gt;
SDK位置：&amp;lt;code&amp;gt;归档资料/2-协议栈SDK/nRF5_SDK_Mesh_v310.zip&amp;lt;/code&amp;gt;，注意文件名中的Mesh字样。&lt;br /&gt;
&lt;br /&gt;
和BLE协议栈一样，将该压缩包直接解压到BLE协议栈同级目录即可使用，解压后的SDK路径如下：&amp;lt;code&amp;gt;E:\project-nordic\nRF5_SDK_Mesh_v310&amp;lt;/code&amp;gt;&lt;br /&gt;
[[文件:NRF52832DK-MESH-SDK-INSTALL.gif|居中|无框|750x750像素]]&lt;br /&gt;
&lt;br /&gt;
=== 集成开发软件SES ===&lt;br /&gt;
SES时是Segger Embedded Studio编译器的缩写，用来编译蓝牙Mesh代码，SES功能与IAR或Keil类似。&lt;br /&gt;
&lt;br /&gt;
备注：Nordic为何使用SES而不是IAR，原因不得而知。&lt;br /&gt;
&lt;br /&gt;
==== SES安装 ====&lt;br /&gt;
SES软件位置：&amp;lt;code&amp;gt;归档资料/7-编译器SES/Setup_EmbeddedStudio_ARM_v410_win_x64.exe&amp;lt;/code&amp;gt;。&lt;br /&gt;
&lt;br /&gt;
我们双击打开安装界面，按照如下动图所示方式进行安装（可以全部默认安装）。[[文件:Ses install.gif|边框|居中|无框|556x556像素]]&lt;br /&gt;
&lt;br /&gt;
==== SES许可证激活 ====&lt;br /&gt;
我们双击打开安装好的SES，选择编译自带测试例程，此时弹出需要激活的界面。&lt;br /&gt;
[[文件:Licensed1.jpg|边框|居中|无框|1205x1205像素]]&lt;br /&gt;
由于我们准备使用SES编译nordic的NRF52832的SDK，所以这里我们选择Activate Your Free License。这里因为nordic公司已经帮客户想SEGGER公司买下了使用的版权，所以是免费的许可证。&lt;br /&gt;
[[文件:Licensed2.jpg|边框|居中|无框|736x736像素]]&lt;br /&gt;
我们按照要求填写好信息，主要是邮箱地址（用于接收SEGGER公司发给我们的许可证），填写完成之后点击Request License。下面是我们接收的邮件信息，我们将红色框内的内容复制下来。&lt;br /&gt;
[[文件:Ses install3.png|边框|居中|无框|1301x1301像素]]&lt;br /&gt;
打开SES，选择Tools\License Manager...，我们选择Activate Embedded Studio，打开如下界面，并将我们的许可证粘贴进去，然后点击Install License。&lt;br /&gt;
[[文件:Licensed4.png|边框|居中|无框|481x481像素]]&lt;br /&gt;
安装好许可证之后，跳转到如下的界面。&lt;br /&gt;
[[文件:Licensed5.png|边框|居中|无框|481x481像素]]&lt;br /&gt;
此时我们的许可证激活完毕（有时候需要等待一段时间，才会显示激活成功）。&lt;br /&gt;
[[文件:Licensed6.png|边框|居中|无框|515x515像素]]&lt;br /&gt;
&lt;br /&gt;
== mesh实验学习流程概论 ==&lt;br /&gt;
1、首先大家一定要先查看一下我们资料中的手册“BLE-Mesh技术揭秘”，这个是很重要的一个mesh基本概念的介绍，只有看完这个文档，我们才能明白mesh组网是什么，是怎么完成的，后续的我们实验内容，也会在这个文档中查到相应的对照。&lt;br /&gt;
&lt;br /&gt;
2、按照Light_switch测试实验的实验说明，跑通测试流程（可以固件清零重新烧写多测试几遍，有助于增强我们的流程熟练度，利于后面的开发）。light_switch实验是现在nordic提供给我们的唯一的真正意义上的mesh组网通信例程。&lt;br /&gt;
&lt;br /&gt;
3、根据我们下面拆分的组网流程，对照依次的实验说明，进行mesh组网流程的分步学习。前期实验部分我们主要讲解节点设备，配置者设备功能我们使用手机app完成，目的是减少大家的学习难度，配置者设备我们会放在最后一个实验给大家做介绍。&lt;br /&gt;
&lt;br /&gt;
①广播（通信TX）——了解节点设备的广播功能，分为三个重要部分：PB-ADV和Unprov Beacon，PB-GATT的广播，以及模型的Publish用于发送数据。&lt;br /&gt;
&lt;br /&gt;
②扫描（通信RX）——了解节点设备的扫描功能，分为两个重要部分：普通的BLE SCAN功能，以及模块的Subscription用于接收数据。&lt;br /&gt;
&lt;br /&gt;
③入网验证——了解节点设备接入网络的验证过程，实验中使用的是PB-GATT去配置的，包含了：交换公共密钥，完成验证（是否有带外OOB）。&lt;br /&gt;
&lt;br /&gt;
④设备信息分配——启动配置数据分配，给节点分配如下数据：网络key（Netkey）、应用key（Appkey）以及节点设备地址（nodeAddr）。&lt;br /&gt;
&lt;br /&gt;
⑤元素——了解节点设备的元素与模型定义。&lt;br /&gt;
&lt;br /&gt;
⑥模型——了解模型的功能和创建方法，模型的订阅subscription和发布publish功能。&lt;br /&gt;
&lt;br /&gt;
== Light_switch测试实验 ==&lt;br /&gt;
=== 实验简介 ===&lt;br /&gt;
此示例演示了包含充当两个角色的设备的网状生态系统：配置角色（Provisioner role）和节点角色（Node role、provisionee role）。 它还通过在应用程序中使用[Generic OnOff model]来演示了如何使用Mesh模型。&lt;br /&gt;
&lt;br /&gt;
该示例由三个较小的示例组成：&lt;br /&gt;
&lt;br /&gt;
Light switch server：一个实现了[Generic OnOff server model]的简约服务器，用于接收状态数据并控制板上LED 1的状态。&lt;br /&gt;
&lt;br /&gt;
Light switch client：一个实现了[Generic OnOff client model]的简约客户端。当用户按下任意按钮时，OnOff Set消息将发送到配置的目标地址。&lt;br /&gt;
&lt;br /&gt;
Mesh Provisioner：一个简单的静态配置设备应用，用于建立演示网络，该配置设备在一个网状网络中配置所有节点。 此外，配置设备还可以在这些节点上配置网格模型[Generic OnOff model]实例的绑定以及发布和订阅设置，以使它们能够相互通信。&lt;br /&gt;
&lt;br /&gt;
[Generic OnOff Client/Server]模型用于操纵打开/关闭状态。请注意，当服务器设置了发布地址（如本例所示）时，服务器会将其状态更改的任何操作披露到其发布地址。&lt;br /&gt;
&lt;br /&gt;
下图给出了将由静态供应商设置的网状网络的整体视图， 括号中的数字表示预配置程序分配给这些节点的地址。&lt;br /&gt;
[[文件:Mesh network.png|边框|居中|无框|727x727像素]]&lt;br /&gt;
灯开关服务器和灯开关客户端示例均具有预配方角色。它们支持通过广告承载（PB-ADV）和GATT承载（PB-GATT）进行配置，并且还支持网状代理服务器（Proxy Service），但是不支持代理客户端（Proxy Client）。&lt;br /&gt;
&lt;br /&gt;
=== 硬件说明 ===&lt;br /&gt;
完成这个实验，我们最少需要两个开发板硬件用做我们的节点设备（Node）：&lt;br /&gt;
* 一个开发板用做client&lt;br /&gt;
* 一个或者多个开发板用做server&lt;br /&gt;
此外，我们还需要以下之一作为我们的配置设备（Provisioner）：&lt;br /&gt;
* 如果您决定使用静态预配器示例，则多准备一个开发板&lt;br /&gt;
* 如果您决定使用app程序进行设置，则需要安装手机app@link_nrf_mesh_app（@link_nrf_mesh_app_ios或@link_nrf_mesh_app_android）。&lt;br /&gt;
{{Note|text=默认我们使用3个开发板完成我们的实验，3个开发板分别对应client、server以及provisioner三个类型。|type=warning}}&lt;br /&gt;
&lt;br /&gt;
=== 软件说明 ===&lt;br /&gt;
1、我们使用NrfGo上位机，依次对三个开发板进行擦除及softdevice的烧写&lt;br /&gt;
[[文件:Lightswitch nrfgo.png|边框|居中|无框|1013x1013像素]]&lt;br /&gt;
2、使用SEGGER Embedded Studio for ARM编译器分别打开我们`&amp;lt;InstallFolder&amp;gt;/examples/light_switch`路径下的3个实验，并完成编译&lt;br /&gt;
&lt;br /&gt;
●\client\light_switch_client_nrf52832_xxAA_s132_6_1_0.emProject&lt;br /&gt;
&lt;br /&gt;
●\server\light_switch_server_nrf52832_xxAA_s132_6_1_0.emProject&lt;br /&gt;
&lt;br /&gt;
●\provisioner\light_switch_provisioner_nrf52832_xxAA_s132_6_1_0.emProject&lt;br /&gt;
[[文件:Lightswitch SES build.png|边框|居中|无框|1013x1013像素]]&lt;br /&gt;
3、此时我们对3个开发板分别进行固件的烧写，分别烧写client、server、provisioner（建议大家给3个开发板贴上标签，方便后续区分）&lt;br /&gt;
[[文件:Lightswitch SES download.png.gif|边框|居中|无框|1009x1009像素]]&lt;br /&gt;
&lt;br /&gt;
=== 实验现象 ===&lt;br /&gt;
{{Note|text=注意：保证我们的3个设备都是刚按照软件说明部分的步骤配置完成，并且没有进行过任何硬件控制（按键操作）。目的是保证所有设备均未被我们人为配置过，没有进行过网络配置，否则可能有任意异常（不同网络配置，导致的不同现象）|type=danger}}&lt;br /&gt;
1、首先我们分别给client和provisioner供电，然后我们按下provisioner的按键S1：&lt;br /&gt;
&lt;br /&gt;
●provisoner：LED灯点亮，代表正在配置查找设备，配置组网&lt;br /&gt;
&lt;br /&gt;
●client：首先LED3和LED4闪烁，代表正在组网；LED1-LED4四个灯一起闪烁，代表组网完成&lt;br /&gt;
&lt;br /&gt;
我们如果打开RTT检测log打印，可以看到如下的信息，这个时候我们已经给client设备分配了Node Address为0x0100。provisioner信息（上），client信息（下）。&lt;br /&gt;
[[文件:Lightswitch rtt provisoner1.png|边框|无框|1080x1080像素|居中]]&lt;br /&gt;
[[文件:Lightswitch_rtt_node_client.png|边框|居中|无框|656x656像素]]&lt;br /&gt;
&lt;br /&gt;
2、使用provisioner配置好client之后，我们给server开发板上电，这个时候provisioner将会配置server入网：&lt;br /&gt;
&lt;br /&gt;
●provisoner：LED1点亮代表正在配置网络，LED2点亮代表网络配置完成&lt;br /&gt;
&lt;br /&gt;
●server：首先LED3和LED4闪烁，代表正在组网；LED1-LED4四个灯一起闪烁，代表组网完成&lt;br /&gt;
&lt;br /&gt;
我们打开RTT检测log打印，可以看到server已经配置入网，并且被分配了地址Node Address为0x0104。provisioner信息（上），server信息（下）。&lt;br /&gt;
[[文件:Lightswitch rtt provisoner2.png|边框|无框|710x710像素|居中]]&lt;br /&gt;
[[文件:Lightswitch rtt node server.png|边框|无框|656x656像素|居中]]&lt;br /&gt;
&lt;br /&gt;
3、此时整个网络的配置已经完成，这个时候我们可以按下client上的按键来控制server上的LED点亮或者熄灭。&lt;br /&gt;
&lt;br /&gt;
由于我们的server被provisioner分配的Node Address是0x0104（偶数），所以我们通过client的S3控制server的LED1点亮，通过client的S4控制server的LED1熄灭。&lt;br /&gt;
{{Note|text=client设备对于server设备的控制，通过Node Address的奇偶位不同，分成了两个组。&lt;br /&gt;
client的S1和S2分别控制Node Address为奇数的server设备的LED1点亮和熄灭。&lt;br /&gt;
&lt;br /&gt;
client的S3和S4分别控制Node Address为偶数的server设备的LED1点亮和熄灭。|type=warning}}&lt;br /&gt;
我们打开RTT检测log打印，可以看到client分别按下button2（S3）以及button3（S4），分别会设置server的GPIO输出高低电平。client信息（上），server信息（下）。&lt;br /&gt;
[[文件:Lightswitch rtt node client2.png|边框|无框|656x656像素|居中]]&lt;br /&gt;
[[文件:Lightswitch rtt node server2.png|边框|无框|656x656像素|居中]]&lt;br /&gt;
&lt;br /&gt;
4、至此我们的mesh组网的lightswitch实验测试完成。&lt;br /&gt;
&lt;br /&gt;
== ①广播（通信TX） ==&lt;br /&gt;
有关广播部分，我们需要给大家讲解三个方面，分别如下：&lt;br /&gt;
&lt;br /&gt;
1、PB-ADV，这个是mesh中的广播承载层，用于我们启动配置过程（暂时的例程和手机app都是用的PB-GATT来配置的，但是源码中PB-ADV的流程是包含的）。&lt;br /&gt;
&lt;br /&gt;
2、Unprov Beacon，这个是广播一个未被配置的beacon，就是一个特殊形式的广播数据包，用于声明自己是未经配置的设备。&lt;br /&gt;
&lt;br /&gt;
3、PB-GATT，这个是mesh中的GATT承载层，用于我们启动配置过程（当前手机app就是使用PB-GATT，也就是连接之后，通过GATT服务去发送配置信息）。&lt;br /&gt;
&lt;br /&gt;
4、Publish，这个是我们用于模型发布数据的部分，我们将需要发送的数据通过advData广播出去。&lt;br /&gt;
&lt;br /&gt;
=== PB-ADV ===&lt;br /&gt;
PB-ADV说明请查看“BLE-Mesh技术揭秘”第8.1章节。&lt;br /&gt;
&lt;br /&gt;
我们来看下PB-ADV整个的流程，首先在mian文件中，我们的start()函数下，我们调用了mesh_provisionee_prov_start()函数去启动节点设备的等待配置。&lt;br /&gt;
[[文件:PB-ADV-01.png|边框|居中|无框|1200x1200px]]我们继续追踪mesh_provisionee_prov_start()，可以看到在使能宏定义MESH_FEATURE_PB_ADV_ENABLED的情况下，可以追踪到nrf_mesh_prov_bearer_adv_interface_get()函数。&lt;br /&gt;
[[文件:PB-ADV-02.png|边框|居中|无框|1200x1200px]]在nrf_mesh_prov_bearer_adv_interface_get()函数中，我们可以看到prov_bearer_interface_t结构体，这个结构体下面提供了我们配置承载层需要的API函数。&lt;br /&gt;
[[文件:PB-ADV-03.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
=== Unprov Beacon ===&lt;br /&gt;
Unprov Beacon说明请查看“BLE-Mesh技术揭秘”第5.5及5.5.1章节。&lt;br /&gt;
&lt;br /&gt;
上面我们有说到PB-ADV是承载层用于配置节点的功能，而Unprov Beacon则是我们的节点设备用于声明自己未经配置，所以这两个其实是一脉相承的。&lt;br /&gt;
&lt;br /&gt;
紧接着上面的nrf_mesh_prov_bearer_adv_interface_get()函数，其中的prov_bearer_interface_t结构体包含的是处理承载层的API函数，在这个结构体当中有一个.listen_start = prov_bearer_adv_listen，是用于开始监听传入的配置链接的功能，在这个函数中我们可以看到send_unprov_beacon()函数。&lt;br /&gt;
[[文件:PB-ADV-04.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
send_unprov_beacon()函数是用于发送Unprov Beacon数据的函数，在这个函数中我们调用prov_beacon_unprov_build函数去创建用于广播的Unprov Beacon数据包，然后调用advertiser_interval_set函数去设置广播的间隔，最后调用advertiser_packet_send函数去将我们的数据包发送出去。&lt;br /&gt;
&lt;br /&gt;
默认的Unprov Beacon广播间隔是NRF_MESH_PROV_BEARER_ADV_UNPROV_BEACON_INTERVAL_MS 2000。&lt;br /&gt;
&lt;br /&gt;
最终调用的是advertiser_packet_send(0函数去将广播数据发送出去的。&lt;br /&gt;
[[文件:PB-ADV-05.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Publish-12.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
这里我们再最后看一下prov_beacon_unprov_build()函数，这个函数是生成我们的Unprov Beacon的数据包。&lt;br /&gt;
&lt;br /&gt;
其中标识位是0x2B，这个flag是beacon的意思。data的第一个字节0x00代表是我们的BEACON_TYPE_UNPROV（未配置的节点设备），接下来的16个字节是device uuid（这个是寄存器FICR中的device id生成的），接下来2字节是oob info（这里是00 00，代表没有使能OOB），最后的4个字节是URI通过AES-CMAC算法计算出来的值。&lt;br /&gt;
[[文件:PB-GATT-11.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:PB-GATT-12.png|边框|居中|无框]]&lt;br /&gt;
&lt;br /&gt;
=== PB-GATT ===&lt;br /&gt;
PB-GATT说明请查看“BLE-Mesh技术揭秘”第8.1章节。&lt;br /&gt;
&lt;br /&gt;
和PB-ADV一样的，我们从main文件中的start()函数，追踪到mesh_provisionee_prov_start()，可以看到在使能宏定义MESH_FEATURE_PB_GATT_ENABLED的情况下，可以追踪到nrf_mesh_prov_bearer_gatt_init()以及nrf_mesh_prov_bearer_gatt_interface_get()函数。&lt;br /&gt;
[[文件:PB-GATT-01.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
首先我们看一下nrf_mesh_prov_bearer_gatt_init()函数，这个是初始化并设置PB-GATT服务（通过Mesh GATT接口）的函数。对比我们使用nrf master control panel手机APP连接之后获取到的服务，可以看到其中的PB-GATT的服务以及特征值。&lt;br /&gt;
&lt;br /&gt;
再往下的mesh_gatt_init()函数，这个就不给大家分析了，是和我们普通的BLE一样的方式去注册一个服务。&lt;br /&gt;
[[文件:PB-GATT-04.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:PB-GATT-03.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:PB-GATT-02.png|边框|居中|无框]]&lt;br /&gt;
在nrf_mesh_prov_bearer_gatt_interface_get()函数中，我们可以看到prov_bearer_interface_t结构体，这个结构体下面提供了我们配置承载层需要的API函数（这个是和上方的PB-ADV是一样的）。&lt;br /&gt;
[[文件:PB-GATT-05.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
和Unprov Beacon类似的，当我们开始监听配置链接的时候，会触发.listen_start = listen_start_cb的回调，在这个回调中我们触发FSM（有限状态机）的事件E_LISTEN_START，然后调用link_evt_send()去post顺序承载事件。&lt;br /&gt;
[[文件:PB-GATT-06.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
然后我们来到状态机列表m_pb_gatt_fsm_transition_table[]，可以看到E_LISTEN_START事件ID代表的动作ID是A_LISTEN_START。而最终我们的A_LISTEN_START动作，执行的是a_listen_start函数功能（这其中涉及的代码逻辑，暂时先不说明，大家可以自己查看源码的引用）。&lt;br /&gt;
[[文件:PB-GATT-07.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:PB-GATT-08.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
然后我们继续追踪a_listen_start()，可以看到其中调用了mesh_adv_params_set函数设置了我们广播的超时时间MESH_ADV_TIMEOUT_INFINITE以及广播间隔NRF_MESH_PROV_BEARER_GATT_UNPROV_BEACON_INTERVAL_MS，调用mesh_adv_data_set函数设置了我们的广播数据内容，最后调用mesh_adv_start函数启动广播。&lt;br /&gt;
&lt;br /&gt;
默认的PB-GATT的广播数据的广播周期是NRF_MESH_PROV_BEARER_GATT_UNPROV_BEACON_INTERVAL_MS 200。&lt;br /&gt;
[[文件:PB-GATT-09.png|边框|居中|无框|1200x1200像素]]我们同样的看一下mesh_adv_data_set()函数最终配置生成的广播数据，可以看到广播数据的flag是0x06，服务的UUID就是我们上面提到的PB-GATT服务的UUID，接下来16字节UUID是和PB-ADV一样的FICR寄存器的device id值，最后的扫描回调数据携带的是设备的全名“nRF5x Mesh Light”。&lt;br /&gt;
[[文件:PB-GATT-13.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:PB-GATT-14.png|边框|居中|无框]]最后是我们的mesh_adv_start()函数去启动广播，可以看到调用是我们ble stack sotfdevice当中的API函数sd_ble_gap_adv_start()去启动的。&lt;br /&gt;
[[文件:PB-GATT-15.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
=== Publish（模型发布） ===&lt;br /&gt;
这一章节我们只给大家讲解模块publish的数据发布流程，暂时不说明模型的创建方法，以及如何使用，这个放在下面单独的模型章节进行说明。&lt;br /&gt;
&lt;br /&gt;
以generic_onoff_server模型为例，我们找到access_model_publish以及access_model_reply函数，这两个函数的功能类似的，都是用于我们用户的模型发送数据的。&lt;br /&gt;
&lt;br /&gt;
其中access_model_publish是用于模型主动发送，而access_model_reply是用于模型给对应的Opcode回应数据。&lt;br /&gt;
&lt;br /&gt;
这里我们以access_model_publish函数为主，来看下发布数据的代码流程。&lt;br /&gt;
[[文件:Publish-01.png|边框|居中|无框|1200x1200像素]]查看一下access_model_publish()函数，我们可以看到packet_alloc_and_tx()函数，这个是我们继续发送数据的函数。下面还有一个蓝牙方框中的内容，是我们定义的重传机制（mesh数据为了保证传输的成功率，默认都是会重传数据包的，默认重传次数是4次）。[[文件:Publish-02.png|边框|居中|无框|1200x1200像素]]然后我们继续看一下packet_alloc_and_tx()函数，在这个函数中，我们最终会调用packet_tx()函数将数据发送出去。[[文件:Publish-03.png|边框|居中|无框|1200x1200像素]]继续追踪packet_tx()函数，这个函数的内容比较多，我们只截取了其中数据发送的部分，也就是我们的nrf_mesh_packet_send()函数，我们在他的tx_params中赋值好我们需要发送出去的数据内容。[[文件:Publish-04.png|边框|居中|无框|1199x1199像素]]nrf_mesh_packet_send()函数最终调用的是transport_tx()函数发送数据。而transport_tx()函数，最终是调用upper_transport_tx()函数进行的数据发送。[[文件:Publish-05.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Publish-06.png|边框|居中|无框|1200x1200像素]]我们再继续追踪upper_transport_tx()函数，可以看到我们会根据p_metadata-&amp;gt;segmented来判断数据是否要分包发送。&lt;br /&gt;
[[文件:Publish-13.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
接下里我们以不分包发送函数unsegmented_packet_tx()为例，来继续说明数据传输流程。&lt;br /&gt;
&lt;br /&gt;
我们可以看到我们的unsegmented_packet_tx()函数中，最终是调用的network_packet_send()函数。（分包的segmented_packet_tx()函数最终也是调用的network_packet_send()，大家可以自己追踪代码看一下）&lt;br /&gt;
[[文件:Publish-14.png|边框|居中|无框|1200x1200像素]]继续追踪我们的network_packet_send()函数，可以看到我们的net_packet_encrypt()加密函数，这个是将我们的明文数据，做好加密工作。&lt;br /&gt;
&lt;br /&gt;
然后调用core_tx_packet_send()函数发送出去。[[文件:Publish-15.png|边框|居中|无框|1200x1200像素]]最后我们追踪到core_tx_packet_send()函数，可以看到调用的是p_bearer-&amp;gt;p_interface-&amp;gt;packet_send()函数去发送的。所以接下来我们要找到我们的packet_send()功能是在哪边实现的。[[文件:Publish-07.png|边框|居中|无框|1200x1200像素]]可以看到我们是在core_tx_bearer_add函数中去获得的p_interface这个功能。[[文件:Publish-08.png|边框|居中|无框|1200x1200像素]]我们追踪core_tx_bearer_add()函数的引用路径，可以看到是在core_tx_adv_init()当中。（除了此处，还有另一处地方也有引用，功能是类似的，大家可以自行了解）。&lt;br /&gt;
&lt;br /&gt;
我们看下参数m_interface，可以看到m_interface-&amp;gt;packet_send功能，就是对应我们在core_tx_packet_send()函数调用的p_bearer-&amp;gt;p_interface-&amp;gt;packet_send()。[[文件:Publish-09.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Publish-10.png|边框|居中|无框|1200x1200像素]]所以我们接下来来看下m_interface结构体当中的packet_send()函数实现的是什么功能，可以看到最终调用的是advertiser_packet_send()函数将数据发送出去。看到这边我们就很熟悉了，和上面的几个广播功能，最后调用的都是相同的广播包发送函数。[[文件:Publish-11.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Publish-12.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
== ②扫描（通信RX） ==&lt;br /&gt;
&lt;br /&gt;
=== 标准BLE SCAN ===&lt;br /&gt;
看过了我们的广播功能之后，我们来看下扫描的部分。在ble mesh中，我们的扫描功能scan，就是用于获取数据的，类似于数据通信的RX。&lt;br /&gt;
&lt;br /&gt;
我们从main文件中的initialize()函数开始，首先是我们mesh_init()函数，最终我们会调用mesh_stack_init()函数。&lt;br /&gt;
[[文件:SCAN-01.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
在mesh_satck_init函数中，我们可以看到调用了nrf_mesh_init函数去初始化core的参数。&lt;br /&gt;
[[文件:SCAN-02.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
这一段是nrf_mesh_init函数部分，由于这个初始化函数太长，所以我们只截取了scan相关的部分。&lt;br /&gt;
&lt;br /&gt;
首先是第一点scanner_init(scanner_packet_process_cb)函数，在这个函数中，我们回去初始化扫描的参数配置，并且会开启扫描功能。最终我们可以从scanner_packet_process_cb这个回调中得到我们扫描到的数据（获得所有的BLE广播数据）。&lt;br /&gt;
&lt;br /&gt;
然后是第二点ad_listener_subscribe(&amp;amp;m_nrf_mesh_listener)，这个是用来侦听订阅的消息的（从获得的所有BLE广播数据中，筛选出的订阅的消息）。&lt;br /&gt;
[[文件:SCAN-03.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
然后我们继续看一下scanner_init的扫描初始化函数，在这个函数中我们首先调用scanner_config_reset()函数去配置扫描的默认参数。其中包含了：&lt;br /&gt;
&lt;br /&gt;
1、扫描的通道SCANNER_CHANNELS_DEFAULT，也就是BLE的｛37，38，39｝三个channels都去扫描。&lt;br /&gt;
&lt;br /&gt;
2、规范定义的非连接状态访问地址BEARER_ACCESS_ADDR_DEFAULT。&lt;br /&gt;
&lt;br /&gt;
3、配置物理层协议是1Mbit模式。&lt;br /&gt;
&lt;br /&gt;
4、扫描的间隔时间BEARER_SCAN_INT_DEFAULT_MS，扫描的窗口时间BEARER_SCAN_WINDOW_DEFAULT_MS&lt;br /&gt;
&lt;br /&gt;
5、射频的发射功率RADIO_POWER_NRF_0DBM。&lt;br /&gt;
&lt;br /&gt;
6、BLE广播包最大长度RADIO_CONFIG_ADV_MAX_PAYLOAD_SIZE。&lt;br /&gt;
&lt;br /&gt;
最后我们会在nrf_mesh_enable()函数中调用scanner_enable()函数去启动扫描，这个大家可以自己查看一下代码，比较容易理解。&lt;br /&gt;
[[文件:SCAN-04.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:SCAN-05.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
最后我们来看一下scanner_packet_process_cb()回调函数，其中我们可以直接拿到的扫描的数据包是rx_data，包含了数据的类型、数据内容以及数据长度等等信息。到这边我们的扫描功能是已经完成的，但是我们还需要再看下下面一部分的ad_listener的流程。&lt;br /&gt;
&lt;br /&gt;
=== Subscription（模型订阅） ===&lt;br /&gt;
我们需要注意下ad_listener_process()函数，这个函数是将我们扫描到的订阅数据（不符合标准的BLE广播数据，则认为是侦听到的订阅数据）传递到订阅者进行处理（订阅者指的是带订阅功能的模型）。这个是和我们上方说的关注点二ad_listener_subscribe()函数是一脉相承的。&lt;br /&gt;
[[文件:SCAN-06.png|边框|居中|无框|1200x1200像素]]然后我们来对照着看一下ad_listener_subscribe()函数以及ad_listener_process()函数。这两个函数一个是定义了侦听订阅，一个是将扫描获取到的数据传递给订阅者。这两者间的运作关系，大家有兴趣的可以继续查阅他的底层代码逻辑处理，一个是向list中添加订阅者信息，一个是将数据通过p_listener-&amp;gt;handler()将数据传递给订阅者处理。&lt;br /&gt;
[[文件:SCAN-07.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:SCAN-08.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
最终当我们扫描到订阅消息，并调用ad_listener_process()函数传递时，会触发我们注册的回调nrf_mesh_listen（调用ad_listener_subscribe(&amp;amp;m_nrf_mesh_listener)时，在m_nrf_mesh_listener中）。我们来看下nrf_mesh_listen函数中的处理，其中我们先从数据包中解析了数据的类型type，然后根据不同类型的数据进行相应的处理。&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+&lt;br /&gt;
|AD_TYPE_MESH &lt;br /&gt;
|蓝牙网格物体的AD类型&lt;br /&gt;
|-&lt;br /&gt;
|AD_TYPE_BEACON &lt;br /&gt;
|蓝牙网状信标的AD类型&lt;br /&gt;
|-&lt;br /&gt;
|AD_TYPE_PB_ADV &lt;br /&gt;
|PB-ADV消息的AD类型&lt;br /&gt;
|-&lt;br /&gt;
|AD_TYPE_DFU &lt;br /&gt;
|nRF OpenMesh消息的AD类型&lt;br /&gt;
|}&lt;br /&gt;
[[文件:SCAN-09.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
== ③入网验证 ==&lt;br /&gt;
经过前两章的学习，我们对于BLE MESH的广播及扫描的代码流程已经有了一定的了解，也就是已经大概的明白了数据的发送和接收的流程。&lt;br /&gt;
&lt;br /&gt;
那么从这一章节开始，我们将会给大家展示，如何配置一个节点设备入网。配置者设备，我们使用的是手机APP：nRF Mesh。&lt;br /&gt;
&lt;br /&gt;
我们从main文件中的start()函数开始，来看一下入网的代码处理及其流程。在start()函数中我们最后是调用的mesh_provisionee_prov_start()函数来启动的节点配置功能，我们来查看一下它所携带的参数prov_start_params。这部分的参数都是比较重要的，有携带的是设备的信息的、有携带认证密码的，有回调返回的，所以我们会挨个介绍一下。&lt;br /&gt;
[[文件:Prov-01.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
首先携带的是static_auth_data，这个是我们带外OOB认证的密码。如果我们在使用nRF Mesh去进行身份验证的时候，选择使用Static OOB，那么当我们在进行PB-GATT的连接过程中，会弹出一个新的窗口，要求我们去输入这个静态的128bit的OOB密钥。&lt;br /&gt;
[[文件:Prov-02.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Prov-14.png|边框|居中|无框]]&lt;br /&gt;
[[文件:Prov-15.png|边框|居中|无框]]&lt;br /&gt;
接下来携带的是provisioning_complete_cb()回调函数，这个函数处理的是节点设备成功的被配置入网，在这里我们可以获取到我们的节点设备被分配的node_Address。&lt;br /&gt;
[[文件:Prov-03.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
然后携带是device_identification_start_cb()回调函数，这个回调函数是用于通知开发者，我们的节点设备当前已经开始配置。&lt;br /&gt;
[[文件:Prov-04.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
再然后携带的是URI【统一资源标识符（Uniform Resource Identifier，URI)】，这个是用于标识我们的设备类型的，这个数据会通过AES-CMAC算法加密后添加到我们的Unprov Beacon数据的末尾。&lt;br /&gt;
&lt;br /&gt;
这里我们是light switch的server设备，所以我们定义URI是EX_URI_LS_SERVER。&lt;br /&gt;
[[文件:Prov-05.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
分析完携带的参数之后，我们继续来查看mesh_provisionee_prov_start()函数。在这个函数中，我们首先是定义了一下prov_caps，也就是配置支持的OOB功能。然后我们调用nrf_mesh_generate_keys()函数去生成有效的密钥对等待使用。&lt;br /&gt;
&lt;br /&gt;
再然后是调用nrf_mesh_prov_init()函数去初始化prov配置信息的结构体参数，里面有结构体本身m_pro_ctx，需要配置的参数公钥m_public、私钥m_private，以及我们刚刚定义的oob功能prov_caps，最后还携带了一个事件回调函数prov_ect_handler。&lt;br /&gt;
&lt;br /&gt;
接下来是PB-ADV以及PB-GATT的处理过程，这个前面已经讲解过，这里不再赘述。&lt;br /&gt;
&lt;br /&gt;
最后我们调用provisionee_start()函数去开启配置。&lt;br /&gt;
[[文件:Prov-06.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
我们继续来追踪一下provisionee_start()函数，在这个函数中我们调用nrf_mesh_prov_listen()函数去开启侦听承载层的消息。这里我们需要侦听PB-ADV以及PB-GATT两个。&lt;br /&gt;
[[文件:Prov-07.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
然后在nrf_mesh_prov_listen()函数中，我们调用prov_provisionee_listen()函数携带我们刚刚传入的nrf_mesh_prov_ctx_t结构体，以及p_bearer、URI、oob_info_source等参数信息，继续向下传递。&lt;br /&gt;
[[文件:Prov-08.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
最终我们来查看下prov_provisionee_listen()函数，在这个函数中我们传递了p_bearer-&amp;gt;p_callbacks给m_prov_callbacks回调，然后调用p_bearer-&amp;gt;p_interface-&amp;gt;listen_start()函数去启动PB-ADV以及PB-GATT的侦听。&lt;br /&gt;
[[文件:Prov-09.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
当我们在侦听之后，接收到任何配置相关的消息，都将通过m_prov_callbacks()函数回调传递给我们处理，大家可以自行看下其中4个不同的功能函数，具体处理的都是什么消息。&lt;br /&gt;
[[文件:Prov-10.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
在prov_provisionee_listen()函数中，我们可以看到回调函数指针m_prov_callbacks指向的是p_bearer-&amp;gt;p_callbacks，我们一路向上查找，最终可以找到这个p_bearer-&amp;gt;p_callbacks函数就是指的prov_evt_handler()函数。所以其实我们用户可以在这一层去查看比较重要的配置事件返回，而不需要到上面的m_prov_callbacks中这样偏底层的地方去查看。&lt;br /&gt;
&lt;br /&gt;
在这个回调函数中，我们可以看到mian文件中注册的prov_device_identification_start_cb、prov_device_identification_stop_cb、provisioning_aborted_cb的返回。&lt;br /&gt;
[[文件:Prov-11.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
那么除了上方已经存在的几个回调，我们可以看到mian文件中的start()函数下的prov_start_params参数，还缺少一个配置完成的回调prov_complete_cb()。&lt;br /&gt;
&lt;br /&gt;
这个回调的返回方式比较特殊，和上方其他的都不同，是由SD（softdevice）直接从底层返回给我们的。注册这个回调的函数是NRF_SDH_STATE_OBSERVER，返回的回调函数是sd_state_evt_handler。其中触发的事件NRF_SDH_EVT_STATE_ENABLED，这个是在mian文件中的initialize()函数下的ble_stack_init()函数中就被触发的，这个大家可以自行查看一下，不太重要，因为都是nordic底层处理好的消息。&lt;br /&gt;
[[文件:Prov-13.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Prov-12.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
以上整个配置流程的处理，不太好分步展示相应的现象，这个大家可以自己使用SES在线仿真的方式去调试一下流程，这里我们直接给大家展示整个流程打印出的相应的信息，以及nRF Mesh中暂时出来的现象。&lt;br /&gt;
[[文件:Prov-16.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Prov-17.png|边框|居中|无框]]&lt;br /&gt;
&lt;br /&gt;
== ④设备信息分配 ==&lt;br /&gt;
当我们成功的配置节点设备入网之后，我们需要给节点设备主要分配如下几个信息，有关这几个要点的介绍，可以查看“BLE-Mesh技术揭秘”第3章节：&lt;br /&gt;
&lt;br /&gt;
1、节点在网络中的地址Node Address&lt;br /&gt;
&lt;br /&gt;
2、网络密钥Netkey&lt;br /&gt;
&lt;br /&gt;
3、应用密钥Appkey&lt;br /&gt;
&lt;br /&gt;
4、元素的地址Element Address&lt;br /&gt;
&lt;br /&gt;
5、绑定model的Appkey，分配Publish Address，Subscription Address&lt;br /&gt;
&lt;br /&gt;
=== Node Address ===&lt;br /&gt;
首先我们来看一下我们使用nRF Mesh成功配置我们的节点之后，打印出来的消息，开始的紫色字体显示了我们给当前节点设备分配的Node Address。&lt;br /&gt;
&lt;br /&gt;
后面紧接着还有四段黄色字体的内容（这个部分大家的工程是不会打印的，是我们修改例程打印出来给大家展示一下），这四个部分就是我们配置节点的时候，配置者设备（nRF Mesh）给节点设备的四个主要指令，这些指令的具体功能，大家可以查看mesh的协议手册。[[文件:Config-13.png|边框|居中|无框|1080x1080像素]]在代码中，我们会在mian文件的provisioning_complete_cb()回调函数中获取并打印我们的Node Address，这个回调触发的含义就是我们的节点配置已经完成了。&lt;br /&gt;
&lt;br /&gt;
具体这个回调在代码中的回调及触发流程，大家看可以查看“③入网验证”的说明。[[文件:Config-20.png|边框|居中|无框|1200x1200像素]]我们使用nRF Mesh配置好节点设备之后，app界面显示内容如下，包含了节点的信息（地址、名称、配置时间等等），元素和模型，以及Netkey和Appkey等几个重要的设备信息。[[文件:Config-01.jpg|边框|居中|无框]]&lt;br /&gt;
[[文件:Config-04.jpg|边框|居中|无框]]&lt;br /&gt;
&lt;br /&gt;
=== Netkey ===&lt;br /&gt;
我们点击查看一下Network key，可以看到里面有一个Network Key 1，这是一个16字节的网络密钥。我们可以在nRF Mesh的Settings界面去增加或者删除一个network key，用户可以自定义此密钥。[[文件:Config-02.jpg|边框|居中|无框]]在我们的代码中，我们是在pro_evt_handler回调的NRF_MESH_PROV_EVT_COMPLETE事件中获取这个netkey，这个回调函数的触发流程同样是在“③入网验证”已经有说明。[[文件:Prov-11.png|边框|居中|无框|1200x1200像素]]我们直接看下NRF_MESH_PROV_EVT_COMPLETE事件下的mesh_stack_provisioning_data_store()函数，在这个函数中我们主要的任务，就是将配置者分配给我们的node address、netkey、devkey等等信息，分别做一些存储以及功能绑定。&lt;br /&gt;
[[文件:Netkey-01.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
=== Appkey ===&lt;br /&gt;
同样的我们打开Application Key，可以看到其中包含的3个密钥，这里我们只选用了Application Key 1作为我们的密钥。大家同样可以在nRF Mesh的Settings界面去增加或者删除一个App key，用户可以自定义此密钥。[[文件:Config-03.jpg|边框|居中|无框]]appkey这里和netkey不同，netkey是我们网络中的节点设备以及我们配置者设备共有的一个密钥，只有拥有这个密钥，我们才能在当前网络中通信，所以当我们的节点设备配置入网后，会立刻得到netkey（必须要分配给我的节点设备才行）。&lt;br /&gt;
&lt;br /&gt;
而appkey只是我们的某个模型用于通信的密钥，所以我们称它为应用密钥，这个appkey的分配，是在我们的config server下。这个config_server_init的初始化流程，是在我们的mesh_stack_init()函数下，就去初始化配置好的。最终当我们有信息配置的时候，会触发opcode_handlers，我们可以从这个操作码列表中查找相应的信息处理。[[文件:Config-22.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Config-23.png|边框|居中|无框|1200x1200像素]]我们来看下config server的opcode_handlers[]操作码处理程序列表，使用黄色标记给大家圈了几个操作码指令，这几个是我们配置一个最简单的server节点设备，都需要使用到的，包含了：&lt;br /&gt;
&lt;br /&gt;
1、appkey的添加，CONFIG_OPCODE_APPKEY_ADD&lt;br /&gt;
&lt;br /&gt;
2、publish发布地址，CONFIG_OPCODE_MODEL_PUBLICATION_VIRTUAL_ADDRESS_SET&lt;br /&gt;
&lt;br /&gt;
3、subscription订阅地址，CONFIG_OPCODE_MODEL_SUBSCRIPTION_ADD&lt;br /&gt;
&lt;br /&gt;
4、模型appkey的绑定，CONFIG_OPCODE_MODEL_APP_BIND&lt;br /&gt;
&lt;br /&gt;
5、netkey的更新，CONFIG_OPCODE_NETKEY_ADD[[文件:Config-21.png|边框|居中|无框|1200x1200像素]]我们可以看到当接收到CONFIG_OPCODE_APPKEY_ADD操作码的数据，会调用handle_appkey_add()函数去处理，在这个函数中我们最终会调用dsm_appkey_add()函数去存储我们的appkey。[[文件:Appkey-01.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
== ⑤Element元素 ==&lt;br /&gt;
元素简单的说就是定义了节点的功能，一些具有复杂功能的节点设备，会包含多个元素。一个节点设备最少需要包含一个元素，我们称之为主元素。&lt;br /&gt;
&lt;br /&gt;
从主元素开始（如果有多个元素，则第一个元素就是主元素），我们会给它分配相应的元素地址。主元素的地址就是节点的地址，第二个元素的地址就是主元素的地址基础上加上1，依次类推。&lt;br /&gt;
&lt;br /&gt;
可以看到在我们的主元素Element：0x0002中包含了3个不同的服务，这3个服务就是我们的模型Model，其中Configuration Server与Health Server这两个服务，是每一个节点都必须携带的模型。Configuration Server（配置服务）是用于配置节点设备信息，Health Server（健康服务）是用于节点进行网络内的心跳联系。剩下的Generic On Off Server服务，这个就是我们用于控制一个开关量的模型（在例程中我们通过控制LED的IO口高低电平来展示）。[[文件:Config-05.jpg|边框|居中|无框]]在我们的代码中，我们首先需要在nrf_mesh_config_app.h中定义好我们节点设备的最大元素数量，可以看到我们定义#define ACCESS_ELEMENT_COUNT (1)，也就是说我们当前的例程，只含有一个主元素。&lt;br /&gt;
[[文件:Element-01.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
而这个ACCESS_ELEMENT_COUNT ，是在我们开启节点配置的地方被调用的，这个mesh_provsionionee_prov_start()函数在前面的内容中有多次讲到，这里就不再说明了。&lt;br /&gt;
[[文件:Element-02.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
== ⑥model模型 ==&lt;br /&gt;
如果说元素是一个功能集合，那么模型就是元素下的具体的某一个功能。&lt;br /&gt;
&lt;br /&gt;
我们以灯的控制为例，灯的常用控制有开关、亮度、色温几种，其中开关、亮度和色温就是具体的模块，而处理灯控的集合就是元素。&lt;br /&gt;
&lt;br /&gt;
=== Configure Server ===&lt;br /&gt;
配置服务模型，这个的大概代码处理流程在我们的“④设备信息分配-&amp;gt;Appkey”已经有了说明，它是我们节点设备必须包含的一个特殊功能的模型。&lt;br /&gt;
&lt;br /&gt;
它的实际功能就像它的名字一样，就是用于配置（configuration）我们的设备信息，比如appkey的添加删除，密钥的绑定删除，其他的模型的发布和订阅控制。具体包含的操作码的使用方法，我们会在generic on off server模型当中使用的时候来说明。&lt;br /&gt;
&lt;br /&gt;
=== Health Server ===&lt;br /&gt;
健康服务，顾名思义就是判断我们的设备是否在网络中保持健康（正常）工作，它同样是一个必须包含的特殊功能的模型。&lt;br /&gt;
&lt;br /&gt;
它的初始化和config server是一样的，都是放在了mesh_stack_init()函数下。&lt;br /&gt;
[[文件:Health-01.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
我们来看下health_server_init()函数，最终我们会调用access_model_add()函数去初始化模型，并将模型添加到对应的元素中。&lt;br /&gt;
&lt;br /&gt;
其中的access_model_add_params_t参数，包含的分别是元素的索引（将模型添加到第几个元素当中），模型的ID（由mesh协议手册确定好了），以及最最重要的操作码列表，这个列表中会包含我们的指令码，以及对应的函数功能处理。&lt;br /&gt;
[[文件:Health-02.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
最后我们看下具体包含了哪些健康模型操作码：&lt;br /&gt;
&lt;br /&gt;
1、“运行状况当前状态”消息的操作码，HEALTH_OPCODE_CURRENT_STATUS&lt;br /&gt;
&lt;br /&gt;
2、“ 健康故障状态”消息的操作码，HEALTH_OPCODE_FAULT_STATUS&lt;br /&gt;
&lt;br /&gt;
3、“  获取健康注意事项”消息的操作码，HEALTH_OPCODE_ATTENTION_GET&lt;br /&gt;
&lt;br /&gt;
4、“ 健康注意集”消息的操作码，HEALTH_OPCODE_ATTENTION_SET&lt;br /&gt;
&lt;br /&gt;
5、“未确认健康注意事项”消息的操作码，HEALTH_OPCODE_ATTENTION_SET_UNACKED&lt;br /&gt;
&lt;br /&gt;
6、“健康注意状态”消息的操作码，HEALTH_OPCODE_ATTENTION_STATUS&lt;br /&gt;
&lt;br /&gt;
7、“健康故障清除”消息的操作码，HEALTH_OPCODE_FAULT_CLEAR&lt;br /&gt;
&lt;br /&gt;
8、“健康故障清除未确认”消息的操作码，HEALTH_OPCODE_FAULT_CLEAR_UNACKED&lt;br /&gt;
&lt;br /&gt;
9、“健康故障获取”消息的操作码，HEALTH_OPCODE_FAULT_GET&lt;br /&gt;
&lt;br /&gt;
10、“健康故障测试”消息的操作码，HEALTH_OPCODE_FAULT_TEST&lt;br /&gt;
&lt;br /&gt;
11、“未确认健康故障测试”消息的操作码，HEALTH_OPCODE_FAULT_TEST_UNACKED&lt;br /&gt;
&lt;br /&gt;
12、“健康期获取”消息的操作码，HEALTH_OPCODE_PERIOD_GET&lt;br /&gt;
&lt;br /&gt;
13、“健康期设置”消息的操作码，HEALTH_OPCODE_PERIOD_SET&lt;br /&gt;
&lt;br /&gt;
14、“未确认健康期设置”消息的操作码，HEALTH_OPCODE_PERIOD_SET_UNACKED&lt;br /&gt;
&lt;br /&gt;
15、“健康期状态”消息的操作码，HEALTH_OPCODE_PERIOD_STATUS&lt;br /&gt;
[[文件:Health-03.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
在大家的前期使用中，一般也不会真的需要大家去处理health server下的指令，毕竟我们刚开始使用，也很难让设备在网络中出现各种运行问题（节点少功能少）。但是当大家自己开始开发实际功能时，就一定要注意分析health server下的指令码返回，用来快速解决我们网络中的问题。&lt;br /&gt;
&lt;br /&gt;
=== Generic On Off Server ===&lt;br /&gt;
接下来我们来看下Generic On Off Server服务，这个服务和上方的两个服务不太一样，他不是我们的特殊必要服务，是一个通用的模型（用于具体的功能数据的处理，这里是用于开关功能）。借用这个服务模型，我们给大家介绍一下如何创建一个符合BLE MESH协议的模型。&lt;br /&gt;
&lt;br /&gt;
我们看下模型的初始化方法，在generic_onoff_server_init()函数中，首先我们使用access_model_add_params_t定义一个模型分配参数的结构体init_params，在这个结构体当中，我们要给定：&lt;br /&gt;
&lt;br /&gt;
1、model_id（模型的ID），这个是mesh协议规定好的，我们可以查看“归档资料\8-蓝牙协议手册和芯片手册\蓝牙mesh协议手册\《MshMDLv1.0.1.pdf》”的第7.3章节有归纳&lt;br /&gt;
&lt;br /&gt;
2、element_index（元素索引），这个看我们准备将这个服务添加到哪一个元素下，这里我们因为只有一个主元素，所以我们的元素索引为0&lt;br /&gt;
&lt;br /&gt;
3、p_opcode_handlers（操作码列表，指令码列表），这个也是mesh协议规定好的，我们可以查看“归档资料\8-蓝牙协议手册和芯片手册\蓝牙mesh协议手册\《MshMDLv1.0.1.pdf》”的第7.1章节归纳，具体的操作码的功能及要求，大家需要对应这个pdf手册，自己查看一下，对于一个模型ID，我们不一定要注册所有的属于它的opcode，只要注册我们需要使用的即可&lt;br /&gt;
&lt;br /&gt;
4、opcode_count（操作码数量），准备注册的操作码的数量&lt;br /&gt;
&lt;br /&gt;
5、p_args（通用参数指针），用于参数的传递&lt;br /&gt;
&lt;br /&gt;
6、publish_timeout_cb（发布超时时间），发布的定时器到期，会进入此回调，我们可以做一些处理（例如重新发送数据等等）。如果我们的模型不支持定期发布，可以将此值设置为NULL。&lt;br /&gt;
&lt;br /&gt;
当我们配置好模型的参数结构体之后，会调用access_model_add()函数去初始化模型并添加到元素，同时会返回给我们用于存储此模型的句柄指针p_server-&amp;gt;model_handle。&lt;br /&gt;
&lt;br /&gt;
因为我们的onoff模型，需要订阅消息，所以我们需要调用access_model_subscription_list_alloc()函数，携带我们的模型句柄去分配模型的订阅列表。&lt;br /&gt;
[[文件:Onoff-01.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
看完模型的初始化注册之后，我们来看下模型包含的opcode操作码，可以看到在m_opcode_handlers[]操作码列表中包含了三个opcode。这三个opcode我们可以看下他的定义，以及对照一下《MshMDLv1.0.1.pdf》第7.1章节的归纳，可以看到是一一对应的关系。具体这三个opcode的功能，大家可以查看手册中的说明，或者直接看下他们对应的功能处理函数。&lt;br /&gt;
[[文件:Onoff-02.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Onoff-03.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Onoff-04.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
接下来我们看下GENERIC_ONOFF_OPCODE_SET与GENERIC_ONOFF_OPCODE_SET_UNACKNOWLEDGED对应的handle_set，可以看到在这个函数的最后，我们会去调用status_send()函数去发送数据。&lt;br /&gt;
[[文件:Onoff-05.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
然后我们看下status_send()函数，在这个函数中，我们最终会调用access_model_publish来发布我们的数据，或者使用accsee_model_reply来回复数据。&lt;br /&gt;
&lt;br /&gt;
这两个函数再往下就是我们之前在广播部分讲解的packet_alloc_and_tx函数去发送广播数据了，这个有不清楚的，大家可以到前面去查看一下。&lt;br /&gt;
[[文件:Onoff-06.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
==== model bind appkey ====&lt;br /&gt;
那么按照上方的方法注册好模型，然后我们的节点设备被成功的配置入网之后，我们如何让这个模型工作起来。&lt;br /&gt;
&lt;br /&gt;
首先第一步是需要将我们的模型绑定到相应的appkey，只有这样我们的模型才能拿到用于通信的密钥，例如如下的截图当中，我们利用手机app软件nRF Mesh去给我们的Generic On Off Server模型绑定Appkey1。[[文件:Config-14.png|边框|居中|无框|1040x1040像素]]&lt;br /&gt;
[[文件:Config-06.jpg|边框|居中|无框]]&lt;br /&gt;
[[文件:Config-07.jpg|边框|居中|无框]]&lt;br /&gt;
[[文件:Config-08.jpg|边框|居中|无框]]当我们在使用app去绑定appkey的时候，在我们的代码中的体现是在config server下的opcode_handlers[]操作码列表中的CONFIG_OPCODE_MODEL_APP_BIND。&lt;br /&gt;
[[文件:Onoff-07.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
我们追踪一下CONFIG_OPCODE_MODEL_APP_BIND对应的handle_model_app_bind_unbind()，这个函数比较长，这边只给大家截取了部分，在为截取到部分，还有获取元素索引的get_element_index，获取模型ID和模型handle的access_handle_get，然后才是我们的dsm_appkey_index_to_appkey_handle函数用于将appkey索引给到appkey句柄。然后调用access_model_application_bind函数去绑定模型和appkey，最终如果是成功绑定，则调用access_flash_config_store函数去存储修改的绑定信息。&lt;br /&gt;
[[文件:Onoff-08.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
==== model publish address ====&lt;br /&gt;
[[文件:Config-09.jpg|边框|居中|无框]]&lt;br /&gt;
[[文件:Config-10.jpg|边框|居中|无框]]&lt;br /&gt;
&lt;br /&gt;
==== model subscription address ====&lt;br /&gt;
[[文件:Config-11.jpg|边框|居中|无框]]&lt;br /&gt;
[[分类:NRF52832DK]]&lt;br /&gt;
[[分类:实验手册]]&lt;/div&gt;</summary>
		<author><name>Jinx</name></author>
		
	</entry>
	<entry>
		<id>http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:Onoff-08.png&amp;diff=2800</id>
		<title>文件:Onoff-08.png</title>
		<link rel="alternate" type="text/html" href="http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:Onoff-08.png&amp;diff=2800"/>
		<updated>2020-04-01T08:51:34Z</updated>

		<summary type="html">&lt;p&gt;Jinx：&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Onoff-08&lt;/div&gt;</summary>
		<author><name>Jinx</name></author>
		
	</entry>
	<entry>
		<id>http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:Onoff-07.png&amp;diff=2799</id>
		<title>文件:Onoff-07.png</title>
		<link rel="alternate" type="text/html" href="http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:Onoff-07.png&amp;diff=2799"/>
		<updated>2020-04-01T08:49:12Z</updated>

		<summary type="html">&lt;p&gt;Jinx：&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Onoff-07&lt;/div&gt;</summary>
		<author><name>Jinx</name></author>
		
	</entry>
	<entry>
		<id>http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:Onoff-06.png&amp;diff=2798</id>
		<title>文件:Onoff-06.png</title>
		<link rel="alternate" type="text/html" href="http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:Onoff-06.png&amp;diff=2798"/>
		<updated>2020-04-01T07:28:14Z</updated>

		<summary type="html">&lt;p&gt;Jinx：&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Onoff-06&lt;/div&gt;</summary>
		<author><name>Jinx</name></author>
		
	</entry>
	<entry>
		<id>http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:Onoff-05.png&amp;diff=2797</id>
		<title>文件:Onoff-05.png</title>
		<link rel="alternate" type="text/html" href="http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:Onoff-05.png&amp;diff=2797"/>
		<updated>2020-04-01T07:27:57Z</updated>

		<summary type="html">&lt;p&gt;Jinx：&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Onoff-05&lt;/div&gt;</summary>
		<author><name>Jinx</name></author>
		
	</entry>
	<entry>
		<id>http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:Onoff-04.png&amp;diff=2796</id>
		<title>文件:Onoff-04.png</title>
		<link rel="alternate" type="text/html" href="http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:Onoff-04.png&amp;diff=2796"/>
		<updated>2020-04-01T07:12:11Z</updated>

		<summary type="html">&lt;p&gt;Jinx：&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Onoff-04&lt;/div&gt;</summary>
		<author><name>Jinx</name></author>
		
	</entry>
	<entry>
		<id>http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:Onoff-03.png&amp;diff=2795</id>
		<title>文件:Onoff-03.png</title>
		<link rel="alternate" type="text/html" href="http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:Onoff-03.png&amp;diff=2795"/>
		<updated>2020-04-01T07:11:53Z</updated>

		<summary type="html">&lt;p&gt;Jinx：&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Onoff-03&lt;/div&gt;</summary>
		<author><name>Jinx</name></author>
		
	</entry>
	<entry>
		<id>http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:Onoff-02.png&amp;diff=2794</id>
		<title>文件:Onoff-02.png</title>
		<link rel="alternate" type="text/html" href="http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:Onoff-02.png&amp;diff=2794"/>
		<updated>2020-04-01T07:11:37Z</updated>

		<summary type="html">&lt;p&gt;Jinx：&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Onoff-02&lt;/div&gt;</summary>
		<author><name>Jinx</name></author>
		
	</entry>
	<entry>
		<id>http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:Onoff-01.png&amp;diff=2793</id>
		<title>文件:Onoff-01.png</title>
		<link rel="alternate" type="text/html" href="http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:Onoff-01.png&amp;diff=2793"/>
		<updated>2020-04-01T06:08:46Z</updated>

		<summary type="html">&lt;p&gt;Jinx：&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Onoff-01&lt;/div&gt;</summary>
		<author><name>Jinx</name></author>
		
	</entry>
	<entry>
		<id>http://doc.iotxx.com/index.php?title=NRF52832DK-Mesh%E7%BB%84%E7%BD%91%E5%AE%9E%E9%AA%8C&amp;diff=2792</id>
		<title>NRF52832DK-Mesh组网实验</title>
		<link rel="alternate" type="text/html" href="http://doc.iotxx.com/index.php?title=NRF52832DK-Mesh%E7%BB%84%E7%BD%91%E5%AE%9E%E9%AA%8C&amp;diff=2792"/>
		<updated>2020-03-31T08:52:00Z</updated>

		<summary type="html">&lt;p&gt;Jinx：/* Mesh开发环境 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;蓝牙Mesh和BLE是并行发展的一个独立分支，虽然底层共用Physical Layer和Link Layer，但是上层协议并不相同。因此，nRF52832的Mesh例程，并不在BLE协议栈sdk中，而是安装一个扩展包。&lt;br /&gt;
&lt;br /&gt;
本文介绍蓝牙Mesh相关的环境搭建和实验介绍。&lt;br /&gt;
&lt;br /&gt;
== Mesh开发环境 ==&lt;br /&gt;
&lt;br /&gt;
=== 蓝牙Mesh SDK ===&lt;br /&gt;
SDK位置：&amp;lt;code&amp;gt;归档资料/2-协议栈SDK/nRF5_SDK_Mesh_v310.zip&amp;lt;/code&amp;gt;，注意文件名中的Mesh字样。&lt;br /&gt;
&lt;br /&gt;
和BLE协议栈一样，将该压缩包直接解压到BLE协议栈同级目录即可使用，解压后的SDK路径如下：&amp;lt;code&amp;gt;E:\project-nordic\nRF5_SDK_Mesh_v310&amp;lt;/code&amp;gt;&lt;br /&gt;
[[文件:NRF52832DK-MESH-SDK-INSTALL.gif|居中|无框|750x750像素]]&lt;br /&gt;
&lt;br /&gt;
=== 集成开发软件SES ===&lt;br /&gt;
SES时是Segger Embedded Studio编译器的缩写，用来编译蓝牙Mesh代码，SES功能与IAR或Keil类似。&lt;br /&gt;
&lt;br /&gt;
备注：Nordic为何使用SES而不是IAR，原因不得而知。&lt;br /&gt;
&lt;br /&gt;
==== SES安装 ====&lt;br /&gt;
SES软件位置：&amp;lt;code&amp;gt;归档资料/7-编译器SES/Setup_EmbeddedStudio_ARM_v410_win_x64.exe&amp;lt;/code&amp;gt;。&lt;br /&gt;
&lt;br /&gt;
我们双击打开安装界面，按照如下动图所示方式进行安装（可以全部默认安装）。[[文件:Ses install.gif|边框|居中|无框|556x556像素]]&lt;br /&gt;
&lt;br /&gt;
==== SES许可证激活 ====&lt;br /&gt;
我们双击打开安装好的SES，选择编译自带测试例程，此时弹出需要激活的界面。&lt;br /&gt;
[[文件:Licensed1.jpg|边框|居中|无框|1205x1205像素]]&lt;br /&gt;
由于我们准备使用SES编译nordic的NRF52832的SDK，所以这里我们选择Activate Your Free License。这里因为nordic公司已经帮客户想SEGGER公司买下了使用的版权，所以是免费的许可证。&lt;br /&gt;
[[文件:Licensed2.jpg|边框|居中|无框|736x736像素]]&lt;br /&gt;
我们按照要求填写好信息，主要是邮箱地址（用于接收SEGGER公司发给我们的许可证），填写完成之后点击Request License。下面是我们接收的邮件信息，我们将红色框内的内容复制下来。&lt;br /&gt;
[[文件:Ses install3.png|边框|居中|无框|1301x1301像素]]&lt;br /&gt;
打开SES，选择Tools\License Manager...，我们选择Activate Embedded Studio，打开如下界面，并将我们的许可证粘贴进去，然后点击Install License。&lt;br /&gt;
[[文件:Licensed4.png|边框|居中|无框|481x481像素]]&lt;br /&gt;
安装好许可证之后，跳转到如下的界面。&lt;br /&gt;
[[文件:Licensed5.png|边框|居中|无框|481x481像素]]&lt;br /&gt;
此时我们的许可证激活完毕（有时候需要等待一段时间，才会显示激活成功）。&lt;br /&gt;
[[文件:Licensed6.png|边框|居中|无框|515x515像素]]&lt;br /&gt;
&lt;br /&gt;
== mesh实验学习流程概论 ==&lt;br /&gt;
1、首先大家一定要先查看一下我们资料中的手册“BLE-Mesh技术揭秘”，这个是很重要的一个mesh基本概念的介绍，只有看完这个文档，我们才能明白mesh组网是什么，是怎么完成的，后续的我们实验内容，也会在这个文档中查到相应的对照。&lt;br /&gt;
&lt;br /&gt;
2、按照Light_switch测试实验的实验说明，跑通测试流程（可以固件清零重新烧写多测试几遍，有助于增强我们的流程熟练度，利于后面的开发）。light_switch实验是现在nordic提供给我们的唯一的真正意义上的mesh组网通信例程。&lt;br /&gt;
&lt;br /&gt;
3、根据我们下面拆分的组网流程，对照依次的实验说明，进行mesh组网流程的分步学习。前期实验部分我们主要讲解节点设备，配置者设备功能我们使用手机app完成，目的是减少大家的学习难度，配置者设备我们会放在最后一个实验给大家做介绍。&lt;br /&gt;
&lt;br /&gt;
①广播（通信TX）——了解节点设备的广播功能，分为三个重要部分：PB-ADV和Unprov Beacon，PB-GATT的广播，以及模型的Publish用于发送数据。&lt;br /&gt;
&lt;br /&gt;
②扫描（通信RX）——了解节点设备的扫描功能，分为两个重要部分：普通的BLE SCAN功能，以及模块的Subscription用于接收数据。&lt;br /&gt;
&lt;br /&gt;
③入网验证——了解节点设备接入网络的验证过程，实验中使用的是PB-GATT去配置的，包含了：交换公共密钥，完成验证（是否有带外OOB）。&lt;br /&gt;
&lt;br /&gt;
④设备信息分配——启动配置数据分配，给节点分配如下数据：网络key（Netkey）、应用key（Appkey）以及节点设备地址（nodeAddr）。&lt;br /&gt;
&lt;br /&gt;
⑤元素——了解节点设备的元素与模型定义。&lt;br /&gt;
&lt;br /&gt;
⑥模型——了解模型的功能和创建方法，模型的订阅subscription和发布publish功能。&lt;br /&gt;
&lt;br /&gt;
== Light_switch测试实验 ==&lt;br /&gt;
=== 实验简介 ===&lt;br /&gt;
此示例演示了包含充当两个角色的设备的网状生态系统：配置角色（Provisioner role）和节点角色（Node role、provisionee role）。 它还通过在应用程序中使用[Generic OnOff model]来演示了如何使用Mesh模型。&lt;br /&gt;
&lt;br /&gt;
该示例由三个较小的示例组成：&lt;br /&gt;
&lt;br /&gt;
Light switch server：一个实现了[Generic OnOff server model]的简约服务器，用于接收状态数据并控制板上LED 1的状态。&lt;br /&gt;
&lt;br /&gt;
Light switch client：一个实现了[Generic OnOff client model]的简约客户端。当用户按下任意按钮时，OnOff Set消息将发送到配置的目标地址。&lt;br /&gt;
&lt;br /&gt;
Mesh Provisioner：一个简单的静态配置设备应用，用于建立演示网络，该配置设备在一个网状网络中配置所有节点。 此外，配置设备还可以在这些节点上配置网格模型[Generic OnOff model]实例的绑定以及发布和订阅设置，以使它们能够相互通信。&lt;br /&gt;
&lt;br /&gt;
[Generic OnOff Client/Server]模型用于操纵打开/关闭状态。请注意，当服务器设置了发布地址（如本例所示）时，服务器会将其状态更改的任何操作披露到其发布地址。&lt;br /&gt;
&lt;br /&gt;
下图给出了将由静态供应商设置的网状网络的整体视图， 括号中的数字表示预配置程序分配给这些节点的地址。&lt;br /&gt;
[[文件:Mesh network.png|边框|居中|无框|727x727像素]]&lt;br /&gt;
灯开关服务器和灯开关客户端示例均具有预配方角色。它们支持通过广告承载（PB-ADV）和GATT承载（PB-GATT）进行配置，并且还支持网状代理服务器（Proxy Service），但是不支持代理客户端（Proxy Client）。&lt;br /&gt;
&lt;br /&gt;
=== 硬件说明 ===&lt;br /&gt;
完成这个实验，我们最少需要两个开发板硬件用做我们的节点设备（Node）：&lt;br /&gt;
* 一个开发板用做client&lt;br /&gt;
* 一个或者多个开发板用做server&lt;br /&gt;
此外，我们还需要以下之一作为我们的配置设备（Provisioner）：&lt;br /&gt;
* 如果您决定使用静态预配器示例，则多准备一个开发板&lt;br /&gt;
* 如果您决定使用app程序进行设置，则需要安装手机app@link_nrf_mesh_app（@link_nrf_mesh_app_ios或@link_nrf_mesh_app_android）。&lt;br /&gt;
{{Note|text=默认我们使用3个开发板完成我们的实验，3个开发板分别对应client、server以及provisioner三个类型。|type=warning}}&lt;br /&gt;
&lt;br /&gt;
=== 软件说明 ===&lt;br /&gt;
1、我们使用NrfGo上位机，依次对三个开发板进行擦除及softdevice的烧写&lt;br /&gt;
[[文件:Lightswitch nrfgo.png|边框|居中|无框|1013x1013像素]]&lt;br /&gt;
2、使用SEGGER Embedded Studio for ARM编译器分别打开我们`&amp;lt;InstallFolder&amp;gt;/examples/light_switch`路径下的3个实验，并完成编译&lt;br /&gt;
&lt;br /&gt;
●\client\light_switch_client_nrf52832_xxAA_s132_6_1_0.emProject&lt;br /&gt;
&lt;br /&gt;
●\server\light_switch_server_nrf52832_xxAA_s132_6_1_0.emProject&lt;br /&gt;
&lt;br /&gt;
●\provisioner\light_switch_provisioner_nrf52832_xxAA_s132_6_1_0.emProject&lt;br /&gt;
[[文件:Lightswitch SES build.png|边框|居中|无框|1013x1013像素]]&lt;br /&gt;
3、此时我们对3个开发板分别进行固件的烧写，分别烧写client、server、provisioner（建议大家给3个开发板贴上标签，方便后续区分）&lt;br /&gt;
[[文件:Lightswitch SES download.png.gif|边框|居中|无框|1009x1009像素]]&lt;br /&gt;
&lt;br /&gt;
=== 实验现象 ===&lt;br /&gt;
{{Note|text=注意：保证我们的3个设备都是刚按照软件说明部分的步骤配置完成，并且没有进行过任何硬件控制（按键操作）。目的是保证所有设备均未被我们人为配置过，没有进行过网络配置，否则可能有任意异常（不同网络配置，导致的不同现象）|type=danger}}&lt;br /&gt;
1、首先我们分别给client和provisioner供电，然后我们按下provisioner的按键S1：&lt;br /&gt;
&lt;br /&gt;
●provisoner：LED灯点亮，代表正在配置查找设备，配置组网&lt;br /&gt;
&lt;br /&gt;
●client：首先LED3和LED4闪烁，代表正在组网；LED1-LED4四个灯一起闪烁，代表组网完成&lt;br /&gt;
&lt;br /&gt;
我们如果打开RTT检测log打印，可以看到如下的信息，这个时候我们已经给client设备分配了Node Address为0x0100。provisioner信息（上），client信息（下）。&lt;br /&gt;
[[文件:Lightswitch rtt provisoner1.png|边框|无框|1080x1080像素|居中]]&lt;br /&gt;
[[文件:Lightswitch_rtt_node_client.png|边框|居中|无框|656x656像素]]&lt;br /&gt;
&lt;br /&gt;
2、使用provisioner配置好client之后，我们给server开发板上电，这个时候provisioner将会配置server入网：&lt;br /&gt;
&lt;br /&gt;
●provisoner：LED1点亮代表正在配置网络，LED2点亮代表网络配置完成&lt;br /&gt;
&lt;br /&gt;
●server：首先LED3和LED4闪烁，代表正在组网；LED1-LED4四个灯一起闪烁，代表组网完成&lt;br /&gt;
&lt;br /&gt;
我们打开RTT检测log打印，可以看到server已经配置入网，并且被分配了地址Node Address为0x0104。provisioner信息（上），server信息（下）。&lt;br /&gt;
[[文件:Lightswitch rtt provisoner2.png|边框|无框|710x710像素|居中]]&lt;br /&gt;
[[文件:Lightswitch rtt node server.png|边框|无框|656x656像素|居中]]&lt;br /&gt;
&lt;br /&gt;
3、此时整个网络的配置已经完成，这个时候我们可以按下client上的按键来控制server上的LED点亮或者熄灭。&lt;br /&gt;
&lt;br /&gt;
由于我们的server被provisioner分配的Node Address是0x0104（偶数），所以我们通过client的S3控制server的LED1点亮，通过client的S4控制server的LED1熄灭。&lt;br /&gt;
{{Note|text=client设备对于server设备的控制，通过Node Address的奇偶位不同，分成了两个组。&lt;br /&gt;
client的S1和S2分别控制Node Address为奇数的server设备的LED1点亮和熄灭。&lt;br /&gt;
&lt;br /&gt;
client的S3和S4分别控制Node Address为偶数的server设备的LED1点亮和熄灭。|type=warning}}&lt;br /&gt;
我们打开RTT检测log打印，可以看到client分别按下button2（S3）以及button3（S4），分别会设置server的GPIO输出高低电平。client信息（上），server信息（下）。&lt;br /&gt;
[[文件:Lightswitch rtt node client2.png|边框|无框|656x656像素|居中]]&lt;br /&gt;
[[文件:Lightswitch rtt node server2.png|边框|无框|656x656像素|居中]]&lt;br /&gt;
&lt;br /&gt;
4、至此我们的mesh组网的lightswitch实验测试完成。&lt;br /&gt;
&lt;br /&gt;
== ①广播（通信TX） ==&lt;br /&gt;
有关广播部分，我们需要给大家讲解三个方面，分别如下：&lt;br /&gt;
&lt;br /&gt;
1、PB-ADV，这个是mesh中的广播承载层，用于我们启动配置过程（暂时的例程和手机app都是用的PB-GATT来配置的，但是源码中PB-ADV的流程是包含的）。&lt;br /&gt;
&lt;br /&gt;
2、Unprov Beacon，这个是广播一个未被配置的beacon，就是一个特殊形式的广播数据包，用于声明自己是未经配置的设备。&lt;br /&gt;
&lt;br /&gt;
3、PB-GATT，这个是mesh中的GATT承载层，用于我们启动配置过程（当前手机app就是使用PB-GATT，也就是连接之后，通过GATT服务去发送配置信息）。&lt;br /&gt;
&lt;br /&gt;
4、Publish，这个是我们用于模型发布数据的部分，我们将需要发送的数据通过advData广播出去。&lt;br /&gt;
&lt;br /&gt;
=== PB-ADV ===&lt;br /&gt;
PB-ADV说明请查看“BLE-Mesh技术揭秘”第8.1章节。&lt;br /&gt;
&lt;br /&gt;
我们来看下PB-ADV整个的流程，首先在mian文件中，我们的start()函数下，我们调用了mesh_provisionee_prov_start()函数去启动节点设备的等待配置。&lt;br /&gt;
[[文件:PB-ADV-01.png|边框|居中|无框|1200x1200px]]我们继续追踪mesh_provisionee_prov_start()，可以看到在使能宏定义MESH_FEATURE_PB_ADV_ENABLED的情况下，可以追踪到nrf_mesh_prov_bearer_adv_interface_get()函数。&lt;br /&gt;
[[文件:PB-ADV-02.png|边框|居中|无框|1200x1200px]]在nrf_mesh_prov_bearer_adv_interface_get()函数中，我们可以看到prov_bearer_interface_t结构体，这个结构体下面提供了我们配置承载层需要的API函数。&lt;br /&gt;
[[文件:PB-ADV-03.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
=== Unprov Beacon ===&lt;br /&gt;
Unprov Beacon说明请查看“BLE-Mesh技术揭秘”第5.5及5.5.1章节。&lt;br /&gt;
&lt;br /&gt;
上面我们有说到PB-ADV是承载层用于配置节点的功能，而Unprov Beacon则是我们的节点设备用于声明自己未经配置，所以这两个其实是一脉相承的。&lt;br /&gt;
&lt;br /&gt;
紧接着上面的nrf_mesh_prov_bearer_adv_interface_get()函数，其中的prov_bearer_interface_t结构体包含的是处理承载层的API函数，在这个结构体当中有一个.listen_start = prov_bearer_adv_listen，是用于开始监听传入的配置链接的功能，在这个函数中我们可以看到send_unprov_beacon()函数。&lt;br /&gt;
[[文件:PB-ADV-04.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
send_unprov_beacon()函数是用于发送Unprov Beacon数据的函数，在这个函数中我们调用prov_beacon_unprov_build函数去创建用于广播的Unprov Beacon数据包，然后调用advertiser_interval_set函数去设置广播的间隔，最后调用advertiser_packet_send函数去将我们的数据包发送出去。&lt;br /&gt;
&lt;br /&gt;
默认的Unprov Beacon广播间隔是NRF_MESH_PROV_BEARER_ADV_UNPROV_BEACON_INTERVAL_MS 2000。&lt;br /&gt;
&lt;br /&gt;
最终调用的是advertiser_packet_send(0函数去将广播数据发送出去的。&lt;br /&gt;
[[文件:PB-ADV-05.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Publish-12.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
这里我们再最后看一下prov_beacon_unprov_build()函数，这个函数是生成我们的Unprov Beacon的数据包。&lt;br /&gt;
&lt;br /&gt;
其中标识位是0x2B，这个flag是beacon的意思。data的第一个字节0x00代表是我们的BEACON_TYPE_UNPROV（未配置的节点设备），接下来的16个字节是device uuid（这个是寄存器FICR中的device id生成的），接下来2字节是oob info（这里是00 00，代表没有使能OOB），最后的4个字节是URI通过AES-CMAC算法计算出来的值。&lt;br /&gt;
[[文件:PB-GATT-11.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:PB-GATT-12.png|边框|居中|无框]]&lt;br /&gt;
&lt;br /&gt;
=== PB-GATT ===&lt;br /&gt;
PB-GATT说明请查看“BLE-Mesh技术揭秘”第8.1章节。&lt;br /&gt;
&lt;br /&gt;
和PB-ADV一样的，我们从main文件中的start()函数，追踪到mesh_provisionee_prov_start()，可以看到在使能宏定义MESH_FEATURE_PB_GATT_ENABLED的情况下，可以追踪到nrf_mesh_prov_bearer_gatt_init()以及nrf_mesh_prov_bearer_gatt_interface_get()函数。&lt;br /&gt;
[[文件:PB-GATT-01.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
首先我们看一下nrf_mesh_prov_bearer_gatt_init()函数，这个是初始化并设置PB-GATT服务（通过Mesh GATT接口）的函数。对比我们使用nrf master control panel手机APP连接之后获取到的服务，可以看到其中的PB-GATT的服务以及特征值。&lt;br /&gt;
&lt;br /&gt;
再往下的mesh_gatt_init()函数，这个就不给大家分析了，是和我们普通的BLE一样的方式去注册一个服务。&lt;br /&gt;
[[文件:PB-GATT-04.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:PB-GATT-03.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:PB-GATT-02.png|边框|居中|无框]]&lt;br /&gt;
在nrf_mesh_prov_bearer_gatt_interface_get()函数中，我们可以看到prov_bearer_interface_t结构体，这个结构体下面提供了我们配置承载层需要的API函数（这个是和上方的PB-ADV是一样的）。&lt;br /&gt;
[[文件:PB-GATT-05.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
和Unprov Beacon类似的，当我们开始监听配置链接的时候，会触发.listen_start = listen_start_cb的回调，在这个回调中我们触发FSM（有限状态机）的事件E_LISTEN_START，然后调用link_evt_send()去post顺序承载事件。&lt;br /&gt;
[[文件:PB-GATT-06.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
然后我们来到状态机列表m_pb_gatt_fsm_transition_table[]，可以看到E_LISTEN_START事件ID代表的动作ID是A_LISTEN_START。而最终我们的A_LISTEN_START动作，执行的是a_listen_start函数功能（这其中涉及的代码逻辑，暂时先不说明，大家可以自己查看源码的引用）。&lt;br /&gt;
[[文件:PB-GATT-07.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:PB-GATT-08.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
然后我们继续追踪a_listen_start()，可以看到其中调用了mesh_adv_params_set函数设置了我们广播的超时时间MESH_ADV_TIMEOUT_INFINITE以及广播间隔NRF_MESH_PROV_BEARER_GATT_UNPROV_BEACON_INTERVAL_MS，调用mesh_adv_data_set函数设置了我们的广播数据内容，最后调用mesh_adv_start函数启动广播。&lt;br /&gt;
&lt;br /&gt;
默认的PB-GATT的广播数据的广播周期是NRF_MESH_PROV_BEARER_GATT_UNPROV_BEACON_INTERVAL_MS 200。&lt;br /&gt;
[[文件:PB-GATT-09.png|边框|居中|无框|1200x1200像素]]我们同样的看一下mesh_adv_data_set()函数最终配置生成的广播数据，可以看到广播数据的flag是0x06，服务的UUID就是我们上面提到的PB-GATT服务的UUID，接下来16字节UUID是和PB-ADV一样的FICR寄存器的device id值，最后的扫描回调数据携带的是设备的全名“nRF5x Mesh Light”。&lt;br /&gt;
[[文件:PB-GATT-13.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:PB-GATT-14.png|边框|居中|无框]]最后是我们的mesh_adv_start()函数去启动广播，可以看到调用是我们ble stack sotfdevice当中的API函数sd_ble_gap_adv_start()去启动的。&lt;br /&gt;
[[文件:PB-GATT-15.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
=== Publish（模型发布） ===&lt;br /&gt;
这一章节我们只给大家讲解模块publish的数据发布流程，暂时不说明模型的创建方法，以及如何使用，这个放在下面单独的模型章节进行说明。&lt;br /&gt;
&lt;br /&gt;
以generic_onoff_server模型为例，我们找到access_model_publish以及access_model_reply函数，这两个函数的功能类似的，都是用于我们用户的模型发送数据的。&lt;br /&gt;
&lt;br /&gt;
其中access_model_publish是用于模型主动发送，而access_model_reply是用于模型给对应的Opcode回应数据。&lt;br /&gt;
&lt;br /&gt;
这里我们以access_model_publish函数为主，来看下发布数据的代码流程。&lt;br /&gt;
[[文件:Publish-01.png|边框|居中|无框|1200x1200像素]]查看一下access_model_publish()函数，我们可以看到packet_alloc_and_tx()函数，这个是我们继续发送数据的函数。下面还有一个蓝牙方框中的内容，是我们定义的重传机制（mesh数据为了保证传输的成功率，默认都是会重传数据包的，默认重传次数是4次）。[[文件:Publish-02.png|边框|居中|无框|1200x1200像素]]然后我们继续看一下packet_alloc_and_tx()函数，在这个函数中，我们最终会调用packet_tx()函数将数据发送出去。[[文件:Publish-03.png|边框|居中|无框|1200x1200像素]]继续追踪packet_tx()函数，这个函数的内容比较多，我们只截取了其中数据发送的部分，也就是我们的nrf_mesh_packet_send()函数，我们在他的tx_params中赋值好我们需要发送出去的数据内容。[[文件:Publish-04.png|边框|居中|无框|1199x1199像素]]nrf_mesh_packet_send()函数最终调用的是transport_tx()函数发送数据。而transport_tx()函数，最终是调用upper_transport_tx()函数进行的数据发送。[[文件:Publish-05.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Publish-06.png|边框|居中|无框|1200x1200像素]]我们再继续追踪upper_transport_tx()函数，可以看到我们会根据p_metadata-&amp;gt;segmented来判断数据是否要分包发送。&lt;br /&gt;
[[文件:Publish-13.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
接下里我们以不分包发送函数unsegmented_packet_tx()为例，来继续说明数据传输流程。&lt;br /&gt;
&lt;br /&gt;
我们可以看到我们的unsegmented_packet_tx()函数中，最终是调用的network_packet_send()函数。（分包的segmented_packet_tx()函数最终也是调用的network_packet_send()，大家可以自己追踪代码看一下）&lt;br /&gt;
[[文件:Publish-14.png|边框|居中|无框|1200x1200像素]]继续追踪我们的network_packet_send()函数，可以看到我们的net_packet_encrypt()加密函数，这个是将我们的明文数据，做好加密工作。&lt;br /&gt;
&lt;br /&gt;
然后调用core_tx_packet_send()函数发送出去。[[文件:Publish-15.png|边框|居中|无框|1200x1200像素]]最后我们追踪到core_tx_packet_send()函数，可以看到调用的是p_bearer-&amp;gt;p_interface-&amp;gt;packet_send()函数去发送的。所以接下来我们要找到我们的packet_send()功能是在哪边实现的。[[文件:Publish-07.png|边框|居中|无框|1200x1200像素]]可以看到我们是在core_tx_bearer_add函数中去获得的p_interface这个功能。[[文件:Publish-08.png|边框|居中|无框|1200x1200像素]]我们追踪core_tx_bearer_add()函数的引用路径，可以看到是在core_tx_adv_init()当中。（除了此处，还有另一处地方也有引用，功能是类似的，大家可以自行了解）。&lt;br /&gt;
&lt;br /&gt;
我们看下参数m_interface，可以看到m_interface-&amp;gt;packet_send功能，就是对应我们在core_tx_packet_send()函数调用的p_bearer-&amp;gt;p_interface-&amp;gt;packet_send()。[[文件:Publish-09.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Publish-10.png|边框|居中|无框|1200x1200像素]]所以我们接下来来看下m_interface结构体当中的packet_send()函数实现的是什么功能，可以看到最终调用的是advertiser_packet_send()函数将数据发送出去。看到这边我们就很熟悉了，和上面的几个广播功能，最后调用的都是相同的广播包发送函数。[[文件:Publish-11.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Publish-12.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
== ②扫描（通信RX） ==&lt;br /&gt;
&lt;br /&gt;
=== 标准BLE SCAN ===&lt;br /&gt;
看过了我们的广播功能之后，我们来看下扫描的部分。在ble mesh中，我们的扫描功能scan，就是用于获取数据的，类似于数据通信的RX。&lt;br /&gt;
&lt;br /&gt;
我们从main文件中的initialize()函数开始，首先是我们mesh_init()函数，最终我们会调用mesh_stack_init()函数。&lt;br /&gt;
[[文件:SCAN-01.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
在mesh_satck_init函数中，我们可以看到调用了nrf_mesh_init函数去初始化core的参数。&lt;br /&gt;
[[文件:SCAN-02.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
这一段是nrf_mesh_init函数部分，由于这个初始化函数太长，所以我们只截取了scan相关的部分。&lt;br /&gt;
&lt;br /&gt;
首先是第一点scanner_init(scanner_packet_process_cb)函数，在这个函数中，我们回去初始化扫描的参数配置，并且会开启扫描功能。最终我们可以从scanner_packet_process_cb这个回调中得到我们扫描到的数据（获得所有的BLE广播数据）。&lt;br /&gt;
&lt;br /&gt;
然后是第二点ad_listener_subscribe(&amp;amp;m_nrf_mesh_listener)，这个是用来侦听订阅的消息的（从获得的所有BLE广播数据中，筛选出的订阅的消息）。&lt;br /&gt;
[[文件:SCAN-03.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
然后我们继续看一下scanner_init的扫描初始化函数，在这个函数中我们首先调用scanner_config_reset()函数去配置扫描的默认参数。其中包含了：&lt;br /&gt;
&lt;br /&gt;
1、扫描的通道SCANNER_CHANNELS_DEFAULT，也就是BLE的｛37，38，39｝三个channels都去扫描。&lt;br /&gt;
&lt;br /&gt;
2、规范定义的非连接状态访问地址BEARER_ACCESS_ADDR_DEFAULT。&lt;br /&gt;
&lt;br /&gt;
3、配置物理层协议是1Mbit模式。&lt;br /&gt;
&lt;br /&gt;
4、扫描的间隔时间BEARER_SCAN_INT_DEFAULT_MS，扫描的窗口时间BEARER_SCAN_WINDOW_DEFAULT_MS&lt;br /&gt;
&lt;br /&gt;
5、射频的发射功率RADIO_POWER_NRF_0DBM。&lt;br /&gt;
&lt;br /&gt;
6、BLE广播包最大长度RADIO_CONFIG_ADV_MAX_PAYLOAD_SIZE。&lt;br /&gt;
&lt;br /&gt;
最后我们会在nrf_mesh_enable()函数中调用scanner_enable()函数去启动扫描，这个大家可以自己查看一下代码，比较容易理解。&lt;br /&gt;
[[文件:SCAN-04.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:SCAN-05.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
最后我们来看一下scanner_packet_process_cb()回调函数，其中我们可以直接拿到的扫描的数据包是rx_data，包含了数据的类型、数据内容以及数据长度等等信息。到这边我们的扫描功能是已经完成的，但是我们还需要再看下下面一部分的ad_listener的流程。&lt;br /&gt;
&lt;br /&gt;
=== Subscription（模型订阅） ===&lt;br /&gt;
我们需要注意下ad_listener_process()函数，这个函数是将我们扫描到的订阅数据（不符合标准的BLE广播数据，则认为是侦听到的订阅数据）传递到订阅者进行处理（订阅者指的是带订阅功能的模型）。这个是和我们上方说的关注点二ad_listener_subscribe()函数是一脉相承的。&lt;br /&gt;
[[文件:SCAN-06.png|边框|居中|无框|1200x1200像素]]然后我们来对照着看一下ad_listener_subscribe()函数以及ad_listener_process()函数。这两个函数一个是定义了侦听订阅，一个是将扫描获取到的数据传递给订阅者。这两者间的运作关系，大家有兴趣的可以继续查阅他的底层代码逻辑处理，一个是向list中添加订阅者信息，一个是将数据通过p_listener-&amp;gt;handler()将数据传递给订阅者处理。&lt;br /&gt;
[[文件:SCAN-07.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:SCAN-08.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
最终当我们扫描到订阅消息，并调用ad_listener_process()函数传递时，会触发我们注册的回调nrf_mesh_listen（调用ad_listener_subscribe(&amp;amp;m_nrf_mesh_listener)时，在m_nrf_mesh_listener中）。我们来看下nrf_mesh_listen函数中的处理，其中我们先从数据包中解析了数据的类型type，然后根据不同类型的数据进行相应的处理。&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+&lt;br /&gt;
|AD_TYPE_MESH &lt;br /&gt;
|蓝牙网格物体的AD类型&lt;br /&gt;
|-&lt;br /&gt;
|AD_TYPE_BEACON &lt;br /&gt;
|蓝牙网状信标的AD类型&lt;br /&gt;
|-&lt;br /&gt;
|AD_TYPE_PB_ADV &lt;br /&gt;
|PB-ADV消息的AD类型&lt;br /&gt;
|-&lt;br /&gt;
|AD_TYPE_DFU &lt;br /&gt;
|nRF OpenMesh消息的AD类型&lt;br /&gt;
|}&lt;br /&gt;
[[文件:SCAN-09.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
== ③入网验证 ==&lt;br /&gt;
经过前两章的学习，我们对于BLE MESH的广播及扫描的代码流程已经有了一定的了解，也就是已经大概的明白了数据的发送和接收的流程。&lt;br /&gt;
&lt;br /&gt;
那么从这一章节开始，我们将会给大家展示，如何配置一个节点设备入网。配置者设备，我们使用的是手机APP：nRF Mesh。&lt;br /&gt;
&lt;br /&gt;
我们从main文件中的start()函数开始，来看一下入网的代码处理及其流程。在start()函数中我们最后是调用的mesh_provisionee_prov_start()函数来启动的节点配置功能，我们来查看一下它所携带的参数prov_start_params。这部分的参数都是比较重要的，有携带的是设备的信息的、有携带认证密码的，有回调返回的，所以我们会挨个介绍一下。&lt;br /&gt;
[[文件:Prov-01.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
首先携带的是static_auth_data，这个是我们带外OOB认证的密码。如果我们在使用nRF Mesh去进行身份验证的时候，选择使用Static OOB，那么当我们在进行PB-GATT的连接过程中，会弹出一个新的窗口，要求我们去输入这个静态的128bit的OOB密钥。&lt;br /&gt;
[[文件:Prov-02.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Prov-14.png|边框|居中|无框]]&lt;br /&gt;
[[文件:Prov-15.png|边框|居中|无框]]&lt;br /&gt;
接下来携带的是provisioning_complete_cb()回调函数，这个函数处理的是节点设备成功的被配置入网，在这里我们可以获取到我们的节点设备被分配的node_Address。&lt;br /&gt;
[[文件:Prov-03.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
然后携带是device_identification_start_cb()回调函数，这个回调函数是用于通知开发者，我们的节点设备当前已经开始配置。&lt;br /&gt;
[[文件:Prov-04.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
再然后携带的是URI【统一资源标识符（Uniform Resource Identifier，URI)】，这个是用于标识我们的设备类型的，这个数据会通过AES-CMAC算法加密后添加到我们的Unprov Beacon数据的末尾。&lt;br /&gt;
&lt;br /&gt;
这里我们是light switch的server设备，所以我们定义URI是EX_URI_LS_SERVER。&lt;br /&gt;
[[文件:Prov-05.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
分析完携带的参数之后，我们继续来查看mesh_provisionee_prov_start()函数。在这个函数中，我们首先是定义了一下prov_caps，也就是配置支持的OOB功能。然后我们调用nrf_mesh_generate_keys()函数去生成有效的密钥对等待使用。&lt;br /&gt;
&lt;br /&gt;
再然后是调用nrf_mesh_prov_init()函数去初始化prov配置信息的结构体参数，里面有结构体本身m_pro_ctx，需要配置的参数公钥m_public、私钥m_private，以及我们刚刚定义的oob功能prov_caps，最后还携带了一个事件回调函数prov_ect_handler。&lt;br /&gt;
&lt;br /&gt;
接下来是PB-ADV以及PB-GATT的处理过程，这个前面已经讲解过，这里不再赘述。&lt;br /&gt;
&lt;br /&gt;
最后我们调用provisionee_start()函数去开启配置。&lt;br /&gt;
[[文件:Prov-06.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
我们继续来追踪一下provisionee_start()函数，在这个函数中我们调用nrf_mesh_prov_listen()函数去开启侦听承载层的消息。这里我们需要侦听PB-ADV以及PB-GATT两个。&lt;br /&gt;
[[文件:Prov-07.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
然后在nrf_mesh_prov_listen()函数中，我们调用prov_provisionee_listen()函数携带我们刚刚传入的nrf_mesh_prov_ctx_t结构体，以及p_bearer、URI、oob_info_source等参数信息，继续向下传递。&lt;br /&gt;
[[文件:Prov-08.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
最终我们来查看下prov_provisionee_listen()函数，在这个函数中我们传递了p_bearer-&amp;gt;p_callbacks给m_prov_callbacks回调，然后调用p_bearer-&amp;gt;p_interface-&amp;gt;listen_start()函数去启动PB-ADV以及PB-GATT的侦听。&lt;br /&gt;
[[文件:Prov-09.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
当我们在侦听之后，接收到任何配置相关的消息，都将通过m_prov_callbacks()函数回调传递给我们处理，大家可以自行看下其中4个不同的功能函数，具体处理的都是什么消息。&lt;br /&gt;
[[文件:Prov-10.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
在prov_provisionee_listen()函数中，我们可以看到回调函数指针m_prov_callbacks指向的是p_bearer-&amp;gt;p_callbacks，我们一路向上查找，最终可以找到这个p_bearer-&amp;gt;p_callbacks函数就是指的prov_evt_handler()函数。所以其实我们用户可以在这一层去查看比较重要的配置事件返回，而不需要到上面的m_prov_callbacks中这样偏底层的地方去查看。&lt;br /&gt;
&lt;br /&gt;
在这个回调函数中，我们可以看到mian文件中注册的prov_device_identification_start_cb、prov_device_identification_stop_cb、provisioning_aborted_cb的返回。&lt;br /&gt;
[[文件:Prov-11.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
那么除了上方已经存在的几个回调，我们可以看到mian文件中的start()函数下的prov_start_params参数，还缺少一个配置完成的回调prov_complete_cb()。&lt;br /&gt;
&lt;br /&gt;
这个回调的返回方式比较特殊，和上方其他的都不同，是由SD（softdevice）直接从底层返回给我们的。注册这个回调的函数是NRF_SDH_STATE_OBSERVER，返回的回调函数是sd_state_evt_handler。其中触发的事件NRF_SDH_EVT_STATE_ENABLED，这个是在mian文件中的initialize()函数下的ble_stack_init()函数中就被触发的，这个大家可以自行查看一下，不太重要，因为都是nordic底层处理好的消息。&lt;br /&gt;
[[文件:Prov-13.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Prov-12.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
以上整个配置流程的处理，不太好分步展示相应的现象，这个大家可以自己使用SES在线仿真的方式去调试一下流程，这里我们直接给大家展示整个流程打印出的相应的信息，以及nRF Mesh中暂时出来的现象。&lt;br /&gt;
[[文件:Prov-16.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Prov-17.png|边框|居中|无框]]&lt;br /&gt;
&lt;br /&gt;
== ④设备信息分配 ==&lt;br /&gt;
当我们成功的配置节点设备入网之后，我们需要给节点设备主要分配如下几个信息，有关这几个要点的介绍，可以查看“BLE-Mesh技术揭秘”第3章节：&lt;br /&gt;
&lt;br /&gt;
1、节点在网络中的地址Node Address&lt;br /&gt;
&lt;br /&gt;
2、网络密钥Netkey&lt;br /&gt;
&lt;br /&gt;
3、应用密钥Appkey&lt;br /&gt;
&lt;br /&gt;
4、元素的地址Element Address&lt;br /&gt;
&lt;br /&gt;
5、绑定model的Appkey，分配Publish Address，Subscription Address&lt;br /&gt;
&lt;br /&gt;
=== Node Address ===&lt;br /&gt;
首先我们来看一下我们使用nRF Mesh成功配置我们的节点之后，打印出来的消息，开始的紫色字体显示了我们给当前节点设备分配的Node Address。&lt;br /&gt;
&lt;br /&gt;
后面紧接着还有四段黄色字体的内容（这个部分大家的工程是不会打印的，是我们修改例程打印出来给大家展示一下），这四个部分就是我们配置节点的时候，配置者设备（nRF Mesh）给节点设备的四个主要指令，这些指令的具体功能，大家可以查看mesh的协议手册。[[文件:Config-13.png|边框|居中|无框|1080x1080像素]]在代码中，我们会在mian文件的provisioning_complete_cb()回调函数中获取并打印我们的Node Address，这个回调触发的含义就是我们的节点配置已经完成了。&lt;br /&gt;
&lt;br /&gt;
具体这个回调在代码中的回调及触发流程，大家看可以查看“③入网验证”的说明。[[文件:Config-20.png|边框|居中|无框|1200x1200像素]]我们使用nRF Mesh配置好节点设备之后，app界面显示内容如下，包含了节点的信息（地址、名称、配置时间等等），元素和模型，以及Netkey和Appkey等几个重要的设备信息。[[文件:Config-01.jpg|边框|居中|无框]]&lt;br /&gt;
[[文件:Config-04.jpg|边框|居中|无框]]&lt;br /&gt;
&lt;br /&gt;
=== Netkey ===&lt;br /&gt;
我们点击查看一下Network key，可以看到里面有一个Network Key 1，这是一个16字节的网络密钥。我们可以在nRF Mesh的Settings界面去增加或者删除一个network key，用户可以自定义此密钥。[[文件:Config-02.jpg|边框|居中|无框]]在我们的代码中，我们是在pro_evt_handler回调的NRF_MESH_PROV_EVT_COMPLETE事件中获取这个netkey，这个回调函数的触发流程同样是在“③入网验证”已经有说明。[[文件:Prov-11.png|边框|居中|无框|1200x1200像素]]我们直接看下NRF_MESH_PROV_EVT_COMPLETE事件下的mesh_stack_provisioning_data_store()函数，在这个函数中我们主要的任务，就是将配置者分配给我们的node address、netkey、devkey等等信息，分别做一些存储以及功能绑定。&lt;br /&gt;
[[文件:Netkey-01.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
=== Appkey ===&lt;br /&gt;
同样的我们打开Application Key，可以看到其中包含的3个密钥，这里我们只选用了Application Key 1作为我们的密钥。大家同样可以在nRF Mesh的Settings界面去增加或者删除一个App key，用户可以自定义此密钥。[[文件:Config-03.jpg|边框|居中|无框]]appkey这里和netkey不同，netkey是我们网络中的节点设备以及我们配置者设备共有的一个密钥，只有拥有这个密钥，我们才能在当前网络中通信，所以当我们的节点设备配置入网后，会立刻得到netkey（必须要分配给我的节点设备才行）。&lt;br /&gt;
&lt;br /&gt;
而appkey只是我们的某个模型用于通信的密钥，所以我们称它为应用密钥，这个appkey的分配，是在我们的config server下。这个config_server_init的初始化流程，是在我们的mesh_stack_init()函数下，就去初始化配置好的。最终当我们有信息配置的时候，会触发opcode_handlers，我们可以从这个操作码列表中查找相应的信息处理。[[文件:Config-22.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Config-23.png|边框|居中|无框|1200x1200像素]]我们来看下config server的opcode_handlers[]操作码处理程序列表，使用黄色标记给大家圈了几个操作码指令，这几个是我们配置一个最简单的server节点设备，都需要使用到的，包含了：&lt;br /&gt;
&lt;br /&gt;
1、appkey的添加，CONFIG_OPCODE_APPKEY_ADD&lt;br /&gt;
&lt;br /&gt;
2、publish发布地址，CONFIG_OPCODE_MODEL_PUBLICATION_VIRTUAL_ADDRESS_SET&lt;br /&gt;
&lt;br /&gt;
3、subscription订阅地址，CONFIG_OPCODE_MODEL_SUBSCRIPTION_ADD&lt;br /&gt;
&lt;br /&gt;
4、模型appkey的绑定，CONFIG_OPCODE_MODEL_APP_BIND&lt;br /&gt;
&lt;br /&gt;
5、netkey的更新，CONFIG_OPCODE_NETKEY_ADD[[文件:Config-21.png|边框|居中|无框|1200x1200像素]]我们可以看到当接收到CONFIG_OPCODE_APPKEY_ADD操作码的数据，会调用handle_appkey_add()函数去处理，在这个函数中我们最终会调用dsm_appkey_add()函数去存储我们的appkey。[[文件:Appkey-01.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
== ⑤Element元素 ==&lt;br /&gt;
元素简单的说就是定义了节点的功能，一些具有复杂功能的节点设备，会包含多个元素。一个节点设备最少需要包含一个元素，我们称之为主元素。&lt;br /&gt;
&lt;br /&gt;
从主元素开始（如果有多个元素，则第一个元素就是主元素），我们会给它分配相应的元素地址。主元素的地址就是节点的地址，第二个元素的地址就是主元素的地址基础上加上1，依次类推。&lt;br /&gt;
&lt;br /&gt;
可以看到在我们的主元素Element：0x0002中包含了3个不同的服务，这3个服务就是我们的模型Model，其中Configuration Server与Health Server这两个服务，是每一个节点都必须携带的模型。Configuration Server（配置服务）是用于配置节点设备信息，Health Server（健康服务）是用于节点进行网络内的心跳联系。剩下的Generic On Off Server服务，这个就是我们用于控制一个开关量的模型（在例程中我们通过控制LED的IO口高低电平来展示）。[[文件:Config-05.jpg|边框|居中|无框]]在我们的代码中，我们首先需要在nrf_mesh_config_app.h中定义好我们节点设备的最大元素数量，可以看到我们定义#define ACCESS_ELEMENT_COUNT (1)，也就是说我们当前的例程，只含有一个主元素。&lt;br /&gt;
[[文件:Element-01.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
而这个ACCESS_ELEMENT_COUNT ，是在我们开启节点配置的地方被调用的，这个mesh_provsionionee_prov_start()函数在前面的内容中有多次讲到，这里就不再说明了。&lt;br /&gt;
[[文件:Element-02.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
== ⑥model模型 ==&lt;br /&gt;
如果说元素是一个功能集合，那么模型就是元素下的具体的某一个功能。&lt;br /&gt;
&lt;br /&gt;
我们以灯的控制为例，灯的常用控制有开关、亮度、色温几种，其中开关、亮度和色温就是具体的模块，而处理灯控的集合就是元素。&lt;br /&gt;
&lt;br /&gt;
=== Configure Server ===&lt;br /&gt;
配置服务模型，这个的大概代码处理流程在我们的“④设备信息分配-&amp;gt;Appkey”已经有了说明，它是我们节点设备必须包含的一个特殊功能的模型。&lt;br /&gt;
&lt;br /&gt;
它的实际功能就像它的名字一样，就是用于配置（configuration）我们的设备信息，比如appkey的添加删除，密钥的绑定删除，其他的模型的发布和订阅控制。具体包含的操作码的使用方法，我们会在generic on off server模型当中使用的时候来说明。&lt;br /&gt;
&lt;br /&gt;
=== Health Server ===&lt;br /&gt;
健康服务，顾名思义就是判断我们的设备是否在网络中保持健康（正常）工作，它同样是一个必须包含的特殊功能的模型。&lt;br /&gt;
&lt;br /&gt;
它的初始化和config server是一样的，都是放在了mesh_stack_init()函数下。&lt;br /&gt;
[[文件:Health-01.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
我们来看下health_server_init()函数，最终我们会调用access_model_add()函数去初始化模型，并将模型添加到对应的元素中。&lt;br /&gt;
&lt;br /&gt;
其中的access_model_add_params_t参数，包含的分别是元素的索引（将模型添加到第几个元素当中），模型的ID（由mesh协议手册确定好了），以及最最重要的操作码列表，这个列表中会包含我们的指令码，以及对应的函数功能处理。&lt;br /&gt;
[[文件:Health-02.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
最后我们看下具体包含了哪些健康模型操作码：&lt;br /&gt;
&lt;br /&gt;
1、“运行状况当前状态”消息的操作码，HEALTH_OPCODE_CURRENT_STATUS&lt;br /&gt;
&lt;br /&gt;
2、“ 健康故障状态”消息的操作码，HEALTH_OPCODE_FAULT_STATUS&lt;br /&gt;
&lt;br /&gt;
3、“  获取健康注意事项”消息的操作码，HEALTH_OPCODE_ATTENTION_GET&lt;br /&gt;
&lt;br /&gt;
4、“ 健康注意集”消息的操作码，HEALTH_OPCODE_ATTENTION_SET&lt;br /&gt;
&lt;br /&gt;
5、“未确认健康注意事项”消息的操作码，HEALTH_OPCODE_ATTENTION_SET_UNACKED&lt;br /&gt;
&lt;br /&gt;
6、“健康注意状态”消息的操作码，HEALTH_OPCODE_ATTENTION_STATUS&lt;br /&gt;
&lt;br /&gt;
7、“健康故障清除”消息的操作码，HEALTH_OPCODE_FAULT_CLEAR&lt;br /&gt;
&lt;br /&gt;
8、“健康故障清除未确认”消息的操作码，HEALTH_OPCODE_FAULT_CLEAR_UNACKED&lt;br /&gt;
&lt;br /&gt;
9、“健康故障获取”消息的操作码，HEALTH_OPCODE_FAULT_GET&lt;br /&gt;
&lt;br /&gt;
10、“健康故障测试”消息的操作码，HEALTH_OPCODE_FAULT_TEST&lt;br /&gt;
&lt;br /&gt;
11、“未确认健康故障测试”消息的操作码，HEALTH_OPCODE_FAULT_TEST_UNACKED&lt;br /&gt;
&lt;br /&gt;
12、“健康期获取”消息的操作码，HEALTH_OPCODE_PERIOD_GET&lt;br /&gt;
&lt;br /&gt;
13、“健康期设置”消息的操作码，HEALTH_OPCODE_PERIOD_SET&lt;br /&gt;
&lt;br /&gt;
14、“未确认健康期设置”消息的操作码，HEALTH_OPCODE_PERIOD_SET_UNACKED&lt;br /&gt;
&lt;br /&gt;
15、“健康期状态”消息的操作码，HEALTH_OPCODE_PERIOD_STATUS&lt;br /&gt;
[[文件:Health-03.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
在大家的前期使用中，一般也不会真的需要大家去处理health server下的指令，毕竟我们刚开始使用，也很难让设备在网络中出现各种运行问题（节点少功能少）。但是当大家自己开始开发实际功能时，就一定要注意分析health server下的指令码返回，用来快速解决我们网络中的问题。&lt;br /&gt;
&lt;br /&gt;
=== Generic On Off Server ===&lt;br /&gt;
我们来看下Generic On Off Server的[[文件:Config-06.jpg|边框|居中|无框]]&lt;br /&gt;
[[文件:Config-12.jpg|边框|居中|无框]]&lt;br /&gt;
&lt;br /&gt;
==== model bind appkey ====&lt;br /&gt;
[[文件:Config-14.png|边框|居中|无框|1040x1040像素]][[文件:Config-07.jpg|边框|居中|无框]]&lt;br /&gt;
[[文件:Config-08.jpg|边框|居中|无框]]&lt;br /&gt;
&lt;br /&gt;
==== model publish address ====&lt;br /&gt;
[[文件:Config-09.jpg|边框|居中|无框]]&lt;br /&gt;
[[文件:Config-10.jpg|边框|居中|无框]]&lt;br /&gt;
&lt;br /&gt;
==== model subscription address ====&lt;br /&gt;
[[文件:Config-15.png|边框|居中|无框|1040x1040像素]]&lt;br /&gt;
[[文件:Config-11.jpg|边框|居中|无框]]&lt;br /&gt;
[[分类:NRF52832DK]]&lt;br /&gt;
[[分类:实验手册]]&lt;/div&gt;</summary>
		<author><name>Jinx</name></author>
		
	</entry>
	<entry>
		<id>http://doc.iotxx.com/index.php?title=NRF52832DK-Mesh%E7%BB%84%E7%BD%91%E5%AE%9E%E9%AA%8C&amp;diff=2791</id>
		<title>NRF52832DK-Mesh组网实验</title>
		<link rel="alternate" type="text/html" href="http://doc.iotxx.com/index.php?title=NRF52832DK-Mesh%E7%BB%84%E7%BD%91%E5%AE%9E%E9%AA%8C&amp;diff=2791"/>
		<updated>2020-03-31T08:41:42Z</updated>

		<summary type="html">&lt;p&gt;Jinx：/* Generic On Off Server */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;蓝牙Mesh和BLE是并行发展的一个独立分支，虽然底层共用Physical Layer和Link Layer，但是上层协议并不相同。因此，nRF52832的Mesh例程，并不在BLE协议栈sdk中，而是安装一个扩展包。&lt;br /&gt;
&lt;br /&gt;
本文介绍蓝牙Mesh相关的环境搭建和实验介绍。&lt;br /&gt;
&lt;br /&gt;
== Mesh开发环境 ==&lt;br /&gt;
&lt;br /&gt;
=== 蓝牙Mesh SDK ===&lt;br /&gt;
SDK位置：&amp;lt;code&amp;gt;归档资料/2-协议栈SDK/nRF5_SDK_Mesh_v310.zip&amp;lt;/code&amp;gt;，注意文件名中的Mesh字样。&lt;br /&gt;
&lt;br /&gt;
和BLE协议栈一样，将该压缩包直接解压到BLE协议栈同级目录即可使用，解压后的SDK路径如下：&amp;lt;code&amp;gt;E:\project-nordic\nRF5_SDK_Mesh_v310&amp;lt;/code&amp;gt;&lt;br /&gt;
[[文件:NRF52832DK-MESH-SDK-INSTALL.gif|居中|无框|750x750像素]]&lt;br /&gt;
&lt;br /&gt;
=== 集成开发软件SES ===&lt;br /&gt;
SES时是Segger Embedded Studio编译器的缩写，用来编译蓝牙Mesh代码，SES功能与IAR或Keil类似。&lt;br /&gt;
&lt;br /&gt;
备注：Nordic为何使用SES而不是IAR，原因不得而知。&lt;br /&gt;
&lt;br /&gt;
==== SES安装 ====&lt;br /&gt;
SES软件位置：&amp;lt;code&amp;gt;归档资料/7-编译器SES/Setup_EmbeddedStudio_ARM_v410_win_x64.exe&amp;lt;/code&amp;gt;。&lt;br /&gt;
&lt;br /&gt;
我们双击打开安装界面，按照如下动图所示方式进行安装（可以全部默认安装）。[[文件:Ses install.gif|边框|居中|无框|556x556像素]]&lt;br /&gt;
&lt;br /&gt;
==== SES许可证激活 ====&lt;br /&gt;
我们双击打开安装好的SES，选择编译自带测试例程，此时弹出需要激活的界面。&lt;br /&gt;
[[文件:Licensed1.jpg|边框|居中|无框|1205x1205像素]]&lt;br /&gt;
由于我们准备使用SES编译nordic的NRF52832的SDK，所以这里我们选择Activate Your Free License。这里因为nordic公司已经帮客户想SEGGER公司买下了使用的版权，所以是免费的许可证。&lt;br /&gt;
[[文件:Licensed2.jpg|边框|居中|无框|736x736像素]]&lt;br /&gt;
我们按照要求填写好信息，主要是邮箱地址（用于接收SEGGER公司发给我们的许可证），填写完成之后点击Request License。下面是我们接收的邮件信息，我们将红色框内的内容复制下来。&lt;br /&gt;
[[文件:Ses install3.png|边框|居中|无框|1301x1301像素]]&lt;br /&gt;
打开SES，选择Tools\License Manager...，我们选择Activate Embedded Studio，打开如下界面，并将我们的许可证粘贴进去，然后点击Install License。&lt;br /&gt;
[[文件:Licensed4.png|边框|居中|无框|481x481像素]]&lt;br /&gt;
安装好许可证之后，跳转到如下的界面。&lt;br /&gt;
[[文件:Licensed5.png|边框|居中|无框|481x481像素]]&lt;br /&gt;
此时我们的许可证激活完毕（有时候需要等待一段时间，才会显示激活成功）。&lt;br /&gt;
[[文件:Licensed6.png|边框|居中|无框|515x515像素]]&lt;br /&gt;
&lt;br /&gt;
== Light_switch ==&lt;br /&gt;
&lt;br /&gt;
=== 实验简介 ===&lt;br /&gt;
此示例演示了包含充当两个角色的设备的网状生态系统：配置角色（Provisioner role）和节点角色（Node role、provisionee role）。 它还通过在应用程序中使用[Generic OnOff model]来演示了如何使用Mesh模型。&lt;br /&gt;
&lt;br /&gt;
该示例由三个较小的示例组成：&lt;br /&gt;
&lt;br /&gt;
Light switch server：一个实现了[Generic OnOff server model]的简约服务器，用于接收状态数据并控制板上LED 1的状态。&lt;br /&gt;
&lt;br /&gt;
Light switch client：一个实现了[Generic OnOff client model]的简约客户端。当用户按下任意按钮时，OnOff Set消息将发送到配置的目标地址。&lt;br /&gt;
&lt;br /&gt;
Mesh Provisioner：一个简单的静态配置设备应用，用于建立演示网络，该配置设备在一个网状网络中配置所有节点。 此外，配置设备还可以在这些节点上配置网格模型[Generic OnOff model]实例的绑定以及发布和订阅设置，以使它们能够相互通信。&lt;br /&gt;
&lt;br /&gt;
[Generic OnOff Client/Server]模型用于操纵打开/关闭状态。请注意，当服务器设置了发布地址（如本例所示）时，服务器会将其状态更改的任何操作披露到其发布地址。&lt;br /&gt;
&lt;br /&gt;
下图给出了将由静态供应商设置的网状网络的整体视图， 括号中的数字表示预配置程序分配给这些节点的地址。&lt;br /&gt;
[[文件:Mesh network.png|边框|居中|无框|727x727像素]]&lt;br /&gt;
灯开关服务器和灯开关客户端示例均具有预配方角色。它们支持通过广告承载（PB-ADV）和GATT承载（PB-GATT）进行配置，并且还支持网状代理服务器（Proxy Service），但是不支持代理客户端（Proxy Client）。&lt;br /&gt;
&lt;br /&gt;
=== 硬件说明 ===&lt;br /&gt;
完成这个实验，我们最少需要两个开发板硬件用做我们的节点设备（Node）：&lt;br /&gt;
* 一个开发板用做client&lt;br /&gt;
* 一个或者多个开发板用做server&lt;br /&gt;
此外，我们还需要以下之一作为我们的配置设备（Provisioner）：&lt;br /&gt;
* 如果您决定使用静态预配器示例，则多准备一个开发板&lt;br /&gt;
* 如果您决定使用app程序进行设置，则需要安装手机app@link_nrf_mesh_app（@link_nrf_mesh_app_ios或@link_nrf_mesh_app_android）。&lt;br /&gt;
{{Note|text=默认我们使用3个开发板完成我们的实验，3个开发板分别对应client、server以及provisioner三个类型。|type=warning}}&lt;br /&gt;
&lt;br /&gt;
=== 软件说明 ===&lt;br /&gt;
1、我们使用NrfGo上位机，依次对三个开发板进行擦除及softdevice的烧写&lt;br /&gt;
[[文件:Lightswitch nrfgo.png|边框|居中|无框|1013x1013像素]]&lt;br /&gt;
2、使用SEGGER Embedded Studio for ARM编译器分别打开我们`&amp;lt;InstallFolder&amp;gt;/examples/light_switch`路径下的3个实验，并完成编译&lt;br /&gt;
&lt;br /&gt;
●\client\light_switch_client_nrf52832_xxAA_s132_6_1_0.emProject&lt;br /&gt;
&lt;br /&gt;
●\server\light_switch_server_nrf52832_xxAA_s132_6_1_0.emProject&lt;br /&gt;
&lt;br /&gt;
●\provisioner\light_switch_provisioner_nrf52832_xxAA_s132_6_1_0.emProject&lt;br /&gt;
[[文件:Lightswitch SES build.png|边框|居中|无框|1013x1013像素]]&lt;br /&gt;
3、此时我们对3个开发板分别进行固件的烧写，分别烧写client、server、provisioner（建议大家给3个开发板贴上标签，方便后续区分）&lt;br /&gt;
[[文件:Lightswitch SES download.png.gif|边框|居中|无框|1009x1009像素]]&lt;br /&gt;
&lt;br /&gt;
=== 实验现象 ===&lt;br /&gt;
{{Note|text=注意：保证我们的3个设备都是刚按照软件说明部分的步骤配置完成，并且没有进行过任何硬件控制（按键操作）。目的是保证所有设备均未被我们人为配置过，没有进行过网络配置，否则可能有任意异常（不同网络配置，导致的不同现象）|type=danger}}&lt;br /&gt;
1、首先我们分别给client和provisioner供电，然后我们按下provisioner的按键S1：&lt;br /&gt;
&lt;br /&gt;
●provisoner：LED灯点亮，代表正在配置查找设备，配置组网&lt;br /&gt;
&lt;br /&gt;
●client：首先LED3和LED4闪烁，代表正在组网；LED1-LED4四个灯一起闪烁，代表组网完成&lt;br /&gt;
&lt;br /&gt;
我们如果打开RTT检测log打印，可以看到如下的信息，这个时候我们已经给client设备分配了Node Address为0x0100。provisioner信息（上），client信息（下）。&lt;br /&gt;
[[文件:Lightswitch rtt provisoner1.png|边框|无框|1080x1080像素|居中]]&lt;br /&gt;
[[文件:Lightswitch_rtt_node_client.png|边框|居中|无框|656x656像素]]&lt;br /&gt;
&lt;br /&gt;
2、使用provisioner配置好client之后，我们给server开发板上电，这个时候provisioner将会配置server入网：&lt;br /&gt;
&lt;br /&gt;
●provisoner：LED1点亮代表正在配置网络，LED2点亮代表网络配置完成&lt;br /&gt;
&lt;br /&gt;
●server：首先LED3和LED4闪烁，代表正在组网；LED1-LED4四个灯一起闪烁，代表组网完成&lt;br /&gt;
&lt;br /&gt;
我们打开RTT检测log打印，可以看到server已经配置入网，并且被分配了地址Node Address为0x0104。provisioner信息（上），server信息（下）。&lt;br /&gt;
[[文件:Lightswitch rtt provisoner2.png|边框|无框|710x710像素|居中]]&lt;br /&gt;
[[文件:Lightswitch rtt node server.png|边框|无框|656x656像素|居中]]&lt;br /&gt;
&lt;br /&gt;
3、此时整个网络的配置已经完成，这个时候我们可以按下client上的按键来控制server上的LED点亮或者熄灭。&lt;br /&gt;
&lt;br /&gt;
由于我们的server被provisioner分配的Node Address是0x0104（偶数），所以我们通过client的S3控制server的LED1点亮，通过client的S4控制server的LED1熄灭。&lt;br /&gt;
{{Note|text=client设备对于server设备的控制，通过Node Address的奇偶位不同，分成了两个组。&lt;br /&gt;
client的S1和S2分别控制Node Address为奇数的server设备的LED1点亮和熄灭。&lt;br /&gt;
&lt;br /&gt;
client的S3和S4分别控制Node Address为偶数的server设备的LED1点亮和熄灭。|type=warning}}&lt;br /&gt;
我们打开RTT检测log打印，可以看到client分别按下button2（S3）以及button3（S4），分别会设置server的GPIO输出高低电平。client信息（上），server信息（下）。&lt;br /&gt;
[[文件:Lightswitch rtt node client2.png|边框|无框|656x656像素|居中]]&lt;br /&gt;
[[文件:Lightswitch rtt node server2.png|边框|无框|656x656像素|居中]]&lt;br /&gt;
&lt;br /&gt;
4、至此我们的mesh组网的lightswitch实验测试完成。&lt;br /&gt;
&lt;br /&gt;
=== 源码详解 ===&lt;br /&gt;
&lt;br /&gt;
== Beaconing example ==&lt;br /&gt;
&lt;br /&gt;
=== 实验简介 ===&lt;br /&gt;
&lt;br /&gt;
=== 硬件说明 ===&lt;br /&gt;
&lt;br /&gt;
=== 软件说明 ===&lt;br /&gt;
&lt;br /&gt;
=== 实验现象 ===&lt;br /&gt;
&lt;br /&gt;
=== 源码详解 ===&lt;br /&gt;
&lt;br /&gt;
== Coexistence example ==&lt;br /&gt;
&lt;br /&gt;
=== 实验简介 ===&lt;br /&gt;
&lt;br /&gt;
=== 硬件说明 ===&lt;br /&gt;
&lt;br /&gt;
=== 软件说明 ===&lt;br /&gt;
&lt;br /&gt;
==== 将Mesh集成到nRF5 SDK示例中 ====&lt;br /&gt;
用于网格的nRF5 SDK与Nordic的nRF5 SDK兼容。 这使您可以将nRF5 SDK中的资源包括在现有的Mesh项目中，也可以将nRF5 SDK中的Mesh功能包括在nRF5 SDK示例中。&lt;br /&gt;
&lt;br /&gt;
请参阅构建网格堆栈和示例，以获取有关如何下载和安装nRF5 SDK的信息。 查看共存示例，以了解如何将nRF5 SDK功能与用于网格的nRF5 SDK同时使用。&lt;br /&gt;
&lt;br /&gt;
===== 动态记忆 =====&lt;br /&gt;
在将nRF5 SDK功能与nRF5 Mesh SDK功能一起使用时（如并存示例所示），您可能会遇到以下情况：应用程序注意事项可能需要更改动态内存的分配方式和可分配的动态内存量。 网格堆栈使用网格内存管理器界面进行动态内存分配。 默认的后端mesh_mem_stdlib.c使用标准库malloc（），该库需要定义足够大的堆大小。 可以通过用另一个内存管理器替换后端来更改此行为。&lt;br /&gt;
&lt;br /&gt;
如果使用Segger Embedded Studio构建应用程序，请在“项目选项”&amp;gt;“代码”&amp;gt;“运行时内存区域”设置中将“堆大小”设置为8192字节。&lt;br /&gt;
&lt;br /&gt;
===== 并发的SoftDevice和Mesh活动 =====&lt;br /&gt;
同时运行SoftDevice和Mesh时，最大的性能问题通常来自无线电时间争用。虽然SoftDevice通常在预定的短脉冲中运行，但Mesh会尝试尽可能多地使用无线电。只要SoftDevice没有无线电活动，Mesh就会连续扫描和通告。 SoftDevice活动将减少Mesh广播的时间，并且为了保持一致的Mesh性能，必须在不影响用户体验的情况下尽可能保守地设置SoftDevice无线电参数。&lt;br /&gt;
&lt;br /&gt;
在使用SoftDevice进行广告时，请尝试使用您的使用情况可以容许的最大广告间隔。如果可能，请在不需要时关闭SoftDevice广告商，仅在您希望收到连接请求时才激活它。如果您只需要发送不可连接的，不可扫描的广告（例如，针对第三方信标协议），请使用Mesh Advertiser API，因为它与Mesh一起使用时可以进行最少的上下文切换。&lt;br /&gt;
&lt;br /&gt;
当SoftDevice在连接中运行时：&lt;br /&gt;
&lt;br /&gt;
尝试协商应用程序可以允许的最大连接间隔。如果需要通过连接的高吞吐量，则在每个连接事件中发送更多的数据要比减少连接间隔更好，因为大部分开销来自上下文切换。&lt;br /&gt;
&lt;br /&gt;
如果网状设备在其SoftDevice连接中充当外围设备（从设备），则还可以增加“从设备等待时间”，这应使SoftDevice跳过连接事件，而不会增加通过链接的任何传出数据传输的等待时间。&lt;br /&gt;
&lt;br /&gt;
与广告一样，建议仅在需要时才保持连接活动。空闲的软设备连接对网状网性能的影响几乎与具有大量流量的连接一样多，特别是在从属延迟较低的情况下。&lt;br /&gt;
&lt;br /&gt;
基于SoftDevice的扫描对所有SoftDevice活动的Mesh性能影响最大。当SoftDevice正在扫描时，Mesh无法接收数据包，因此每个SoftDevice扫描窗口都会替换Mesh扫描。仅在尝试建立连接或需要活动扫描时才应使用SoftDevice扫描。如果需要常规的被动BLE扫描（用于侦听信标或其他第三方活动），请通过使用nrf_mesh_rx_cb_set函数设置RX回调来挂接到Mesh扫描器。如果您的应用程序需要主动扫描或需要启动连接，则应尽可能保守地设置扫描参数。较长的扫描间隔和较短的扫描窗口将使Mesh拥有更多的时间进行自身的无线电活动。类似地，建立上下文时在短时间内执行连续扫描可能比执行长时间运行的占空比扫描有利，因为上下文切换会导致很多不必要的开销。最后，强烈建议为SoftDevice连接启动调用设置超时，以避免长时间的空闲扫描。&lt;br /&gt;
&lt;br /&gt;
由于Mesh无法主动阻止SoftDevice无线电活动，因此减少同时运行SoftDevice活动的设备上的Mesh活动不会直接影响SoftDevice的性能。但是，附近的其他Mesh设备会干扰广告渠道中的SoftDevice活动，这可能会使连接启动花费更长的时间。&lt;br /&gt;
&lt;br /&gt;
通常，Mesh必须在SoftDevice活动之间执行其所有无线电操作，因此，如果设备在执行SoftDevice无线电操作时正在发送大量Mesh数据包，则它将在此上花费大部分Mesh时间，而不是接收传入的数据。为了解决这个问题，请根据可用的无线电时间调整输出数据包的数量，以减少Mesh数据包的发送。如果可能，请通过在CORE_TX_ROLE_RELAY角色上调用mesh_opt_core_adv_set来暂停耗时的SoftDevice操作期间的Mesh数据包中继。&lt;br /&gt;
&lt;br /&gt;
===== 在用于网格示例的nRF5 SDK中包括nRF5 SDK =====&lt;br /&gt;
根据您的工具链：&lt;br /&gt;
&lt;br /&gt;
使用Segger Embedded Studio时，请添加代码文件并包括相应SES项目文件的路径。&lt;br /&gt;
&lt;br /&gt;
使用CMake构建用于网格堆栈的nRF5 SDK时，添加代码文件并包括指向相应CMakeLists.txt文件的路径。 SDK_ROOT根符号用于引用nRF5 SDK安装文件夹（例如，请参阅Light Switch服务器示例中的CMakeLists.txt）。&lt;br /&gt;
&lt;br /&gt;
网格示例项目已经在其include /目录中包含sdk_config.h文件。 这些文件是默认SDK配置文件的副本，并且网格示例所需的所有更改都包含在示例目录中的include / app_config.h文件中。&lt;br /&gt;
&lt;br /&gt;
注意，&lt;br /&gt;
&lt;br /&gt;
必须先在SDK配置文件中显式启用某些SDK功能，然后才能使用它们。 有关详细信息，请参见SDK文档页面SDK配置头文件。&lt;br /&gt;
&lt;br /&gt;
===== 在nRF5 SDK示例中包括用于网格功能的nRF5 SDK =====&lt;br /&gt;
在nRF5 SDK示例的项目文件中包括来自nRF5 SDK for Mesh的以下源文件：&lt;br /&gt;
* All C files in &amp;lt;code&amp;gt;mesh/core/src&amp;lt;/code&amp;gt;&lt;br /&gt;
* All C files in &amp;lt;code&amp;gt;mesh/bearer/src&amp;lt;/code&amp;gt;&lt;br /&gt;
* All C files in &amp;lt;code&amp;gt;mesh/prov/src&amp;lt;/code&amp;gt; except nrf_mesh_prov_bearer_gatt.c&lt;br /&gt;
* All C files in &amp;lt;code&amp;gt;mesh/access/src&amp;lt;/code&amp;gt;&lt;br /&gt;
* All C files in &amp;lt;code&amp;gt;mesh/dfu/src&amp;lt;/code&amp;gt;&lt;br /&gt;
* All C files in &amp;lt;code&amp;gt;mesh/stack/src&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;models/foundation/config/src/config_server.c&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;models/foundation/config/src/composition_data.c&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;models/foundation/config/src/packed_index_list.c&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;models/foundation/health/src/health_server.c&amp;lt;/code&amp;gt;&lt;br /&gt;
* Any other mesh models that are used in your application&lt;br /&gt;
* &amp;lt;code&amp;gt;external/micro-ecc/uECC.c&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;examples/common/src/assertion_handler_weak.c&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;examples/common/src/mesh_provisionee.c&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
注意&lt;br /&gt;
&lt;br /&gt;
如果不需要各种网格特征（例如DFU），则可以从项目文件中省略相应的文件。但是，在其位置添加examples / nrf_mesh_weak.c以提供缺少的API函数的存根。&lt;br /&gt;
&lt;br /&gt;
将以下文件夹添加到nRF5 SDK示例的项目包含路径：&lt;br /&gt;
* &amp;lt;code&amp;gt;mesh/core/api&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;mesh/core/include&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;mesh/bearer/api&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;mesh/bearer/include&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;mesh/prov/api&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;mesh/prov/include&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;mesh/access/api&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;mesh/access/include&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;mesh/dfu/api&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;mesh/dfu/include&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;mesh/stack/api&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;models/foundation/config/include&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;models/foundation/health/include&amp;lt;/code&amp;gt;&lt;br /&gt;
* Path to include folder of any other mesh models that are used in your application&lt;br /&gt;
* &amp;lt;code&amp;gt;external/micro-ecc&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;examples/common/include&amp;lt;/code&amp;gt;&lt;br /&gt;
* Path to any other resources in the mesh examples that are used in your application&lt;br /&gt;
将以下预处理器符号添加到nRF5 SDK示例的项目文件中：&lt;br /&gt;
* &amp;lt;code&amp;gt;NRF52_SERIES&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;NRF_MESH_LOG_ENABLE=NRF_LOG_USES_RTT&amp;lt;/code&amp;gt; (because logging in the mesh stack relies on RTT)&lt;br /&gt;
* &amp;lt;code&amp;gt;CONFIG_APP_IN_CORE&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== 可选变更 =====&lt;br /&gt;
此外，您可能需要应用以下一项或多项更改：&lt;br /&gt;
&lt;br /&gt;
如果与nRF5 SDK集成在一起，则可能需要更新在网格堆栈中使用simple_hal模块的示例以使用Nordic nRF5 SDK bsp模块。可以同时使用两者，但是在这种情况下，必须从其中之一删除GPIOTE_IRQHandler，并且只有一个模块可以注册回调函数。&lt;br /&gt;
&lt;br /&gt;
如果原始的Nordic nRF5 SDK示例使用了SoftDevice，请确保在启用SoftDevice之后初始化并启用了网格堆栈。在这种情况下，必须将SoftDevice事件转发到网格堆栈。将以下代码添加到您的应用程序：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;#&amp;lt;/nowiki&amp;gt;include“ nrf_sdh_soc.h”&lt;br /&gt;
&lt;br /&gt;
＃定义MESH_SOC_OBSERVER_PRIO 0&lt;br /&gt;
&lt;br /&gt;
静态void mesh_soc_evt_handler（uint32_t evt_id，void * p_context）&lt;br /&gt;
&lt;br /&gt;
{&lt;br /&gt;
&lt;br /&gt;
    nrf_mesh_on_sd_evt（evt_id）;&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
NRF_SDH_SOC_OBSERVER（m_mesh_soc_observer，MESH_SOC_OBSERVER_PRIO，mesh_soc_evt_handler，NULL）;&lt;br /&gt;
&lt;br /&gt;
如果您有多个SOC观察者，请确保一次仅从其中一个观察者将SOC观察者事件转发到网格堆栈。&lt;br /&gt;
&lt;br /&gt;
默认情况下，在网格堆栈以及某些Nordic nRF5 SDK应用程序中启用网络配置的闪存存储。用于此目的的闪光区域可能会重叠并导致错误。为了使闪存存储模块Flash管理器与网状堆栈中的闪存管理器以及Nordic nRF5 SDK中的闪存存储模块安全地共存，请在nrf_mesh_config_app.h中添加以下代码块：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;#&amp;lt;/nowiki&amp;gt;include“ fds.h”&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;#&amp;lt;/nowiki&amp;gt;include“ fds_internal_defs.h”&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;#&amp;lt;/nowiki&amp;gt;define FLASH_MANAGER_RECOVERY_PAGE_OFFSET_PAGES FDS_PHY_PAGES&lt;br /&gt;
&lt;br /&gt;
如果要添加自己的网格功能，而不是使用现有的网格示例，则还需要添加文件nrf_mesh_config_app.h。将其从网格堆栈存储库中的examples / templates文件夹复制到您的项目文件夹中，并删除文件顶部的#error消息。对文件内容进行其他适当的更改，例如将ACCESS_ELEMENT_COUNT和ACCESS_MODEL_COUNT调整为所需数量的元素和模型。&lt;br /&gt;
&lt;br /&gt;
===== Flash放置项目文件 =====&lt;br /&gt;
Segger Embedded Studio项目旁边都有一个flash_placement.xml文件，该文件充当链接器的输入。在flash_placement.xml文件中，nRF5 SDK配置了一组ProgramSection列表，用于放置某些变量。除了nRF5 SDK组件所需的所有ProgramSections外，网格还需要另外两个部分，即nrf_mesh_flash和nrf_mesh_ram。&lt;br /&gt;
&lt;br /&gt;
必须将其他与网格相关的部分添加到flash_placement.xml文件中：&lt;br /&gt;
&lt;br /&gt;
将以下行添加到标记为&amp;lt;MemorySegment name =“ FLASH” ...&amp;gt;的内存段中：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ProgramSection alignment =“ 4” keep =“是” load =“是” name =“。nrf_mesh_flash”输入节=“ *（SORT（.nrf_mesh_flash。*））”“ address_symbol =” __ start_nrf_mesh_flash“ end_symbol =” __ stop_nrf_mesh_flash“ /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
将以下行添加到标记为&amp;lt;MemorySegment name =“ RAM” ...&amp;gt;的内存段中：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ProgramSection alignment =“ 4” keep =“是” load =“否” name =“。nrf_mesh_ram” inputsections =“ *（SORT（.nrf_mesh_ram。*））”“ address_symbol =” __ start_nrf_mesh_ram“ end_symbol =” __ stop_nrf_mesh_ram“ /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
注意&lt;br /&gt;
&lt;br /&gt;
对于共存示例已经进行了此更改。在编辑任何现有nRF5 SDK示例的Flash放置文件时，可以在这些示例中使用flash_placement.xml文件作为参考。&lt;br /&gt;
&lt;br /&gt;
=== 实验现象 ===&lt;br /&gt;
&lt;br /&gt;
=== 源码详解 ===&lt;br /&gt;
&lt;br /&gt;
== Coexistence Uart example ==&lt;br /&gt;
&lt;br /&gt;
=== 实验简介 ===&lt;br /&gt;
&lt;br /&gt;
=== 硬件说明 ===&lt;br /&gt;
&lt;br /&gt;
=== 软件说明 ===&lt;br /&gt;
&lt;br /&gt;
==== SDK UART共存示例 ====&lt;br /&gt;
本示例演示了如何同时使用适用于Mesh的nRF5 SDK和nRF5 SDK示例。它围绕两个示例构建，分为两个部分：&lt;br /&gt;
&lt;br /&gt;
此示例的网格部分从nRF5 SDK for Mesh实现了灯开关客户端示例。&lt;br /&gt;
&lt;br /&gt;
该示例的BLE部分实现了nRF5 SDK的ble_app_uart示例，但以下更改除外：&lt;br /&gt;
* BSP事件SLEEP，DISCONNECT和WHITELIST_OFF被忽略。这使得板子按钮仅控制应用程序的网格部分&lt;br /&gt;
* 增加广告间隔以为网格堆栈留出更多时间&lt;br /&gt;
* 可以通过发送要模拟的灯光开关客户端按钮编号，通过BLE UART来控制灯光开关服务器&lt;br /&gt;
运行此示例的结果是，您将能够使用网状网络，该示例中的网状网络可以代替电灯开关客户端示例。&lt;br /&gt;
&lt;br /&gt;
在开始测试此共存示例之前，请参阅以下页面：&lt;br /&gt;
&lt;br /&gt;
将Mesh集成到nRF5 SDK示例中&lt;br /&gt;
&lt;br /&gt;
灯光开关示例和灯光开关客户端详细信息以及Mesh API&lt;br /&gt;
&lt;br /&gt;
===== 测试示例 =====&lt;br /&gt;
将ble_app_uart_coexist文件夹复制到nRF5 SDK安装路径下的examples / ble_peripheral文件夹中。&lt;br /&gt;
&lt;br /&gt;
在ble_app_uart_coexist / pca10040 / s132 / ses / ble_app_uart_pca10040_s132.emProject中打开Segger Embedded Studio项目。&lt;br /&gt;
&lt;br /&gt;
将MESH_ROOT添加到您的Segger Embedded Studio全局宏列表：&lt;br /&gt;
&lt;br /&gt;
在SES菜单栏中，单击工具&amp;gt;选项...。&lt;br /&gt;
&lt;br /&gt;
在左列中，单击建筑物。&lt;br /&gt;
&lt;br /&gt;
在右列中，双击“全局宏”。&lt;br /&gt;
&lt;br /&gt;
在新行中添加网格根目录：MESH_ROOT = &amp;lt;网格安装路径&amp;gt;。&lt;br /&gt;
&lt;br /&gt;
如灯光开关示例中所述对灯光开关示例设备进行编程，但以下情况除外：&lt;br /&gt;
&lt;br /&gt;
用examples / ble_peripheral / ble_app_uart_coexist / pca10040 / s132 / ses / Output / Release / Exe / ble_app_uart_pca10040_s132.hex替换电灯开关客户端。&lt;br /&gt;
&lt;br /&gt;
现在，您可以并行或顺序运行两个并存的示例：&lt;br /&gt;
&lt;br /&gt;
按照电灯开关示例中的说明运行电灯开关示例。&lt;br /&gt;
&lt;br /&gt;
按照nRF5 SDK文档中的描述运行ble_app_uart示例。&lt;br /&gt;
&lt;br /&gt;
编写与UART RX特性中的数字之一（1、2、3或4）等效的十六进制ASCII码，以模拟按钮按下。&lt;br /&gt;
&lt;br /&gt;
=== 实验现象 ===&lt;br /&gt;
&lt;br /&gt;
=== 源码详解 ===&lt;br /&gt;
&lt;br /&gt;
== mesh工程文件简介 ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
!分组&lt;br /&gt;
!文件&lt;br /&gt;
!说明&lt;br /&gt;
|-&lt;br /&gt;
!Access&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; |'''访问层：'''&lt;br /&gt;
'''访问层定义高层应用程序如何使用上层传输层。 它定义了应用程序数据的格式； 它定义并控制在上层传输层执行的应用程序数据加密和解密； 并检查是否已在正确的网络和应用程序密钥的上下文中接收到传入的应用程序数据，然后再将其转发到更高层。'''&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
|access&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
|access_loopback&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
|access_publish&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
|access_publish_retransmissior&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
|access_reliable&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
|device_state_manager&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
!Application&lt;br /&gt;
! colspan=&amp;quot;2&amp;quot; |&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
|app_error_weak&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
|app_onoff&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
|assertion_handler_weak&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
|ble_softdevice_support&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
|main&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
|mesh_adv&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
|mesh_app_utils&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
|mesh_provisionee&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
|nrf_mesh_weak&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
|rtt_input&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
|sdk_config&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
|simple_hal&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== ***************************************正式整理章节************************************** ==&lt;br /&gt;
&lt;br /&gt;
== mesh实验学习流程概论 ==&lt;br /&gt;
1、首先大家一定要先查看一下我们资料中的手册“BLE-Mesh技术揭秘”，这个是很重要的一个mesh基本概念的介绍，只有看完这个文档，我们才能明白mesh组网是什么，是怎么完成的，后续的我们实验内容，也会在这个文档中查到相应的对照。&lt;br /&gt;
&lt;br /&gt;
2、按照Light_switch测试实验的实验说明，跑通测试流程（可以固件清零重新烧写多测试几遍，有助于增强我们的流程熟练度，利于后面的开发）。light_switch实验是现在nordic提供给我们的唯一的真正意义上的mesh组网通信例程。&lt;br /&gt;
&lt;br /&gt;
3、根据我们下面拆分的组网流程，对照依次的实验说明，进行mesh组网流程的分步学习。前期实验部分我们主要讲解节点设备，配置者设备功能我们使用手机app完成，目的是减少大家的学习难度，配置者设备我们会放在最后一个实验给大家做介绍。&lt;br /&gt;
&lt;br /&gt;
①广播（通信TX）——了解节点设备的广播功能，分为三个重要部分：PB-ADV和Unprov Beacon，PB-GATT的广播，以及模型的Publish用于发送数据。&lt;br /&gt;
&lt;br /&gt;
②扫描（通信RX）——了解节点设备的扫描功能，分为两个重要部分：普通的BLE SCAN功能，以及模块的Subscription用于接收数据。&lt;br /&gt;
&lt;br /&gt;
③入网验证——了解节点设备接入网络的验证过程，实验中使用的是PB-GATT去配置的，包含了：交换公共密钥，完成验证（是否有带外OOB）。&lt;br /&gt;
&lt;br /&gt;
④设备信息分配——启动配置数据分配，给节点分配如下数据：网络key（Netkey）、应用key（Appkey）以及节点设备地址（nodeAddr）。&lt;br /&gt;
&lt;br /&gt;
⑤元素——了解节点设备的元素与模型定义。&lt;br /&gt;
&lt;br /&gt;
⑥模型——了解模型的功能和创建方法，模型的订阅subscription和发布publish功能。&lt;br /&gt;
&lt;br /&gt;
== Light_switch测试实验 ==&lt;br /&gt;
=== 实验简介 ===&lt;br /&gt;
此示例演示了包含充当两个角色的设备的网状生态系统：配置角色（Provisioner role）和节点角色（Node role、provisionee role）。 它还通过在应用程序中使用[Generic OnOff model]来演示了如何使用Mesh模型。&lt;br /&gt;
&lt;br /&gt;
该示例由三个较小的示例组成：&lt;br /&gt;
&lt;br /&gt;
Light switch server：一个实现了[Generic OnOff server model]的简约服务器，用于接收状态数据并控制板上LED 1的状态。&lt;br /&gt;
&lt;br /&gt;
Light switch client：一个实现了[Generic OnOff client model]的简约客户端。当用户按下任意按钮时，OnOff Set消息将发送到配置的目标地址。&lt;br /&gt;
&lt;br /&gt;
Mesh Provisioner：一个简单的静态配置设备应用，用于建立演示网络，该配置设备在一个网状网络中配置所有节点。 此外，配置设备还可以在这些节点上配置网格模型[Generic OnOff model]实例的绑定以及发布和订阅设置，以使它们能够相互通信。&lt;br /&gt;
&lt;br /&gt;
[Generic OnOff Client/Server]模型用于操纵打开/关闭状态。请注意，当服务器设置了发布地址（如本例所示）时，服务器会将其状态更改的任何操作披露到其发布地址。&lt;br /&gt;
&lt;br /&gt;
下图给出了将由静态供应商设置的网状网络的整体视图， 括号中的数字表示预配置程序分配给这些节点的地址。&lt;br /&gt;
[[文件:Mesh network.png|边框|居中|无框|727x727像素]]&lt;br /&gt;
灯开关服务器和灯开关客户端示例均具有预配方角色。它们支持通过广告承载（PB-ADV）和GATT承载（PB-GATT）进行配置，并且还支持网状代理服务器（Proxy Service），但是不支持代理客户端（Proxy Client）。&lt;br /&gt;
&lt;br /&gt;
=== 硬件说明 ===&lt;br /&gt;
完成这个实验，我们最少需要两个开发板硬件用做我们的节点设备（Node）：&lt;br /&gt;
* 一个开发板用做client&lt;br /&gt;
* 一个或者多个开发板用做server&lt;br /&gt;
此外，我们还需要以下之一作为我们的配置设备（Provisioner）：&lt;br /&gt;
* 如果您决定使用静态预配器示例，则多准备一个开发板&lt;br /&gt;
* 如果您决定使用app程序进行设置，则需要安装手机app@link_nrf_mesh_app（@link_nrf_mesh_app_ios或@link_nrf_mesh_app_android）。&lt;br /&gt;
{{Note|text=默认我们使用3个开发板完成我们的实验，3个开发板分别对应client、server以及provisioner三个类型。|type=warning}}&lt;br /&gt;
&lt;br /&gt;
=== 软件说明 ===&lt;br /&gt;
1、我们使用NrfGo上位机，依次对三个开发板进行擦除及softdevice的烧写&lt;br /&gt;
[[文件:Lightswitch nrfgo.png|边框|居中|无框|1013x1013像素]]&lt;br /&gt;
2、使用SEGGER Embedded Studio for ARM编译器分别打开我们`&amp;lt;InstallFolder&amp;gt;/examples/light_switch`路径下的3个实验，并完成编译&lt;br /&gt;
&lt;br /&gt;
●\client\light_switch_client_nrf52832_xxAA_s132_6_1_0.emProject&lt;br /&gt;
&lt;br /&gt;
●\server\light_switch_server_nrf52832_xxAA_s132_6_1_0.emProject&lt;br /&gt;
&lt;br /&gt;
●\provisioner\light_switch_provisioner_nrf52832_xxAA_s132_6_1_0.emProject&lt;br /&gt;
[[文件:Lightswitch SES build.png|边框|居中|无框|1013x1013像素]]&lt;br /&gt;
3、此时我们对3个开发板分别进行固件的烧写，分别烧写client、server、provisioner（建议大家给3个开发板贴上标签，方便后续区分）&lt;br /&gt;
[[文件:Lightswitch SES download.png.gif|边框|居中|无框|1009x1009像素]]&lt;br /&gt;
&lt;br /&gt;
=== 实验现象 ===&lt;br /&gt;
{{Note|text=注意：保证我们的3个设备都是刚按照软件说明部分的步骤配置完成，并且没有进行过任何硬件控制（按键操作）。目的是保证所有设备均未被我们人为配置过，没有进行过网络配置，否则可能有任意异常（不同网络配置，导致的不同现象）|type=danger}}&lt;br /&gt;
1、首先我们分别给client和provisioner供电，然后我们按下provisioner的按键S1：&lt;br /&gt;
&lt;br /&gt;
●provisoner：LED灯点亮，代表正在配置查找设备，配置组网&lt;br /&gt;
&lt;br /&gt;
●client：首先LED3和LED4闪烁，代表正在组网；LED1-LED4四个灯一起闪烁，代表组网完成&lt;br /&gt;
&lt;br /&gt;
我们如果打开RTT检测log打印，可以看到如下的信息，这个时候我们已经给client设备分配了Node Address为0x0100。provisioner信息（上），client信息（下）。&lt;br /&gt;
[[文件:Lightswitch rtt provisoner1.png|边框|无框|1080x1080像素|居中]]&lt;br /&gt;
[[文件:Lightswitch_rtt_node_client.png|边框|居中|无框|656x656像素]]&lt;br /&gt;
&lt;br /&gt;
2、使用provisioner配置好client之后，我们给server开发板上电，这个时候provisioner将会配置server入网：&lt;br /&gt;
&lt;br /&gt;
●provisoner：LED1点亮代表正在配置网络，LED2点亮代表网络配置完成&lt;br /&gt;
&lt;br /&gt;
●server：首先LED3和LED4闪烁，代表正在组网；LED1-LED4四个灯一起闪烁，代表组网完成&lt;br /&gt;
&lt;br /&gt;
我们打开RTT检测log打印，可以看到server已经配置入网，并且被分配了地址Node Address为0x0104。provisioner信息（上），server信息（下）。&lt;br /&gt;
[[文件:Lightswitch rtt provisoner2.png|边框|无框|710x710像素|居中]]&lt;br /&gt;
[[文件:Lightswitch rtt node server.png|边框|无框|656x656像素|居中]]&lt;br /&gt;
&lt;br /&gt;
3、此时整个网络的配置已经完成，这个时候我们可以按下client上的按键来控制server上的LED点亮或者熄灭。&lt;br /&gt;
&lt;br /&gt;
由于我们的server被provisioner分配的Node Address是0x0104（偶数），所以我们通过client的S3控制server的LED1点亮，通过client的S4控制server的LED1熄灭。&lt;br /&gt;
{{Note|text=client设备对于server设备的控制，通过Node Address的奇偶位不同，分成了两个组。&lt;br /&gt;
client的S1和S2分别控制Node Address为奇数的server设备的LED1点亮和熄灭。&lt;br /&gt;
&lt;br /&gt;
client的S3和S4分别控制Node Address为偶数的server设备的LED1点亮和熄灭。|type=warning}}&lt;br /&gt;
我们打开RTT检测log打印，可以看到client分别按下button2（S3）以及button3（S4），分别会设置server的GPIO输出高低电平。client信息（上），server信息（下）。&lt;br /&gt;
[[文件:Lightswitch rtt node client2.png|边框|无框|656x656像素|居中]]&lt;br /&gt;
[[文件:Lightswitch rtt node server2.png|边框|无框|656x656像素|居中]]&lt;br /&gt;
&lt;br /&gt;
4、至此我们的mesh组网的lightswitch实验测试完成。&lt;br /&gt;
&lt;br /&gt;
== ①广播（通信TX） ==&lt;br /&gt;
有关广播部分，我们需要给大家讲解三个方面，分别如下：&lt;br /&gt;
&lt;br /&gt;
1、PB-ADV，这个是mesh中的广播承载层，用于我们启动配置过程（暂时的例程和手机app都是用的PB-GATT来配置的，但是源码中PB-ADV的流程是包含的）。&lt;br /&gt;
&lt;br /&gt;
2、Unprov Beacon，这个是广播一个未被配置的beacon，就是一个特殊形式的广播数据包，用于声明自己是未经配置的设备。&lt;br /&gt;
&lt;br /&gt;
3、PB-GATT，这个是mesh中的GATT承载层，用于我们启动配置过程（当前手机app就是使用PB-GATT，也就是连接之后，通过GATT服务去发送配置信息）。&lt;br /&gt;
&lt;br /&gt;
4、Publish，这个是我们用于模型发布数据的部分，我们将需要发送的数据通过advData广播出去。&lt;br /&gt;
&lt;br /&gt;
=== PB-ADV ===&lt;br /&gt;
PB-ADV说明请查看“BLE-Mesh技术揭秘”第8.1章节。&lt;br /&gt;
&lt;br /&gt;
我们来看下PB-ADV整个的流程，首先在mian文件中，我们的start()函数下，我们调用了mesh_provisionee_prov_start()函数去启动节点设备的等待配置。&lt;br /&gt;
[[文件:PB-ADV-01.png|边框|居中|无框|1200x1200px]]我们继续追踪mesh_provisionee_prov_start()，可以看到在使能宏定义MESH_FEATURE_PB_ADV_ENABLED的情况下，可以追踪到nrf_mesh_prov_bearer_adv_interface_get()函数。&lt;br /&gt;
[[文件:PB-ADV-02.png|边框|居中|无框|1200x1200px]]在nrf_mesh_prov_bearer_adv_interface_get()函数中，我们可以看到prov_bearer_interface_t结构体，这个结构体下面提供了我们配置承载层需要的API函数。&lt;br /&gt;
[[文件:PB-ADV-03.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
=== Unprov Beacon ===&lt;br /&gt;
Unprov Beacon说明请查看“BLE-Mesh技术揭秘”第5.5及5.5.1章节。&lt;br /&gt;
&lt;br /&gt;
上面我们有说到PB-ADV是承载层用于配置节点的功能，而Unprov Beacon则是我们的节点设备用于声明自己未经配置，所以这两个其实是一脉相承的。&lt;br /&gt;
&lt;br /&gt;
紧接着上面的nrf_mesh_prov_bearer_adv_interface_get()函数，其中的prov_bearer_interface_t结构体包含的是处理承载层的API函数，在这个结构体当中有一个.listen_start = prov_bearer_adv_listen，是用于开始监听传入的配置链接的功能，在这个函数中我们可以看到send_unprov_beacon()函数。&lt;br /&gt;
[[文件:PB-ADV-04.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
send_unprov_beacon()函数是用于发送Unprov Beacon数据的函数，在这个函数中我们调用prov_beacon_unprov_build函数去创建用于广播的Unprov Beacon数据包，然后调用advertiser_interval_set函数去设置广播的间隔，最后调用advertiser_packet_send函数去将我们的数据包发送出去。&lt;br /&gt;
&lt;br /&gt;
默认的Unprov Beacon广播间隔是NRF_MESH_PROV_BEARER_ADV_UNPROV_BEACON_INTERVAL_MS 2000。&lt;br /&gt;
&lt;br /&gt;
最终调用的是advertiser_packet_send(0函数去将广播数据发送出去的。&lt;br /&gt;
[[文件:PB-ADV-05.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Publish-12.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
这里我们再最后看一下prov_beacon_unprov_build()函数，这个函数是生成我们的Unprov Beacon的数据包。&lt;br /&gt;
&lt;br /&gt;
其中标识位是0x2B，这个flag是beacon的意思。data的第一个字节0x00代表是我们的BEACON_TYPE_UNPROV（未配置的节点设备），接下来的16个字节是device uuid（这个是寄存器FICR中的device id生成的），接下来2字节是oob info（这里是00 00，代表没有使能OOB），最后的4个字节是URI通过AES-CMAC算法计算出来的值。&lt;br /&gt;
[[文件:PB-GATT-11.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:PB-GATT-12.png|边框|居中|无框]]&lt;br /&gt;
&lt;br /&gt;
=== PB-GATT ===&lt;br /&gt;
PB-GATT说明请查看“BLE-Mesh技术揭秘”第8.1章节。&lt;br /&gt;
&lt;br /&gt;
和PB-ADV一样的，我们从main文件中的start()函数，追踪到mesh_provisionee_prov_start()，可以看到在使能宏定义MESH_FEATURE_PB_GATT_ENABLED的情况下，可以追踪到nrf_mesh_prov_bearer_gatt_init()以及nrf_mesh_prov_bearer_gatt_interface_get()函数。&lt;br /&gt;
[[文件:PB-GATT-01.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
首先我们看一下nrf_mesh_prov_bearer_gatt_init()函数，这个是初始化并设置PB-GATT服务（通过Mesh GATT接口）的函数。对比我们使用nrf master control panel手机APP连接之后获取到的服务，可以看到其中的PB-GATT的服务以及特征值。&lt;br /&gt;
&lt;br /&gt;
再往下的mesh_gatt_init()函数，这个就不给大家分析了，是和我们普通的BLE一样的方式去注册一个服务。&lt;br /&gt;
[[文件:PB-GATT-04.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:PB-GATT-03.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:PB-GATT-02.png|边框|居中|无框]]&lt;br /&gt;
在nrf_mesh_prov_bearer_gatt_interface_get()函数中，我们可以看到prov_bearer_interface_t结构体，这个结构体下面提供了我们配置承载层需要的API函数（这个是和上方的PB-ADV是一样的）。&lt;br /&gt;
[[文件:PB-GATT-05.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
和Unprov Beacon类似的，当我们开始监听配置链接的时候，会触发.listen_start = listen_start_cb的回调，在这个回调中我们触发FSM（有限状态机）的事件E_LISTEN_START，然后调用link_evt_send()去post顺序承载事件。&lt;br /&gt;
[[文件:PB-GATT-06.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
然后我们来到状态机列表m_pb_gatt_fsm_transition_table[]，可以看到E_LISTEN_START事件ID代表的动作ID是A_LISTEN_START。而最终我们的A_LISTEN_START动作，执行的是a_listen_start函数功能（这其中涉及的代码逻辑，暂时先不说明，大家可以自己查看源码的引用）。&lt;br /&gt;
[[文件:PB-GATT-07.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:PB-GATT-08.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
然后我们继续追踪a_listen_start()，可以看到其中调用了mesh_adv_params_set函数设置了我们广播的超时时间MESH_ADV_TIMEOUT_INFINITE以及广播间隔NRF_MESH_PROV_BEARER_GATT_UNPROV_BEACON_INTERVAL_MS，调用mesh_adv_data_set函数设置了我们的广播数据内容，最后调用mesh_adv_start函数启动广播。&lt;br /&gt;
&lt;br /&gt;
默认的PB-GATT的广播数据的广播周期是NRF_MESH_PROV_BEARER_GATT_UNPROV_BEACON_INTERVAL_MS 200。&lt;br /&gt;
[[文件:PB-GATT-09.png|边框|居中|无框|1200x1200像素]]我们同样的看一下mesh_adv_data_set()函数最终配置生成的广播数据，可以看到广播数据的flag是0x06，服务的UUID就是我们上面提到的PB-GATT服务的UUID，接下来16字节UUID是和PB-ADV一样的FICR寄存器的device id值，最后的扫描回调数据携带的是设备的全名“nRF5x Mesh Light”。&lt;br /&gt;
[[文件:PB-GATT-13.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:PB-GATT-14.png|边框|居中|无框]]最后是我们的mesh_adv_start()函数去启动广播，可以看到调用是我们ble stack sotfdevice当中的API函数sd_ble_gap_adv_start()去启动的。&lt;br /&gt;
[[文件:PB-GATT-15.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
=== Publish（模型发布） ===&lt;br /&gt;
这一章节我们只给大家讲解模块publish的数据发布流程，暂时不说明模型的创建方法，以及如何使用，这个放在下面单独的模型章节进行说明。&lt;br /&gt;
&lt;br /&gt;
以generic_onoff_server模型为例，我们找到access_model_publish以及access_model_reply函数，这两个函数的功能类似的，都是用于我们用户的模型发送数据的。&lt;br /&gt;
&lt;br /&gt;
其中access_model_publish是用于模型主动发送，而access_model_reply是用于模型给对应的Opcode回应数据。&lt;br /&gt;
&lt;br /&gt;
这里我们以access_model_publish函数为主，来看下发布数据的代码流程。&lt;br /&gt;
[[文件:Publish-01.png|边框|居中|无框|1200x1200像素]]查看一下access_model_publish()函数，我们可以看到packet_alloc_and_tx()函数，这个是我们继续发送数据的函数。下面还有一个蓝牙方框中的内容，是我们定义的重传机制（mesh数据为了保证传输的成功率，默认都是会重传数据包的，默认重传次数是4次）。[[文件:Publish-02.png|边框|居中|无框|1200x1200像素]]然后我们继续看一下packet_alloc_and_tx()函数，在这个函数中，我们最终会调用packet_tx()函数将数据发送出去。[[文件:Publish-03.png|边框|居中|无框|1200x1200像素]]继续追踪packet_tx()函数，这个函数的内容比较多，我们只截取了其中数据发送的部分，也就是我们的nrf_mesh_packet_send()函数，我们在他的tx_params中赋值好我们需要发送出去的数据内容。[[文件:Publish-04.png|边框|居中|无框|1199x1199像素]]nrf_mesh_packet_send()函数最终调用的是transport_tx()函数发送数据。而transport_tx()函数，最终是调用upper_transport_tx()函数进行的数据发送。[[文件:Publish-05.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Publish-06.png|边框|居中|无框|1200x1200像素]]我们再继续追踪upper_transport_tx()函数，可以看到我们会根据p_metadata-&amp;gt;segmented来判断数据是否要分包发送。&lt;br /&gt;
[[文件:Publish-13.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
接下里我们以不分包发送函数unsegmented_packet_tx()为例，来继续说明数据传输流程。&lt;br /&gt;
&lt;br /&gt;
我们可以看到我们的unsegmented_packet_tx()函数中，最终是调用的network_packet_send()函数。（分包的segmented_packet_tx()函数最终也是调用的network_packet_send()，大家可以自己追踪代码看一下）&lt;br /&gt;
[[文件:Publish-14.png|边框|居中|无框|1200x1200像素]]继续追踪我们的network_packet_send()函数，可以看到我们的net_packet_encrypt()加密函数，这个是将我们的明文数据，做好加密工作。&lt;br /&gt;
&lt;br /&gt;
然后调用core_tx_packet_send()函数发送出去。[[文件:Publish-15.png|边框|居中|无框|1200x1200像素]]最后我们追踪到core_tx_packet_send()函数，可以看到调用的是p_bearer-&amp;gt;p_interface-&amp;gt;packet_send()函数去发送的。所以接下来我们要找到我们的packet_send()功能是在哪边实现的。[[文件:Publish-07.png|边框|居中|无框|1200x1200像素]]可以看到我们是在core_tx_bearer_add函数中去获得的p_interface这个功能。[[文件:Publish-08.png|边框|居中|无框|1200x1200像素]]我们追踪core_tx_bearer_add()函数的引用路径，可以看到是在core_tx_adv_init()当中。（除了此处，还有另一处地方也有引用，功能是类似的，大家可以自行了解）。&lt;br /&gt;
&lt;br /&gt;
我们看下参数m_interface，可以看到m_interface-&amp;gt;packet_send功能，就是对应我们在core_tx_packet_send()函数调用的p_bearer-&amp;gt;p_interface-&amp;gt;packet_send()。[[文件:Publish-09.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Publish-10.png|边框|居中|无框|1200x1200像素]]所以我们接下来来看下m_interface结构体当中的packet_send()函数实现的是什么功能，可以看到最终调用的是advertiser_packet_send()函数将数据发送出去。看到这边我们就很熟悉了，和上面的几个广播功能，最后调用的都是相同的广播包发送函数。[[文件:Publish-11.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Publish-12.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
== ②扫描（通信RX） ==&lt;br /&gt;
&lt;br /&gt;
=== 标准BLE SCAN ===&lt;br /&gt;
看过了我们的广播功能之后，我们来看下扫描的部分。在ble mesh中，我们的扫描功能scan，就是用于获取数据的，类似于数据通信的RX。&lt;br /&gt;
&lt;br /&gt;
我们从main文件中的initialize()函数开始，首先是我们mesh_init()函数，最终我们会调用mesh_stack_init()函数。&lt;br /&gt;
[[文件:SCAN-01.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
在mesh_satck_init函数中，我们可以看到调用了nrf_mesh_init函数去初始化core的参数。&lt;br /&gt;
[[文件:SCAN-02.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
这一段是nrf_mesh_init函数部分，由于这个初始化函数太长，所以我们只截取了scan相关的部分。&lt;br /&gt;
&lt;br /&gt;
首先是第一点scanner_init(scanner_packet_process_cb)函数，在这个函数中，我们回去初始化扫描的参数配置，并且会开启扫描功能。最终我们可以从scanner_packet_process_cb这个回调中得到我们扫描到的数据（获得所有的BLE广播数据）。&lt;br /&gt;
&lt;br /&gt;
然后是第二点ad_listener_subscribe(&amp;amp;m_nrf_mesh_listener)，这个是用来侦听订阅的消息的（从获得的所有BLE广播数据中，筛选出的订阅的消息）。&lt;br /&gt;
[[文件:SCAN-03.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
然后我们继续看一下scanner_init的扫描初始化函数，在这个函数中我们首先调用scanner_config_reset()函数去配置扫描的默认参数。其中包含了：&lt;br /&gt;
&lt;br /&gt;
1、扫描的通道SCANNER_CHANNELS_DEFAULT，也就是BLE的｛37，38，39｝三个channels都去扫描。&lt;br /&gt;
&lt;br /&gt;
2、规范定义的非连接状态访问地址BEARER_ACCESS_ADDR_DEFAULT。&lt;br /&gt;
&lt;br /&gt;
3、配置物理层协议是1Mbit模式。&lt;br /&gt;
&lt;br /&gt;
4、扫描的间隔时间BEARER_SCAN_INT_DEFAULT_MS，扫描的窗口时间BEARER_SCAN_WINDOW_DEFAULT_MS&lt;br /&gt;
&lt;br /&gt;
5、射频的发射功率RADIO_POWER_NRF_0DBM。&lt;br /&gt;
&lt;br /&gt;
6、BLE广播包最大长度RADIO_CONFIG_ADV_MAX_PAYLOAD_SIZE。&lt;br /&gt;
&lt;br /&gt;
最后我们会在nrf_mesh_enable()函数中调用scanner_enable()函数去启动扫描，这个大家可以自己查看一下代码，比较容易理解。&lt;br /&gt;
[[文件:SCAN-04.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:SCAN-05.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
最后我们来看一下scanner_packet_process_cb()回调函数，其中我们可以直接拿到的扫描的数据包是rx_data，包含了数据的类型、数据内容以及数据长度等等信息。到这边我们的扫描功能是已经完成的，但是我们还需要再看下下面一部分的ad_listener的流程。&lt;br /&gt;
&lt;br /&gt;
=== Subscription（模型订阅） ===&lt;br /&gt;
我们需要注意下ad_listener_process()函数，这个函数是将我们扫描到的订阅数据（不符合标准的BLE广播数据，则认为是侦听到的订阅数据）传递到订阅者进行处理（订阅者指的是带订阅功能的模型）。这个是和我们上方说的关注点二ad_listener_subscribe()函数是一脉相承的。&lt;br /&gt;
[[文件:SCAN-06.png|边框|居中|无框|1200x1200像素]]然后我们来对照着看一下ad_listener_subscribe()函数以及ad_listener_process()函数。这两个函数一个是定义了侦听订阅，一个是将扫描获取到的数据传递给订阅者。这两者间的运作关系，大家有兴趣的可以继续查阅他的底层代码逻辑处理，一个是向list中添加订阅者信息，一个是将数据通过p_listener-&amp;gt;handler()将数据传递给订阅者处理。&lt;br /&gt;
[[文件:SCAN-07.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:SCAN-08.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
最终当我们扫描到订阅消息，并调用ad_listener_process()函数传递时，会触发我们注册的回调nrf_mesh_listen（调用ad_listener_subscribe(&amp;amp;m_nrf_mesh_listener)时，在m_nrf_mesh_listener中）。我们来看下nrf_mesh_listen函数中的处理，其中我们先从数据包中解析了数据的类型type，然后根据不同类型的数据进行相应的处理。&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+&lt;br /&gt;
|AD_TYPE_MESH &lt;br /&gt;
|蓝牙网格物体的AD类型&lt;br /&gt;
|-&lt;br /&gt;
|AD_TYPE_BEACON &lt;br /&gt;
|蓝牙网状信标的AD类型&lt;br /&gt;
|-&lt;br /&gt;
|AD_TYPE_PB_ADV &lt;br /&gt;
|PB-ADV消息的AD类型&lt;br /&gt;
|-&lt;br /&gt;
|AD_TYPE_DFU &lt;br /&gt;
|nRF OpenMesh消息的AD类型&lt;br /&gt;
|}&lt;br /&gt;
[[文件:SCAN-09.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
== ③入网验证 ==&lt;br /&gt;
经过前两章的学习，我们对于BLE MESH的广播及扫描的代码流程已经有了一定的了解，也就是已经大概的明白了数据的发送和接收的流程。&lt;br /&gt;
&lt;br /&gt;
那么从这一章节开始，我们将会给大家展示，如何配置一个节点设备入网。配置者设备，我们使用的是手机APP：nRF Mesh。&lt;br /&gt;
&lt;br /&gt;
我们从main文件中的start()函数开始，来看一下入网的代码处理及其流程。在start()函数中我们最后是调用的mesh_provisionee_prov_start()函数来启动的节点配置功能，我们来查看一下它所携带的参数prov_start_params。这部分的参数都是比较重要的，有携带的是设备的信息的、有携带认证密码的，有回调返回的，所以我们会挨个介绍一下。&lt;br /&gt;
[[文件:Prov-01.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
首先携带的是static_auth_data，这个是我们带外OOB认证的密码。如果我们在使用nRF Mesh去进行身份验证的时候，选择使用Static OOB，那么当我们在进行PB-GATT的连接过程中，会弹出一个新的窗口，要求我们去输入这个静态的128bit的OOB密钥。&lt;br /&gt;
[[文件:Prov-02.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Prov-14.png|边框|居中|无框]]&lt;br /&gt;
[[文件:Prov-15.png|边框|居中|无框]]&lt;br /&gt;
接下来携带的是provisioning_complete_cb()回调函数，这个函数处理的是节点设备成功的被配置入网，在这里我们可以获取到我们的节点设备被分配的node_Address。&lt;br /&gt;
[[文件:Prov-03.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
然后携带是device_identification_start_cb()回调函数，这个回调函数是用于通知开发者，我们的节点设备当前已经开始配置。&lt;br /&gt;
[[文件:Prov-04.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
再然后携带的是URI【统一资源标识符（Uniform Resource Identifier，URI)】，这个是用于标识我们的设备类型的，这个数据会通过AES-CMAC算法加密后添加到我们的Unprov Beacon数据的末尾。&lt;br /&gt;
&lt;br /&gt;
这里我们是light switch的server设备，所以我们定义URI是EX_URI_LS_SERVER。&lt;br /&gt;
[[文件:Prov-05.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
分析完携带的参数之后，我们继续来查看mesh_provisionee_prov_start()函数。在这个函数中，我们首先是定义了一下prov_caps，也就是配置支持的OOB功能。然后我们调用nrf_mesh_generate_keys()函数去生成有效的密钥对等待使用。&lt;br /&gt;
&lt;br /&gt;
再然后是调用nrf_mesh_prov_init()函数去初始化prov配置信息的结构体参数，里面有结构体本身m_pro_ctx，需要配置的参数公钥m_public、私钥m_private，以及我们刚刚定义的oob功能prov_caps，最后还携带了一个事件回调函数prov_ect_handler。&lt;br /&gt;
&lt;br /&gt;
接下来是PB-ADV以及PB-GATT的处理过程，这个前面已经讲解过，这里不再赘述。&lt;br /&gt;
&lt;br /&gt;
最后我们调用provisionee_start()函数去开启配置。&lt;br /&gt;
[[文件:Prov-06.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
我们继续来追踪一下provisionee_start()函数，在这个函数中我们调用nrf_mesh_prov_listen()函数去开启侦听承载层的消息。这里我们需要侦听PB-ADV以及PB-GATT两个。&lt;br /&gt;
[[文件:Prov-07.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
然后在nrf_mesh_prov_listen()函数中，我们调用prov_provisionee_listen()函数携带我们刚刚传入的nrf_mesh_prov_ctx_t结构体，以及p_bearer、URI、oob_info_source等参数信息，继续向下传递。&lt;br /&gt;
[[文件:Prov-08.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
最终我们来查看下prov_provisionee_listen()函数，在这个函数中我们传递了p_bearer-&amp;gt;p_callbacks给m_prov_callbacks回调，然后调用p_bearer-&amp;gt;p_interface-&amp;gt;listen_start()函数去启动PB-ADV以及PB-GATT的侦听。&lt;br /&gt;
[[文件:Prov-09.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
当我们在侦听之后，接收到任何配置相关的消息，都将通过m_prov_callbacks()函数回调传递给我们处理，大家可以自行看下其中4个不同的功能函数，具体处理的都是什么消息。&lt;br /&gt;
[[文件:Prov-10.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
在prov_provisionee_listen()函数中，我们可以看到回调函数指针m_prov_callbacks指向的是p_bearer-&amp;gt;p_callbacks，我们一路向上查找，最终可以找到这个p_bearer-&amp;gt;p_callbacks函数就是指的prov_evt_handler()函数。所以其实我们用户可以在这一层去查看比较重要的配置事件返回，而不需要到上面的m_prov_callbacks中这样偏底层的地方去查看。&lt;br /&gt;
&lt;br /&gt;
在这个回调函数中，我们可以看到mian文件中注册的prov_device_identification_start_cb、prov_device_identification_stop_cb、provisioning_aborted_cb的返回。&lt;br /&gt;
[[文件:Prov-11.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
那么除了上方已经存在的几个回调，我们可以看到mian文件中的start()函数下的prov_start_params参数，还缺少一个配置完成的回调prov_complete_cb()。&lt;br /&gt;
&lt;br /&gt;
这个回调的返回方式比较特殊，和上方其他的都不同，是由SD（softdevice）直接从底层返回给我们的。注册这个回调的函数是NRF_SDH_STATE_OBSERVER，返回的回调函数是sd_state_evt_handler。其中触发的事件NRF_SDH_EVT_STATE_ENABLED，这个是在mian文件中的initialize()函数下的ble_stack_init()函数中就被触发的，这个大家可以自行查看一下，不太重要，因为都是nordic底层处理好的消息。&lt;br /&gt;
[[文件:Prov-13.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Prov-12.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
以上整个配置流程的处理，不太好分步展示相应的现象，这个大家可以自己使用SES在线仿真的方式去调试一下流程，这里我们直接给大家展示整个流程打印出的相应的信息，以及nRF Mesh中暂时出来的现象。&lt;br /&gt;
[[文件:Prov-16.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Prov-17.png|边框|居中|无框]]&lt;br /&gt;
&lt;br /&gt;
== ④设备信息分配 ==&lt;br /&gt;
当我们成功的配置节点设备入网之后，我们需要给节点设备主要分配如下几个信息，有关这几个要点的介绍，可以查看“BLE-Mesh技术揭秘”第3章节：&lt;br /&gt;
&lt;br /&gt;
1、节点在网络中的地址Node Address&lt;br /&gt;
&lt;br /&gt;
2、网络密钥Netkey&lt;br /&gt;
&lt;br /&gt;
3、应用密钥Appkey&lt;br /&gt;
&lt;br /&gt;
4、元素的地址Element Address&lt;br /&gt;
&lt;br /&gt;
5、绑定model的Appkey，分配Publish Address，Subscription Address&lt;br /&gt;
&lt;br /&gt;
=== Node Address ===&lt;br /&gt;
首先我们来看一下我们使用nRF Mesh成功配置我们的节点之后，打印出来的消息，开始的紫色字体显示了我们给当前节点设备分配的Node Address。&lt;br /&gt;
&lt;br /&gt;
后面紧接着还有四段黄色字体的内容（这个部分大家的工程是不会打印的，是我们修改例程打印出来给大家展示一下），这四个部分就是我们配置节点的时候，配置者设备（nRF Mesh）给节点设备的四个主要指令，这些指令的具体功能，大家可以查看mesh的协议手册。[[文件:Config-13.png|边框|居中|无框|1080x1080像素]]在代码中，我们会在mian文件的provisioning_complete_cb()回调函数中获取并打印我们的Node Address，这个回调触发的含义就是我们的节点配置已经完成了。&lt;br /&gt;
&lt;br /&gt;
具体这个回调在代码中的回调及触发流程，大家看可以查看“③入网验证”的说明。[[文件:Config-20.png|边框|居中|无框|1200x1200像素]]我们使用nRF Mesh配置好节点设备之后，app界面显示内容如下，包含了节点的信息（地址、名称、配置时间等等），元素和模型，以及Netkey和Appkey等几个重要的设备信息。[[文件:Config-01.jpg|边框|居中|无框]]&lt;br /&gt;
[[文件:Config-04.jpg|边框|居中|无框]]&lt;br /&gt;
&lt;br /&gt;
=== Netkey ===&lt;br /&gt;
我们点击查看一下Network key，可以看到里面有一个Network Key 1，这是一个16字节的网络密钥。我们可以在nRF Mesh的Settings界面去增加或者删除一个network key，用户可以自定义此密钥。[[文件:Config-02.jpg|边框|居中|无框]]在我们的代码中，我们是在pro_evt_handler回调的NRF_MESH_PROV_EVT_COMPLETE事件中获取这个netkey，这个回调函数的触发流程同样是在“③入网验证”已经有说明。[[文件:Prov-11.png|边框|居中|无框|1200x1200像素]]我们直接看下NRF_MESH_PROV_EVT_COMPLETE事件下的mesh_stack_provisioning_data_store()函数，在这个函数中我们主要的任务，就是将配置者分配给我们的node address、netkey、devkey等等信息，分别做一些存储以及功能绑定。&lt;br /&gt;
[[文件:Netkey-01.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
=== Appkey ===&lt;br /&gt;
同样的我们打开Application Key，可以看到其中包含的3个密钥，这里我们只选用了Application Key 1作为我们的密钥。大家同样可以在nRF Mesh的Settings界面去增加或者删除一个App key，用户可以自定义此密钥。[[文件:Config-03.jpg|边框|居中|无框]]appkey这里和netkey不同，netkey是我们网络中的节点设备以及我们配置者设备共有的一个密钥，只有拥有这个密钥，我们才能在当前网络中通信，所以当我们的节点设备配置入网后，会立刻得到netkey（必须要分配给我的节点设备才行）。&lt;br /&gt;
&lt;br /&gt;
而appkey只是我们的某个模型用于通信的密钥，所以我们称它为应用密钥，这个appkey的分配，是在我们的config server下。这个config_server_init的初始化流程，是在我们的mesh_stack_init()函数下，就去初始化配置好的。最终当我们有信息配置的时候，会触发opcode_handlers，我们可以从这个操作码列表中查找相应的信息处理。[[文件:Config-22.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Config-23.png|边框|居中|无框|1200x1200像素]]我们来看下config server的opcode_handlers[]操作码处理程序列表，使用黄色标记给大家圈了几个操作码指令，这几个是我们配置一个最简单的server节点设备，都需要使用到的，包含了：&lt;br /&gt;
&lt;br /&gt;
1、appkey的添加，CONFIG_OPCODE_APPKEY_ADD&lt;br /&gt;
&lt;br /&gt;
2、publish发布地址，CONFIG_OPCODE_MODEL_PUBLICATION_VIRTUAL_ADDRESS_SET&lt;br /&gt;
&lt;br /&gt;
3、subscription订阅地址，CONFIG_OPCODE_MODEL_SUBSCRIPTION_ADD&lt;br /&gt;
&lt;br /&gt;
4、模型appkey的绑定，CONFIG_OPCODE_MODEL_APP_BIND&lt;br /&gt;
&lt;br /&gt;
5、netkey的更新，CONFIG_OPCODE_NETKEY_ADD[[文件:Config-21.png|边框|居中|无框|1200x1200像素]]我们可以看到当接收到CONFIG_OPCODE_APPKEY_ADD操作码的数据，会调用handle_appkey_add()函数去处理，在这个函数中我们最终会调用dsm_appkey_add()函数去存储我们的appkey。[[文件:Appkey-01.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
== ⑤Element元素 ==&lt;br /&gt;
元素简单的说就是定义了节点的功能，一些具有复杂功能的节点设备，会包含多个元素。一个节点设备最少需要包含一个元素，我们称之为主元素。&lt;br /&gt;
&lt;br /&gt;
从主元素开始（如果有多个元素，则第一个元素就是主元素），我们会给它分配相应的元素地址。主元素的地址就是节点的地址，第二个元素的地址就是主元素的地址基础上加上1，依次类推。&lt;br /&gt;
&lt;br /&gt;
可以看到在我们的主元素Element：0x0002中包含了3个不同的服务，这3个服务就是我们的模型Model，其中Configuration Server与Health Server这两个服务，是每一个节点都必须携带的模型。Configuration Server（配置服务）是用于配置节点设备信息，Health Server（健康服务）是用于节点进行网络内的心跳联系。剩下的Generic On Off Server服务，这个就是我们用于控制一个开关量的模型（在例程中我们通过控制LED的IO口高低电平来展示）。[[文件:Config-05.jpg|边框|居中|无框]]在我们的代码中，我们首先需要在nrf_mesh_config_app.h中定义好我们节点设备的最大元素数量，可以看到我们定义#define ACCESS_ELEMENT_COUNT (1)，也就是说我们当前的例程，只含有一个主元素。&lt;br /&gt;
[[文件:Element-01.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
而这个ACCESS_ELEMENT_COUNT ，是在我们开启节点配置的地方被调用的，这个mesh_provsionionee_prov_start()函数在前面的内容中有多次讲到，这里就不再说明了。&lt;br /&gt;
[[文件:Element-02.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
== ⑥model模型 ==&lt;br /&gt;
如果说元素是一个功能集合，那么模型就是元素下的具体的某一个功能。&lt;br /&gt;
&lt;br /&gt;
我们以灯的控制为例，灯的常用控制有开关、亮度、色温几种，其中开关、亮度和色温就是具体的模块，而处理灯控的集合就是元素。&lt;br /&gt;
&lt;br /&gt;
=== Configure Server ===&lt;br /&gt;
配置服务模型，这个的大概代码处理流程在我们的“④设备信息分配-&amp;gt;Appkey”已经有了说明，它是我们节点设备必须包含的一个特殊功能的模型。&lt;br /&gt;
&lt;br /&gt;
它的实际功能就像它的名字一样，就是用于配置（configuration）我们的设备信息，比如appkey的添加删除，密钥的绑定删除，其他的模型的发布和订阅控制。具体包含的操作码的使用方法，我们会在generic on off server模型当中使用的时候来说明。&lt;br /&gt;
&lt;br /&gt;
=== Health Server ===&lt;br /&gt;
健康服务，顾名思义就是判断我们的设备是否在网络中保持健康（正常）工作，它同样是一个必须包含的特殊功能的模型。&lt;br /&gt;
&lt;br /&gt;
它的初始化和config server是一样的，都是放在了mesh_stack_init()函数下。&lt;br /&gt;
[[文件:Health-01.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
我们来看下health_server_init()函数，最终我们会调用access_model_add()函数去初始化模型，并将模型添加到对应的元素中。&lt;br /&gt;
&lt;br /&gt;
其中的access_model_add_params_t参数，包含的分别是元素的索引（将模型添加到第几个元素当中），模型的ID（由mesh协议手册确定好了），以及最最重要的操作码列表，这个列表中会包含我们的指令码，以及对应的函数功能处理。&lt;br /&gt;
[[文件:Health-02.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
最后我们看下具体包含了哪些健康模型操作码：&lt;br /&gt;
&lt;br /&gt;
1、“运行状况当前状态”消息的操作码，HEALTH_OPCODE_CURRENT_STATUS&lt;br /&gt;
&lt;br /&gt;
2、“ 健康故障状态”消息的操作码，HEALTH_OPCODE_FAULT_STATUS&lt;br /&gt;
&lt;br /&gt;
3、“  获取健康注意事项”消息的操作码，HEALTH_OPCODE_ATTENTION_GET&lt;br /&gt;
&lt;br /&gt;
4、“ 健康注意集”消息的操作码，HEALTH_OPCODE_ATTENTION_SET&lt;br /&gt;
&lt;br /&gt;
5、“未确认健康注意事项”消息的操作码，HEALTH_OPCODE_ATTENTION_SET_UNACKED&lt;br /&gt;
&lt;br /&gt;
6、“健康注意状态”消息的操作码，HEALTH_OPCODE_ATTENTION_STATUS&lt;br /&gt;
&lt;br /&gt;
7、“健康故障清除”消息的操作码，HEALTH_OPCODE_FAULT_CLEAR&lt;br /&gt;
&lt;br /&gt;
8、“健康故障清除未确认”消息的操作码，HEALTH_OPCODE_FAULT_CLEAR_UNACKED&lt;br /&gt;
&lt;br /&gt;
9、“健康故障获取”消息的操作码，HEALTH_OPCODE_FAULT_GET&lt;br /&gt;
&lt;br /&gt;
10、“健康故障测试”消息的操作码，HEALTH_OPCODE_FAULT_TEST&lt;br /&gt;
&lt;br /&gt;
11、“未确认健康故障测试”消息的操作码，HEALTH_OPCODE_FAULT_TEST_UNACKED&lt;br /&gt;
&lt;br /&gt;
12、“健康期获取”消息的操作码，HEALTH_OPCODE_PERIOD_GET&lt;br /&gt;
&lt;br /&gt;
13、“健康期设置”消息的操作码，HEALTH_OPCODE_PERIOD_SET&lt;br /&gt;
&lt;br /&gt;
14、“未确认健康期设置”消息的操作码，HEALTH_OPCODE_PERIOD_SET_UNACKED&lt;br /&gt;
&lt;br /&gt;
15、“健康期状态”消息的操作码，HEALTH_OPCODE_PERIOD_STATUS&lt;br /&gt;
[[文件:Health-03.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
在大家的前期使用中，一般也不会真的需要大家去处理health server下的指令，毕竟我们刚开始使用，也很难让设备在网络中出现各种运行问题（节点少功能少）。但是当大家自己开始开发实际功能时，就一定要注意分析health server下的指令码返回，用来快速解决我们网络中的问题。&lt;br /&gt;
&lt;br /&gt;
=== Generic On Off Server ===&lt;br /&gt;
我们来看下Generic On Off Server的[[文件:Config-06.jpg|边框|居中|无框]]&lt;br /&gt;
[[文件:Config-12.jpg|边框|居中|无框]]&lt;br /&gt;
&lt;br /&gt;
==== model bind appkey ====&lt;br /&gt;
[[文件:Config-14.png|边框|居中|无框|1040x1040像素]][[文件:Config-07.jpg|边框|居中|无框]]&lt;br /&gt;
[[文件:Config-08.jpg|边框|居中|无框]]&lt;br /&gt;
&lt;br /&gt;
==== model publish address ====&lt;br /&gt;
[[文件:Config-09.jpg|边框|居中|无框]]&lt;br /&gt;
[[文件:Config-10.jpg|边框|居中|无框]]&lt;br /&gt;
&lt;br /&gt;
==== model subscription address ====&lt;br /&gt;
[[文件:Config-15.png|边框|居中|无框|1040x1040像素]]&lt;br /&gt;
[[文件:Config-11.jpg|边框|居中|无框]]&lt;br /&gt;
[[分类:NRF52832DK]]&lt;br /&gt;
[[分类:实验手册]]&lt;/div&gt;</summary>
		<author><name>Jinx</name></author>
		
	</entry>
	<entry>
		<id>http://doc.iotxx.com/index.php?title=NRF52832DK-Mesh%E7%BB%84%E7%BD%91%E5%AE%9E%E9%AA%8C&amp;diff=2790</id>
		<title>NRF52832DK-Mesh组网实验</title>
		<link rel="alternate" type="text/html" href="http://doc.iotxx.com/index.php?title=NRF52832DK-Mesh%E7%BB%84%E7%BD%91%E5%AE%9E%E9%AA%8C&amp;diff=2790"/>
		<updated>2020-03-31T07:26:39Z</updated>

		<summary type="html">&lt;p&gt;Jinx：/* Configure Server */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;蓝牙Mesh和BLE是并行发展的一个独立分支，虽然底层共用Physical Layer和Link Layer，但是上层协议并不相同。因此，nRF52832的Mesh例程，并不在BLE协议栈sdk中，而是安装一个扩展包。&lt;br /&gt;
&lt;br /&gt;
本文介绍蓝牙Mesh相关的环境搭建和实验介绍。&lt;br /&gt;
&lt;br /&gt;
== Mesh开发环境 ==&lt;br /&gt;
&lt;br /&gt;
=== 蓝牙Mesh SDK ===&lt;br /&gt;
SDK位置：&amp;lt;code&amp;gt;归档资料/2-协议栈SDK/nRF5_SDK_Mesh_v310.zip&amp;lt;/code&amp;gt;，注意文件名中的Mesh字样。&lt;br /&gt;
&lt;br /&gt;
和BLE协议栈一样，将该压缩包直接解压到BLE协议栈同级目录即可使用，解压后的SDK路径如下：&amp;lt;code&amp;gt;E:\project-nordic\nRF5_SDK_Mesh_v310&amp;lt;/code&amp;gt;&lt;br /&gt;
[[文件:NRF52832DK-MESH-SDK-INSTALL.gif|居中|无框|750x750像素]]&lt;br /&gt;
&lt;br /&gt;
=== 集成开发软件SES ===&lt;br /&gt;
SES时是Segger Embedded Studio编译器的缩写，用来编译蓝牙Mesh代码，SES功能与IAR或Keil类似。&lt;br /&gt;
&lt;br /&gt;
备注：Nordic为何使用SES而不是IAR，原因不得而知。&lt;br /&gt;
&lt;br /&gt;
==== SES安装 ====&lt;br /&gt;
SES软件位置：&amp;lt;code&amp;gt;归档资料/7-编译器SES/Setup_EmbeddedStudio_ARM_v410_win_x64.exe&amp;lt;/code&amp;gt;。&lt;br /&gt;
&lt;br /&gt;
我们双击打开安装界面，按照如下动图所示方式进行安装（可以全部默认安装）。[[文件:Ses install.gif|边框|居中|无框|556x556像素]]&lt;br /&gt;
&lt;br /&gt;
==== SES许可证激活 ====&lt;br /&gt;
我们双击打开安装好的SES，选择编译自带测试例程，此时弹出需要激活的界面。&lt;br /&gt;
[[文件:Licensed1.jpg|边框|居中|无框|1205x1205像素]]&lt;br /&gt;
由于我们准备使用SES编译nordic的NRF52832的SDK，所以这里我们选择Activate Your Free License。这里因为nordic公司已经帮客户想SEGGER公司买下了使用的版权，所以是免费的许可证。&lt;br /&gt;
[[文件:Licensed2.jpg|边框|居中|无框|736x736像素]]&lt;br /&gt;
我们按照要求填写好信息，主要是邮箱地址（用于接收SEGGER公司发给我们的许可证），填写完成之后点击Request License。下面是我们接收的邮件信息，我们将红色框内的内容复制下来。&lt;br /&gt;
[[文件:Ses install3.png|边框|居中|无框|1301x1301像素]]&lt;br /&gt;
打开SES，选择Tools\License Manager...，我们选择Activate Embedded Studio，打开如下界面，并将我们的许可证粘贴进去，然后点击Install License。&lt;br /&gt;
[[文件:Licensed4.png|边框|居中|无框|481x481像素]]&lt;br /&gt;
安装好许可证之后，跳转到如下的界面。&lt;br /&gt;
[[文件:Licensed5.png|边框|居中|无框|481x481像素]]&lt;br /&gt;
此时我们的许可证激活完毕（有时候需要等待一段时间，才会显示激活成功）。&lt;br /&gt;
[[文件:Licensed6.png|边框|居中|无框|515x515像素]]&lt;br /&gt;
&lt;br /&gt;
== Light_switch ==&lt;br /&gt;
&lt;br /&gt;
=== 实验简介 ===&lt;br /&gt;
此示例演示了包含充当两个角色的设备的网状生态系统：配置角色（Provisioner role）和节点角色（Node role、provisionee role）。 它还通过在应用程序中使用[Generic OnOff model]来演示了如何使用Mesh模型。&lt;br /&gt;
&lt;br /&gt;
该示例由三个较小的示例组成：&lt;br /&gt;
&lt;br /&gt;
Light switch server：一个实现了[Generic OnOff server model]的简约服务器，用于接收状态数据并控制板上LED 1的状态。&lt;br /&gt;
&lt;br /&gt;
Light switch client：一个实现了[Generic OnOff client model]的简约客户端。当用户按下任意按钮时，OnOff Set消息将发送到配置的目标地址。&lt;br /&gt;
&lt;br /&gt;
Mesh Provisioner：一个简单的静态配置设备应用，用于建立演示网络，该配置设备在一个网状网络中配置所有节点。 此外，配置设备还可以在这些节点上配置网格模型[Generic OnOff model]实例的绑定以及发布和订阅设置，以使它们能够相互通信。&lt;br /&gt;
&lt;br /&gt;
[Generic OnOff Client/Server]模型用于操纵打开/关闭状态。请注意，当服务器设置了发布地址（如本例所示）时，服务器会将其状态更改的任何操作披露到其发布地址。&lt;br /&gt;
&lt;br /&gt;
下图给出了将由静态供应商设置的网状网络的整体视图， 括号中的数字表示预配置程序分配给这些节点的地址。&lt;br /&gt;
[[文件:Mesh network.png|边框|居中|无框|727x727像素]]&lt;br /&gt;
灯开关服务器和灯开关客户端示例均具有预配方角色。它们支持通过广告承载（PB-ADV）和GATT承载（PB-GATT）进行配置，并且还支持网状代理服务器（Proxy Service），但是不支持代理客户端（Proxy Client）。&lt;br /&gt;
&lt;br /&gt;
=== 硬件说明 ===&lt;br /&gt;
完成这个实验，我们最少需要两个开发板硬件用做我们的节点设备（Node）：&lt;br /&gt;
* 一个开发板用做client&lt;br /&gt;
* 一个或者多个开发板用做server&lt;br /&gt;
此外，我们还需要以下之一作为我们的配置设备（Provisioner）：&lt;br /&gt;
* 如果您决定使用静态预配器示例，则多准备一个开发板&lt;br /&gt;
* 如果您决定使用app程序进行设置，则需要安装手机app@link_nrf_mesh_app（@link_nrf_mesh_app_ios或@link_nrf_mesh_app_android）。&lt;br /&gt;
{{Note|text=默认我们使用3个开发板完成我们的实验，3个开发板分别对应client、server以及provisioner三个类型。|type=warning}}&lt;br /&gt;
&lt;br /&gt;
=== 软件说明 ===&lt;br /&gt;
1、我们使用NrfGo上位机，依次对三个开发板进行擦除及softdevice的烧写&lt;br /&gt;
[[文件:Lightswitch nrfgo.png|边框|居中|无框|1013x1013像素]]&lt;br /&gt;
2、使用SEGGER Embedded Studio for ARM编译器分别打开我们`&amp;lt;InstallFolder&amp;gt;/examples/light_switch`路径下的3个实验，并完成编译&lt;br /&gt;
&lt;br /&gt;
●\client\light_switch_client_nrf52832_xxAA_s132_6_1_0.emProject&lt;br /&gt;
&lt;br /&gt;
●\server\light_switch_server_nrf52832_xxAA_s132_6_1_0.emProject&lt;br /&gt;
&lt;br /&gt;
●\provisioner\light_switch_provisioner_nrf52832_xxAA_s132_6_1_0.emProject&lt;br /&gt;
[[文件:Lightswitch SES build.png|边框|居中|无框|1013x1013像素]]&lt;br /&gt;
3、此时我们对3个开发板分别进行固件的烧写，分别烧写client、server、provisioner（建议大家给3个开发板贴上标签，方便后续区分）&lt;br /&gt;
[[文件:Lightswitch SES download.png.gif|边框|居中|无框|1009x1009像素]]&lt;br /&gt;
&lt;br /&gt;
=== 实验现象 ===&lt;br /&gt;
{{Note|text=注意：保证我们的3个设备都是刚按照软件说明部分的步骤配置完成，并且没有进行过任何硬件控制（按键操作）。目的是保证所有设备均未被我们人为配置过，没有进行过网络配置，否则可能有任意异常（不同网络配置，导致的不同现象）|type=danger}}&lt;br /&gt;
1、首先我们分别给client和provisioner供电，然后我们按下provisioner的按键S1：&lt;br /&gt;
&lt;br /&gt;
●provisoner：LED灯点亮，代表正在配置查找设备，配置组网&lt;br /&gt;
&lt;br /&gt;
●client：首先LED3和LED4闪烁，代表正在组网；LED1-LED4四个灯一起闪烁，代表组网完成&lt;br /&gt;
&lt;br /&gt;
我们如果打开RTT检测log打印，可以看到如下的信息，这个时候我们已经给client设备分配了Node Address为0x0100。provisioner信息（上），client信息（下）。&lt;br /&gt;
[[文件:Lightswitch rtt provisoner1.png|边框|无框|1080x1080像素|居中]]&lt;br /&gt;
[[文件:Lightswitch_rtt_node_client.png|边框|居中|无框|656x656像素]]&lt;br /&gt;
&lt;br /&gt;
2、使用provisioner配置好client之后，我们给server开发板上电，这个时候provisioner将会配置server入网：&lt;br /&gt;
&lt;br /&gt;
●provisoner：LED1点亮代表正在配置网络，LED2点亮代表网络配置完成&lt;br /&gt;
&lt;br /&gt;
●server：首先LED3和LED4闪烁，代表正在组网；LED1-LED4四个灯一起闪烁，代表组网完成&lt;br /&gt;
&lt;br /&gt;
我们打开RTT检测log打印，可以看到server已经配置入网，并且被分配了地址Node Address为0x0104。provisioner信息（上），server信息（下）。&lt;br /&gt;
[[文件:Lightswitch rtt provisoner2.png|边框|无框|710x710像素|居中]]&lt;br /&gt;
[[文件:Lightswitch rtt node server.png|边框|无框|656x656像素|居中]]&lt;br /&gt;
&lt;br /&gt;
3、此时整个网络的配置已经完成，这个时候我们可以按下client上的按键来控制server上的LED点亮或者熄灭。&lt;br /&gt;
&lt;br /&gt;
由于我们的server被provisioner分配的Node Address是0x0104（偶数），所以我们通过client的S3控制server的LED1点亮，通过client的S4控制server的LED1熄灭。&lt;br /&gt;
{{Note|text=client设备对于server设备的控制，通过Node Address的奇偶位不同，分成了两个组。&lt;br /&gt;
client的S1和S2分别控制Node Address为奇数的server设备的LED1点亮和熄灭。&lt;br /&gt;
&lt;br /&gt;
client的S3和S4分别控制Node Address为偶数的server设备的LED1点亮和熄灭。|type=warning}}&lt;br /&gt;
我们打开RTT检测log打印，可以看到client分别按下button2（S3）以及button3（S4），分别会设置server的GPIO输出高低电平。client信息（上），server信息（下）。&lt;br /&gt;
[[文件:Lightswitch rtt node client2.png|边框|无框|656x656像素|居中]]&lt;br /&gt;
[[文件:Lightswitch rtt node server2.png|边框|无框|656x656像素|居中]]&lt;br /&gt;
&lt;br /&gt;
4、至此我们的mesh组网的lightswitch实验测试完成。&lt;br /&gt;
&lt;br /&gt;
=== 源码详解 ===&lt;br /&gt;
&lt;br /&gt;
== Beaconing example ==&lt;br /&gt;
&lt;br /&gt;
=== 实验简介 ===&lt;br /&gt;
&lt;br /&gt;
=== 硬件说明 ===&lt;br /&gt;
&lt;br /&gt;
=== 软件说明 ===&lt;br /&gt;
&lt;br /&gt;
=== 实验现象 ===&lt;br /&gt;
&lt;br /&gt;
=== 源码详解 ===&lt;br /&gt;
&lt;br /&gt;
== Coexistence example ==&lt;br /&gt;
&lt;br /&gt;
=== 实验简介 ===&lt;br /&gt;
&lt;br /&gt;
=== 硬件说明 ===&lt;br /&gt;
&lt;br /&gt;
=== 软件说明 ===&lt;br /&gt;
&lt;br /&gt;
==== 将Mesh集成到nRF5 SDK示例中 ====&lt;br /&gt;
用于网格的nRF5 SDK与Nordic的nRF5 SDK兼容。 这使您可以将nRF5 SDK中的资源包括在现有的Mesh项目中，也可以将nRF5 SDK中的Mesh功能包括在nRF5 SDK示例中。&lt;br /&gt;
&lt;br /&gt;
请参阅构建网格堆栈和示例，以获取有关如何下载和安装nRF5 SDK的信息。 查看共存示例，以了解如何将nRF5 SDK功能与用于网格的nRF5 SDK同时使用。&lt;br /&gt;
&lt;br /&gt;
===== 动态记忆 =====&lt;br /&gt;
在将nRF5 SDK功能与nRF5 Mesh SDK功能一起使用时（如并存示例所示），您可能会遇到以下情况：应用程序注意事项可能需要更改动态内存的分配方式和可分配的动态内存量。 网格堆栈使用网格内存管理器界面进行动态内存分配。 默认的后端mesh_mem_stdlib.c使用标准库malloc（），该库需要定义足够大的堆大小。 可以通过用另一个内存管理器替换后端来更改此行为。&lt;br /&gt;
&lt;br /&gt;
如果使用Segger Embedded Studio构建应用程序，请在“项目选项”&amp;gt;“代码”&amp;gt;“运行时内存区域”设置中将“堆大小”设置为8192字节。&lt;br /&gt;
&lt;br /&gt;
===== 并发的SoftDevice和Mesh活动 =====&lt;br /&gt;
同时运行SoftDevice和Mesh时，最大的性能问题通常来自无线电时间争用。虽然SoftDevice通常在预定的短脉冲中运行，但Mesh会尝试尽可能多地使用无线电。只要SoftDevice没有无线电活动，Mesh就会连续扫描和通告。 SoftDevice活动将减少Mesh广播的时间，并且为了保持一致的Mesh性能，必须在不影响用户体验的情况下尽可能保守地设置SoftDevice无线电参数。&lt;br /&gt;
&lt;br /&gt;
在使用SoftDevice进行广告时，请尝试使用您的使用情况可以容许的最大广告间隔。如果可能，请在不需要时关闭SoftDevice广告商，仅在您希望收到连接请求时才激活它。如果您只需要发送不可连接的，不可扫描的广告（例如，针对第三方信标协议），请使用Mesh Advertiser API，因为它与Mesh一起使用时可以进行最少的上下文切换。&lt;br /&gt;
&lt;br /&gt;
当SoftDevice在连接中运行时：&lt;br /&gt;
&lt;br /&gt;
尝试协商应用程序可以允许的最大连接间隔。如果需要通过连接的高吞吐量，则在每个连接事件中发送更多的数据要比减少连接间隔更好，因为大部分开销来自上下文切换。&lt;br /&gt;
&lt;br /&gt;
如果网状设备在其SoftDevice连接中充当外围设备（从设备），则还可以增加“从设备等待时间”，这应使SoftDevice跳过连接事件，而不会增加通过链接的任何传出数据传输的等待时间。&lt;br /&gt;
&lt;br /&gt;
与广告一样，建议仅在需要时才保持连接活动。空闲的软设备连接对网状网性能的影响几乎与具有大量流量的连接一样多，特别是在从属延迟较低的情况下。&lt;br /&gt;
&lt;br /&gt;
基于SoftDevice的扫描对所有SoftDevice活动的Mesh性能影响最大。当SoftDevice正在扫描时，Mesh无法接收数据包，因此每个SoftDevice扫描窗口都会替换Mesh扫描。仅在尝试建立连接或需要活动扫描时才应使用SoftDevice扫描。如果需要常规的被动BLE扫描（用于侦听信标或其他第三方活动），请通过使用nrf_mesh_rx_cb_set函数设置RX回调来挂接到Mesh扫描器。如果您的应用程序需要主动扫描或需要启动连接，则应尽可能保守地设置扫描参数。较长的扫描间隔和较短的扫描窗口将使Mesh拥有更多的时间进行自身的无线电活动。类似地，建立上下文时在短时间内执行连续扫描可能比执行长时间运行的占空比扫描有利，因为上下文切换会导致很多不必要的开销。最后，强烈建议为SoftDevice连接启动调用设置超时，以避免长时间的空闲扫描。&lt;br /&gt;
&lt;br /&gt;
由于Mesh无法主动阻止SoftDevice无线电活动，因此减少同时运行SoftDevice活动的设备上的Mesh活动不会直接影响SoftDevice的性能。但是，附近的其他Mesh设备会干扰广告渠道中的SoftDevice活动，这可能会使连接启动花费更长的时间。&lt;br /&gt;
&lt;br /&gt;
通常，Mesh必须在SoftDevice活动之间执行其所有无线电操作，因此，如果设备在执行SoftDevice无线电操作时正在发送大量Mesh数据包，则它将在此上花费大部分Mesh时间，而不是接收传入的数据。为了解决这个问题，请根据可用的无线电时间调整输出数据包的数量，以减少Mesh数据包的发送。如果可能，请通过在CORE_TX_ROLE_RELAY角色上调用mesh_opt_core_adv_set来暂停耗时的SoftDevice操作期间的Mesh数据包中继。&lt;br /&gt;
&lt;br /&gt;
===== 在用于网格示例的nRF5 SDK中包括nRF5 SDK =====&lt;br /&gt;
根据您的工具链：&lt;br /&gt;
&lt;br /&gt;
使用Segger Embedded Studio时，请添加代码文件并包括相应SES项目文件的路径。&lt;br /&gt;
&lt;br /&gt;
使用CMake构建用于网格堆栈的nRF5 SDK时，添加代码文件并包括指向相应CMakeLists.txt文件的路径。 SDK_ROOT根符号用于引用nRF5 SDK安装文件夹（例如，请参阅Light Switch服务器示例中的CMakeLists.txt）。&lt;br /&gt;
&lt;br /&gt;
网格示例项目已经在其include /目录中包含sdk_config.h文件。 这些文件是默认SDK配置文件的副本，并且网格示例所需的所有更改都包含在示例目录中的include / app_config.h文件中。&lt;br /&gt;
&lt;br /&gt;
注意，&lt;br /&gt;
&lt;br /&gt;
必须先在SDK配置文件中显式启用某些SDK功能，然后才能使用它们。 有关详细信息，请参见SDK文档页面SDK配置头文件。&lt;br /&gt;
&lt;br /&gt;
===== 在nRF5 SDK示例中包括用于网格功能的nRF5 SDK =====&lt;br /&gt;
在nRF5 SDK示例的项目文件中包括来自nRF5 SDK for Mesh的以下源文件：&lt;br /&gt;
* All C files in &amp;lt;code&amp;gt;mesh/core/src&amp;lt;/code&amp;gt;&lt;br /&gt;
* All C files in &amp;lt;code&amp;gt;mesh/bearer/src&amp;lt;/code&amp;gt;&lt;br /&gt;
* All C files in &amp;lt;code&amp;gt;mesh/prov/src&amp;lt;/code&amp;gt; except nrf_mesh_prov_bearer_gatt.c&lt;br /&gt;
* All C files in &amp;lt;code&amp;gt;mesh/access/src&amp;lt;/code&amp;gt;&lt;br /&gt;
* All C files in &amp;lt;code&amp;gt;mesh/dfu/src&amp;lt;/code&amp;gt;&lt;br /&gt;
* All C files in &amp;lt;code&amp;gt;mesh/stack/src&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;models/foundation/config/src/config_server.c&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;models/foundation/config/src/composition_data.c&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;models/foundation/config/src/packed_index_list.c&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;models/foundation/health/src/health_server.c&amp;lt;/code&amp;gt;&lt;br /&gt;
* Any other mesh models that are used in your application&lt;br /&gt;
* &amp;lt;code&amp;gt;external/micro-ecc/uECC.c&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;examples/common/src/assertion_handler_weak.c&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;examples/common/src/mesh_provisionee.c&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
注意&lt;br /&gt;
&lt;br /&gt;
如果不需要各种网格特征（例如DFU），则可以从项目文件中省略相应的文件。但是，在其位置添加examples / nrf_mesh_weak.c以提供缺少的API函数的存根。&lt;br /&gt;
&lt;br /&gt;
将以下文件夹添加到nRF5 SDK示例的项目包含路径：&lt;br /&gt;
* &amp;lt;code&amp;gt;mesh/core/api&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;mesh/core/include&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;mesh/bearer/api&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;mesh/bearer/include&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;mesh/prov/api&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;mesh/prov/include&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;mesh/access/api&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;mesh/access/include&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;mesh/dfu/api&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;mesh/dfu/include&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;mesh/stack/api&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;models/foundation/config/include&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;models/foundation/health/include&amp;lt;/code&amp;gt;&lt;br /&gt;
* Path to include folder of any other mesh models that are used in your application&lt;br /&gt;
* &amp;lt;code&amp;gt;external/micro-ecc&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;examples/common/include&amp;lt;/code&amp;gt;&lt;br /&gt;
* Path to any other resources in the mesh examples that are used in your application&lt;br /&gt;
将以下预处理器符号添加到nRF5 SDK示例的项目文件中：&lt;br /&gt;
* &amp;lt;code&amp;gt;NRF52_SERIES&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;NRF_MESH_LOG_ENABLE=NRF_LOG_USES_RTT&amp;lt;/code&amp;gt; (because logging in the mesh stack relies on RTT)&lt;br /&gt;
* &amp;lt;code&amp;gt;CONFIG_APP_IN_CORE&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== 可选变更 =====&lt;br /&gt;
此外，您可能需要应用以下一项或多项更改：&lt;br /&gt;
&lt;br /&gt;
如果与nRF5 SDK集成在一起，则可能需要更新在网格堆栈中使用simple_hal模块的示例以使用Nordic nRF5 SDK bsp模块。可以同时使用两者，但是在这种情况下，必须从其中之一删除GPIOTE_IRQHandler，并且只有一个模块可以注册回调函数。&lt;br /&gt;
&lt;br /&gt;
如果原始的Nordic nRF5 SDK示例使用了SoftDevice，请确保在启用SoftDevice之后初始化并启用了网格堆栈。在这种情况下，必须将SoftDevice事件转发到网格堆栈。将以下代码添加到您的应用程序：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;#&amp;lt;/nowiki&amp;gt;include“ nrf_sdh_soc.h”&lt;br /&gt;
&lt;br /&gt;
＃定义MESH_SOC_OBSERVER_PRIO 0&lt;br /&gt;
&lt;br /&gt;
静态void mesh_soc_evt_handler（uint32_t evt_id，void * p_context）&lt;br /&gt;
&lt;br /&gt;
{&lt;br /&gt;
&lt;br /&gt;
    nrf_mesh_on_sd_evt（evt_id）;&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
NRF_SDH_SOC_OBSERVER（m_mesh_soc_observer，MESH_SOC_OBSERVER_PRIO，mesh_soc_evt_handler，NULL）;&lt;br /&gt;
&lt;br /&gt;
如果您有多个SOC观察者，请确保一次仅从其中一个观察者将SOC观察者事件转发到网格堆栈。&lt;br /&gt;
&lt;br /&gt;
默认情况下，在网格堆栈以及某些Nordic nRF5 SDK应用程序中启用网络配置的闪存存储。用于此目的的闪光区域可能会重叠并导致错误。为了使闪存存储模块Flash管理器与网状堆栈中的闪存管理器以及Nordic nRF5 SDK中的闪存存储模块安全地共存，请在nrf_mesh_config_app.h中添加以下代码块：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;#&amp;lt;/nowiki&amp;gt;include“ fds.h”&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;#&amp;lt;/nowiki&amp;gt;include“ fds_internal_defs.h”&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;#&amp;lt;/nowiki&amp;gt;define FLASH_MANAGER_RECOVERY_PAGE_OFFSET_PAGES FDS_PHY_PAGES&lt;br /&gt;
&lt;br /&gt;
如果要添加自己的网格功能，而不是使用现有的网格示例，则还需要添加文件nrf_mesh_config_app.h。将其从网格堆栈存储库中的examples / templates文件夹复制到您的项目文件夹中，并删除文件顶部的#error消息。对文件内容进行其他适当的更改，例如将ACCESS_ELEMENT_COUNT和ACCESS_MODEL_COUNT调整为所需数量的元素和模型。&lt;br /&gt;
&lt;br /&gt;
===== Flash放置项目文件 =====&lt;br /&gt;
Segger Embedded Studio项目旁边都有一个flash_placement.xml文件，该文件充当链接器的输入。在flash_placement.xml文件中，nRF5 SDK配置了一组ProgramSection列表，用于放置某些变量。除了nRF5 SDK组件所需的所有ProgramSections外，网格还需要另外两个部分，即nrf_mesh_flash和nrf_mesh_ram。&lt;br /&gt;
&lt;br /&gt;
必须将其他与网格相关的部分添加到flash_placement.xml文件中：&lt;br /&gt;
&lt;br /&gt;
将以下行添加到标记为&amp;lt;MemorySegment name =“ FLASH” ...&amp;gt;的内存段中：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ProgramSection alignment =“ 4” keep =“是” load =“是” name =“。nrf_mesh_flash”输入节=“ *（SORT（.nrf_mesh_flash。*））”“ address_symbol =” __ start_nrf_mesh_flash“ end_symbol =” __ stop_nrf_mesh_flash“ /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
将以下行添加到标记为&amp;lt;MemorySegment name =“ RAM” ...&amp;gt;的内存段中：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ProgramSection alignment =“ 4” keep =“是” load =“否” name =“。nrf_mesh_ram” inputsections =“ *（SORT（.nrf_mesh_ram。*））”“ address_symbol =” __ start_nrf_mesh_ram“ end_symbol =” __ stop_nrf_mesh_ram“ /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
注意&lt;br /&gt;
&lt;br /&gt;
对于共存示例已经进行了此更改。在编辑任何现有nRF5 SDK示例的Flash放置文件时，可以在这些示例中使用flash_placement.xml文件作为参考。&lt;br /&gt;
&lt;br /&gt;
=== 实验现象 ===&lt;br /&gt;
&lt;br /&gt;
=== 源码详解 ===&lt;br /&gt;
&lt;br /&gt;
== Coexistence Uart example ==&lt;br /&gt;
&lt;br /&gt;
=== 实验简介 ===&lt;br /&gt;
&lt;br /&gt;
=== 硬件说明 ===&lt;br /&gt;
&lt;br /&gt;
=== 软件说明 ===&lt;br /&gt;
&lt;br /&gt;
==== SDK UART共存示例 ====&lt;br /&gt;
本示例演示了如何同时使用适用于Mesh的nRF5 SDK和nRF5 SDK示例。它围绕两个示例构建，分为两个部分：&lt;br /&gt;
&lt;br /&gt;
此示例的网格部分从nRF5 SDK for Mesh实现了灯开关客户端示例。&lt;br /&gt;
&lt;br /&gt;
该示例的BLE部分实现了nRF5 SDK的ble_app_uart示例，但以下更改除外：&lt;br /&gt;
* BSP事件SLEEP，DISCONNECT和WHITELIST_OFF被忽略。这使得板子按钮仅控制应用程序的网格部分&lt;br /&gt;
* 增加广告间隔以为网格堆栈留出更多时间&lt;br /&gt;
* 可以通过发送要模拟的灯光开关客户端按钮编号，通过BLE UART来控制灯光开关服务器&lt;br /&gt;
运行此示例的结果是，您将能够使用网状网络，该示例中的网状网络可以代替电灯开关客户端示例。&lt;br /&gt;
&lt;br /&gt;
在开始测试此共存示例之前，请参阅以下页面：&lt;br /&gt;
&lt;br /&gt;
将Mesh集成到nRF5 SDK示例中&lt;br /&gt;
&lt;br /&gt;
灯光开关示例和灯光开关客户端详细信息以及Mesh API&lt;br /&gt;
&lt;br /&gt;
===== 测试示例 =====&lt;br /&gt;
将ble_app_uart_coexist文件夹复制到nRF5 SDK安装路径下的examples / ble_peripheral文件夹中。&lt;br /&gt;
&lt;br /&gt;
在ble_app_uart_coexist / pca10040 / s132 / ses / ble_app_uart_pca10040_s132.emProject中打开Segger Embedded Studio项目。&lt;br /&gt;
&lt;br /&gt;
将MESH_ROOT添加到您的Segger Embedded Studio全局宏列表：&lt;br /&gt;
&lt;br /&gt;
在SES菜单栏中，单击工具&amp;gt;选项...。&lt;br /&gt;
&lt;br /&gt;
在左列中，单击建筑物。&lt;br /&gt;
&lt;br /&gt;
在右列中，双击“全局宏”。&lt;br /&gt;
&lt;br /&gt;
在新行中添加网格根目录：MESH_ROOT = &amp;lt;网格安装路径&amp;gt;。&lt;br /&gt;
&lt;br /&gt;
如灯光开关示例中所述对灯光开关示例设备进行编程，但以下情况除外：&lt;br /&gt;
&lt;br /&gt;
用examples / ble_peripheral / ble_app_uart_coexist / pca10040 / s132 / ses / Output / Release / Exe / ble_app_uart_pca10040_s132.hex替换电灯开关客户端。&lt;br /&gt;
&lt;br /&gt;
现在，您可以并行或顺序运行两个并存的示例：&lt;br /&gt;
&lt;br /&gt;
按照电灯开关示例中的说明运行电灯开关示例。&lt;br /&gt;
&lt;br /&gt;
按照nRF5 SDK文档中的描述运行ble_app_uart示例。&lt;br /&gt;
&lt;br /&gt;
编写与UART RX特性中的数字之一（1、2、3或4）等效的十六进制ASCII码，以模拟按钮按下。&lt;br /&gt;
&lt;br /&gt;
=== 实验现象 ===&lt;br /&gt;
&lt;br /&gt;
=== 源码详解 ===&lt;br /&gt;
&lt;br /&gt;
== mesh工程文件简介 ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
!分组&lt;br /&gt;
!文件&lt;br /&gt;
!说明&lt;br /&gt;
|-&lt;br /&gt;
!Access&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; |'''访问层：'''&lt;br /&gt;
'''访问层定义高层应用程序如何使用上层传输层。 它定义了应用程序数据的格式； 它定义并控制在上层传输层执行的应用程序数据加密和解密； 并检查是否已在正确的网络和应用程序密钥的上下文中接收到传入的应用程序数据，然后再将其转发到更高层。'''&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
|access&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
|access_loopback&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
|access_publish&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
|access_publish_retransmissior&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
|access_reliable&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
|device_state_manager&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
!Application&lt;br /&gt;
! colspan=&amp;quot;2&amp;quot; |&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
|app_error_weak&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
|app_onoff&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
|assertion_handler_weak&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
|ble_softdevice_support&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
|main&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
|mesh_adv&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
|mesh_app_utils&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
|mesh_provisionee&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
|nrf_mesh_weak&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
|rtt_input&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
|sdk_config&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
|simple_hal&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== ***************************************正式整理章节************************************** ==&lt;br /&gt;
&lt;br /&gt;
== mesh实验学习流程概论 ==&lt;br /&gt;
1、首先大家一定要先查看一下我们资料中的手册“BLE-Mesh技术揭秘”，这个是很重要的一个mesh基本概念的介绍，只有看完这个文档，我们才能明白mesh组网是什么，是怎么完成的，后续的我们实验内容，也会在这个文档中查到相应的对照。&lt;br /&gt;
&lt;br /&gt;
2、按照Light_switch测试实验的实验说明，跑通测试流程（可以固件清零重新烧写多测试几遍，有助于增强我们的流程熟练度，利于后面的开发）。light_switch实验是现在nordic提供给我们的唯一的真正意义上的mesh组网通信例程。&lt;br /&gt;
&lt;br /&gt;
3、根据我们下面拆分的组网流程，对照依次的实验说明，进行mesh组网流程的分步学习。前期实验部分我们主要讲解节点设备，配置者设备功能我们使用手机app完成，目的是减少大家的学习难度，配置者设备我们会放在最后一个实验给大家做介绍。&lt;br /&gt;
&lt;br /&gt;
①广播（通信TX）——了解节点设备的广播功能，分为三个重要部分：PB-ADV和Unprov Beacon，PB-GATT的广播，以及模型的Publish用于发送数据。&lt;br /&gt;
&lt;br /&gt;
②扫描（通信RX）——了解节点设备的扫描功能，分为两个重要部分：普通的BLE SCAN功能，以及模块的Subscription用于接收数据。&lt;br /&gt;
&lt;br /&gt;
③入网验证——了解节点设备接入网络的验证过程，实验中使用的是PB-GATT去配置的，包含了：交换公共密钥，完成验证（是否有带外OOB）。&lt;br /&gt;
&lt;br /&gt;
④设备信息分配——启动配置数据分配，给节点分配如下数据：网络key（Netkey）、应用key（Appkey）以及节点设备地址（nodeAddr）。&lt;br /&gt;
&lt;br /&gt;
⑤元素——了解节点设备的元素与模型定义。&lt;br /&gt;
&lt;br /&gt;
⑥模型——了解模型的功能和创建方法，模型的订阅subscription和发布publish功能。&lt;br /&gt;
&lt;br /&gt;
== Light_switch测试实验 ==&lt;br /&gt;
=== 实验简介 ===&lt;br /&gt;
此示例演示了包含充当两个角色的设备的网状生态系统：配置角色（Provisioner role）和节点角色（Node role、provisionee role）。 它还通过在应用程序中使用[Generic OnOff model]来演示了如何使用Mesh模型。&lt;br /&gt;
&lt;br /&gt;
该示例由三个较小的示例组成：&lt;br /&gt;
&lt;br /&gt;
Light switch server：一个实现了[Generic OnOff server model]的简约服务器，用于接收状态数据并控制板上LED 1的状态。&lt;br /&gt;
&lt;br /&gt;
Light switch client：一个实现了[Generic OnOff client model]的简约客户端。当用户按下任意按钮时，OnOff Set消息将发送到配置的目标地址。&lt;br /&gt;
&lt;br /&gt;
Mesh Provisioner：一个简单的静态配置设备应用，用于建立演示网络，该配置设备在一个网状网络中配置所有节点。 此外，配置设备还可以在这些节点上配置网格模型[Generic OnOff model]实例的绑定以及发布和订阅设置，以使它们能够相互通信。&lt;br /&gt;
&lt;br /&gt;
[Generic OnOff Client/Server]模型用于操纵打开/关闭状态。请注意，当服务器设置了发布地址（如本例所示）时，服务器会将其状态更改的任何操作披露到其发布地址。&lt;br /&gt;
&lt;br /&gt;
下图给出了将由静态供应商设置的网状网络的整体视图， 括号中的数字表示预配置程序分配给这些节点的地址。&lt;br /&gt;
[[文件:Mesh network.png|边框|居中|无框|727x727像素]]&lt;br /&gt;
灯开关服务器和灯开关客户端示例均具有预配方角色。它们支持通过广告承载（PB-ADV）和GATT承载（PB-GATT）进行配置，并且还支持网状代理服务器（Proxy Service），但是不支持代理客户端（Proxy Client）。&lt;br /&gt;
&lt;br /&gt;
=== 硬件说明 ===&lt;br /&gt;
完成这个实验，我们最少需要两个开发板硬件用做我们的节点设备（Node）：&lt;br /&gt;
* 一个开发板用做client&lt;br /&gt;
* 一个或者多个开发板用做server&lt;br /&gt;
此外，我们还需要以下之一作为我们的配置设备（Provisioner）：&lt;br /&gt;
* 如果您决定使用静态预配器示例，则多准备一个开发板&lt;br /&gt;
* 如果您决定使用app程序进行设置，则需要安装手机app@link_nrf_mesh_app（@link_nrf_mesh_app_ios或@link_nrf_mesh_app_android）。&lt;br /&gt;
{{Note|text=默认我们使用3个开发板完成我们的实验，3个开发板分别对应client、server以及provisioner三个类型。|type=warning}}&lt;br /&gt;
&lt;br /&gt;
=== 软件说明 ===&lt;br /&gt;
1、我们使用NrfGo上位机，依次对三个开发板进行擦除及softdevice的烧写&lt;br /&gt;
[[文件:Lightswitch nrfgo.png|边框|居中|无框|1013x1013像素]]&lt;br /&gt;
2、使用SEGGER Embedded Studio for ARM编译器分别打开我们`&amp;lt;InstallFolder&amp;gt;/examples/light_switch`路径下的3个实验，并完成编译&lt;br /&gt;
&lt;br /&gt;
●\client\light_switch_client_nrf52832_xxAA_s132_6_1_0.emProject&lt;br /&gt;
&lt;br /&gt;
●\server\light_switch_server_nrf52832_xxAA_s132_6_1_0.emProject&lt;br /&gt;
&lt;br /&gt;
●\provisioner\light_switch_provisioner_nrf52832_xxAA_s132_6_1_0.emProject&lt;br /&gt;
[[文件:Lightswitch SES build.png|边框|居中|无框|1013x1013像素]]&lt;br /&gt;
3、此时我们对3个开发板分别进行固件的烧写，分别烧写client、server、provisioner（建议大家给3个开发板贴上标签，方便后续区分）&lt;br /&gt;
[[文件:Lightswitch SES download.png.gif|边框|居中|无框|1009x1009像素]]&lt;br /&gt;
&lt;br /&gt;
=== 实验现象 ===&lt;br /&gt;
{{Note|text=注意：保证我们的3个设备都是刚按照软件说明部分的步骤配置完成，并且没有进行过任何硬件控制（按键操作）。目的是保证所有设备均未被我们人为配置过，没有进行过网络配置，否则可能有任意异常（不同网络配置，导致的不同现象）|type=danger}}&lt;br /&gt;
1、首先我们分别给client和provisioner供电，然后我们按下provisioner的按键S1：&lt;br /&gt;
&lt;br /&gt;
●provisoner：LED灯点亮，代表正在配置查找设备，配置组网&lt;br /&gt;
&lt;br /&gt;
●client：首先LED3和LED4闪烁，代表正在组网；LED1-LED4四个灯一起闪烁，代表组网完成&lt;br /&gt;
&lt;br /&gt;
我们如果打开RTT检测log打印，可以看到如下的信息，这个时候我们已经给client设备分配了Node Address为0x0100。provisioner信息（上），client信息（下）。&lt;br /&gt;
[[文件:Lightswitch rtt provisoner1.png|边框|无框|1080x1080像素|居中]]&lt;br /&gt;
[[文件:Lightswitch_rtt_node_client.png|边框|居中|无框|656x656像素]]&lt;br /&gt;
&lt;br /&gt;
2、使用provisioner配置好client之后，我们给server开发板上电，这个时候provisioner将会配置server入网：&lt;br /&gt;
&lt;br /&gt;
●provisoner：LED1点亮代表正在配置网络，LED2点亮代表网络配置完成&lt;br /&gt;
&lt;br /&gt;
●server：首先LED3和LED4闪烁，代表正在组网；LED1-LED4四个灯一起闪烁，代表组网完成&lt;br /&gt;
&lt;br /&gt;
我们打开RTT检测log打印，可以看到server已经配置入网，并且被分配了地址Node Address为0x0104。provisioner信息（上），server信息（下）。&lt;br /&gt;
[[文件:Lightswitch rtt provisoner2.png|边框|无框|710x710像素|居中]]&lt;br /&gt;
[[文件:Lightswitch rtt node server.png|边框|无框|656x656像素|居中]]&lt;br /&gt;
&lt;br /&gt;
3、此时整个网络的配置已经完成，这个时候我们可以按下client上的按键来控制server上的LED点亮或者熄灭。&lt;br /&gt;
&lt;br /&gt;
由于我们的server被provisioner分配的Node Address是0x0104（偶数），所以我们通过client的S3控制server的LED1点亮，通过client的S4控制server的LED1熄灭。&lt;br /&gt;
{{Note|text=client设备对于server设备的控制，通过Node Address的奇偶位不同，分成了两个组。&lt;br /&gt;
client的S1和S2分别控制Node Address为奇数的server设备的LED1点亮和熄灭。&lt;br /&gt;
&lt;br /&gt;
client的S3和S4分别控制Node Address为偶数的server设备的LED1点亮和熄灭。|type=warning}}&lt;br /&gt;
我们打开RTT检测log打印，可以看到client分别按下button2（S3）以及button3（S4），分别会设置server的GPIO输出高低电平。client信息（上），server信息（下）。&lt;br /&gt;
[[文件:Lightswitch rtt node client2.png|边框|无框|656x656像素|居中]]&lt;br /&gt;
[[文件:Lightswitch rtt node server2.png|边框|无框|656x656像素|居中]]&lt;br /&gt;
&lt;br /&gt;
4、至此我们的mesh组网的lightswitch实验测试完成。&lt;br /&gt;
&lt;br /&gt;
== ①广播（通信TX） ==&lt;br /&gt;
有关广播部分，我们需要给大家讲解三个方面，分别如下：&lt;br /&gt;
&lt;br /&gt;
1、PB-ADV，这个是mesh中的广播承载层，用于我们启动配置过程（暂时的例程和手机app都是用的PB-GATT来配置的，但是源码中PB-ADV的流程是包含的）。&lt;br /&gt;
&lt;br /&gt;
2、Unprov Beacon，这个是广播一个未被配置的beacon，就是一个特殊形式的广播数据包，用于声明自己是未经配置的设备。&lt;br /&gt;
&lt;br /&gt;
3、PB-GATT，这个是mesh中的GATT承载层，用于我们启动配置过程（当前手机app就是使用PB-GATT，也就是连接之后，通过GATT服务去发送配置信息）。&lt;br /&gt;
&lt;br /&gt;
4、Publish，这个是我们用于模型发布数据的部分，我们将需要发送的数据通过advData广播出去。&lt;br /&gt;
&lt;br /&gt;
=== PB-ADV ===&lt;br /&gt;
PB-ADV说明请查看“BLE-Mesh技术揭秘”第8.1章节。&lt;br /&gt;
&lt;br /&gt;
我们来看下PB-ADV整个的流程，首先在mian文件中，我们的start()函数下，我们调用了mesh_provisionee_prov_start()函数去启动节点设备的等待配置。&lt;br /&gt;
[[文件:PB-ADV-01.png|边框|居中|无框|1200x1200px]]我们继续追踪mesh_provisionee_prov_start()，可以看到在使能宏定义MESH_FEATURE_PB_ADV_ENABLED的情况下，可以追踪到nrf_mesh_prov_bearer_adv_interface_get()函数。&lt;br /&gt;
[[文件:PB-ADV-02.png|边框|居中|无框|1200x1200px]]在nrf_mesh_prov_bearer_adv_interface_get()函数中，我们可以看到prov_bearer_interface_t结构体，这个结构体下面提供了我们配置承载层需要的API函数。&lt;br /&gt;
[[文件:PB-ADV-03.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
=== Unprov Beacon ===&lt;br /&gt;
Unprov Beacon说明请查看“BLE-Mesh技术揭秘”第5.5及5.5.1章节。&lt;br /&gt;
&lt;br /&gt;
上面我们有说到PB-ADV是承载层用于配置节点的功能，而Unprov Beacon则是我们的节点设备用于声明自己未经配置，所以这两个其实是一脉相承的。&lt;br /&gt;
&lt;br /&gt;
紧接着上面的nrf_mesh_prov_bearer_adv_interface_get()函数，其中的prov_bearer_interface_t结构体包含的是处理承载层的API函数，在这个结构体当中有一个.listen_start = prov_bearer_adv_listen，是用于开始监听传入的配置链接的功能，在这个函数中我们可以看到send_unprov_beacon()函数。&lt;br /&gt;
[[文件:PB-ADV-04.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
send_unprov_beacon()函数是用于发送Unprov Beacon数据的函数，在这个函数中我们调用prov_beacon_unprov_build函数去创建用于广播的Unprov Beacon数据包，然后调用advertiser_interval_set函数去设置广播的间隔，最后调用advertiser_packet_send函数去将我们的数据包发送出去。&lt;br /&gt;
&lt;br /&gt;
默认的Unprov Beacon广播间隔是NRF_MESH_PROV_BEARER_ADV_UNPROV_BEACON_INTERVAL_MS 2000。&lt;br /&gt;
&lt;br /&gt;
最终调用的是advertiser_packet_send(0函数去将广播数据发送出去的。&lt;br /&gt;
[[文件:PB-ADV-05.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Publish-12.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
这里我们再最后看一下prov_beacon_unprov_build()函数，这个函数是生成我们的Unprov Beacon的数据包。&lt;br /&gt;
&lt;br /&gt;
其中标识位是0x2B，这个flag是beacon的意思。data的第一个字节0x00代表是我们的BEACON_TYPE_UNPROV（未配置的节点设备），接下来的16个字节是device uuid（这个是寄存器FICR中的device id生成的），接下来2字节是oob info（这里是00 00，代表没有使能OOB），最后的4个字节是URI通过AES-CMAC算法计算出来的值。&lt;br /&gt;
[[文件:PB-GATT-11.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:PB-GATT-12.png|边框|居中|无框]]&lt;br /&gt;
&lt;br /&gt;
=== PB-GATT ===&lt;br /&gt;
PB-GATT说明请查看“BLE-Mesh技术揭秘”第8.1章节。&lt;br /&gt;
&lt;br /&gt;
和PB-ADV一样的，我们从main文件中的start()函数，追踪到mesh_provisionee_prov_start()，可以看到在使能宏定义MESH_FEATURE_PB_GATT_ENABLED的情况下，可以追踪到nrf_mesh_prov_bearer_gatt_init()以及nrf_mesh_prov_bearer_gatt_interface_get()函数。&lt;br /&gt;
[[文件:PB-GATT-01.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
首先我们看一下nrf_mesh_prov_bearer_gatt_init()函数，这个是初始化并设置PB-GATT服务（通过Mesh GATT接口）的函数。对比我们使用nrf master control panel手机APP连接之后获取到的服务，可以看到其中的PB-GATT的服务以及特征值。&lt;br /&gt;
&lt;br /&gt;
再往下的mesh_gatt_init()函数，这个就不给大家分析了，是和我们普通的BLE一样的方式去注册一个服务。&lt;br /&gt;
[[文件:PB-GATT-04.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:PB-GATT-03.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:PB-GATT-02.png|边框|居中|无框]]&lt;br /&gt;
在nrf_mesh_prov_bearer_gatt_interface_get()函数中，我们可以看到prov_bearer_interface_t结构体，这个结构体下面提供了我们配置承载层需要的API函数（这个是和上方的PB-ADV是一样的）。&lt;br /&gt;
[[文件:PB-GATT-05.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
和Unprov Beacon类似的，当我们开始监听配置链接的时候，会触发.listen_start = listen_start_cb的回调，在这个回调中我们触发FSM（有限状态机）的事件E_LISTEN_START，然后调用link_evt_send()去post顺序承载事件。&lt;br /&gt;
[[文件:PB-GATT-06.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
然后我们来到状态机列表m_pb_gatt_fsm_transition_table[]，可以看到E_LISTEN_START事件ID代表的动作ID是A_LISTEN_START。而最终我们的A_LISTEN_START动作，执行的是a_listen_start函数功能（这其中涉及的代码逻辑，暂时先不说明，大家可以自己查看源码的引用）。&lt;br /&gt;
[[文件:PB-GATT-07.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:PB-GATT-08.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
然后我们继续追踪a_listen_start()，可以看到其中调用了mesh_adv_params_set函数设置了我们广播的超时时间MESH_ADV_TIMEOUT_INFINITE以及广播间隔NRF_MESH_PROV_BEARER_GATT_UNPROV_BEACON_INTERVAL_MS，调用mesh_adv_data_set函数设置了我们的广播数据内容，最后调用mesh_adv_start函数启动广播。&lt;br /&gt;
&lt;br /&gt;
默认的PB-GATT的广播数据的广播周期是NRF_MESH_PROV_BEARER_GATT_UNPROV_BEACON_INTERVAL_MS 200。&lt;br /&gt;
[[文件:PB-GATT-09.png|边框|居中|无框|1200x1200像素]]我们同样的看一下mesh_adv_data_set()函数最终配置生成的广播数据，可以看到广播数据的flag是0x06，服务的UUID就是我们上面提到的PB-GATT服务的UUID，接下来16字节UUID是和PB-ADV一样的FICR寄存器的device id值，最后的扫描回调数据携带的是设备的全名“nRF5x Mesh Light”。&lt;br /&gt;
[[文件:PB-GATT-13.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:PB-GATT-14.png|边框|居中|无框]]最后是我们的mesh_adv_start()函数去启动广播，可以看到调用是我们ble stack sotfdevice当中的API函数sd_ble_gap_adv_start()去启动的。&lt;br /&gt;
[[文件:PB-GATT-15.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
=== Publish（模型发布） ===&lt;br /&gt;
这一章节我们只给大家讲解模块publish的数据发布流程，暂时不说明模型的创建方法，以及如何使用，这个放在下面单独的模型章节进行说明。&lt;br /&gt;
&lt;br /&gt;
以generic_onoff_server模型为例，我们找到access_model_publish以及access_model_reply函数，这两个函数的功能类似的，都是用于我们用户的模型发送数据的。&lt;br /&gt;
&lt;br /&gt;
其中access_model_publish是用于模型主动发送，而access_model_reply是用于模型给对应的Opcode回应数据。&lt;br /&gt;
&lt;br /&gt;
这里我们以access_model_publish函数为主，来看下发布数据的代码流程。&lt;br /&gt;
[[文件:Publish-01.png|边框|居中|无框|1200x1200像素]]查看一下access_model_publish()函数，我们可以看到packet_alloc_and_tx()函数，这个是我们继续发送数据的函数。下面还有一个蓝牙方框中的内容，是我们定义的重传机制（mesh数据为了保证传输的成功率，默认都是会重传数据包的，默认重传次数是4次）。[[文件:Publish-02.png|边框|居中|无框|1200x1200像素]]然后我们继续看一下packet_alloc_and_tx()函数，在这个函数中，我们最终会调用packet_tx()函数将数据发送出去。[[文件:Publish-03.png|边框|居中|无框|1200x1200像素]]继续追踪packet_tx()函数，这个函数的内容比较多，我们只截取了其中数据发送的部分，也就是我们的nrf_mesh_packet_send()函数，我们在他的tx_params中赋值好我们需要发送出去的数据内容。[[文件:Publish-04.png|边框|居中|无框|1199x1199像素]]nrf_mesh_packet_send()函数最终调用的是transport_tx()函数发送数据。而transport_tx()函数，最终是调用upper_transport_tx()函数进行的数据发送。[[文件:Publish-05.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Publish-06.png|边框|居中|无框|1200x1200像素]]我们再继续追踪upper_transport_tx()函数，可以看到我们会根据p_metadata-&amp;gt;segmented来判断数据是否要分包发送。&lt;br /&gt;
[[文件:Publish-13.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
接下里我们以不分包发送函数unsegmented_packet_tx()为例，来继续说明数据传输流程。&lt;br /&gt;
&lt;br /&gt;
我们可以看到我们的unsegmented_packet_tx()函数中，最终是调用的network_packet_send()函数。（分包的segmented_packet_tx()函数最终也是调用的network_packet_send()，大家可以自己追踪代码看一下）&lt;br /&gt;
[[文件:Publish-14.png|边框|居中|无框|1200x1200像素]]继续追踪我们的network_packet_send()函数，可以看到我们的net_packet_encrypt()加密函数，这个是将我们的明文数据，做好加密工作。&lt;br /&gt;
&lt;br /&gt;
然后调用core_tx_packet_send()函数发送出去。[[文件:Publish-15.png|边框|居中|无框|1200x1200像素]]最后我们追踪到core_tx_packet_send()函数，可以看到调用的是p_bearer-&amp;gt;p_interface-&amp;gt;packet_send()函数去发送的。所以接下来我们要找到我们的packet_send()功能是在哪边实现的。[[文件:Publish-07.png|边框|居中|无框|1200x1200像素]]可以看到我们是在core_tx_bearer_add函数中去获得的p_interface这个功能。[[文件:Publish-08.png|边框|居中|无框|1200x1200像素]]我们追踪core_tx_bearer_add()函数的引用路径，可以看到是在core_tx_adv_init()当中。（除了此处，还有另一处地方也有引用，功能是类似的，大家可以自行了解）。&lt;br /&gt;
&lt;br /&gt;
我们看下参数m_interface，可以看到m_interface-&amp;gt;packet_send功能，就是对应我们在core_tx_packet_send()函数调用的p_bearer-&amp;gt;p_interface-&amp;gt;packet_send()。[[文件:Publish-09.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Publish-10.png|边框|居中|无框|1200x1200像素]]所以我们接下来来看下m_interface结构体当中的packet_send()函数实现的是什么功能，可以看到最终调用的是advertiser_packet_send()函数将数据发送出去。看到这边我们就很熟悉了，和上面的几个广播功能，最后调用的都是相同的广播包发送函数。[[文件:Publish-11.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Publish-12.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
== ②扫描（通信RX） ==&lt;br /&gt;
&lt;br /&gt;
=== 标准BLE SCAN ===&lt;br /&gt;
看过了我们的广播功能之后，我们来看下扫描的部分。在ble mesh中，我们的扫描功能scan，就是用于获取数据的，类似于数据通信的RX。&lt;br /&gt;
&lt;br /&gt;
我们从main文件中的initialize()函数开始，首先是我们mesh_init()函数，最终我们会调用mesh_stack_init()函数。&lt;br /&gt;
[[文件:SCAN-01.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
在mesh_satck_init函数中，我们可以看到调用了nrf_mesh_init函数去初始化core的参数。&lt;br /&gt;
[[文件:SCAN-02.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
这一段是nrf_mesh_init函数部分，由于这个初始化函数太长，所以我们只截取了scan相关的部分。&lt;br /&gt;
&lt;br /&gt;
首先是第一点scanner_init(scanner_packet_process_cb)函数，在这个函数中，我们回去初始化扫描的参数配置，并且会开启扫描功能。最终我们可以从scanner_packet_process_cb这个回调中得到我们扫描到的数据（获得所有的BLE广播数据）。&lt;br /&gt;
&lt;br /&gt;
然后是第二点ad_listener_subscribe(&amp;amp;m_nrf_mesh_listener)，这个是用来侦听订阅的消息的（从获得的所有BLE广播数据中，筛选出的订阅的消息）。&lt;br /&gt;
[[文件:SCAN-03.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
然后我们继续看一下scanner_init的扫描初始化函数，在这个函数中我们首先调用scanner_config_reset()函数去配置扫描的默认参数。其中包含了：&lt;br /&gt;
&lt;br /&gt;
1、扫描的通道SCANNER_CHANNELS_DEFAULT，也就是BLE的｛37，38，39｝三个channels都去扫描。&lt;br /&gt;
&lt;br /&gt;
2、规范定义的非连接状态访问地址BEARER_ACCESS_ADDR_DEFAULT。&lt;br /&gt;
&lt;br /&gt;
3、配置物理层协议是1Mbit模式。&lt;br /&gt;
&lt;br /&gt;
4、扫描的间隔时间BEARER_SCAN_INT_DEFAULT_MS，扫描的窗口时间BEARER_SCAN_WINDOW_DEFAULT_MS&lt;br /&gt;
&lt;br /&gt;
5、射频的发射功率RADIO_POWER_NRF_0DBM。&lt;br /&gt;
&lt;br /&gt;
6、BLE广播包最大长度RADIO_CONFIG_ADV_MAX_PAYLOAD_SIZE。&lt;br /&gt;
&lt;br /&gt;
最后我们会在nrf_mesh_enable()函数中调用scanner_enable()函数去启动扫描，这个大家可以自己查看一下代码，比较容易理解。&lt;br /&gt;
[[文件:SCAN-04.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:SCAN-05.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
最后我们来看一下scanner_packet_process_cb()回调函数，其中我们可以直接拿到的扫描的数据包是rx_data，包含了数据的类型、数据内容以及数据长度等等信息。到这边我们的扫描功能是已经完成的，但是我们还需要再看下下面一部分的ad_listener的流程。&lt;br /&gt;
&lt;br /&gt;
=== Subscription（模型订阅） ===&lt;br /&gt;
我们需要注意下ad_listener_process()函数，这个函数是将我们扫描到的订阅数据（不符合标准的BLE广播数据，则认为是侦听到的订阅数据）传递到订阅者进行处理（订阅者指的是带订阅功能的模型）。这个是和我们上方说的关注点二ad_listener_subscribe()函数是一脉相承的。&lt;br /&gt;
[[文件:SCAN-06.png|边框|居中|无框|1200x1200像素]]然后我们来对照着看一下ad_listener_subscribe()函数以及ad_listener_process()函数。这两个函数一个是定义了侦听订阅，一个是将扫描获取到的数据传递给订阅者。这两者间的运作关系，大家有兴趣的可以继续查阅他的底层代码逻辑处理，一个是向list中添加订阅者信息，一个是将数据通过p_listener-&amp;gt;handler()将数据传递给订阅者处理。&lt;br /&gt;
[[文件:SCAN-07.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:SCAN-08.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
最终当我们扫描到订阅消息，并调用ad_listener_process()函数传递时，会触发我们注册的回调nrf_mesh_listen（调用ad_listener_subscribe(&amp;amp;m_nrf_mesh_listener)时，在m_nrf_mesh_listener中）。我们来看下nrf_mesh_listen函数中的处理，其中我们先从数据包中解析了数据的类型type，然后根据不同类型的数据进行相应的处理。&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+&lt;br /&gt;
|AD_TYPE_MESH &lt;br /&gt;
|蓝牙网格物体的AD类型&lt;br /&gt;
|-&lt;br /&gt;
|AD_TYPE_BEACON &lt;br /&gt;
|蓝牙网状信标的AD类型&lt;br /&gt;
|-&lt;br /&gt;
|AD_TYPE_PB_ADV &lt;br /&gt;
|PB-ADV消息的AD类型&lt;br /&gt;
|-&lt;br /&gt;
|AD_TYPE_DFU &lt;br /&gt;
|nRF OpenMesh消息的AD类型&lt;br /&gt;
|}&lt;br /&gt;
[[文件:SCAN-09.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
== ③入网验证 ==&lt;br /&gt;
经过前两章的学习，我们对于BLE MESH的广播及扫描的代码流程已经有了一定的了解，也就是已经大概的明白了数据的发送和接收的流程。&lt;br /&gt;
&lt;br /&gt;
那么从这一章节开始，我们将会给大家展示，如何配置一个节点设备入网。配置者设备，我们使用的是手机APP：nRF Mesh。&lt;br /&gt;
&lt;br /&gt;
我们从main文件中的start()函数开始，来看一下入网的代码处理及其流程。在start()函数中我们最后是调用的mesh_provisionee_prov_start()函数来启动的节点配置功能，我们来查看一下它所携带的参数prov_start_params。这部分的参数都是比较重要的，有携带的是设备的信息的、有携带认证密码的，有回调返回的，所以我们会挨个介绍一下。&lt;br /&gt;
[[文件:Prov-01.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
首先携带的是static_auth_data，这个是我们带外OOB认证的密码。如果我们在使用nRF Mesh去进行身份验证的时候，选择使用Static OOB，那么当我们在进行PB-GATT的连接过程中，会弹出一个新的窗口，要求我们去输入这个静态的128bit的OOB密钥。&lt;br /&gt;
[[文件:Prov-02.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Prov-14.png|边框|居中|无框]]&lt;br /&gt;
[[文件:Prov-15.png|边框|居中|无框]]&lt;br /&gt;
接下来携带的是provisioning_complete_cb()回调函数，这个函数处理的是节点设备成功的被配置入网，在这里我们可以获取到我们的节点设备被分配的node_Address。&lt;br /&gt;
[[文件:Prov-03.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
然后携带是device_identification_start_cb()回调函数，这个回调函数是用于通知开发者，我们的节点设备当前已经开始配置。&lt;br /&gt;
[[文件:Prov-04.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
再然后携带的是URI【统一资源标识符（Uniform Resource Identifier，URI)】，这个是用于标识我们的设备类型的，这个数据会通过AES-CMAC算法加密后添加到我们的Unprov Beacon数据的末尾。&lt;br /&gt;
&lt;br /&gt;
这里我们是light switch的server设备，所以我们定义URI是EX_URI_LS_SERVER。&lt;br /&gt;
[[文件:Prov-05.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
分析完携带的参数之后，我们继续来查看mesh_provisionee_prov_start()函数。在这个函数中，我们首先是定义了一下prov_caps，也就是配置支持的OOB功能。然后我们调用nrf_mesh_generate_keys()函数去生成有效的密钥对等待使用。&lt;br /&gt;
&lt;br /&gt;
再然后是调用nrf_mesh_prov_init()函数去初始化prov配置信息的结构体参数，里面有结构体本身m_pro_ctx，需要配置的参数公钥m_public、私钥m_private，以及我们刚刚定义的oob功能prov_caps，最后还携带了一个事件回调函数prov_ect_handler。&lt;br /&gt;
&lt;br /&gt;
接下来是PB-ADV以及PB-GATT的处理过程，这个前面已经讲解过，这里不再赘述。&lt;br /&gt;
&lt;br /&gt;
最后我们调用provisionee_start()函数去开启配置。&lt;br /&gt;
[[文件:Prov-06.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
我们继续来追踪一下provisionee_start()函数，在这个函数中我们调用nrf_mesh_prov_listen()函数去开启侦听承载层的消息。这里我们需要侦听PB-ADV以及PB-GATT两个。&lt;br /&gt;
[[文件:Prov-07.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
然后在nrf_mesh_prov_listen()函数中，我们调用prov_provisionee_listen()函数携带我们刚刚传入的nrf_mesh_prov_ctx_t结构体，以及p_bearer、URI、oob_info_source等参数信息，继续向下传递。&lt;br /&gt;
[[文件:Prov-08.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
最终我们来查看下prov_provisionee_listen()函数，在这个函数中我们传递了p_bearer-&amp;gt;p_callbacks给m_prov_callbacks回调，然后调用p_bearer-&amp;gt;p_interface-&amp;gt;listen_start()函数去启动PB-ADV以及PB-GATT的侦听。&lt;br /&gt;
[[文件:Prov-09.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
当我们在侦听之后，接收到任何配置相关的消息，都将通过m_prov_callbacks()函数回调传递给我们处理，大家可以自行看下其中4个不同的功能函数，具体处理的都是什么消息。&lt;br /&gt;
[[文件:Prov-10.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
在prov_provisionee_listen()函数中，我们可以看到回调函数指针m_prov_callbacks指向的是p_bearer-&amp;gt;p_callbacks，我们一路向上查找，最终可以找到这个p_bearer-&amp;gt;p_callbacks函数就是指的prov_evt_handler()函数。所以其实我们用户可以在这一层去查看比较重要的配置事件返回，而不需要到上面的m_prov_callbacks中这样偏底层的地方去查看。&lt;br /&gt;
&lt;br /&gt;
在这个回调函数中，我们可以看到mian文件中注册的prov_device_identification_start_cb、prov_device_identification_stop_cb、provisioning_aborted_cb的返回。&lt;br /&gt;
[[文件:Prov-11.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
那么除了上方已经存在的几个回调，我们可以看到mian文件中的start()函数下的prov_start_params参数，还缺少一个配置完成的回调prov_complete_cb()。&lt;br /&gt;
&lt;br /&gt;
这个回调的返回方式比较特殊，和上方其他的都不同，是由SD（softdevice）直接从底层返回给我们的。注册这个回调的函数是NRF_SDH_STATE_OBSERVER，返回的回调函数是sd_state_evt_handler。其中触发的事件NRF_SDH_EVT_STATE_ENABLED，这个是在mian文件中的initialize()函数下的ble_stack_init()函数中就被触发的，这个大家可以自行查看一下，不太重要，因为都是nordic底层处理好的消息。&lt;br /&gt;
[[文件:Prov-13.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Prov-12.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
以上整个配置流程的处理，不太好分步展示相应的现象，这个大家可以自己使用SES在线仿真的方式去调试一下流程，这里我们直接给大家展示整个流程打印出的相应的信息，以及nRF Mesh中暂时出来的现象。&lt;br /&gt;
[[文件:Prov-16.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Prov-17.png|边框|居中|无框]]&lt;br /&gt;
&lt;br /&gt;
== ④设备信息分配 ==&lt;br /&gt;
当我们成功的配置节点设备入网之后，我们需要给节点设备主要分配如下几个信息，有关这几个要点的介绍，可以查看“BLE-Mesh技术揭秘”第3章节：&lt;br /&gt;
&lt;br /&gt;
1、节点在网络中的地址Node Address&lt;br /&gt;
&lt;br /&gt;
2、网络密钥Netkey&lt;br /&gt;
&lt;br /&gt;
3、应用密钥Appkey&lt;br /&gt;
&lt;br /&gt;
4、元素的地址Element Address&lt;br /&gt;
&lt;br /&gt;
5、绑定model的Appkey，分配Publish Address，Subscription Address&lt;br /&gt;
&lt;br /&gt;
=== Node Address ===&lt;br /&gt;
首先我们来看一下我们使用nRF Mesh成功配置我们的节点之后，打印出来的消息，开始的紫色字体显示了我们给当前节点设备分配的Node Address。&lt;br /&gt;
&lt;br /&gt;
后面紧接着还有四段黄色字体的内容（这个部分大家的工程是不会打印的，是我们修改例程打印出来给大家展示一下），这四个部分就是我们配置节点的时候，配置者设备（nRF Mesh）给节点设备的四个主要指令，这些指令的具体功能，大家可以查看mesh的协议手册。[[文件:Config-13.png|边框|居中|无框|1080x1080像素]]在代码中，我们会在mian文件的provisioning_complete_cb()回调函数中获取并打印我们的Node Address，这个回调触发的含义就是我们的节点配置已经完成了。&lt;br /&gt;
&lt;br /&gt;
具体这个回调在代码中的回调及触发流程，大家看可以查看“③入网验证”的说明。[[文件:Config-20.png|边框|居中|无框|1200x1200像素]]我们使用nRF Mesh配置好节点设备之后，app界面显示内容如下，包含了节点的信息（地址、名称、配置时间等等），元素和模型，以及Netkey和Appkey等几个重要的设备信息。[[文件:Config-01.jpg|边框|居中|无框]]&lt;br /&gt;
[[文件:Config-04.jpg|边框|居中|无框]]&lt;br /&gt;
&lt;br /&gt;
=== Netkey ===&lt;br /&gt;
我们点击查看一下Network key，可以看到里面有一个Network Key 1，这是一个16字节的网络密钥。我们可以在nRF Mesh的Settings界面去增加或者删除一个network key，用户可以自定义此密钥。[[文件:Config-02.jpg|边框|居中|无框]]在我们的代码中，我们是在pro_evt_handler回调的NRF_MESH_PROV_EVT_COMPLETE事件中获取这个netkey，这个回调函数的触发流程同样是在“③入网验证”已经有说明。[[文件:Prov-11.png|边框|居中|无框|1200x1200像素]]我们直接看下NRF_MESH_PROV_EVT_COMPLETE事件下的mesh_stack_provisioning_data_store()函数，在这个函数中我们主要的任务，就是将配置者分配给我们的node address、netkey、devkey等等信息，分别做一些存储以及功能绑定。&lt;br /&gt;
[[文件:Netkey-01.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
=== Appkey ===&lt;br /&gt;
同样的我们打开Application Key，可以看到其中包含的3个密钥，这里我们只选用了Application Key 1作为我们的密钥。大家同样可以在nRF Mesh的Settings界面去增加或者删除一个App key，用户可以自定义此密钥。[[文件:Config-03.jpg|边框|居中|无框]]appkey这里和netkey不同，netkey是我们网络中的节点设备以及我们配置者设备共有的一个密钥，只有拥有这个密钥，我们才能在当前网络中通信，所以当我们的节点设备配置入网后，会立刻得到netkey（必须要分配给我的节点设备才行）。&lt;br /&gt;
&lt;br /&gt;
而appkey只是我们的某个模型用于通信的密钥，所以我们称它为应用密钥，这个appkey的分配，是在我们的config server下。这个config_server_init的初始化流程，是在我们的mesh_stack_init()函数下，就去初始化配置好的。最终当我们有信息配置的时候，会触发opcode_handlers，我们可以从这个操作码列表中查找相应的信息处理。[[文件:Config-22.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Config-23.png|边框|居中|无框|1200x1200像素]]我们来看下config server的opcode_handlers[]操作码处理程序列表，使用黄色标记给大家圈了几个操作码指令，这几个是我们配置一个最简单的server节点设备，都需要使用到的，包含了：&lt;br /&gt;
&lt;br /&gt;
1、appkey的添加，CONFIG_OPCODE_APPKEY_ADD&lt;br /&gt;
&lt;br /&gt;
2、publish发布地址，CONFIG_OPCODE_MODEL_PUBLICATION_VIRTUAL_ADDRESS_SET&lt;br /&gt;
&lt;br /&gt;
3、subscription订阅地址，CONFIG_OPCODE_MODEL_SUBSCRIPTION_ADD&lt;br /&gt;
&lt;br /&gt;
4、模型appkey的绑定，CONFIG_OPCODE_MODEL_APP_BIND&lt;br /&gt;
&lt;br /&gt;
5、netkey的更新，CONFIG_OPCODE_NETKEY_ADD[[文件:Config-21.png|边框|居中|无框|1200x1200像素]]我们可以看到当接收到CONFIG_OPCODE_APPKEY_ADD操作码的数据，会调用handle_appkey_add()函数去处理，在这个函数中我们最终会调用dsm_appkey_add()函数去存储我们的appkey。[[文件:Appkey-01.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
== ⑤Element元素 ==&lt;br /&gt;
元素简单的说就是定义了节点的功能，一些具有复杂功能的节点设备，会包含多个元素。一个节点设备最少需要包含一个元素，我们称之为主元素。&lt;br /&gt;
&lt;br /&gt;
从主元素开始（如果有多个元素，则第一个元素就是主元素），我们会给它分配相应的元素地址。主元素的地址就是节点的地址，第二个元素的地址就是主元素的地址基础上加上1，依次类推。&lt;br /&gt;
&lt;br /&gt;
可以看到在我们的主元素Element：0x0002中包含了3个不同的服务，这3个服务就是我们的模型Model，其中Configuration Server与Health Server这两个服务，是每一个节点都必须携带的模型。Configuration Server（配置服务）是用于配置节点设备信息，Health Server（健康服务）是用于节点进行网络内的心跳联系。剩下的Generic On Off Server服务，这个就是我们用于控制一个开关量的模型（在例程中我们通过控制LED的IO口高低电平来展示）。[[文件:Config-05.jpg|边框|居中|无框]]在我们的代码中，我们首先需要在nrf_mesh_config_app.h中定义好我们节点设备的最大元素数量，可以看到我们定义#define ACCESS_ELEMENT_COUNT (1)，也就是说我们当前的例程，只含有一个主元素。&lt;br /&gt;
[[文件:Element-01.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
而这个ACCESS_ELEMENT_COUNT ，是在我们开启节点配置的地方被调用的，这个mesh_provsionionee_prov_start()函数在前面的内容中有多次讲到，这里就不再说明了。&lt;br /&gt;
[[文件:Element-02.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
== ⑥model模型 ==&lt;br /&gt;
如果说元素是一个功能集合，那么模型就是元素下的具体的某一个功能。&lt;br /&gt;
&lt;br /&gt;
我们以灯的控制为例，灯的常用控制有开关、亮度、色温几种，其中开关、亮度和色温就是具体的模块，而处理灯控的集合就是元素。&lt;br /&gt;
&lt;br /&gt;
=== Configure Server ===&lt;br /&gt;
配置服务模型，这个的大概代码处理流程在我们的“④设备信息分配-&amp;gt;Appkey”已经有了说明，它是我们节点设备必须包含的一个特殊功能的模型。&lt;br /&gt;
&lt;br /&gt;
它的实际功能就像它的名字一样，就是用于配置（configuration）我们的设备信息，比如appkey的添加删除，密钥的绑定删除，其他的模型的发布和订阅控制。具体包含的操作码的使用方法，我们会在generic on off server模型当中使用的时候来说明。&lt;br /&gt;
&lt;br /&gt;
=== Health Server ===&lt;br /&gt;
健康服务，顾名思义就是判断我们的设备是否在网络中保持健康（正常）工作，它同样是一个必须包含的特殊功能的模型。&lt;br /&gt;
&lt;br /&gt;
它的初始化和config server是一样的，都是放在了mesh_stack_init()函数下。&lt;br /&gt;
[[文件:Health-01.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
我们来看下health_server_init()函数，最终我们会调用access_model_add()函数去初始化模型，并将模型添加到对应的元素中。&lt;br /&gt;
&lt;br /&gt;
其中的access_model_add_params_t参数，包含的分别是元素的索引（将模型添加到第几个元素当中），模型的ID（由mesh协议手册确定好了），以及最最重要的操作码列表，这个列表中会包含我们的指令码，以及对应的函数功能处理。&lt;br /&gt;
[[文件:Health-02.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
最后我们看下具体包含了哪些健康模型操作码：&lt;br /&gt;
&lt;br /&gt;
1、“运行状况当前状态”消息的操作码，HEALTH_OPCODE_CURRENT_STATUS&lt;br /&gt;
&lt;br /&gt;
2、“ 健康故障状态”消息的操作码，HEALTH_OPCODE_FAULT_STATUS&lt;br /&gt;
&lt;br /&gt;
3、“  获取健康注意事项”消息的操作码，HEALTH_OPCODE_ATTENTION_GET&lt;br /&gt;
&lt;br /&gt;
4、“ 健康注意集”消息的操作码，HEALTH_OPCODE_ATTENTION_SET&lt;br /&gt;
&lt;br /&gt;
5、“未确认健康注意事项”消息的操作码，HEALTH_OPCODE_ATTENTION_SET_UNACKED&lt;br /&gt;
&lt;br /&gt;
6、“健康注意状态”消息的操作码，HEALTH_OPCODE_ATTENTION_STATUS&lt;br /&gt;
&lt;br /&gt;
7、“健康故障清除”消息的操作码，HEALTH_OPCODE_FAULT_CLEAR&lt;br /&gt;
&lt;br /&gt;
8、“健康故障清除未确认”消息的操作码，HEALTH_OPCODE_FAULT_CLEAR_UNACKED&lt;br /&gt;
&lt;br /&gt;
9、“健康故障获取”消息的操作码，HEALTH_OPCODE_FAULT_GET&lt;br /&gt;
&lt;br /&gt;
10、“健康故障测试”消息的操作码，HEALTH_OPCODE_FAULT_TEST&lt;br /&gt;
&lt;br /&gt;
11、“未确认健康故障测试”消息的操作码，HEALTH_OPCODE_FAULT_TEST_UNACKED&lt;br /&gt;
&lt;br /&gt;
12、“健康期获取”消息的操作码，HEALTH_OPCODE_PERIOD_GET&lt;br /&gt;
&lt;br /&gt;
13、“健康期设置”消息的操作码，HEALTH_OPCODE_PERIOD_SET&lt;br /&gt;
&lt;br /&gt;
14、“未确认健康期设置”消息的操作码，HEALTH_OPCODE_PERIOD_SET_UNACKED&lt;br /&gt;
&lt;br /&gt;
15、“健康期状态”消息的操作码，HEALTH_OPCODE_PERIOD_STATUS&lt;br /&gt;
[[文件:Health-03.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
在大家的前期使用中，一般也不会真的需要大家去处理health server下的指令，毕竟我们刚开始使用，也很难让设备在网络中出现各种运行问题。但是当大家自己开始开发实际功能时，就一定要注意分析health server下的指令码返回，用来快速解决我们网络中的问题。&lt;br /&gt;
&lt;br /&gt;
=== Generic On Off Server ===&lt;br /&gt;
我们来看下Generic On Off Server的[[文件:Config-14.png|边框|居中|无框|1040x1040像素]]&lt;br /&gt;
[[文件:Config-06.jpg|边框|居中|无框]]&lt;br /&gt;
[[文件:Config-12.jpg|边框|居中|无框]]&lt;br /&gt;
&lt;br /&gt;
==== model bind appkey ====&lt;br /&gt;
[[文件:Config-07.jpg|边框|居中|无框]]&lt;br /&gt;
[[文件:Config-08.jpg|边框|居中|无框]]&lt;br /&gt;
&lt;br /&gt;
==== model publish address ====&lt;br /&gt;
[[文件:Config-09.jpg|边框|居中|无框]]&lt;br /&gt;
[[文件:Config-10.jpg|边框|居中|无框]]&lt;br /&gt;
&lt;br /&gt;
==== model subscription address ====&lt;br /&gt;
[[文件:Config-15.png|边框|居中|无框|1040x1040像素]]&lt;br /&gt;
[[文件:Config-11.jpg|边框|居中|无框]]&lt;br /&gt;
[[分类:NRF52832DK]]&lt;br /&gt;
[[分类:实验手册]]&lt;/div&gt;</summary>
		<author><name>Jinx</name></author>
		
	</entry>
	<entry>
		<id>http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:Health-03.png&amp;diff=2789</id>
		<title>文件:Health-03.png</title>
		<link rel="alternate" type="text/html" href="http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:Health-03.png&amp;diff=2789"/>
		<updated>2020-03-31T07:15:31Z</updated>

		<summary type="html">&lt;p&gt;Jinx：&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Health-03&lt;/div&gt;</summary>
		<author><name>Jinx</name></author>
		
	</entry>
	<entry>
		<id>http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:Health-02.png&amp;diff=2788</id>
		<title>文件:Health-02.png</title>
		<link rel="alternate" type="text/html" href="http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:Health-02.png&amp;diff=2788"/>
		<updated>2020-03-31T07:10:37Z</updated>

		<summary type="html">&lt;p&gt;Jinx：&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Health-02&lt;/div&gt;</summary>
		<author><name>Jinx</name></author>
		
	</entry>
	<entry>
		<id>http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:Health-01.png&amp;diff=2787</id>
		<title>文件:Health-01.png</title>
		<link rel="alternate" type="text/html" href="http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:Health-01.png&amp;diff=2787"/>
		<updated>2020-03-31T06:58:18Z</updated>

		<summary type="html">&lt;p&gt;Jinx：&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Health-01&lt;/div&gt;</summary>
		<author><name>Jinx</name></author>
		
	</entry>
	<entry>
		<id>http://doc.iotxx.com/index.php?title=NRF52832DK-Mesh%E7%BB%84%E7%BD%91%E5%AE%9E%E9%AA%8C&amp;diff=2786</id>
		<title>NRF52832DK-Mesh组网实验</title>
		<link rel="alternate" type="text/html" href="http://doc.iotxx.com/index.php?title=NRF52832DK-Mesh%E7%BB%84%E7%BD%91%E5%AE%9E%E9%AA%8C&amp;diff=2786"/>
		<updated>2020-03-31T06:49:54Z</updated>

		<summary type="html">&lt;p&gt;Jinx：/* ⑥model模型 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;蓝牙Mesh和BLE是并行发展的一个独立分支，虽然底层共用Physical Layer和Link Layer，但是上层协议并不相同。因此，nRF52832的Mesh例程，并不在BLE协议栈sdk中，而是安装一个扩展包。&lt;br /&gt;
&lt;br /&gt;
本文介绍蓝牙Mesh相关的环境搭建和实验介绍。&lt;br /&gt;
&lt;br /&gt;
== Mesh开发环境 ==&lt;br /&gt;
&lt;br /&gt;
=== 蓝牙Mesh SDK ===&lt;br /&gt;
SDK位置：&amp;lt;code&amp;gt;归档资料/2-协议栈SDK/nRF5_SDK_Mesh_v310.zip&amp;lt;/code&amp;gt;，注意文件名中的Mesh字样。&lt;br /&gt;
&lt;br /&gt;
和BLE协议栈一样，将该压缩包直接解压到BLE协议栈同级目录即可使用，解压后的SDK路径如下：&amp;lt;code&amp;gt;E:\project-nordic\nRF5_SDK_Mesh_v310&amp;lt;/code&amp;gt;&lt;br /&gt;
[[文件:NRF52832DK-MESH-SDK-INSTALL.gif|居中|无框|750x750像素]]&lt;br /&gt;
&lt;br /&gt;
=== 集成开发软件SES ===&lt;br /&gt;
SES时是Segger Embedded Studio编译器的缩写，用来编译蓝牙Mesh代码，SES功能与IAR或Keil类似。&lt;br /&gt;
&lt;br /&gt;
备注：Nordic为何使用SES而不是IAR，原因不得而知。&lt;br /&gt;
&lt;br /&gt;
==== SES安装 ====&lt;br /&gt;
SES软件位置：&amp;lt;code&amp;gt;归档资料/7-编译器SES/Setup_EmbeddedStudio_ARM_v410_win_x64.exe&amp;lt;/code&amp;gt;。&lt;br /&gt;
&lt;br /&gt;
我们双击打开安装界面，按照如下动图所示方式进行安装（可以全部默认安装）。[[文件:Ses install.gif|边框|居中|无框|556x556像素]]&lt;br /&gt;
&lt;br /&gt;
==== SES许可证激活 ====&lt;br /&gt;
我们双击打开安装好的SES，选择编译自带测试例程，此时弹出需要激活的界面。&lt;br /&gt;
[[文件:Licensed1.jpg|边框|居中|无框|1205x1205像素]]&lt;br /&gt;
由于我们准备使用SES编译nordic的NRF52832的SDK，所以这里我们选择Activate Your Free License。这里因为nordic公司已经帮客户想SEGGER公司买下了使用的版权，所以是免费的许可证。&lt;br /&gt;
[[文件:Licensed2.jpg|边框|居中|无框|736x736像素]]&lt;br /&gt;
我们按照要求填写好信息，主要是邮箱地址（用于接收SEGGER公司发给我们的许可证），填写完成之后点击Request License。下面是我们接收的邮件信息，我们将红色框内的内容复制下来。&lt;br /&gt;
[[文件:Ses install3.png|边框|居中|无框|1301x1301像素]]&lt;br /&gt;
打开SES，选择Tools\License Manager...，我们选择Activate Embedded Studio，打开如下界面，并将我们的许可证粘贴进去，然后点击Install License。&lt;br /&gt;
[[文件:Licensed4.png|边框|居中|无框|481x481像素]]&lt;br /&gt;
安装好许可证之后，跳转到如下的界面。&lt;br /&gt;
[[文件:Licensed5.png|边框|居中|无框|481x481像素]]&lt;br /&gt;
此时我们的许可证激活完毕（有时候需要等待一段时间，才会显示激活成功）。&lt;br /&gt;
[[文件:Licensed6.png|边框|居中|无框|515x515像素]]&lt;br /&gt;
&lt;br /&gt;
== Light_switch ==&lt;br /&gt;
&lt;br /&gt;
=== 实验简介 ===&lt;br /&gt;
此示例演示了包含充当两个角色的设备的网状生态系统：配置角色（Provisioner role）和节点角色（Node role、provisionee role）。 它还通过在应用程序中使用[Generic OnOff model]来演示了如何使用Mesh模型。&lt;br /&gt;
&lt;br /&gt;
该示例由三个较小的示例组成：&lt;br /&gt;
&lt;br /&gt;
Light switch server：一个实现了[Generic OnOff server model]的简约服务器，用于接收状态数据并控制板上LED 1的状态。&lt;br /&gt;
&lt;br /&gt;
Light switch client：一个实现了[Generic OnOff client model]的简约客户端。当用户按下任意按钮时，OnOff Set消息将发送到配置的目标地址。&lt;br /&gt;
&lt;br /&gt;
Mesh Provisioner：一个简单的静态配置设备应用，用于建立演示网络，该配置设备在一个网状网络中配置所有节点。 此外，配置设备还可以在这些节点上配置网格模型[Generic OnOff model]实例的绑定以及发布和订阅设置，以使它们能够相互通信。&lt;br /&gt;
&lt;br /&gt;
[Generic OnOff Client/Server]模型用于操纵打开/关闭状态。请注意，当服务器设置了发布地址（如本例所示）时，服务器会将其状态更改的任何操作披露到其发布地址。&lt;br /&gt;
&lt;br /&gt;
下图给出了将由静态供应商设置的网状网络的整体视图， 括号中的数字表示预配置程序分配给这些节点的地址。&lt;br /&gt;
[[文件:Mesh network.png|边框|居中|无框|727x727像素]]&lt;br /&gt;
灯开关服务器和灯开关客户端示例均具有预配方角色。它们支持通过广告承载（PB-ADV）和GATT承载（PB-GATT）进行配置，并且还支持网状代理服务器（Proxy Service），但是不支持代理客户端（Proxy Client）。&lt;br /&gt;
&lt;br /&gt;
=== 硬件说明 ===&lt;br /&gt;
完成这个实验，我们最少需要两个开发板硬件用做我们的节点设备（Node）：&lt;br /&gt;
* 一个开发板用做client&lt;br /&gt;
* 一个或者多个开发板用做server&lt;br /&gt;
此外，我们还需要以下之一作为我们的配置设备（Provisioner）：&lt;br /&gt;
* 如果您决定使用静态预配器示例，则多准备一个开发板&lt;br /&gt;
* 如果您决定使用app程序进行设置，则需要安装手机app@link_nrf_mesh_app（@link_nrf_mesh_app_ios或@link_nrf_mesh_app_android）。&lt;br /&gt;
{{Note|text=默认我们使用3个开发板完成我们的实验，3个开发板分别对应client、server以及provisioner三个类型。|type=warning}}&lt;br /&gt;
&lt;br /&gt;
=== 软件说明 ===&lt;br /&gt;
1、我们使用NrfGo上位机，依次对三个开发板进行擦除及softdevice的烧写&lt;br /&gt;
[[文件:Lightswitch nrfgo.png|边框|居中|无框|1013x1013像素]]&lt;br /&gt;
2、使用SEGGER Embedded Studio for ARM编译器分别打开我们`&amp;lt;InstallFolder&amp;gt;/examples/light_switch`路径下的3个实验，并完成编译&lt;br /&gt;
&lt;br /&gt;
●\client\light_switch_client_nrf52832_xxAA_s132_6_1_0.emProject&lt;br /&gt;
&lt;br /&gt;
●\server\light_switch_server_nrf52832_xxAA_s132_6_1_0.emProject&lt;br /&gt;
&lt;br /&gt;
●\provisioner\light_switch_provisioner_nrf52832_xxAA_s132_6_1_0.emProject&lt;br /&gt;
[[文件:Lightswitch SES build.png|边框|居中|无框|1013x1013像素]]&lt;br /&gt;
3、此时我们对3个开发板分别进行固件的烧写，分别烧写client、server、provisioner（建议大家给3个开发板贴上标签，方便后续区分）&lt;br /&gt;
[[文件:Lightswitch SES download.png.gif|边框|居中|无框|1009x1009像素]]&lt;br /&gt;
&lt;br /&gt;
=== 实验现象 ===&lt;br /&gt;
{{Note|text=注意：保证我们的3个设备都是刚按照软件说明部分的步骤配置完成，并且没有进行过任何硬件控制（按键操作）。目的是保证所有设备均未被我们人为配置过，没有进行过网络配置，否则可能有任意异常（不同网络配置，导致的不同现象）|type=danger}}&lt;br /&gt;
1、首先我们分别给client和provisioner供电，然后我们按下provisioner的按键S1：&lt;br /&gt;
&lt;br /&gt;
●provisoner：LED灯点亮，代表正在配置查找设备，配置组网&lt;br /&gt;
&lt;br /&gt;
●client：首先LED3和LED4闪烁，代表正在组网；LED1-LED4四个灯一起闪烁，代表组网完成&lt;br /&gt;
&lt;br /&gt;
我们如果打开RTT检测log打印，可以看到如下的信息，这个时候我们已经给client设备分配了Node Address为0x0100。provisioner信息（上），client信息（下）。&lt;br /&gt;
[[文件:Lightswitch rtt provisoner1.png|边框|无框|1080x1080像素|居中]]&lt;br /&gt;
[[文件:Lightswitch_rtt_node_client.png|边框|居中|无框|656x656像素]]&lt;br /&gt;
&lt;br /&gt;
2、使用provisioner配置好client之后，我们给server开发板上电，这个时候provisioner将会配置server入网：&lt;br /&gt;
&lt;br /&gt;
●provisoner：LED1点亮代表正在配置网络，LED2点亮代表网络配置完成&lt;br /&gt;
&lt;br /&gt;
●server：首先LED3和LED4闪烁，代表正在组网；LED1-LED4四个灯一起闪烁，代表组网完成&lt;br /&gt;
&lt;br /&gt;
我们打开RTT检测log打印，可以看到server已经配置入网，并且被分配了地址Node Address为0x0104。provisioner信息（上），server信息（下）。&lt;br /&gt;
[[文件:Lightswitch rtt provisoner2.png|边框|无框|710x710像素|居中]]&lt;br /&gt;
[[文件:Lightswitch rtt node server.png|边框|无框|656x656像素|居中]]&lt;br /&gt;
&lt;br /&gt;
3、此时整个网络的配置已经完成，这个时候我们可以按下client上的按键来控制server上的LED点亮或者熄灭。&lt;br /&gt;
&lt;br /&gt;
由于我们的server被provisioner分配的Node Address是0x0104（偶数），所以我们通过client的S3控制server的LED1点亮，通过client的S4控制server的LED1熄灭。&lt;br /&gt;
{{Note|text=client设备对于server设备的控制，通过Node Address的奇偶位不同，分成了两个组。&lt;br /&gt;
client的S1和S2分别控制Node Address为奇数的server设备的LED1点亮和熄灭。&lt;br /&gt;
&lt;br /&gt;
client的S3和S4分别控制Node Address为偶数的server设备的LED1点亮和熄灭。|type=warning}}&lt;br /&gt;
我们打开RTT检测log打印，可以看到client分别按下button2（S3）以及button3（S4），分别会设置server的GPIO输出高低电平。client信息（上），server信息（下）。&lt;br /&gt;
[[文件:Lightswitch rtt node client2.png|边框|无框|656x656像素|居中]]&lt;br /&gt;
[[文件:Lightswitch rtt node server2.png|边框|无框|656x656像素|居中]]&lt;br /&gt;
&lt;br /&gt;
4、至此我们的mesh组网的lightswitch实验测试完成。&lt;br /&gt;
&lt;br /&gt;
=== 源码详解 ===&lt;br /&gt;
&lt;br /&gt;
== Beaconing example ==&lt;br /&gt;
&lt;br /&gt;
=== 实验简介 ===&lt;br /&gt;
&lt;br /&gt;
=== 硬件说明 ===&lt;br /&gt;
&lt;br /&gt;
=== 软件说明 ===&lt;br /&gt;
&lt;br /&gt;
=== 实验现象 ===&lt;br /&gt;
&lt;br /&gt;
=== 源码详解 ===&lt;br /&gt;
&lt;br /&gt;
== Coexistence example ==&lt;br /&gt;
&lt;br /&gt;
=== 实验简介 ===&lt;br /&gt;
&lt;br /&gt;
=== 硬件说明 ===&lt;br /&gt;
&lt;br /&gt;
=== 软件说明 ===&lt;br /&gt;
&lt;br /&gt;
==== 将Mesh集成到nRF5 SDK示例中 ====&lt;br /&gt;
用于网格的nRF5 SDK与Nordic的nRF5 SDK兼容。 这使您可以将nRF5 SDK中的资源包括在现有的Mesh项目中，也可以将nRF5 SDK中的Mesh功能包括在nRF5 SDK示例中。&lt;br /&gt;
&lt;br /&gt;
请参阅构建网格堆栈和示例，以获取有关如何下载和安装nRF5 SDK的信息。 查看共存示例，以了解如何将nRF5 SDK功能与用于网格的nRF5 SDK同时使用。&lt;br /&gt;
&lt;br /&gt;
===== 动态记忆 =====&lt;br /&gt;
在将nRF5 SDK功能与nRF5 Mesh SDK功能一起使用时（如并存示例所示），您可能会遇到以下情况：应用程序注意事项可能需要更改动态内存的分配方式和可分配的动态内存量。 网格堆栈使用网格内存管理器界面进行动态内存分配。 默认的后端mesh_mem_stdlib.c使用标准库malloc（），该库需要定义足够大的堆大小。 可以通过用另一个内存管理器替换后端来更改此行为。&lt;br /&gt;
&lt;br /&gt;
如果使用Segger Embedded Studio构建应用程序，请在“项目选项”&amp;gt;“代码”&amp;gt;“运行时内存区域”设置中将“堆大小”设置为8192字节。&lt;br /&gt;
&lt;br /&gt;
===== 并发的SoftDevice和Mesh活动 =====&lt;br /&gt;
同时运行SoftDevice和Mesh时，最大的性能问题通常来自无线电时间争用。虽然SoftDevice通常在预定的短脉冲中运行，但Mesh会尝试尽可能多地使用无线电。只要SoftDevice没有无线电活动，Mesh就会连续扫描和通告。 SoftDevice活动将减少Mesh广播的时间，并且为了保持一致的Mesh性能，必须在不影响用户体验的情况下尽可能保守地设置SoftDevice无线电参数。&lt;br /&gt;
&lt;br /&gt;
在使用SoftDevice进行广告时，请尝试使用您的使用情况可以容许的最大广告间隔。如果可能，请在不需要时关闭SoftDevice广告商，仅在您希望收到连接请求时才激活它。如果您只需要发送不可连接的，不可扫描的广告（例如，针对第三方信标协议），请使用Mesh Advertiser API，因为它与Mesh一起使用时可以进行最少的上下文切换。&lt;br /&gt;
&lt;br /&gt;
当SoftDevice在连接中运行时：&lt;br /&gt;
&lt;br /&gt;
尝试协商应用程序可以允许的最大连接间隔。如果需要通过连接的高吞吐量，则在每个连接事件中发送更多的数据要比减少连接间隔更好，因为大部分开销来自上下文切换。&lt;br /&gt;
&lt;br /&gt;
如果网状设备在其SoftDevice连接中充当外围设备（从设备），则还可以增加“从设备等待时间”，这应使SoftDevice跳过连接事件，而不会增加通过链接的任何传出数据传输的等待时间。&lt;br /&gt;
&lt;br /&gt;
与广告一样，建议仅在需要时才保持连接活动。空闲的软设备连接对网状网性能的影响几乎与具有大量流量的连接一样多，特别是在从属延迟较低的情况下。&lt;br /&gt;
&lt;br /&gt;
基于SoftDevice的扫描对所有SoftDevice活动的Mesh性能影响最大。当SoftDevice正在扫描时，Mesh无法接收数据包，因此每个SoftDevice扫描窗口都会替换Mesh扫描。仅在尝试建立连接或需要活动扫描时才应使用SoftDevice扫描。如果需要常规的被动BLE扫描（用于侦听信标或其他第三方活动），请通过使用nrf_mesh_rx_cb_set函数设置RX回调来挂接到Mesh扫描器。如果您的应用程序需要主动扫描或需要启动连接，则应尽可能保守地设置扫描参数。较长的扫描间隔和较短的扫描窗口将使Mesh拥有更多的时间进行自身的无线电活动。类似地，建立上下文时在短时间内执行连续扫描可能比执行长时间运行的占空比扫描有利，因为上下文切换会导致很多不必要的开销。最后，强烈建议为SoftDevice连接启动调用设置超时，以避免长时间的空闲扫描。&lt;br /&gt;
&lt;br /&gt;
由于Mesh无法主动阻止SoftDevice无线电活动，因此减少同时运行SoftDevice活动的设备上的Mesh活动不会直接影响SoftDevice的性能。但是，附近的其他Mesh设备会干扰广告渠道中的SoftDevice活动，这可能会使连接启动花费更长的时间。&lt;br /&gt;
&lt;br /&gt;
通常，Mesh必须在SoftDevice活动之间执行其所有无线电操作，因此，如果设备在执行SoftDevice无线电操作时正在发送大量Mesh数据包，则它将在此上花费大部分Mesh时间，而不是接收传入的数据。为了解决这个问题，请根据可用的无线电时间调整输出数据包的数量，以减少Mesh数据包的发送。如果可能，请通过在CORE_TX_ROLE_RELAY角色上调用mesh_opt_core_adv_set来暂停耗时的SoftDevice操作期间的Mesh数据包中继。&lt;br /&gt;
&lt;br /&gt;
===== 在用于网格示例的nRF5 SDK中包括nRF5 SDK =====&lt;br /&gt;
根据您的工具链：&lt;br /&gt;
&lt;br /&gt;
使用Segger Embedded Studio时，请添加代码文件并包括相应SES项目文件的路径。&lt;br /&gt;
&lt;br /&gt;
使用CMake构建用于网格堆栈的nRF5 SDK时，添加代码文件并包括指向相应CMakeLists.txt文件的路径。 SDK_ROOT根符号用于引用nRF5 SDK安装文件夹（例如，请参阅Light Switch服务器示例中的CMakeLists.txt）。&lt;br /&gt;
&lt;br /&gt;
网格示例项目已经在其include /目录中包含sdk_config.h文件。 这些文件是默认SDK配置文件的副本，并且网格示例所需的所有更改都包含在示例目录中的include / app_config.h文件中。&lt;br /&gt;
&lt;br /&gt;
注意，&lt;br /&gt;
&lt;br /&gt;
必须先在SDK配置文件中显式启用某些SDK功能，然后才能使用它们。 有关详细信息，请参见SDK文档页面SDK配置头文件。&lt;br /&gt;
&lt;br /&gt;
===== 在nRF5 SDK示例中包括用于网格功能的nRF5 SDK =====&lt;br /&gt;
在nRF5 SDK示例的项目文件中包括来自nRF5 SDK for Mesh的以下源文件：&lt;br /&gt;
* All C files in &amp;lt;code&amp;gt;mesh/core/src&amp;lt;/code&amp;gt;&lt;br /&gt;
* All C files in &amp;lt;code&amp;gt;mesh/bearer/src&amp;lt;/code&amp;gt;&lt;br /&gt;
* All C files in &amp;lt;code&amp;gt;mesh/prov/src&amp;lt;/code&amp;gt; except nrf_mesh_prov_bearer_gatt.c&lt;br /&gt;
* All C files in &amp;lt;code&amp;gt;mesh/access/src&amp;lt;/code&amp;gt;&lt;br /&gt;
* All C files in &amp;lt;code&amp;gt;mesh/dfu/src&amp;lt;/code&amp;gt;&lt;br /&gt;
* All C files in &amp;lt;code&amp;gt;mesh/stack/src&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;models/foundation/config/src/config_server.c&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;models/foundation/config/src/composition_data.c&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;models/foundation/config/src/packed_index_list.c&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;models/foundation/health/src/health_server.c&amp;lt;/code&amp;gt;&lt;br /&gt;
* Any other mesh models that are used in your application&lt;br /&gt;
* &amp;lt;code&amp;gt;external/micro-ecc/uECC.c&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;examples/common/src/assertion_handler_weak.c&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;examples/common/src/mesh_provisionee.c&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
注意&lt;br /&gt;
&lt;br /&gt;
如果不需要各种网格特征（例如DFU），则可以从项目文件中省略相应的文件。但是，在其位置添加examples / nrf_mesh_weak.c以提供缺少的API函数的存根。&lt;br /&gt;
&lt;br /&gt;
将以下文件夹添加到nRF5 SDK示例的项目包含路径：&lt;br /&gt;
* &amp;lt;code&amp;gt;mesh/core/api&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;mesh/core/include&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;mesh/bearer/api&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;mesh/bearer/include&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;mesh/prov/api&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;mesh/prov/include&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;mesh/access/api&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;mesh/access/include&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;mesh/dfu/api&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;mesh/dfu/include&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;mesh/stack/api&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;models/foundation/config/include&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;models/foundation/health/include&amp;lt;/code&amp;gt;&lt;br /&gt;
* Path to include folder of any other mesh models that are used in your application&lt;br /&gt;
* &amp;lt;code&amp;gt;external/micro-ecc&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;examples/common/include&amp;lt;/code&amp;gt;&lt;br /&gt;
* Path to any other resources in the mesh examples that are used in your application&lt;br /&gt;
将以下预处理器符号添加到nRF5 SDK示例的项目文件中：&lt;br /&gt;
* &amp;lt;code&amp;gt;NRF52_SERIES&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;NRF_MESH_LOG_ENABLE=NRF_LOG_USES_RTT&amp;lt;/code&amp;gt; (because logging in the mesh stack relies on RTT)&lt;br /&gt;
* &amp;lt;code&amp;gt;CONFIG_APP_IN_CORE&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== 可选变更 =====&lt;br /&gt;
此外，您可能需要应用以下一项或多项更改：&lt;br /&gt;
&lt;br /&gt;
如果与nRF5 SDK集成在一起，则可能需要更新在网格堆栈中使用simple_hal模块的示例以使用Nordic nRF5 SDK bsp模块。可以同时使用两者，但是在这种情况下，必须从其中之一删除GPIOTE_IRQHandler，并且只有一个模块可以注册回调函数。&lt;br /&gt;
&lt;br /&gt;
如果原始的Nordic nRF5 SDK示例使用了SoftDevice，请确保在启用SoftDevice之后初始化并启用了网格堆栈。在这种情况下，必须将SoftDevice事件转发到网格堆栈。将以下代码添加到您的应用程序：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;#&amp;lt;/nowiki&amp;gt;include“ nrf_sdh_soc.h”&lt;br /&gt;
&lt;br /&gt;
＃定义MESH_SOC_OBSERVER_PRIO 0&lt;br /&gt;
&lt;br /&gt;
静态void mesh_soc_evt_handler（uint32_t evt_id，void * p_context）&lt;br /&gt;
&lt;br /&gt;
{&lt;br /&gt;
&lt;br /&gt;
    nrf_mesh_on_sd_evt（evt_id）;&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
NRF_SDH_SOC_OBSERVER（m_mesh_soc_observer，MESH_SOC_OBSERVER_PRIO，mesh_soc_evt_handler，NULL）;&lt;br /&gt;
&lt;br /&gt;
如果您有多个SOC观察者，请确保一次仅从其中一个观察者将SOC观察者事件转发到网格堆栈。&lt;br /&gt;
&lt;br /&gt;
默认情况下，在网格堆栈以及某些Nordic nRF5 SDK应用程序中启用网络配置的闪存存储。用于此目的的闪光区域可能会重叠并导致错误。为了使闪存存储模块Flash管理器与网状堆栈中的闪存管理器以及Nordic nRF5 SDK中的闪存存储模块安全地共存，请在nrf_mesh_config_app.h中添加以下代码块：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;#&amp;lt;/nowiki&amp;gt;include“ fds.h”&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;#&amp;lt;/nowiki&amp;gt;include“ fds_internal_defs.h”&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;#&amp;lt;/nowiki&amp;gt;define FLASH_MANAGER_RECOVERY_PAGE_OFFSET_PAGES FDS_PHY_PAGES&lt;br /&gt;
&lt;br /&gt;
如果要添加自己的网格功能，而不是使用现有的网格示例，则还需要添加文件nrf_mesh_config_app.h。将其从网格堆栈存储库中的examples / templates文件夹复制到您的项目文件夹中，并删除文件顶部的#error消息。对文件内容进行其他适当的更改，例如将ACCESS_ELEMENT_COUNT和ACCESS_MODEL_COUNT调整为所需数量的元素和模型。&lt;br /&gt;
&lt;br /&gt;
===== Flash放置项目文件 =====&lt;br /&gt;
Segger Embedded Studio项目旁边都有一个flash_placement.xml文件，该文件充当链接器的输入。在flash_placement.xml文件中，nRF5 SDK配置了一组ProgramSection列表，用于放置某些变量。除了nRF5 SDK组件所需的所有ProgramSections外，网格还需要另外两个部分，即nrf_mesh_flash和nrf_mesh_ram。&lt;br /&gt;
&lt;br /&gt;
必须将其他与网格相关的部分添加到flash_placement.xml文件中：&lt;br /&gt;
&lt;br /&gt;
将以下行添加到标记为&amp;lt;MemorySegment name =“ FLASH” ...&amp;gt;的内存段中：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ProgramSection alignment =“ 4” keep =“是” load =“是” name =“。nrf_mesh_flash”输入节=“ *（SORT（.nrf_mesh_flash。*））”“ address_symbol =” __ start_nrf_mesh_flash“ end_symbol =” __ stop_nrf_mesh_flash“ /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
将以下行添加到标记为&amp;lt;MemorySegment name =“ RAM” ...&amp;gt;的内存段中：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ProgramSection alignment =“ 4” keep =“是” load =“否” name =“。nrf_mesh_ram” inputsections =“ *（SORT（.nrf_mesh_ram。*））”“ address_symbol =” __ start_nrf_mesh_ram“ end_symbol =” __ stop_nrf_mesh_ram“ /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
注意&lt;br /&gt;
&lt;br /&gt;
对于共存示例已经进行了此更改。在编辑任何现有nRF5 SDK示例的Flash放置文件时，可以在这些示例中使用flash_placement.xml文件作为参考。&lt;br /&gt;
&lt;br /&gt;
=== 实验现象 ===&lt;br /&gt;
&lt;br /&gt;
=== 源码详解 ===&lt;br /&gt;
&lt;br /&gt;
== Coexistence Uart example ==&lt;br /&gt;
&lt;br /&gt;
=== 实验简介 ===&lt;br /&gt;
&lt;br /&gt;
=== 硬件说明 ===&lt;br /&gt;
&lt;br /&gt;
=== 软件说明 ===&lt;br /&gt;
&lt;br /&gt;
==== SDK UART共存示例 ====&lt;br /&gt;
本示例演示了如何同时使用适用于Mesh的nRF5 SDK和nRF5 SDK示例。它围绕两个示例构建，分为两个部分：&lt;br /&gt;
&lt;br /&gt;
此示例的网格部分从nRF5 SDK for Mesh实现了灯开关客户端示例。&lt;br /&gt;
&lt;br /&gt;
该示例的BLE部分实现了nRF5 SDK的ble_app_uart示例，但以下更改除外：&lt;br /&gt;
* BSP事件SLEEP，DISCONNECT和WHITELIST_OFF被忽略。这使得板子按钮仅控制应用程序的网格部分&lt;br /&gt;
* 增加广告间隔以为网格堆栈留出更多时间&lt;br /&gt;
* 可以通过发送要模拟的灯光开关客户端按钮编号，通过BLE UART来控制灯光开关服务器&lt;br /&gt;
运行此示例的结果是，您将能够使用网状网络，该示例中的网状网络可以代替电灯开关客户端示例。&lt;br /&gt;
&lt;br /&gt;
在开始测试此共存示例之前，请参阅以下页面：&lt;br /&gt;
&lt;br /&gt;
将Mesh集成到nRF5 SDK示例中&lt;br /&gt;
&lt;br /&gt;
灯光开关示例和灯光开关客户端详细信息以及Mesh API&lt;br /&gt;
&lt;br /&gt;
===== 测试示例 =====&lt;br /&gt;
将ble_app_uart_coexist文件夹复制到nRF5 SDK安装路径下的examples / ble_peripheral文件夹中。&lt;br /&gt;
&lt;br /&gt;
在ble_app_uart_coexist / pca10040 / s132 / ses / ble_app_uart_pca10040_s132.emProject中打开Segger Embedded Studio项目。&lt;br /&gt;
&lt;br /&gt;
将MESH_ROOT添加到您的Segger Embedded Studio全局宏列表：&lt;br /&gt;
&lt;br /&gt;
在SES菜单栏中，单击工具&amp;gt;选项...。&lt;br /&gt;
&lt;br /&gt;
在左列中，单击建筑物。&lt;br /&gt;
&lt;br /&gt;
在右列中，双击“全局宏”。&lt;br /&gt;
&lt;br /&gt;
在新行中添加网格根目录：MESH_ROOT = &amp;lt;网格安装路径&amp;gt;。&lt;br /&gt;
&lt;br /&gt;
如灯光开关示例中所述对灯光开关示例设备进行编程，但以下情况除外：&lt;br /&gt;
&lt;br /&gt;
用examples / ble_peripheral / ble_app_uart_coexist / pca10040 / s132 / ses / Output / Release / Exe / ble_app_uart_pca10040_s132.hex替换电灯开关客户端。&lt;br /&gt;
&lt;br /&gt;
现在，您可以并行或顺序运行两个并存的示例：&lt;br /&gt;
&lt;br /&gt;
按照电灯开关示例中的说明运行电灯开关示例。&lt;br /&gt;
&lt;br /&gt;
按照nRF5 SDK文档中的描述运行ble_app_uart示例。&lt;br /&gt;
&lt;br /&gt;
编写与UART RX特性中的数字之一（1、2、3或4）等效的十六进制ASCII码，以模拟按钮按下。&lt;br /&gt;
&lt;br /&gt;
=== 实验现象 ===&lt;br /&gt;
&lt;br /&gt;
=== 源码详解 ===&lt;br /&gt;
&lt;br /&gt;
== mesh工程文件简介 ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
!分组&lt;br /&gt;
!文件&lt;br /&gt;
!说明&lt;br /&gt;
|-&lt;br /&gt;
!Access&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; |'''访问层：'''&lt;br /&gt;
'''访问层定义高层应用程序如何使用上层传输层。 它定义了应用程序数据的格式； 它定义并控制在上层传输层执行的应用程序数据加密和解密； 并检查是否已在正确的网络和应用程序密钥的上下文中接收到传入的应用程序数据，然后再将其转发到更高层。'''&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
|access&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
|access_loopback&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
|access_publish&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
|access_publish_retransmissior&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
|access_reliable&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
|device_state_manager&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
!Application&lt;br /&gt;
! colspan=&amp;quot;2&amp;quot; |&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
|app_error_weak&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
|app_onoff&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
|assertion_handler_weak&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
|ble_softdevice_support&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
|main&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
|mesh_adv&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
|mesh_app_utils&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
|mesh_provisionee&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
|nrf_mesh_weak&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
|rtt_input&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
|sdk_config&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
|simple_hal&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== ***************************************正式整理章节************************************** ==&lt;br /&gt;
&lt;br /&gt;
== mesh实验学习流程概论 ==&lt;br /&gt;
1、首先大家一定要先查看一下我们资料中的手册“BLE-Mesh技术揭秘”，这个是很重要的一个mesh基本概念的介绍，只有看完这个文档，我们才能明白mesh组网是什么，是怎么完成的，后续的我们实验内容，也会在这个文档中查到相应的对照。&lt;br /&gt;
&lt;br /&gt;
2、按照Light_switch测试实验的实验说明，跑通测试流程（可以固件清零重新烧写多测试几遍，有助于增强我们的流程熟练度，利于后面的开发）。light_switch实验是现在nordic提供给我们的唯一的真正意义上的mesh组网通信例程。&lt;br /&gt;
&lt;br /&gt;
3、根据我们下面拆分的组网流程，对照依次的实验说明，进行mesh组网流程的分步学习。前期实验部分我们主要讲解节点设备，配置者设备功能我们使用手机app完成，目的是减少大家的学习难度，配置者设备我们会放在最后一个实验给大家做介绍。&lt;br /&gt;
&lt;br /&gt;
①广播（通信TX）——了解节点设备的广播功能，分为三个重要部分：PB-ADV和Unprov Beacon，PB-GATT的广播，以及模型的Publish用于发送数据。&lt;br /&gt;
&lt;br /&gt;
②扫描（通信RX）——了解节点设备的扫描功能，分为两个重要部分：普通的BLE SCAN功能，以及模块的Subscription用于接收数据。&lt;br /&gt;
&lt;br /&gt;
③入网验证——了解节点设备接入网络的验证过程，实验中使用的是PB-GATT去配置的，包含了：交换公共密钥，完成验证（是否有带外OOB）。&lt;br /&gt;
&lt;br /&gt;
④设备信息分配——启动配置数据分配，给节点分配如下数据：网络key（Netkey）、应用key（Appkey）以及节点设备地址（nodeAddr）。&lt;br /&gt;
&lt;br /&gt;
⑤元素——了解节点设备的元素与模型定义。&lt;br /&gt;
&lt;br /&gt;
⑥模型——了解模型的功能和创建方法，模型的订阅subscription和发布publish功能。&lt;br /&gt;
&lt;br /&gt;
== Light_switch测试实验 ==&lt;br /&gt;
=== 实验简介 ===&lt;br /&gt;
此示例演示了包含充当两个角色的设备的网状生态系统：配置角色（Provisioner role）和节点角色（Node role、provisionee role）。 它还通过在应用程序中使用[Generic OnOff model]来演示了如何使用Mesh模型。&lt;br /&gt;
&lt;br /&gt;
该示例由三个较小的示例组成：&lt;br /&gt;
&lt;br /&gt;
Light switch server：一个实现了[Generic OnOff server model]的简约服务器，用于接收状态数据并控制板上LED 1的状态。&lt;br /&gt;
&lt;br /&gt;
Light switch client：一个实现了[Generic OnOff client model]的简约客户端。当用户按下任意按钮时，OnOff Set消息将发送到配置的目标地址。&lt;br /&gt;
&lt;br /&gt;
Mesh Provisioner：一个简单的静态配置设备应用，用于建立演示网络，该配置设备在一个网状网络中配置所有节点。 此外，配置设备还可以在这些节点上配置网格模型[Generic OnOff model]实例的绑定以及发布和订阅设置，以使它们能够相互通信。&lt;br /&gt;
&lt;br /&gt;
[Generic OnOff Client/Server]模型用于操纵打开/关闭状态。请注意，当服务器设置了发布地址（如本例所示）时，服务器会将其状态更改的任何操作披露到其发布地址。&lt;br /&gt;
&lt;br /&gt;
下图给出了将由静态供应商设置的网状网络的整体视图， 括号中的数字表示预配置程序分配给这些节点的地址。&lt;br /&gt;
[[文件:Mesh network.png|边框|居中|无框|727x727像素]]&lt;br /&gt;
灯开关服务器和灯开关客户端示例均具有预配方角色。它们支持通过广告承载（PB-ADV）和GATT承载（PB-GATT）进行配置，并且还支持网状代理服务器（Proxy Service），但是不支持代理客户端（Proxy Client）。&lt;br /&gt;
&lt;br /&gt;
=== 硬件说明 ===&lt;br /&gt;
完成这个实验，我们最少需要两个开发板硬件用做我们的节点设备（Node）：&lt;br /&gt;
* 一个开发板用做client&lt;br /&gt;
* 一个或者多个开发板用做server&lt;br /&gt;
此外，我们还需要以下之一作为我们的配置设备（Provisioner）：&lt;br /&gt;
* 如果您决定使用静态预配器示例，则多准备一个开发板&lt;br /&gt;
* 如果您决定使用app程序进行设置，则需要安装手机app@link_nrf_mesh_app（@link_nrf_mesh_app_ios或@link_nrf_mesh_app_android）。&lt;br /&gt;
{{Note|text=默认我们使用3个开发板完成我们的实验，3个开发板分别对应client、server以及provisioner三个类型。|type=warning}}&lt;br /&gt;
&lt;br /&gt;
=== 软件说明 ===&lt;br /&gt;
1、我们使用NrfGo上位机，依次对三个开发板进行擦除及softdevice的烧写&lt;br /&gt;
[[文件:Lightswitch nrfgo.png|边框|居中|无框|1013x1013像素]]&lt;br /&gt;
2、使用SEGGER Embedded Studio for ARM编译器分别打开我们`&amp;lt;InstallFolder&amp;gt;/examples/light_switch`路径下的3个实验，并完成编译&lt;br /&gt;
&lt;br /&gt;
●\client\light_switch_client_nrf52832_xxAA_s132_6_1_0.emProject&lt;br /&gt;
&lt;br /&gt;
●\server\light_switch_server_nrf52832_xxAA_s132_6_1_0.emProject&lt;br /&gt;
&lt;br /&gt;
●\provisioner\light_switch_provisioner_nrf52832_xxAA_s132_6_1_0.emProject&lt;br /&gt;
[[文件:Lightswitch SES build.png|边框|居中|无框|1013x1013像素]]&lt;br /&gt;
3、此时我们对3个开发板分别进行固件的烧写，分别烧写client、server、provisioner（建议大家给3个开发板贴上标签，方便后续区分）&lt;br /&gt;
[[文件:Lightswitch SES download.png.gif|边框|居中|无框|1009x1009像素]]&lt;br /&gt;
&lt;br /&gt;
=== 实验现象 ===&lt;br /&gt;
{{Note|text=注意：保证我们的3个设备都是刚按照软件说明部分的步骤配置完成，并且没有进行过任何硬件控制（按键操作）。目的是保证所有设备均未被我们人为配置过，没有进行过网络配置，否则可能有任意异常（不同网络配置，导致的不同现象）|type=danger}}&lt;br /&gt;
1、首先我们分别给client和provisioner供电，然后我们按下provisioner的按键S1：&lt;br /&gt;
&lt;br /&gt;
●provisoner：LED灯点亮，代表正在配置查找设备，配置组网&lt;br /&gt;
&lt;br /&gt;
●client：首先LED3和LED4闪烁，代表正在组网；LED1-LED4四个灯一起闪烁，代表组网完成&lt;br /&gt;
&lt;br /&gt;
我们如果打开RTT检测log打印，可以看到如下的信息，这个时候我们已经给client设备分配了Node Address为0x0100。provisioner信息（上），client信息（下）。&lt;br /&gt;
[[文件:Lightswitch rtt provisoner1.png|边框|无框|1080x1080像素|居中]]&lt;br /&gt;
[[文件:Lightswitch_rtt_node_client.png|边框|居中|无框|656x656像素]]&lt;br /&gt;
&lt;br /&gt;
2、使用provisioner配置好client之后，我们给server开发板上电，这个时候provisioner将会配置server入网：&lt;br /&gt;
&lt;br /&gt;
●provisoner：LED1点亮代表正在配置网络，LED2点亮代表网络配置完成&lt;br /&gt;
&lt;br /&gt;
●server：首先LED3和LED4闪烁，代表正在组网；LED1-LED4四个灯一起闪烁，代表组网完成&lt;br /&gt;
&lt;br /&gt;
我们打开RTT检测log打印，可以看到server已经配置入网，并且被分配了地址Node Address为0x0104。provisioner信息（上），server信息（下）。&lt;br /&gt;
[[文件:Lightswitch rtt provisoner2.png|边框|无框|710x710像素|居中]]&lt;br /&gt;
[[文件:Lightswitch rtt node server.png|边框|无框|656x656像素|居中]]&lt;br /&gt;
&lt;br /&gt;
3、此时整个网络的配置已经完成，这个时候我们可以按下client上的按键来控制server上的LED点亮或者熄灭。&lt;br /&gt;
&lt;br /&gt;
由于我们的server被provisioner分配的Node Address是0x0104（偶数），所以我们通过client的S3控制server的LED1点亮，通过client的S4控制server的LED1熄灭。&lt;br /&gt;
{{Note|text=client设备对于server设备的控制，通过Node Address的奇偶位不同，分成了两个组。&lt;br /&gt;
client的S1和S2分别控制Node Address为奇数的server设备的LED1点亮和熄灭。&lt;br /&gt;
&lt;br /&gt;
client的S3和S4分别控制Node Address为偶数的server设备的LED1点亮和熄灭。|type=warning}}&lt;br /&gt;
我们打开RTT检测log打印，可以看到client分别按下button2（S3）以及button3（S4），分别会设置server的GPIO输出高低电平。client信息（上），server信息（下）。&lt;br /&gt;
[[文件:Lightswitch rtt node client2.png|边框|无框|656x656像素|居中]]&lt;br /&gt;
[[文件:Lightswitch rtt node server2.png|边框|无框|656x656像素|居中]]&lt;br /&gt;
&lt;br /&gt;
4、至此我们的mesh组网的lightswitch实验测试完成。&lt;br /&gt;
&lt;br /&gt;
== ①广播（通信TX） ==&lt;br /&gt;
有关广播部分，我们需要给大家讲解三个方面，分别如下：&lt;br /&gt;
&lt;br /&gt;
1、PB-ADV，这个是mesh中的广播承载层，用于我们启动配置过程（暂时的例程和手机app都是用的PB-GATT来配置的，但是源码中PB-ADV的流程是包含的）。&lt;br /&gt;
&lt;br /&gt;
2、Unprov Beacon，这个是广播一个未被配置的beacon，就是一个特殊形式的广播数据包，用于声明自己是未经配置的设备。&lt;br /&gt;
&lt;br /&gt;
3、PB-GATT，这个是mesh中的GATT承载层，用于我们启动配置过程（当前手机app就是使用PB-GATT，也就是连接之后，通过GATT服务去发送配置信息）。&lt;br /&gt;
&lt;br /&gt;
4、Publish，这个是我们用于模型发布数据的部分，我们将需要发送的数据通过advData广播出去。&lt;br /&gt;
&lt;br /&gt;
=== PB-ADV ===&lt;br /&gt;
PB-ADV说明请查看“BLE-Mesh技术揭秘”第8.1章节。&lt;br /&gt;
&lt;br /&gt;
我们来看下PB-ADV整个的流程，首先在mian文件中，我们的start()函数下，我们调用了mesh_provisionee_prov_start()函数去启动节点设备的等待配置。&lt;br /&gt;
[[文件:PB-ADV-01.png|边框|居中|无框|1200x1200px]]我们继续追踪mesh_provisionee_prov_start()，可以看到在使能宏定义MESH_FEATURE_PB_ADV_ENABLED的情况下，可以追踪到nrf_mesh_prov_bearer_adv_interface_get()函数。&lt;br /&gt;
[[文件:PB-ADV-02.png|边框|居中|无框|1200x1200px]]在nrf_mesh_prov_bearer_adv_interface_get()函数中，我们可以看到prov_bearer_interface_t结构体，这个结构体下面提供了我们配置承载层需要的API函数。&lt;br /&gt;
[[文件:PB-ADV-03.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
=== Unprov Beacon ===&lt;br /&gt;
Unprov Beacon说明请查看“BLE-Mesh技术揭秘”第5.5及5.5.1章节。&lt;br /&gt;
&lt;br /&gt;
上面我们有说到PB-ADV是承载层用于配置节点的功能，而Unprov Beacon则是我们的节点设备用于声明自己未经配置，所以这两个其实是一脉相承的。&lt;br /&gt;
&lt;br /&gt;
紧接着上面的nrf_mesh_prov_bearer_adv_interface_get()函数，其中的prov_bearer_interface_t结构体包含的是处理承载层的API函数，在这个结构体当中有一个.listen_start = prov_bearer_adv_listen，是用于开始监听传入的配置链接的功能，在这个函数中我们可以看到send_unprov_beacon()函数。&lt;br /&gt;
[[文件:PB-ADV-04.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
send_unprov_beacon()函数是用于发送Unprov Beacon数据的函数，在这个函数中我们调用prov_beacon_unprov_build函数去创建用于广播的Unprov Beacon数据包，然后调用advertiser_interval_set函数去设置广播的间隔，最后调用advertiser_packet_send函数去将我们的数据包发送出去。&lt;br /&gt;
&lt;br /&gt;
默认的Unprov Beacon广播间隔是NRF_MESH_PROV_BEARER_ADV_UNPROV_BEACON_INTERVAL_MS 2000。&lt;br /&gt;
&lt;br /&gt;
最终调用的是advertiser_packet_send(0函数去将广播数据发送出去的。&lt;br /&gt;
[[文件:PB-ADV-05.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Publish-12.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
这里我们再最后看一下prov_beacon_unprov_build()函数，这个函数是生成我们的Unprov Beacon的数据包。&lt;br /&gt;
&lt;br /&gt;
其中标识位是0x2B，这个flag是beacon的意思。data的第一个字节0x00代表是我们的BEACON_TYPE_UNPROV（未配置的节点设备），接下来的16个字节是device uuid（这个是寄存器FICR中的device id生成的），接下来2字节是oob info（这里是00 00，代表没有使能OOB），最后的4个字节是URI通过AES-CMAC算法计算出来的值。&lt;br /&gt;
[[文件:PB-GATT-11.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:PB-GATT-12.png|边框|居中|无框]]&lt;br /&gt;
&lt;br /&gt;
=== PB-GATT ===&lt;br /&gt;
PB-GATT说明请查看“BLE-Mesh技术揭秘”第8.1章节。&lt;br /&gt;
&lt;br /&gt;
和PB-ADV一样的，我们从main文件中的start()函数，追踪到mesh_provisionee_prov_start()，可以看到在使能宏定义MESH_FEATURE_PB_GATT_ENABLED的情况下，可以追踪到nrf_mesh_prov_bearer_gatt_init()以及nrf_mesh_prov_bearer_gatt_interface_get()函数。&lt;br /&gt;
[[文件:PB-GATT-01.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
首先我们看一下nrf_mesh_prov_bearer_gatt_init()函数，这个是初始化并设置PB-GATT服务（通过Mesh GATT接口）的函数。对比我们使用nrf master control panel手机APP连接之后获取到的服务，可以看到其中的PB-GATT的服务以及特征值。&lt;br /&gt;
&lt;br /&gt;
再往下的mesh_gatt_init()函数，这个就不给大家分析了，是和我们普通的BLE一样的方式去注册一个服务。&lt;br /&gt;
[[文件:PB-GATT-04.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:PB-GATT-03.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:PB-GATT-02.png|边框|居中|无框]]&lt;br /&gt;
在nrf_mesh_prov_bearer_gatt_interface_get()函数中，我们可以看到prov_bearer_interface_t结构体，这个结构体下面提供了我们配置承载层需要的API函数（这个是和上方的PB-ADV是一样的）。&lt;br /&gt;
[[文件:PB-GATT-05.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
和Unprov Beacon类似的，当我们开始监听配置链接的时候，会触发.listen_start = listen_start_cb的回调，在这个回调中我们触发FSM（有限状态机）的事件E_LISTEN_START，然后调用link_evt_send()去post顺序承载事件。&lt;br /&gt;
[[文件:PB-GATT-06.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
然后我们来到状态机列表m_pb_gatt_fsm_transition_table[]，可以看到E_LISTEN_START事件ID代表的动作ID是A_LISTEN_START。而最终我们的A_LISTEN_START动作，执行的是a_listen_start函数功能（这其中涉及的代码逻辑，暂时先不说明，大家可以自己查看源码的引用）。&lt;br /&gt;
[[文件:PB-GATT-07.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:PB-GATT-08.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
然后我们继续追踪a_listen_start()，可以看到其中调用了mesh_adv_params_set函数设置了我们广播的超时时间MESH_ADV_TIMEOUT_INFINITE以及广播间隔NRF_MESH_PROV_BEARER_GATT_UNPROV_BEACON_INTERVAL_MS，调用mesh_adv_data_set函数设置了我们的广播数据内容，最后调用mesh_adv_start函数启动广播。&lt;br /&gt;
&lt;br /&gt;
默认的PB-GATT的广播数据的广播周期是NRF_MESH_PROV_BEARER_GATT_UNPROV_BEACON_INTERVAL_MS 200。&lt;br /&gt;
[[文件:PB-GATT-09.png|边框|居中|无框|1200x1200像素]]我们同样的看一下mesh_adv_data_set()函数最终配置生成的广播数据，可以看到广播数据的flag是0x06，服务的UUID就是我们上面提到的PB-GATT服务的UUID，接下来16字节UUID是和PB-ADV一样的FICR寄存器的device id值，最后的扫描回调数据携带的是设备的全名“nRF5x Mesh Light”。&lt;br /&gt;
[[文件:PB-GATT-13.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:PB-GATT-14.png|边框|居中|无框]]最后是我们的mesh_adv_start()函数去启动广播，可以看到调用是我们ble stack sotfdevice当中的API函数sd_ble_gap_adv_start()去启动的。&lt;br /&gt;
[[文件:PB-GATT-15.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
=== Publish（模型发布） ===&lt;br /&gt;
这一章节我们只给大家讲解模块publish的数据发布流程，暂时不说明模型的创建方法，以及如何使用，这个放在下面单独的模型章节进行说明。&lt;br /&gt;
&lt;br /&gt;
以generic_onoff_server模型为例，我们找到access_model_publish以及access_model_reply函数，这两个函数的功能类似的，都是用于我们用户的模型发送数据的。&lt;br /&gt;
&lt;br /&gt;
其中access_model_publish是用于模型主动发送，而access_model_reply是用于模型给对应的Opcode回应数据。&lt;br /&gt;
&lt;br /&gt;
这里我们以access_model_publish函数为主，来看下发布数据的代码流程。&lt;br /&gt;
[[文件:Publish-01.png|边框|居中|无框|1200x1200像素]]查看一下access_model_publish()函数，我们可以看到packet_alloc_and_tx()函数，这个是我们继续发送数据的函数。下面还有一个蓝牙方框中的内容，是我们定义的重传机制（mesh数据为了保证传输的成功率，默认都是会重传数据包的，默认重传次数是4次）。[[文件:Publish-02.png|边框|居中|无框|1200x1200像素]]然后我们继续看一下packet_alloc_and_tx()函数，在这个函数中，我们最终会调用packet_tx()函数将数据发送出去。[[文件:Publish-03.png|边框|居中|无框|1200x1200像素]]继续追踪packet_tx()函数，这个函数的内容比较多，我们只截取了其中数据发送的部分，也就是我们的nrf_mesh_packet_send()函数，我们在他的tx_params中赋值好我们需要发送出去的数据内容。[[文件:Publish-04.png|边框|居中|无框|1199x1199像素]]nrf_mesh_packet_send()函数最终调用的是transport_tx()函数发送数据。而transport_tx()函数，最终是调用upper_transport_tx()函数进行的数据发送。[[文件:Publish-05.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Publish-06.png|边框|居中|无框|1200x1200像素]]我们再继续追踪upper_transport_tx()函数，可以看到我们会根据p_metadata-&amp;gt;segmented来判断数据是否要分包发送。&lt;br /&gt;
[[文件:Publish-13.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
接下里我们以不分包发送函数unsegmented_packet_tx()为例，来继续说明数据传输流程。&lt;br /&gt;
&lt;br /&gt;
我们可以看到我们的unsegmented_packet_tx()函数中，最终是调用的network_packet_send()函数。（分包的segmented_packet_tx()函数最终也是调用的network_packet_send()，大家可以自己追踪代码看一下）&lt;br /&gt;
[[文件:Publish-14.png|边框|居中|无框|1200x1200像素]]继续追踪我们的network_packet_send()函数，可以看到我们的net_packet_encrypt()加密函数，这个是将我们的明文数据，做好加密工作。&lt;br /&gt;
&lt;br /&gt;
然后调用core_tx_packet_send()函数发送出去。[[文件:Publish-15.png|边框|居中|无框|1200x1200像素]]最后我们追踪到core_tx_packet_send()函数，可以看到调用的是p_bearer-&amp;gt;p_interface-&amp;gt;packet_send()函数去发送的。所以接下来我们要找到我们的packet_send()功能是在哪边实现的。[[文件:Publish-07.png|边框|居中|无框|1200x1200像素]]可以看到我们是在core_tx_bearer_add函数中去获得的p_interface这个功能。[[文件:Publish-08.png|边框|居中|无框|1200x1200像素]]我们追踪core_tx_bearer_add()函数的引用路径，可以看到是在core_tx_adv_init()当中。（除了此处，还有另一处地方也有引用，功能是类似的，大家可以自行了解）。&lt;br /&gt;
&lt;br /&gt;
我们看下参数m_interface，可以看到m_interface-&amp;gt;packet_send功能，就是对应我们在core_tx_packet_send()函数调用的p_bearer-&amp;gt;p_interface-&amp;gt;packet_send()。[[文件:Publish-09.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Publish-10.png|边框|居中|无框|1200x1200像素]]所以我们接下来来看下m_interface结构体当中的packet_send()函数实现的是什么功能，可以看到最终调用的是advertiser_packet_send()函数将数据发送出去。看到这边我们就很熟悉了，和上面的几个广播功能，最后调用的都是相同的广播包发送函数。[[文件:Publish-11.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Publish-12.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
== ②扫描（通信RX） ==&lt;br /&gt;
&lt;br /&gt;
=== 标准BLE SCAN ===&lt;br /&gt;
看过了我们的广播功能之后，我们来看下扫描的部分。在ble mesh中，我们的扫描功能scan，就是用于获取数据的，类似于数据通信的RX。&lt;br /&gt;
&lt;br /&gt;
我们从main文件中的initialize()函数开始，首先是我们mesh_init()函数，最终我们会调用mesh_stack_init()函数。&lt;br /&gt;
[[文件:SCAN-01.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
在mesh_satck_init函数中，我们可以看到调用了nrf_mesh_init函数去初始化core的参数。&lt;br /&gt;
[[文件:SCAN-02.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
这一段是nrf_mesh_init函数部分，由于这个初始化函数太长，所以我们只截取了scan相关的部分。&lt;br /&gt;
&lt;br /&gt;
首先是第一点scanner_init(scanner_packet_process_cb)函数，在这个函数中，我们回去初始化扫描的参数配置，并且会开启扫描功能。最终我们可以从scanner_packet_process_cb这个回调中得到我们扫描到的数据（获得所有的BLE广播数据）。&lt;br /&gt;
&lt;br /&gt;
然后是第二点ad_listener_subscribe(&amp;amp;m_nrf_mesh_listener)，这个是用来侦听订阅的消息的（从获得的所有BLE广播数据中，筛选出的订阅的消息）。&lt;br /&gt;
[[文件:SCAN-03.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
然后我们继续看一下scanner_init的扫描初始化函数，在这个函数中我们首先调用scanner_config_reset()函数去配置扫描的默认参数。其中包含了：&lt;br /&gt;
&lt;br /&gt;
1、扫描的通道SCANNER_CHANNELS_DEFAULT，也就是BLE的｛37，38，39｝三个channels都去扫描。&lt;br /&gt;
&lt;br /&gt;
2、规范定义的非连接状态访问地址BEARER_ACCESS_ADDR_DEFAULT。&lt;br /&gt;
&lt;br /&gt;
3、配置物理层协议是1Mbit模式。&lt;br /&gt;
&lt;br /&gt;
4、扫描的间隔时间BEARER_SCAN_INT_DEFAULT_MS，扫描的窗口时间BEARER_SCAN_WINDOW_DEFAULT_MS&lt;br /&gt;
&lt;br /&gt;
5、射频的发射功率RADIO_POWER_NRF_0DBM。&lt;br /&gt;
&lt;br /&gt;
6、BLE广播包最大长度RADIO_CONFIG_ADV_MAX_PAYLOAD_SIZE。&lt;br /&gt;
&lt;br /&gt;
最后我们会在nrf_mesh_enable()函数中调用scanner_enable()函数去启动扫描，这个大家可以自己查看一下代码，比较容易理解。&lt;br /&gt;
[[文件:SCAN-04.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:SCAN-05.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
最后我们来看一下scanner_packet_process_cb()回调函数，其中我们可以直接拿到的扫描的数据包是rx_data，包含了数据的类型、数据内容以及数据长度等等信息。到这边我们的扫描功能是已经完成的，但是我们还需要再看下下面一部分的ad_listener的流程。&lt;br /&gt;
&lt;br /&gt;
=== Subscription（模型订阅） ===&lt;br /&gt;
我们需要注意下ad_listener_process()函数，这个函数是将我们扫描到的订阅数据（不符合标准的BLE广播数据，则认为是侦听到的订阅数据）传递到订阅者进行处理（订阅者指的是带订阅功能的模型）。这个是和我们上方说的关注点二ad_listener_subscribe()函数是一脉相承的。&lt;br /&gt;
[[文件:SCAN-06.png|边框|居中|无框|1200x1200像素]]然后我们来对照着看一下ad_listener_subscribe()函数以及ad_listener_process()函数。这两个函数一个是定义了侦听订阅，一个是将扫描获取到的数据传递给订阅者。这两者间的运作关系，大家有兴趣的可以继续查阅他的底层代码逻辑处理，一个是向list中添加订阅者信息，一个是将数据通过p_listener-&amp;gt;handler()将数据传递给订阅者处理。&lt;br /&gt;
[[文件:SCAN-07.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:SCAN-08.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
最终当我们扫描到订阅消息，并调用ad_listener_process()函数传递时，会触发我们注册的回调nrf_mesh_listen（调用ad_listener_subscribe(&amp;amp;m_nrf_mesh_listener)时，在m_nrf_mesh_listener中）。我们来看下nrf_mesh_listen函数中的处理，其中我们先从数据包中解析了数据的类型type，然后根据不同类型的数据进行相应的处理。&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+&lt;br /&gt;
|AD_TYPE_MESH &lt;br /&gt;
|蓝牙网格物体的AD类型&lt;br /&gt;
|-&lt;br /&gt;
|AD_TYPE_BEACON &lt;br /&gt;
|蓝牙网状信标的AD类型&lt;br /&gt;
|-&lt;br /&gt;
|AD_TYPE_PB_ADV &lt;br /&gt;
|PB-ADV消息的AD类型&lt;br /&gt;
|-&lt;br /&gt;
|AD_TYPE_DFU &lt;br /&gt;
|nRF OpenMesh消息的AD类型&lt;br /&gt;
|}&lt;br /&gt;
[[文件:SCAN-09.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
== ③入网验证 ==&lt;br /&gt;
经过前两章的学习，我们对于BLE MESH的广播及扫描的代码流程已经有了一定的了解，也就是已经大概的明白了数据的发送和接收的流程。&lt;br /&gt;
&lt;br /&gt;
那么从这一章节开始，我们将会给大家展示，如何配置一个节点设备入网。配置者设备，我们使用的是手机APP：nRF Mesh。&lt;br /&gt;
&lt;br /&gt;
我们从main文件中的start()函数开始，来看一下入网的代码处理及其流程。在start()函数中我们最后是调用的mesh_provisionee_prov_start()函数来启动的节点配置功能，我们来查看一下它所携带的参数prov_start_params。这部分的参数都是比较重要的，有携带的是设备的信息的、有携带认证密码的，有回调返回的，所以我们会挨个介绍一下。&lt;br /&gt;
[[文件:Prov-01.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
首先携带的是static_auth_data，这个是我们带外OOB认证的密码。如果我们在使用nRF Mesh去进行身份验证的时候，选择使用Static OOB，那么当我们在进行PB-GATT的连接过程中，会弹出一个新的窗口，要求我们去输入这个静态的128bit的OOB密钥。&lt;br /&gt;
[[文件:Prov-02.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Prov-14.png|边框|居中|无框]]&lt;br /&gt;
[[文件:Prov-15.png|边框|居中|无框]]&lt;br /&gt;
接下来携带的是provisioning_complete_cb()回调函数，这个函数处理的是节点设备成功的被配置入网，在这里我们可以获取到我们的节点设备被分配的node_Address。&lt;br /&gt;
[[文件:Prov-03.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
然后携带是device_identification_start_cb()回调函数，这个回调函数是用于通知开发者，我们的节点设备当前已经开始配置。&lt;br /&gt;
[[文件:Prov-04.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
再然后携带的是URI【统一资源标识符（Uniform Resource Identifier，URI)】，这个是用于标识我们的设备类型的，这个数据会通过AES-CMAC算法加密后添加到我们的Unprov Beacon数据的末尾。&lt;br /&gt;
&lt;br /&gt;
这里我们是light switch的server设备，所以我们定义URI是EX_URI_LS_SERVER。&lt;br /&gt;
[[文件:Prov-05.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
分析完携带的参数之后，我们继续来查看mesh_provisionee_prov_start()函数。在这个函数中，我们首先是定义了一下prov_caps，也就是配置支持的OOB功能。然后我们调用nrf_mesh_generate_keys()函数去生成有效的密钥对等待使用。&lt;br /&gt;
&lt;br /&gt;
再然后是调用nrf_mesh_prov_init()函数去初始化prov配置信息的结构体参数，里面有结构体本身m_pro_ctx，需要配置的参数公钥m_public、私钥m_private，以及我们刚刚定义的oob功能prov_caps，最后还携带了一个事件回调函数prov_ect_handler。&lt;br /&gt;
&lt;br /&gt;
接下来是PB-ADV以及PB-GATT的处理过程，这个前面已经讲解过，这里不再赘述。&lt;br /&gt;
&lt;br /&gt;
最后我们调用provisionee_start()函数去开启配置。&lt;br /&gt;
[[文件:Prov-06.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
我们继续来追踪一下provisionee_start()函数，在这个函数中我们调用nrf_mesh_prov_listen()函数去开启侦听承载层的消息。这里我们需要侦听PB-ADV以及PB-GATT两个。&lt;br /&gt;
[[文件:Prov-07.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
然后在nrf_mesh_prov_listen()函数中，我们调用prov_provisionee_listen()函数携带我们刚刚传入的nrf_mesh_prov_ctx_t结构体，以及p_bearer、URI、oob_info_source等参数信息，继续向下传递。&lt;br /&gt;
[[文件:Prov-08.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
最终我们来查看下prov_provisionee_listen()函数，在这个函数中我们传递了p_bearer-&amp;gt;p_callbacks给m_prov_callbacks回调，然后调用p_bearer-&amp;gt;p_interface-&amp;gt;listen_start()函数去启动PB-ADV以及PB-GATT的侦听。&lt;br /&gt;
[[文件:Prov-09.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
当我们在侦听之后，接收到任何配置相关的消息，都将通过m_prov_callbacks()函数回调传递给我们处理，大家可以自行看下其中4个不同的功能函数，具体处理的都是什么消息。&lt;br /&gt;
[[文件:Prov-10.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
在prov_provisionee_listen()函数中，我们可以看到回调函数指针m_prov_callbacks指向的是p_bearer-&amp;gt;p_callbacks，我们一路向上查找，最终可以找到这个p_bearer-&amp;gt;p_callbacks函数就是指的prov_evt_handler()函数。所以其实我们用户可以在这一层去查看比较重要的配置事件返回，而不需要到上面的m_prov_callbacks中这样偏底层的地方去查看。&lt;br /&gt;
&lt;br /&gt;
在这个回调函数中，我们可以看到mian文件中注册的prov_device_identification_start_cb、prov_device_identification_stop_cb、provisioning_aborted_cb的返回。&lt;br /&gt;
[[文件:Prov-11.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
那么除了上方已经存在的几个回调，我们可以看到mian文件中的start()函数下的prov_start_params参数，还缺少一个配置完成的回调prov_complete_cb()。&lt;br /&gt;
&lt;br /&gt;
这个回调的返回方式比较特殊，和上方其他的都不同，是由SD（softdevice）直接从底层返回给我们的。注册这个回调的函数是NRF_SDH_STATE_OBSERVER，返回的回调函数是sd_state_evt_handler。其中触发的事件NRF_SDH_EVT_STATE_ENABLED，这个是在mian文件中的initialize()函数下的ble_stack_init()函数中就被触发的，这个大家可以自行查看一下，不太重要，因为都是nordic底层处理好的消息。&lt;br /&gt;
[[文件:Prov-13.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Prov-12.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
以上整个配置流程的处理，不太好分步展示相应的现象，这个大家可以自己使用SES在线仿真的方式去调试一下流程，这里我们直接给大家展示整个流程打印出的相应的信息，以及nRF Mesh中暂时出来的现象。&lt;br /&gt;
[[文件:Prov-16.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Prov-17.png|边框|居中|无框]]&lt;br /&gt;
&lt;br /&gt;
== ④设备信息分配 ==&lt;br /&gt;
当我们成功的配置节点设备入网之后，我们需要给节点设备主要分配如下几个信息，有关这几个要点的介绍，可以查看“BLE-Mesh技术揭秘”第3章节：&lt;br /&gt;
&lt;br /&gt;
1、节点在网络中的地址Node Address&lt;br /&gt;
&lt;br /&gt;
2、网络密钥Netkey&lt;br /&gt;
&lt;br /&gt;
3、应用密钥Appkey&lt;br /&gt;
&lt;br /&gt;
4、元素的地址Element Address&lt;br /&gt;
&lt;br /&gt;
5、绑定model的Appkey，分配Publish Address，Subscription Address&lt;br /&gt;
&lt;br /&gt;
=== Node Address ===&lt;br /&gt;
首先我们来看一下我们使用nRF Mesh成功配置我们的节点之后，打印出来的消息，开始的紫色字体显示了我们给当前节点设备分配的Node Address。&lt;br /&gt;
&lt;br /&gt;
后面紧接着还有四段黄色字体的内容（这个部分大家的工程是不会打印的，是我们修改例程打印出来给大家展示一下），这四个部分就是我们配置节点的时候，配置者设备（nRF Mesh）给节点设备的四个主要指令，这些指令的具体功能，大家可以查看mesh的协议手册。[[文件:Config-13.png|边框|居中|无框|1080x1080像素]]在代码中，我们会在mian文件的provisioning_complete_cb()回调函数中获取并打印我们的Node Address，这个回调触发的含义就是我们的节点配置已经完成了。&lt;br /&gt;
&lt;br /&gt;
具体这个回调在代码中的回调及触发流程，大家看可以查看“③入网验证”的说明。[[文件:Config-20.png|边框|居中|无框|1200x1200像素]]我们使用nRF Mesh配置好节点设备之后，app界面显示内容如下，包含了节点的信息（地址、名称、配置时间等等），元素和模型，以及Netkey和Appkey等几个重要的设备信息。[[文件:Config-01.jpg|边框|居中|无框]]&lt;br /&gt;
[[文件:Config-04.jpg|边框|居中|无框]]&lt;br /&gt;
&lt;br /&gt;
=== Netkey ===&lt;br /&gt;
我们点击查看一下Network key，可以看到里面有一个Network Key 1，这是一个16字节的网络密钥。我们可以在nRF Mesh的Settings界面去增加或者删除一个network key，用户可以自定义此密钥。[[文件:Config-02.jpg|边框|居中|无框]]在我们的代码中，我们是在pro_evt_handler回调的NRF_MESH_PROV_EVT_COMPLETE事件中获取这个netkey，这个回调函数的触发流程同样是在“③入网验证”已经有说明。[[文件:Prov-11.png|边框|居中|无框|1200x1200像素]]我们直接看下NRF_MESH_PROV_EVT_COMPLETE事件下的mesh_stack_provisioning_data_store()函数，在这个函数中我们主要的任务，就是将配置者分配给我们的node address、netkey、devkey等等信息，分别做一些存储以及功能绑定。&lt;br /&gt;
[[文件:Netkey-01.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
=== Appkey ===&lt;br /&gt;
同样的我们打开Application Key，可以看到其中包含的3个密钥，这里我们只选用了Application Key 1作为我们的密钥。大家同样可以在nRF Mesh的Settings界面去增加或者删除一个App key，用户可以自定义此密钥。[[文件:Config-03.jpg|边框|居中|无框]]appkey这里和netkey不同，netkey是我们网络中的节点设备以及我们配置者设备共有的一个密钥，只有拥有这个密钥，我们才能在当前网络中通信，所以当我们的节点设备配置入网后，会立刻得到netkey（必须要分配给我的节点设备才行）。&lt;br /&gt;
&lt;br /&gt;
而appkey只是我们的某个模型用于通信的密钥，所以我们称它为应用密钥，这个appkey的分配，是在我们的config server下。这个config_server_init的初始化流程，是在我们的mesh_stack_init()函数下，就去初始化配置好的。最终当我们有信息配置的时候，会触发opcode_handlers，我们可以从这个操作码列表中查找相应的信息处理。[[文件:Config-22.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Config-23.png|边框|居中|无框|1200x1200像素]]我们来看下config server的opcode_handlers[]操作码处理程序列表，使用黄色标记给大家圈了几个操作码指令，这几个是我们配置一个最简单的server节点设备，都需要使用到的，包含了：&lt;br /&gt;
&lt;br /&gt;
1、appkey的添加，CONFIG_OPCODE_APPKEY_ADD&lt;br /&gt;
&lt;br /&gt;
2、publish发布地址，CONFIG_OPCODE_MODEL_PUBLICATION_VIRTUAL_ADDRESS_SET&lt;br /&gt;
&lt;br /&gt;
3、subscription订阅地址，CONFIG_OPCODE_MODEL_SUBSCRIPTION_ADD&lt;br /&gt;
&lt;br /&gt;
4、模型appkey的绑定，CONFIG_OPCODE_MODEL_APP_BIND&lt;br /&gt;
&lt;br /&gt;
5、netkey的更新，CONFIG_OPCODE_NETKEY_ADD[[文件:Config-21.png|边框|居中|无框|1200x1200像素]]我们可以看到当接收到CONFIG_OPCODE_APPKEY_ADD操作码的数据，会调用handle_appkey_add()函数去处理，在这个函数中我们最终会调用dsm_appkey_add()函数去存储我们的appkey。[[文件:Appkey-01.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
== ⑤Element元素 ==&lt;br /&gt;
元素简单的说就是定义了节点的功能，一些具有复杂功能的节点设备，会包含多个元素。一个节点设备最少需要包含一个元素，我们称之为主元素。&lt;br /&gt;
&lt;br /&gt;
从主元素开始（如果有多个元素，则第一个元素就是主元素），我们会给它分配相应的元素地址。主元素的地址就是节点的地址，第二个元素的地址就是主元素的地址基础上加上1，依次类推。&lt;br /&gt;
&lt;br /&gt;
可以看到在我们的主元素Element：0x0002中包含了3个不同的服务，这3个服务就是我们的模型Model，其中Configuration Server与Health Server这两个服务，是每一个节点都必须携带的模型。Configuration Server（配置服务）是用于配置节点设备信息，Health Server（健康服务）是用于节点进行网络内的心跳联系。剩下的Generic On Off Server服务，这个就是我们用于控制一个开关量的模型（在例程中我们通过控制LED的IO口高低电平来展示）。[[文件:Config-05.jpg|边框|居中|无框]]在我们的代码中，我们首先需要在nrf_mesh_config_app.h中定义好我们节点设备的最大元素数量，可以看到我们定义#define ACCESS_ELEMENT_COUNT (1)，也就是说我们当前的例程，只含有一个主元素。&lt;br /&gt;
[[文件:Element-01.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
而这个ACCESS_ELEMENT_COUNT ，是在我们开启节点配置的地方被调用的，这个mesh_provsionionee_prov_start()函数在前面的内容中有多次讲到，这里就不再说明了。&lt;br /&gt;
[[文件:Element-02.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
== ⑥model模型 ==&lt;br /&gt;
如果说元素是一个功能集合，那么模型就是元素下的具体的某一个功能。&lt;br /&gt;
&lt;br /&gt;
我们以灯的控制为例，灯的常用控制有开关、亮度、色温几种，其中开关、亮度和色温就是具体的模块，而处理灯控的集合就是元素。&lt;br /&gt;
&lt;br /&gt;
=== Configure Server ===&lt;br /&gt;
&lt;br /&gt;
=== Health Server ===&lt;br /&gt;
&lt;br /&gt;
=== Generic On Off Server ===&lt;br /&gt;
我们来看下Generic On Off Server的[[文件:Config-14.png|边框|居中|无框|1040x1040像素]]&lt;br /&gt;
[[文件:Config-06.jpg|边框|居中|无框]]&lt;br /&gt;
[[文件:Config-12.jpg|边框|居中|无框]]&lt;br /&gt;
&lt;br /&gt;
==== model bind appkey ====&lt;br /&gt;
[[文件:Config-07.jpg|边框|居中|无框]]&lt;br /&gt;
[[文件:Config-08.jpg|边框|居中|无框]]&lt;br /&gt;
&lt;br /&gt;
==== model publish address ====&lt;br /&gt;
[[文件:Config-09.jpg|边框|居中|无框]]&lt;br /&gt;
[[文件:Config-10.jpg|边框|居中|无框]]&lt;br /&gt;
&lt;br /&gt;
==== model subscription address ====&lt;br /&gt;
[[文件:Config-15.png|边框|居中|无框|1040x1040像素]]&lt;br /&gt;
[[文件:Config-11.jpg|边框|居中|无框]]&lt;br /&gt;
[[分类:NRF52832DK]]&lt;br /&gt;
[[分类:实验手册]]&lt;/div&gt;</summary>
		<author><name>Jinx</name></author>
		
	</entry>
	<entry>
		<id>http://doc.iotxx.com/index.php?title=NRF52832DK-Mesh%E7%BB%84%E7%BD%91%E5%AE%9E%E9%AA%8C&amp;diff=2785</id>
		<title>NRF52832DK-Mesh组网实验</title>
		<link rel="alternate" type="text/html" href="http://doc.iotxx.com/index.php?title=NRF52832DK-Mesh%E7%BB%84%E7%BD%91%E5%AE%9E%E9%AA%8C&amp;diff=2785"/>
		<updated>2020-03-31T06:40:25Z</updated>

		<summary type="html">&lt;p&gt;Jinx：/* Element */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;蓝牙Mesh和BLE是并行发展的一个独立分支，虽然底层共用Physical Layer和Link Layer，但是上层协议并不相同。因此，nRF52832的Mesh例程，并不在BLE协议栈sdk中，而是安装一个扩展包。&lt;br /&gt;
&lt;br /&gt;
本文介绍蓝牙Mesh相关的环境搭建和实验介绍。&lt;br /&gt;
&lt;br /&gt;
== Mesh开发环境 ==&lt;br /&gt;
&lt;br /&gt;
=== 蓝牙Mesh SDK ===&lt;br /&gt;
SDK位置：&amp;lt;code&amp;gt;归档资料/2-协议栈SDK/nRF5_SDK_Mesh_v310.zip&amp;lt;/code&amp;gt;，注意文件名中的Mesh字样。&lt;br /&gt;
&lt;br /&gt;
和BLE协议栈一样，将该压缩包直接解压到BLE协议栈同级目录即可使用，解压后的SDK路径如下：&amp;lt;code&amp;gt;E:\project-nordic\nRF5_SDK_Mesh_v310&amp;lt;/code&amp;gt;&lt;br /&gt;
[[文件:NRF52832DK-MESH-SDK-INSTALL.gif|居中|无框|750x750像素]]&lt;br /&gt;
&lt;br /&gt;
=== 集成开发软件SES ===&lt;br /&gt;
SES时是Segger Embedded Studio编译器的缩写，用来编译蓝牙Mesh代码，SES功能与IAR或Keil类似。&lt;br /&gt;
&lt;br /&gt;
备注：Nordic为何使用SES而不是IAR，原因不得而知。&lt;br /&gt;
&lt;br /&gt;
==== SES安装 ====&lt;br /&gt;
SES软件位置：&amp;lt;code&amp;gt;归档资料/7-编译器SES/Setup_EmbeddedStudio_ARM_v410_win_x64.exe&amp;lt;/code&amp;gt;。&lt;br /&gt;
&lt;br /&gt;
我们双击打开安装界面，按照如下动图所示方式进行安装（可以全部默认安装）。[[文件:Ses install.gif|边框|居中|无框|556x556像素]]&lt;br /&gt;
&lt;br /&gt;
==== SES许可证激活 ====&lt;br /&gt;
我们双击打开安装好的SES，选择编译自带测试例程，此时弹出需要激活的界面。&lt;br /&gt;
[[文件:Licensed1.jpg|边框|居中|无框|1205x1205像素]]&lt;br /&gt;
由于我们准备使用SES编译nordic的NRF52832的SDK，所以这里我们选择Activate Your Free License。这里因为nordic公司已经帮客户想SEGGER公司买下了使用的版权，所以是免费的许可证。&lt;br /&gt;
[[文件:Licensed2.jpg|边框|居中|无框|736x736像素]]&lt;br /&gt;
我们按照要求填写好信息，主要是邮箱地址（用于接收SEGGER公司发给我们的许可证），填写完成之后点击Request License。下面是我们接收的邮件信息，我们将红色框内的内容复制下来。&lt;br /&gt;
[[文件:Ses install3.png|边框|居中|无框|1301x1301像素]]&lt;br /&gt;
打开SES，选择Tools\License Manager...，我们选择Activate Embedded Studio，打开如下界面，并将我们的许可证粘贴进去，然后点击Install License。&lt;br /&gt;
[[文件:Licensed4.png|边框|居中|无框|481x481像素]]&lt;br /&gt;
安装好许可证之后，跳转到如下的界面。&lt;br /&gt;
[[文件:Licensed5.png|边框|居中|无框|481x481像素]]&lt;br /&gt;
此时我们的许可证激活完毕（有时候需要等待一段时间，才会显示激活成功）。&lt;br /&gt;
[[文件:Licensed6.png|边框|居中|无框|515x515像素]]&lt;br /&gt;
&lt;br /&gt;
== Light_switch ==&lt;br /&gt;
&lt;br /&gt;
=== 实验简介 ===&lt;br /&gt;
此示例演示了包含充当两个角色的设备的网状生态系统：配置角色（Provisioner role）和节点角色（Node role、provisionee role）。 它还通过在应用程序中使用[Generic OnOff model]来演示了如何使用Mesh模型。&lt;br /&gt;
&lt;br /&gt;
该示例由三个较小的示例组成：&lt;br /&gt;
&lt;br /&gt;
Light switch server：一个实现了[Generic OnOff server model]的简约服务器，用于接收状态数据并控制板上LED 1的状态。&lt;br /&gt;
&lt;br /&gt;
Light switch client：一个实现了[Generic OnOff client model]的简约客户端。当用户按下任意按钮时，OnOff Set消息将发送到配置的目标地址。&lt;br /&gt;
&lt;br /&gt;
Mesh Provisioner：一个简单的静态配置设备应用，用于建立演示网络，该配置设备在一个网状网络中配置所有节点。 此外，配置设备还可以在这些节点上配置网格模型[Generic OnOff model]实例的绑定以及发布和订阅设置，以使它们能够相互通信。&lt;br /&gt;
&lt;br /&gt;
[Generic OnOff Client/Server]模型用于操纵打开/关闭状态。请注意，当服务器设置了发布地址（如本例所示）时，服务器会将其状态更改的任何操作披露到其发布地址。&lt;br /&gt;
&lt;br /&gt;
下图给出了将由静态供应商设置的网状网络的整体视图， 括号中的数字表示预配置程序分配给这些节点的地址。&lt;br /&gt;
[[文件:Mesh network.png|边框|居中|无框|727x727像素]]&lt;br /&gt;
灯开关服务器和灯开关客户端示例均具有预配方角色。它们支持通过广告承载（PB-ADV）和GATT承载（PB-GATT）进行配置，并且还支持网状代理服务器（Proxy Service），但是不支持代理客户端（Proxy Client）。&lt;br /&gt;
&lt;br /&gt;
=== 硬件说明 ===&lt;br /&gt;
完成这个实验，我们最少需要两个开发板硬件用做我们的节点设备（Node）：&lt;br /&gt;
* 一个开发板用做client&lt;br /&gt;
* 一个或者多个开发板用做server&lt;br /&gt;
此外，我们还需要以下之一作为我们的配置设备（Provisioner）：&lt;br /&gt;
* 如果您决定使用静态预配器示例，则多准备一个开发板&lt;br /&gt;
* 如果您决定使用app程序进行设置，则需要安装手机app@link_nrf_mesh_app（@link_nrf_mesh_app_ios或@link_nrf_mesh_app_android）。&lt;br /&gt;
{{Note|text=默认我们使用3个开发板完成我们的实验，3个开发板分别对应client、server以及provisioner三个类型。|type=warning}}&lt;br /&gt;
&lt;br /&gt;
=== 软件说明 ===&lt;br /&gt;
1、我们使用NrfGo上位机，依次对三个开发板进行擦除及softdevice的烧写&lt;br /&gt;
[[文件:Lightswitch nrfgo.png|边框|居中|无框|1013x1013像素]]&lt;br /&gt;
2、使用SEGGER Embedded Studio for ARM编译器分别打开我们`&amp;lt;InstallFolder&amp;gt;/examples/light_switch`路径下的3个实验，并完成编译&lt;br /&gt;
&lt;br /&gt;
●\client\light_switch_client_nrf52832_xxAA_s132_6_1_0.emProject&lt;br /&gt;
&lt;br /&gt;
●\server\light_switch_server_nrf52832_xxAA_s132_6_1_0.emProject&lt;br /&gt;
&lt;br /&gt;
●\provisioner\light_switch_provisioner_nrf52832_xxAA_s132_6_1_0.emProject&lt;br /&gt;
[[文件:Lightswitch SES build.png|边框|居中|无框|1013x1013像素]]&lt;br /&gt;
3、此时我们对3个开发板分别进行固件的烧写，分别烧写client、server、provisioner（建议大家给3个开发板贴上标签，方便后续区分）&lt;br /&gt;
[[文件:Lightswitch SES download.png.gif|边框|居中|无框|1009x1009像素]]&lt;br /&gt;
&lt;br /&gt;
=== 实验现象 ===&lt;br /&gt;
{{Note|text=注意：保证我们的3个设备都是刚按照软件说明部分的步骤配置完成，并且没有进行过任何硬件控制（按键操作）。目的是保证所有设备均未被我们人为配置过，没有进行过网络配置，否则可能有任意异常（不同网络配置，导致的不同现象）|type=danger}}&lt;br /&gt;
1、首先我们分别给client和provisioner供电，然后我们按下provisioner的按键S1：&lt;br /&gt;
&lt;br /&gt;
●provisoner：LED灯点亮，代表正在配置查找设备，配置组网&lt;br /&gt;
&lt;br /&gt;
●client：首先LED3和LED4闪烁，代表正在组网；LED1-LED4四个灯一起闪烁，代表组网完成&lt;br /&gt;
&lt;br /&gt;
我们如果打开RTT检测log打印，可以看到如下的信息，这个时候我们已经给client设备分配了Node Address为0x0100。provisioner信息（上），client信息（下）。&lt;br /&gt;
[[文件:Lightswitch rtt provisoner1.png|边框|无框|1080x1080像素|居中]]&lt;br /&gt;
[[文件:Lightswitch_rtt_node_client.png|边框|居中|无框|656x656像素]]&lt;br /&gt;
&lt;br /&gt;
2、使用provisioner配置好client之后，我们给server开发板上电，这个时候provisioner将会配置server入网：&lt;br /&gt;
&lt;br /&gt;
●provisoner：LED1点亮代表正在配置网络，LED2点亮代表网络配置完成&lt;br /&gt;
&lt;br /&gt;
●server：首先LED3和LED4闪烁，代表正在组网；LED1-LED4四个灯一起闪烁，代表组网完成&lt;br /&gt;
&lt;br /&gt;
我们打开RTT检测log打印，可以看到server已经配置入网，并且被分配了地址Node Address为0x0104。provisioner信息（上），server信息（下）。&lt;br /&gt;
[[文件:Lightswitch rtt provisoner2.png|边框|无框|710x710像素|居中]]&lt;br /&gt;
[[文件:Lightswitch rtt node server.png|边框|无框|656x656像素|居中]]&lt;br /&gt;
&lt;br /&gt;
3、此时整个网络的配置已经完成，这个时候我们可以按下client上的按键来控制server上的LED点亮或者熄灭。&lt;br /&gt;
&lt;br /&gt;
由于我们的server被provisioner分配的Node Address是0x0104（偶数），所以我们通过client的S3控制server的LED1点亮，通过client的S4控制server的LED1熄灭。&lt;br /&gt;
{{Note|text=client设备对于server设备的控制，通过Node Address的奇偶位不同，分成了两个组。&lt;br /&gt;
client的S1和S2分别控制Node Address为奇数的server设备的LED1点亮和熄灭。&lt;br /&gt;
&lt;br /&gt;
client的S3和S4分别控制Node Address为偶数的server设备的LED1点亮和熄灭。|type=warning}}&lt;br /&gt;
我们打开RTT检测log打印，可以看到client分别按下button2（S3）以及button3（S4），分别会设置server的GPIO输出高低电平。client信息（上），server信息（下）。&lt;br /&gt;
[[文件:Lightswitch rtt node client2.png|边框|无框|656x656像素|居中]]&lt;br /&gt;
[[文件:Lightswitch rtt node server2.png|边框|无框|656x656像素|居中]]&lt;br /&gt;
&lt;br /&gt;
4、至此我们的mesh组网的lightswitch实验测试完成。&lt;br /&gt;
&lt;br /&gt;
=== 源码详解 ===&lt;br /&gt;
&lt;br /&gt;
== Beaconing example ==&lt;br /&gt;
&lt;br /&gt;
=== 实验简介 ===&lt;br /&gt;
&lt;br /&gt;
=== 硬件说明 ===&lt;br /&gt;
&lt;br /&gt;
=== 软件说明 ===&lt;br /&gt;
&lt;br /&gt;
=== 实验现象 ===&lt;br /&gt;
&lt;br /&gt;
=== 源码详解 ===&lt;br /&gt;
&lt;br /&gt;
== Coexistence example ==&lt;br /&gt;
&lt;br /&gt;
=== 实验简介 ===&lt;br /&gt;
&lt;br /&gt;
=== 硬件说明 ===&lt;br /&gt;
&lt;br /&gt;
=== 软件说明 ===&lt;br /&gt;
&lt;br /&gt;
==== 将Mesh集成到nRF5 SDK示例中 ====&lt;br /&gt;
用于网格的nRF5 SDK与Nordic的nRF5 SDK兼容。 这使您可以将nRF5 SDK中的资源包括在现有的Mesh项目中，也可以将nRF5 SDK中的Mesh功能包括在nRF5 SDK示例中。&lt;br /&gt;
&lt;br /&gt;
请参阅构建网格堆栈和示例，以获取有关如何下载和安装nRF5 SDK的信息。 查看共存示例，以了解如何将nRF5 SDK功能与用于网格的nRF5 SDK同时使用。&lt;br /&gt;
&lt;br /&gt;
===== 动态记忆 =====&lt;br /&gt;
在将nRF5 SDK功能与nRF5 Mesh SDK功能一起使用时（如并存示例所示），您可能会遇到以下情况：应用程序注意事项可能需要更改动态内存的分配方式和可分配的动态内存量。 网格堆栈使用网格内存管理器界面进行动态内存分配。 默认的后端mesh_mem_stdlib.c使用标准库malloc（），该库需要定义足够大的堆大小。 可以通过用另一个内存管理器替换后端来更改此行为。&lt;br /&gt;
&lt;br /&gt;
如果使用Segger Embedded Studio构建应用程序，请在“项目选项”&amp;gt;“代码”&amp;gt;“运行时内存区域”设置中将“堆大小”设置为8192字节。&lt;br /&gt;
&lt;br /&gt;
===== 并发的SoftDevice和Mesh活动 =====&lt;br /&gt;
同时运行SoftDevice和Mesh时，最大的性能问题通常来自无线电时间争用。虽然SoftDevice通常在预定的短脉冲中运行，但Mesh会尝试尽可能多地使用无线电。只要SoftDevice没有无线电活动，Mesh就会连续扫描和通告。 SoftDevice活动将减少Mesh广播的时间，并且为了保持一致的Mesh性能，必须在不影响用户体验的情况下尽可能保守地设置SoftDevice无线电参数。&lt;br /&gt;
&lt;br /&gt;
在使用SoftDevice进行广告时，请尝试使用您的使用情况可以容许的最大广告间隔。如果可能，请在不需要时关闭SoftDevice广告商，仅在您希望收到连接请求时才激活它。如果您只需要发送不可连接的，不可扫描的广告（例如，针对第三方信标协议），请使用Mesh Advertiser API，因为它与Mesh一起使用时可以进行最少的上下文切换。&lt;br /&gt;
&lt;br /&gt;
当SoftDevice在连接中运行时：&lt;br /&gt;
&lt;br /&gt;
尝试协商应用程序可以允许的最大连接间隔。如果需要通过连接的高吞吐量，则在每个连接事件中发送更多的数据要比减少连接间隔更好，因为大部分开销来自上下文切换。&lt;br /&gt;
&lt;br /&gt;
如果网状设备在其SoftDevice连接中充当外围设备（从设备），则还可以增加“从设备等待时间”，这应使SoftDevice跳过连接事件，而不会增加通过链接的任何传出数据传输的等待时间。&lt;br /&gt;
&lt;br /&gt;
与广告一样，建议仅在需要时才保持连接活动。空闲的软设备连接对网状网性能的影响几乎与具有大量流量的连接一样多，特别是在从属延迟较低的情况下。&lt;br /&gt;
&lt;br /&gt;
基于SoftDevice的扫描对所有SoftDevice活动的Mesh性能影响最大。当SoftDevice正在扫描时，Mesh无法接收数据包，因此每个SoftDevice扫描窗口都会替换Mesh扫描。仅在尝试建立连接或需要活动扫描时才应使用SoftDevice扫描。如果需要常规的被动BLE扫描（用于侦听信标或其他第三方活动），请通过使用nrf_mesh_rx_cb_set函数设置RX回调来挂接到Mesh扫描器。如果您的应用程序需要主动扫描或需要启动连接，则应尽可能保守地设置扫描参数。较长的扫描间隔和较短的扫描窗口将使Mesh拥有更多的时间进行自身的无线电活动。类似地，建立上下文时在短时间内执行连续扫描可能比执行长时间运行的占空比扫描有利，因为上下文切换会导致很多不必要的开销。最后，强烈建议为SoftDevice连接启动调用设置超时，以避免长时间的空闲扫描。&lt;br /&gt;
&lt;br /&gt;
由于Mesh无法主动阻止SoftDevice无线电活动，因此减少同时运行SoftDevice活动的设备上的Mesh活动不会直接影响SoftDevice的性能。但是，附近的其他Mesh设备会干扰广告渠道中的SoftDevice活动，这可能会使连接启动花费更长的时间。&lt;br /&gt;
&lt;br /&gt;
通常，Mesh必须在SoftDevice活动之间执行其所有无线电操作，因此，如果设备在执行SoftDevice无线电操作时正在发送大量Mesh数据包，则它将在此上花费大部分Mesh时间，而不是接收传入的数据。为了解决这个问题，请根据可用的无线电时间调整输出数据包的数量，以减少Mesh数据包的发送。如果可能，请通过在CORE_TX_ROLE_RELAY角色上调用mesh_opt_core_adv_set来暂停耗时的SoftDevice操作期间的Mesh数据包中继。&lt;br /&gt;
&lt;br /&gt;
===== 在用于网格示例的nRF5 SDK中包括nRF5 SDK =====&lt;br /&gt;
根据您的工具链：&lt;br /&gt;
&lt;br /&gt;
使用Segger Embedded Studio时，请添加代码文件并包括相应SES项目文件的路径。&lt;br /&gt;
&lt;br /&gt;
使用CMake构建用于网格堆栈的nRF5 SDK时，添加代码文件并包括指向相应CMakeLists.txt文件的路径。 SDK_ROOT根符号用于引用nRF5 SDK安装文件夹（例如，请参阅Light Switch服务器示例中的CMakeLists.txt）。&lt;br /&gt;
&lt;br /&gt;
网格示例项目已经在其include /目录中包含sdk_config.h文件。 这些文件是默认SDK配置文件的副本，并且网格示例所需的所有更改都包含在示例目录中的include / app_config.h文件中。&lt;br /&gt;
&lt;br /&gt;
注意，&lt;br /&gt;
&lt;br /&gt;
必须先在SDK配置文件中显式启用某些SDK功能，然后才能使用它们。 有关详细信息，请参见SDK文档页面SDK配置头文件。&lt;br /&gt;
&lt;br /&gt;
===== 在nRF5 SDK示例中包括用于网格功能的nRF5 SDK =====&lt;br /&gt;
在nRF5 SDK示例的项目文件中包括来自nRF5 SDK for Mesh的以下源文件：&lt;br /&gt;
* All C files in &amp;lt;code&amp;gt;mesh/core/src&amp;lt;/code&amp;gt;&lt;br /&gt;
* All C files in &amp;lt;code&amp;gt;mesh/bearer/src&amp;lt;/code&amp;gt;&lt;br /&gt;
* All C files in &amp;lt;code&amp;gt;mesh/prov/src&amp;lt;/code&amp;gt; except nrf_mesh_prov_bearer_gatt.c&lt;br /&gt;
* All C files in &amp;lt;code&amp;gt;mesh/access/src&amp;lt;/code&amp;gt;&lt;br /&gt;
* All C files in &amp;lt;code&amp;gt;mesh/dfu/src&amp;lt;/code&amp;gt;&lt;br /&gt;
* All C files in &amp;lt;code&amp;gt;mesh/stack/src&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;models/foundation/config/src/config_server.c&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;models/foundation/config/src/composition_data.c&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;models/foundation/config/src/packed_index_list.c&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;models/foundation/health/src/health_server.c&amp;lt;/code&amp;gt;&lt;br /&gt;
* Any other mesh models that are used in your application&lt;br /&gt;
* &amp;lt;code&amp;gt;external/micro-ecc/uECC.c&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;examples/common/src/assertion_handler_weak.c&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;examples/common/src/mesh_provisionee.c&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
注意&lt;br /&gt;
&lt;br /&gt;
如果不需要各种网格特征（例如DFU），则可以从项目文件中省略相应的文件。但是，在其位置添加examples / nrf_mesh_weak.c以提供缺少的API函数的存根。&lt;br /&gt;
&lt;br /&gt;
将以下文件夹添加到nRF5 SDK示例的项目包含路径：&lt;br /&gt;
* &amp;lt;code&amp;gt;mesh/core/api&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;mesh/core/include&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;mesh/bearer/api&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;mesh/bearer/include&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;mesh/prov/api&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;mesh/prov/include&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;mesh/access/api&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;mesh/access/include&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;mesh/dfu/api&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;mesh/dfu/include&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;mesh/stack/api&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;models/foundation/config/include&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;models/foundation/health/include&amp;lt;/code&amp;gt;&lt;br /&gt;
* Path to include folder of any other mesh models that are used in your application&lt;br /&gt;
* &amp;lt;code&amp;gt;external/micro-ecc&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;examples/common/include&amp;lt;/code&amp;gt;&lt;br /&gt;
* Path to any other resources in the mesh examples that are used in your application&lt;br /&gt;
将以下预处理器符号添加到nRF5 SDK示例的项目文件中：&lt;br /&gt;
* &amp;lt;code&amp;gt;NRF52_SERIES&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;NRF_MESH_LOG_ENABLE=NRF_LOG_USES_RTT&amp;lt;/code&amp;gt; (because logging in the mesh stack relies on RTT)&lt;br /&gt;
* &amp;lt;code&amp;gt;CONFIG_APP_IN_CORE&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== 可选变更 =====&lt;br /&gt;
此外，您可能需要应用以下一项或多项更改：&lt;br /&gt;
&lt;br /&gt;
如果与nRF5 SDK集成在一起，则可能需要更新在网格堆栈中使用simple_hal模块的示例以使用Nordic nRF5 SDK bsp模块。可以同时使用两者，但是在这种情况下，必须从其中之一删除GPIOTE_IRQHandler，并且只有一个模块可以注册回调函数。&lt;br /&gt;
&lt;br /&gt;
如果原始的Nordic nRF5 SDK示例使用了SoftDevice，请确保在启用SoftDevice之后初始化并启用了网格堆栈。在这种情况下，必须将SoftDevice事件转发到网格堆栈。将以下代码添加到您的应用程序：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;#&amp;lt;/nowiki&amp;gt;include“ nrf_sdh_soc.h”&lt;br /&gt;
&lt;br /&gt;
＃定义MESH_SOC_OBSERVER_PRIO 0&lt;br /&gt;
&lt;br /&gt;
静态void mesh_soc_evt_handler（uint32_t evt_id，void * p_context）&lt;br /&gt;
&lt;br /&gt;
{&lt;br /&gt;
&lt;br /&gt;
    nrf_mesh_on_sd_evt（evt_id）;&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
NRF_SDH_SOC_OBSERVER（m_mesh_soc_observer，MESH_SOC_OBSERVER_PRIO，mesh_soc_evt_handler，NULL）;&lt;br /&gt;
&lt;br /&gt;
如果您有多个SOC观察者，请确保一次仅从其中一个观察者将SOC观察者事件转发到网格堆栈。&lt;br /&gt;
&lt;br /&gt;
默认情况下，在网格堆栈以及某些Nordic nRF5 SDK应用程序中启用网络配置的闪存存储。用于此目的的闪光区域可能会重叠并导致错误。为了使闪存存储模块Flash管理器与网状堆栈中的闪存管理器以及Nordic nRF5 SDK中的闪存存储模块安全地共存，请在nrf_mesh_config_app.h中添加以下代码块：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;#&amp;lt;/nowiki&amp;gt;include“ fds.h”&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;#&amp;lt;/nowiki&amp;gt;include“ fds_internal_defs.h”&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;#&amp;lt;/nowiki&amp;gt;define FLASH_MANAGER_RECOVERY_PAGE_OFFSET_PAGES FDS_PHY_PAGES&lt;br /&gt;
&lt;br /&gt;
如果要添加自己的网格功能，而不是使用现有的网格示例，则还需要添加文件nrf_mesh_config_app.h。将其从网格堆栈存储库中的examples / templates文件夹复制到您的项目文件夹中，并删除文件顶部的#error消息。对文件内容进行其他适当的更改，例如将ACCESS_ELEMENT_COUNT和ACCESS_MODEL_COUNT调整为所需数量的元素和模型。&lt;br /&gt;
&lt;br /&gt;
===== Flash放置项目文件 =====&lt;br /&gt;
Segger Embedded Studio项目旁边都有一个flash_placement.xml文件，该文件充当链接器的输入。在flash_placement.xml文件中，nRF5 SDK配置了一组ProgramSection列表，用于放置某些变量。除了nRF5 SDK组件所需的所有ProgramSections外，网格还需要另外两个部分，即nrf_mesh_flash和nrf_mesh_ram。&lt;br /&gt;
&lt;br /&gt;
必须将其他与网格相关的部分添加到flash_placement.xml文件中：&lt;br /&gt;
&lt;br /&gt;
将以下行添加到标记为&amp;lt;MemorySegment name =“ FLASH” ...&amp;gt;的内存段中：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ProgramSection alignment =“ 4” keep =“是” load =“是” name =“。nrf_mesh_flash”输入节=“ *（SORT（.nrf_mesh_flash。*））”“ address_symbol =” __ start_nrf_mesh_flash“ end_symbol =” __ stop_nrf_mesh_flash“ /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
将以下行添加到标记为&amp;lt;MemorySegment name =“ RAM” ...&amp;gt;的内存段中：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ProgramSection alignment =“ 4” keep =“是” load =“否” name =“。nrf_mesh_ram” inputsections =“ *（SORT（.nrf_mesh_ram。*））”“ address_symbol =” __ start_nrf_mesh_ram“ end_symbol =” __ stop_nrf_mesh_ram“ /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
注意&lt;br /&gt;
&lt;br /&gt;
对于共存示例已经进行了此更改。在编辑任何现有nRF5 SDK示例的Flash放置文件时，可以在这些示例中使用flash_placement.xml文件作为参考。&lt;br /&gt;
&lt;br /&gt;
=== 实验现象 ===&lt;br /&gt;
&lt;br /&gt;
=== 源码详解 ===&lt;br /&gt;
&lt;br /&gt;
== Coexistence Uart example ==&lt;br /&gt;
&lt;br /&gt;
=== 实验简介 ===&lt;br /&gt;
&lt;br /&gt;
=== 硬件说明 ===&lt;br /&gt;
&lt;br /&gt;
=== 软件说明 ===&lt;br /&gt;
&lt;br /&gt;
==== SDK UART共存示例 ====&lt;br /&gt;
本示例演示了如何同时使用适用于Mesh的nRF5 SDK和nRF5 SDK示例。它围绕两个示例构建，分为两个部分：&lt;br /&gt;
&lt;br /&gt;
此示例的网格部分从nRF5 SDK for Mesh实现了灯开关客户端示例。&lt;br /&gt;
&lt;br /&gt;
该示例的BLE部分实现了nRF5 SDK的ble_app_uart示例，但以下更改除外：&lt;br /&gt;
* BSP事件SLEEP，DISCONNECT和WHITELIST_OFF被忽略。这使得板子按钮仅控制应用程序的网格部分&lt;br /&gt;
* 增加广告间隔以为网格堆栈留出更多时间&lt;br /&gt;
* 可以通过发送要模拟的灯光开关客户端按钮编号，通过BLE UART来控制灯光开关服务器&lt;br /&gt;
运行此示例的结果是，您将能够使用网状网络，该示例中的网状网络可以代替电灯开关客户端示例。&lt;br /&gt;
&lt;br /&gt;
在开始测试此共存示例之前，请参阅以下页面：&lt;br /&gt;
&lt;br /&gt;
将Mesh集成到nRF5 SDK示例中&lt;br /&gt;
&lt;br /&gt;
灯光开关示例和灯光开关客户端详细信息以及Mesh API&lt;br /&gt;
&lt;br /&gt;
===== 测试示例 =====&lt;br /&gt;
将ble_app_uart_coexist文件夹复制到nRF5 SDK安装路径下的examples / ble_peripheral文件夹中。&lt;br /&gt;
&lt;br /&gt;
在ble_app_uart_coexist / pca10040 / s132 / ses / ble_app_uart_pca10040_s132.emProject中打开Segger Embedded Studio项目。&lt;br /&gt;
&lt;br /&gt;
将MESH_ROOT添加到您的Segger Embedded Studio全局宏列表：&lt;br /&gt;
&lt;br /&gt;
在SES菜单栏中，单击工具&amp;gt;选项...。&lt;br /&gt;
&lt;br /&gt;
在左列中，单击建筑物。&lt;br /&gt;
&lt;br /&gt;
在右列中，双击“全局宏”。&lt;br /&gt;
&lt;br /&gt;
在新行中添加网格根目录：MESH_ROOT = &amp;lt;网格安装路径&amp;gt;。&lt;br /&gt;
&lt;br /&gt;
如灯光开关示例中所述对灯光开关示例设备进行编程，但以下情况除外：&lt;br /&gt;
&lt;br /&gt;
用examples / ble_peripheral / ble_app_uart_coexist / pca10040 / s132 / ses / Output / Release / Exe / ble_app_uart_pca10040_s132.hex替换电灯开关客户端。&lt;br /&gt;
&lt;br /&gt;
现在，您可以并行或顺序运行两个并存的示例：&lt;br /&gt;
&lt;br /&gt;
按照电灯开关示例中的说明运行电灯开关示例。&lt;br /&gt;
&lt;br /&gt;
按照nRF5 SDK文档中的描述运行ble_app_uart示例。&lt;br /&gt;
&lt;br /&gt;
编写与UART RX特性中的数字之一（1、2、3或4）等效的十六进制ASCII码，以模拟按钮按下。&lt;br /&gt;
&lt;br /&gt;
=== 实验现象 ===&lt;br /&gt;
&lt;br /&gt;
=== 源码详解 ===&lt;br /&gt;
&lt;br /&gt;
== mesh工程文件简介 ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
!分组&lt;br /&gt;
!文件&lt;br /&gt;
!说明&lt;br /&gt;
|-&lt;br /&gt;
!Access&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; |'''访问层：'''&lt;br /&gt;
'''访问层定义高层应用程序如何使用上层传输层。 它定义了应用程序数据的格式； 它定义并控制在上层传输层执行的应用程序数据加密和解密； 并检查是否已在正确的网络和应用程序密钥的上下文中接收到传入的应用程序数据，然后再将其转发到更高层。'''&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
|access&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
|access_loopback&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
|access_publish&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
|access_publish_retransmissior&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
|access_reliable&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
|device_state_manager&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
!Application&lt;br /&gt;
! colspan=&amp;quot;2&amp;quot; |&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
|app_error_weak&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
|app_onoff&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
|assertion_handler_weak&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
|ble_softdevice_support&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
|main&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
|mesh_adv&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
|mesh_app_utils&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
|mesh_provisionee&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
|nrf_mesh_weak&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
|rtt_input&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
|sdk_config&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
|simple_hal&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== ***************************************正式整理章节************************************** ==&lt;br /&gt;
&lt;br /&gt;
== mesh实验学习流程概论 ==&lt;br /&gt;
1、首先大家一定要先查看一下我们资料中的手册“BLE-Mesh技术揭秘”，这个是很重要的一个mesh基本概念的介绍，只有看完这个文档，我们才能明白mesh组网是什么，是怎么完成的，后续的我们实验内容，也会在这个文档中查到相应的对照。&lt;br /&gt;
&lt;br /&gt;
2、按照Light_switch测试实验的实验说明，跑通测试流程（可以固件清零重新烧写多测试几遍，有助于增强我们的流程熟练度，利于后面的开发）。light_switch实验是现在nordic提供给我们的唯一的真正意义上的mesh组网通信例程。&lt;br /&gt;
&lt;br /&gt;
3、根据我们下面拆分的组网流程，对照依次的实验说明，进行mesh组网流程的分步学习。前期实验部分我们主要讲解节点设备，配置者设备功能我们使用手机app完成，目的是减少大家的学习难度，配置者设备我们会放在最后一个实验给大家做介绍。&lt;br /&gt;
&lt;br /&gt;
①广播（通信TX）——了解节点设备的广播功能，分为三个重要部分：PB-ADV和Unprov Beacon，PB-GATT的广播，以及模型的Publish用于发送数据。&lt;br /&gt;
&lt;br /&gt;
②扫描（通信RX）——了解节点设备的扫描功能，分为两个重要部分：普通的BLE SCAN功能，以及模块的Subscription用于接收数据。&lt;br /&gt;
&lt;br /&gt;
③入网验证——了解节点设备接入网络的验证过程，实验中使用的是PB-GATT去配置的，包含了：交换公共密钥，完成验证（是否有带外OOB）。&lt;br /&gt;
&lt;br /&gt;
④设备信息分配——启动配置数据分配，给节点分配如下数据：网络key（Netkey）、应用key（Appkey）以及节点设备地址（nodeAddr）。&lt;br /&gt;
&lt;br /&gt;
⑤元素——了解节点设备的元素与模型定义。&lt;br /&gt;
&lt;br /&gt;
⑥模型——了解模型的功能和创建方法，模型的订阅subscription和发布publish功能。&lt;br /&gt;
&lt;br /&gt;
== Light_switch测试实验 ==&lt;br /&gt;
=== 实验简介 ===&lt;br /&gt;
此示例演示了包含充当两个角色的设备的网状生态系统：配置角色（Provisioner role）和节点角色（Node role、provisionee role）。 它还通过在应用程序中使用[Generic OnOff model]来演示了如何使用Mesh模型。&lt;br /&gt;
&lt;br /&gt;
该示例由三个较小的示例组成：&lt;br /&gt;
&lt;br /&gt;
Light switch server：一个实现了[Generic OnOff server model]的简约服务器，用于接收状态数据并控制板上LED 1的状态。&lt;br /&gt;
&lt;br /&gt;
Light switch client：一个实现了[Generic OnOff client model]的简约客户端。当用户按下任意按钮时，OnOff Set消息将发送到配置的目标地址。&lt;br /&gt;
&lt;br /&gt;
Mesh Provisioner：一个简单的静态配置设备应用，用于建立演示网络，该配置设备在一个网状网络中配置所有节点。 此外，配置设备还可以在这些节点上配置网格模型[Generic OnOff model]实例的绑定以及发布和订阅设置，以使它们能够相互通信。&lt;br /&gt;
&lt;br /&gt;
[Generic OnOff Client/Server]模型用于操纵打开/关闭状态。请注意，当服务器设置了发布地址（如本例所示）时，服务器会将其状态更改的任何操作披露到其发布地址。&lt;br /&gt;
&lt;br /&gt;
下图给出了将由静态供应商设置的网状网络的整体视图， 括号中的数字表示预配置程序分配给这些节点的地址。&lt;br /&gt;
[[文件:Mesh network.png|边框|居中|无框|727x727像素]]&lt;br /&gt;
灯开关服务器和灯开关客户端示例均具有预配方角色。它们支持通过广告承载（PB-ADV）和GATT承载（PB-GATT）进行配置，并且还支持网状代理服务器（Proxy Service），但是不支持代理客户端（Proxy Client）。&lt;br /&gt;
&lt;br /&gt;
=== 硬件说明 ===&lt;br /&gt;
完成这个实验，我们最少需要两个开发板硬件用做我们的节点设备（Node）：&lt;br /&gt;
* 一个开发板用做client&lt;br /&gt;
* 一个或者多个开发板用做server&lt;br /&gt;
此外，我们还需要以下之一作为我们的配置设备（Provisioner）：&lt;br /&gt;
* 如果您决定使用静态预配器示例，则多准备一个开发板&lt;br /&gt;
* 如果您决定使用app程序进行设置，则需要安装手机app@link_nrf_mesh_app（@link_nrf_mesh_app_ios或@link_nrf_mesh_app_android）。&lt;br /&gt;
{{Note|text=默认我们使用3个开发板完成我们的实验，3个开发板分别对应client、server以及provisioner三个类型。|type=warning}}&lt;br /&gt;
&lt;br /&gt;
=== 软件说明 ===&lt;br /&gt;
1、我们使用NrfGo上位机，依次对三个开发板进行擦除及softdevice的烧写&lt;br /&gt;
[[文件:Lightswitch nrfgo.png|边框|居中|无框|1013x1013像素]]&lt;br /&gt;
2、使用SEGGER Embedded Studio for ARM编译器分别打开我们`&amp;lt;InstallFolder&amp;gt;/examples/light_switch`路径下的3个实验，并完成编译&lt;br /&gt;
&lt;br /&gt;
●\client\light_switch_client_nrf52832_xxAA_s132_6_1_0.emProject&lt;br /&gt;
&lt;br /&gt;
●\server\light_switch_server_nrf52832_xxAA_s132_6_1_0.emProject&lt;br /&gt;
&lt;br /&gt;
●\provisioner\light_switch_provisioner_nrf52832_xxAA_s132_6_1_0.emProject&lt;br /&gt;
[[文件:Lightswitch SES build.png|边框|居中|无框|1013x1013像素]]&lt;br /&gt;
3、此时我们对3个开发板分别进行固件的烧写，分别烧写client、server、provisioner（建议大家给3个开发板贴上标签，方便后续区分）&lt;br /&gt;
[[文件:Lightswitch SES download.png.gif|边框|居中|无框|1009x1009像素]]&lt;br /&gt;
&lt;br /&gt;
=== 实验现象 ===&lt;br /&gt;
{{Note|text=注意：保证我们的3个设备都是刚按照软件说明部分的步骤配置完成，并且没有进行过任何硬件控制（按键操作）。目的是保证所有设备均未被我们人为配置过，没有进行过网络配置，否则可能有任意异常（不同网络配置，导致的不同现象）|type=danger}}&lt;br /&gt;
1、首先我们分别给client和provisioner供电，然后我们按下provisioner的按键S1：&lt;br /&gt;
&lt;br /&gt;
●provisoner：LED灯点亮，代表正在配置查找设备，配置组网&lt;br /&gt;
&lt;br /&gt;
●client：首先LED3和LED4闪烁，代表正在组网；LED1-LED4四个灯一起闪烁，代表组网完成&lt;br /&gt;
&lt;br /&gt;
我们如果打开RTT检测log打印，可以看到如下的信息，这个时候我们已经给client设备分配了Node Address为0x0100。provisioner信息（上），client信息（下）。&lt;br /&gt;
[[文件:Lightswitch rtt provisoner1.png|边框|无框|1080x1080像素|居中]]&lt;br /&gt;
[[文件:Lightswitch_rtt_node_client.png|边框|居中|无框|656x656像素]]&lt;br /&gt;
&lt;br /&gt;
2、使用provisioner配置好client之后，我们给server开发板上电，这个时候provisioner将会配置server入网：&lt;br /&gt;
&lt;br /&gt;
●provisoner：LED1点亮代表正在配置网络，LED2点亮代表网络配置完成&lt;br /&gt;
&lt;br /&gt;
●server：首先LED3和LED4闪烁，代表正在组网；LED1-LED4四个灯一起闪烁，代表组网完成&lt;br /&gt;
&lt;br /&gt;
我们打开RTT检测log打印，可以看到server已经配置入网，并且被分配了地址Node Address为0x0104。provisioner信息（上），server信息（下）。&lt;br /&gt;
[[文件:Lightswitch rtt provisoner2.png|边框|无框|710x710像素|居中]]&lt;br /&gt;
[[文件:Lightswitch rtt node server.png|边框|无框|656x656像素|居中]]&lt;br /&gt;
&lt;br /&gt;
3、此时整个网络的配置已经完成，这个时候我们可以按下client上的按键来控制server上的LED点亮或者熄灭。&lt;br /&gt;
&lt;br /&gt;
由于我们的server被provisioner分配的Node Address是0x0104（偶数），所以我们通过client的S3控制server的LED1点亮，通过client的S4控制server的LED1熄灭。&lt;br /&gt;
{{Note|text=client设备对于server设备的控制，通过Node Address的奇偶位不同，分成了两个组。&lt;br /&gt;
client的S1和S2分别控制Node Address为奇数的server设备的LED1点亮和熄灭。&lt;br /&gt;
&lt;br /&gt;
client的S3和S4分别控制Node Address为偶数的server设备的LED1点亮和熄灭。|type=warning}}&lt;br /&gt;
我们打开RTT检测log打印，可以看到client分别按下button2（S3）以及button3（S4），分别会设置server的GPIO输出高低电平。client信息（上），server信息（下）。&lt;br /&gt;
[[文件:Lightswitch rtt node client2.png|边框|无框|656x656像素|居中]]&lt;br /&gt;
[[文件:Lightswitch rtt node server2.png|边框|无框|656x656像素|居中]]&lt;br /&gt;
&lt;br /&gt;
4、至此我们的mesh组网的lightswitch实验测试完成。&lt;br /&gt;
&lt;br /&gt;
== ①广播（通信TX） ==&lt;br /&gt;
有关广播部分，我们需要给大家讲解三个方面，分别如下：&lt;br /&gt;
&lt;br /&gt;
1、PB-ADV，这个是mesh中的广播承载层，用于我们启动配置过程（暂时的例程和手机app都是用的PB-GATT来配置的，但是源码中PB-ADV的流程是包含的）。&lt;br /&gt;
&lt;br /&gt;
2、Unprov Beacon，这个是广播一个未被配置的beacon，就是一个特殊形式的广播数据包，用于声明自己是未经配置的设备。&lt;br /&gt;
&lt;br /&gt;
3、PB-GATT，这个是mesh中的GATT承载层，用于我们启动配置过程（当前手机app就是使用PB-GATT，也就是连接之后，通过GATT服务去发送配置信息）。&lt;br /&gt;
&lt;br /&gt;
4、Publish，这个是我们用于模型发布数据的部分，我们将需要发送的数据通过advData广播出去。&lt;br /&gt;
&lt;br /&gt;
=== PB-ADV ===&lt;br /&gt;
PB-ADV说明请查看“BLE-Mesh技术揭秘”第8.1章节。&lt;br /&gt;
&lt;br /&gt;
我们来看下PB-ADV整个的流程，首先在mian文件中，我们的start()函数下，我们调用了mesh_provisionee_prov_start()函数去启动节点设备的等待配置。&lt;br /&gt;
[[文件:PB-ADV-01.png|边框|居中|无框|1200x1200px]]我们继续追踪mesh_provisionee_prov_start()，可以看到在使能宏定义MESH_FEATURE_PB_ADV_ENABLED的情况下，可以追踪到nrf_mesh_prov_bearer_adv_interface_get()函数。&lt;br /&gt;
[[文件:PB-ADV-02.png|边框|居中|无框|1200x1200px]]在nrf_mesh_prov_bearer_adv_interface_get()函数中，我们可以看到prov_bearer_interface_t结构体，这个结构体下面提供了我们配置承载层需要的API函数。&lt;br /&gt;
[[文件:PB-ADV-03.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
=== Unprov Beacon ===&lt;br /&gt;
Unprov Beacon说明请查看“BLE-Mesh技术揭秘”第5.5及5.5.1章节。&lt;br /&gt;
&lt;br /&gt;
上面我们有说到PB-ADV是承载层用于配置节点的功能，而Unprov Beacon则是我们的节点设备用于声明自己未经配置，所以这两个其实是一脉相承的。&lt;br /&gt;
&lt;br /&gt;
紧接着上面的nrf_mesh_prov_bearer_adv_interface_get()函数，其中的prov_bearer_interface_t结构体包含的是处理承载层的API函数，在这个结构体当中有一个.listen_start = prov_bearer_adv_listen，是用于开始监听传入的配置链接的功能，在这个函数中我们可以看到send_unprov_beacon()函数。&lt;br /&gt;
[[文件:PB-ADV-04.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
send_unprov_beacon()函数是用于发送Unprov Beacon数据的函数，在这个函数中我们调用prov_beacon_unprov_build函数去创建用于广播的Unprov Beacon数据包，然后调用advertiser_interval_set函数去设置广播的间隔，最后调用advertiser_packet_send函数去将我们的数据包发送出去。&lt;br /&gt;
&lt;br /&gt;
默认的Unprov Beacon广播间隔是NRF_MESH_PROV_BEARER_ADV_UNPROV_BEACON_INTERVAL_MS 2000。&lt;br /&gt;
&lt;br /&gt;
最终调用的是advertiser_packet_send(0函数去将广播数据发送出去的。&lt;br /&gt;
[[文件:PB-ADV-05.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Publish-12.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
这里我们再最后看一下prov_beacon_unprov_build()函数，这个函数是生成我们的Unprov Beacon的数据包。&lt;br /&gt;
&lt;br /&gt;
其中标识位是0x2B，这个flag是beacon的意思。data的第一个字节0x00代表是我们的BEACON_TYPE_UNPROV（未配置的节点设备），接下来的16个字节是device uuid（这个是寄存器FICR中的device id生成的），接下来2字节是oob info（这里是00 00，代表没有使能OOB），最后的4个字节是URI通过AES-CMAC算法计算出来的值。&lt;br /&gt;
[[文件:PB-GATT-11.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:PB-GATT-12.png|边框|居中|无框]]&lt;br /&gt;
&lt;br /&gt;
=== PB-GATT ===&lt;br /&gt;
PB-GATT说明请查看“BLE-Mesh技术揭秘”第8.1章节。&lt;br /&gt;
&lt;br /&gt;
和PB-ADV一样的，我们从main文件中的start()函数，追踪到mesh_provisionee_prov_start()，可以看到在使能宏定义MESH_FEATURE_PB_GATT_ENABLED的情况下，可以追踪到nrf_mesh_prov_bearer_gatt_init()以及nrf_mesh_prov_bearer_gatt_interface_get()函数。&lt;br /&gt;
[[文件:PB-GATT-01.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
首先我们看一下nrf_mesh_prov_bearer_gatt_init()函数，这个是初始化并设置PB-GATT服务（通过Mesh GATT接口）的函数。对比我们使用nrf master control panel手机APP连接之后获取到的服务，可以看到其中的PB-GATT的服务以及特征值。&lt;br /&gt;
&lt;br /&gt;
再往下的mesh_gatt_init()函数，这个就不给大家分析了，是和我们普通的BLE一样的方式去注册一个服务。&lt;br /&gt;
[[文件:PB-GATT-04.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:PB-GATT-03.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:PB-GATT-02.png|边框|居中|无框]]&lt;br /&gt;
在nrf_mesh_prov_bearer_gatt_interface_get()函数中，我们可以看到prov_bearer_interface_t结构体，这个结构体下面提供了我们配置承载层需要的API函数（这个是和上方的PB-ADV是一样的）。&lt;br /&gt;
[[文件:PB-GATT-05.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
和Unprov Beacon类似的，当我们开始监听配置链接的时候，会触发.listen_start = listen_start_cb的回调，在这个回调中我们触发FSM（有限状态机）的事件E_LISTEN_START，然后调用link_evt_send()去post顺序承载事件。&lt;br /&gt;
[[文件:PB-GATT-06.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
然后我们来到状态机列表m_pb_gatt_fsm_transition_table[]，可以看到E_LISTEN_START事件ID代表的动作ID是A_LISTEN_START。而最终我们的A_LISTEN_START动作，执行的是a_listen_start函数功能（这其中涉及的代码逻辑，暂时先不说明，大家可以自己查看源码的引用）。&lt;br /&gt;
[[文件:PB-GATT-07.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:PB-GATT-08.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
然后我们继续追踪a_listen_start()，可以看到其中调用了mesh_adv_params_set函数设置了我们广播的超时时间MESH_ADV_TIMEOUT_INFINITE以及广播间隔NRF_MESH_PROV_BEARER_GATT_UNPROV_BEACON_INTERVAL_MS，调用mesh_adv_data_set函数设置了我们的广播数据内容，最后调用mesh_adv_start函数启动广播。&lt;br /&gt;
&lt;br /&gt;
默认的PB-GATT的广播数据的广播周期是NRF_MESH_PROV_BEARER_GATT_UNPROV_BEACON_INTERVAL_MS 200。&lt;br /&gt;
[[文件:PB-GATT-09.png|边框|居中|无框|1200x1200像素]]我们同样的看一下mesh_adv_data_set()函数最终配置生成的广播数据，可以看到广播数据的flag是0x06，服务的UUID就是我们上面提到的PB-GATT服务的UUID，接下来16字节UUID是和PB-ADV一样的FICR寄存器的device id值，最后的扫描回调数据携带的是设备的全名“nRF5x Mesh Light”。&lt;br /&gt;
[[文件:PB-GATT-13.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:PB-GATT-14.png|边框|居中|无框]]最后是我们的mesh_adv_start()函数去启动广播，可以看到调用是我们ble stack sotfdevice当中的API函数sd_ble_gap_adv_start()去启动的。&lt;br /&gt;
[[文件:PB-GATT-15.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
=== Publish（模型发布） ===&lt;br /&gt;
这一章节我们只给大家讲解模块publish的数据发布流程，暂时不说明模型的创建方法，以及如何使用，这个放在下面单独的模型章节进行说明。&lt;br /&gt;
&lt;br /&gt;
以generic_onoff_server模型为例，我们找到access_model_publish以及access_model_reply函数，这两个函数的功能类似的，都是用于我们用户的模型发送数据的。&lt;br /&gt;
&lt;br /&gt;
其中access_model_publish是用于模型主动发送，而access_model_reply是用于模型给对应的Opcode回应数据。&lt;br /&gt;
&lt;br /&gt;
这里我们以access_model_publish函数为主，来看下发布数据的代码流程。&lt;br /&gt;
[[文件:Publish-01.png|边框|居中|无框|1200x1200像素]]查看一下access_model_publish()函数，我们可以看到packet_alloc_and_tx()函数，这个是我们继续发送数据的函数。下面还有一个蓝牙方框中的内容，是我们定义的重传机制（mesh数据为了保证传输的成功率，默认都是会重传数据包的，默认重传次数是4次）。[[文件:Publish-02.png|边框|居中|无框|1200x1200像素]]然后我们继续看一下packet_alloc_and_tx()函数，在这个函数中，我们最终会调用packet_tx()函数将数据发送出去。[[文件:Publish-03.png|边框|居中|无框|1200x1200像素]]继续追踪packet_tx()函数，这个函数的内容比较多，我们只截取了其中数据发送的部分，也就是我们的nrf_mesh_packet_send()函数，我们在他的tx_params中赋值好我们需要发送出去的数据内容。[[文件:Publish-04.png|边框|居中|无框|1199x1199像素]]nrf_mesh_packet_send()函数最终调用的是transport_tx()函数发送数据。而transport_tx()函数，最终是调用upper_transport_tx()函数进行的数据发送。[[文件:Publish-05.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Publish-06.png|边框|居中|无框|1200x1200像素]]我们再继续追踪upper_transport_tx()函数，可以看到我们会根据p_metadata-&amp;gt;segmented来判断数据是否要分包发送。&lt;br /&gt;
[[文件:Publish-13.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
接下里我们以不分包发送函数unsegmented_packet_tx()为例，来继续说明数据传输流程。&lt;br /&gt;
&lt;br /&gt;
我们可以看到我们的unsegmented_packet_tx()函数中，最终是调用的network_packet_send()函数。（分包的segmented_packet_tx()函数最终也是调用的network_packet_send()，大家可以自己追踪代码看一下）&lt;br /&gt;
[[文件:Publish-14.png|边框|居中|无框|1200x1200像素]]继续追踪我们的network_packet_send()函数，可以看到我们的net_packet_encrypt()加密函数，这个是将我们的明文数据，做好加密工作。&lt;br /&gt;
&lt;br /&gt;
然后调用core_tx_packet_send()函数发送出去。[[文件:Publish-15.png|边框|居中|无框|1200x1200像素]]最后我们追踪到core_tx_packet_send()函数，可以看到调用的是p_bearer-&amp;gt;p_interface-&amp;gt;packet_send()函数去发送的。所以接下来我们要找到我们的packet_send()功能是在哪边实现的。[[文件:Publish-07.png|边框|居中|无框|1200x1200像素]]可以看到我们是在core_tx_bearer_add函数中去获得的p_interface这个功能。[[文件:Publish-08.png|边框|居中|无框|1200x1200像素]]我们追踪core_tx_bearer_add()函数的引用路径，可以看到是在core_tx_adv_init()当中。（除了此处，还有另一处地方也有引用，功能是类似的，大家可以自行了解）。&lt;br /&gt;
&lt;br /&gt;
我们看下参数m_interface，可以看到m_interface-&amp;gt;packet_send功能，就是对应我们在core_tx_packet_send()函数调用的p_bearer-&amp;gt;p_interface-&amp;gt;packet_send()。[[文件:Publish-09.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Publish-10.png|边框|居中|无框|1200x1200像素]]所以我们接下来来看下m_interface结构体当中的packet_send()函数实现的是什么功能，可以看到最终调用的是advertiser_packet_send()函数将数据发送出去。看到这边我们就很熟悉了，和上面的几个广播功能，最后调用的都是相同的广播包发送函数。[[文件:Publish-11.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Publish-12.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
== ②扫描（通信RX） ==&lt;br /&gt;
&lt;br /&gt;
=== 标准BLE SCAN ===&lt;br /&gt;
看过了我们的广播功能之后，我们来看下扫描的部分。在ble mesh中，我们的扫描功能scan，就是用于获取数据的，类似于数据通信的RX。&lt;br /&gt;
&lt;br /&gt;
我们从main文件中的initialize()函数开始，首先是我们mesh_init()函数，最终我们会调用mesh_stack_init()函数。&lt;br /&gt;
[[文件:SCAN-01.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
在mesh_satck_init函数中，我们可以看到调用了nrf_mesh_init函数去初始化core的参数。&lt;br /&gt;
[[文件:SCAN-02.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
这一段是nrf_mesh_init函数部分，由于这个初始化函数太长，所以我们只截取了scan相关的部分。&lt;br /&gt;
&lt;br /&gt;
首先是第一点scanner_init(scanner_packet_process_cb)函数，在这个函数中，我们回去初始化扫描的参数配置，并且会开启扫描功能。最终我们可以从scanner_packet_process_cb这个回调中得到我们扫描到的数据（获得所有的BLE广播数据）。&lt;br /&gt;
&lt;br /&gt;
然后是第二点ad_listener_subscribe(&amp;amp;m_nrf_mesh_listener)，这个是用来侦听订阅的消息的（从获得的所有BLE广播数据中，筛选出的订阅的消息）。&lt;br /&gt;
[[文件:SCAN-03.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
然后我们继续看一下scanner_init的扫描初始化函数，在这个函数中我们首先调用scanner_config_reset()函数去配置扫描的默认参数。其中包含了：&lt;br /&gt;
&lt;br /&gt;
1、扫描的通道SCANNER_CHANNELS_DEFAULT，也就是BLE的｛37，38，39｝三个channels都去扫描。&lt;br /&gt;
&lt;br /&gt;
2、规范定义的非连接状态访问地址BEARER_ACCESS_ADDR_DEFAULT。&lt;br /&gt;
&lt;br /&gt;
3、配置物理层协议是1Mbit模式。&lt;br /&gt;
&lt;br /&gt;
4、扫描的间隔时间BEARER_SCAN_INT_DEFAULT_MS，扫描的窗口时间BEARER_SCAN_WINDOW_DEFAULT_MS&lt;br /&gt;
&lt;br /&gt;
5、射频的发射功率RADIO_POWER_NRF_0DBM。&lt;br /&gt;
&lt;br /&gt;
6、BLE广播包最大长度RADIO_CONFIG_ADV_MAX_PAYLOAD_SIZE。&lt;br /&gt;
&lt;br /&gt;
最后我们会在nrf_mesh_enable()函数中调用scanner_enable()函数去启动扫描，这个大家可以自己查看一下代码，比较容易理解。&lt;br /&gt;
[[文件:SCAN-04.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:SCAN-05.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
最后我们来看一下scanner_packet_process_cb()回调函数，其中我们可以直接拿到的扫描的数据包是rx_data，包含了数据的类型、数据内容以及数据长度等等信息。到这边我们的扫描功能是已经完成的，但是我们还需要再看下下面一部分的ad_listener的流程。&lt;br /&gt;
&lt;br /&gt;
=== Subscription（模型订阅） ===&lt;br /&gt;
我们需要注意下ad_listener_process()函数，这个函数是将我们扫描到的订阅数据（不符合标准的BLE广播数据，则认为是侦听到的订阅数据）传递到订阅者进行处理（订阅者指的是带订阅功能的模型）。这个是和我们上方说的关注点二ad_listener_subscribe()函数是一脉相承的。&lt;br /&gt;
[[文件:SCAN-06.png|边框|居中|无框|1200x1200像素]]然后我们来对照着看一下ad_listener_subscribe()函数以及ad_listener_process()函数。这两个函数一个是定义了侦听订阅，一个是将扫描获取到的数据传递给订阅者。这两者间的运作关系，大家有兴趣的可以继续查阅他的底层代码逻辑处理，一个是向list中添加订阅者信息，一个是将数据通过p_listener-&amp;gt;handler()将数据传递给订阅者处理。&lt;br /&gt;
[[文件:SCAN-07.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:SCAN-08.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
最终当我们扫描到订阅消息，并调用ad_listener_process()函数传递时，会触发我们注册的回调nrf_mesh_listen（调用ad_listener_subscribe(&amp;amp;m_nrf_mesh_listener)时，在m_nrf_mesh_listener中）。我们来看下nrf_mesh_listen函数中的处理，其中我们先从数据包中解析了数据的类型type，然后根据不同类型的数据进行相应的处理。&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+&lt;br /&gt;
|AD_TYPE_MESH &lt;br /&gt;
|蓝牙网格物体的AD类型&lt;br /&gt;
|-&lt;br /&gt;
|AD_TYPE_BEACON &lt;br /&gt;
|蓝牙网状信标的AD类型&lt;br /&gt;
|-&lt;br /&gt;
|AD_TYPE_PB_ADV &lt;br /&gt;
|PB-ADV消息的AD类型&lt;br /&gt;
|-&lt;br /&gt;
|AD_TYPE_DFU &lt;br /&gt;
|nRF OpenMesh消息的AD类型&lt;br /&gt;
|}&lt;br /&gt;
[[文件:SCAN-09.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
== ③入网验证 ==&lt;br /&gt;
经过前两章的学习，我们对于BLE MESH的广播及扫描的代码流程已经有了一定的了解，也就是已经大概的明白了数据的发送和接收的流程。&lt;br /&gt;
&lt;br /&gt;
那么从这一章节开始，我们将会给大家展示，如何配置一个节点设备入网。配置者设备，我们使用的是手机APP：nRF Mesh。&lt;br /&gt;
&lt;br /&gt;
我们从main文件中的start()函数开始，来看一下入网的代码处理及其流程。在start()函数中我们最后是调用的mesh_provisionee_prov_start()函数来启动的节点配置功能，我们来查看一下它所携带的参数prov_start_params。这部分的参数都是比较重要的，有携带的是设备的信息的、有携带认证密码的，有回调返回的，所以我们会挨个介绍一下。&lt;br /&gt;
[[文件:Prov-01.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
首先携带的是static_auth_data，这个是我们带外OOB认证的密码。如果我们在使用nRF Mesh去进行身份验证的时候，选择使用Static OOB，那么当我们在进行PB-GATT的连接过程中，会弹出一个新的窗口，要求我们去输入这个静态的128bit的OOB密钥。&lt;br /&gt;
[[文件:Prov-02.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Prov-14.png|边框|居中|无框]]&lt;br /&gt;
[[文件:Prov-15.png|边框|居中|无框]]&lt;br /&gt;
接下来携带的是provisioning_complete_cb()回调函数，这个函数处理的是节点设备成功的被配置入网，在这里我们可以获取到我们的节点设备被分配的node_Address。&lt;br /&gt;
[[文件:Prov-03.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
然后携带是device_identification_start_cb()回调函数，这个回调函数是用于通知开发者，我们的节点设备当前已经开始配置。&lt;br /&gt;
[[文件:Prov-04.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
再然后携带的是URI【统一资源标识符（Uniform Resource Identifier，URI)】，这个是用于标识我们的设备类型的，这个数据会通过AES-CMAC算法加密后添加到我们的Unprov Beacon数据的末尾。&lt;br /&gt;
&lt;br /&gt;
这里我们是light switch的server设备，所以我们定义URI是EX_URI_LS_SERVER。&lt;br /&gt;
[[文件:Prov-05.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
分析完携带的参数之后，我们继续来查看mesh_provisionee_prov_start()函数。在这个函数中，我们首先是定义了一下prov_caps，也就是配置支持的OOB功能。然后我们调用nrf_mesh_generate_keys()函数去生成有效的密钥对等待使用。&lt;br /&gt;
&lt;br /&gt;
再然后是调用nrf_mesh_prov_init()函数去初始化prov配置信息的结构体参数，里面有结构体本身m_pro_ctx，需要配置的参数公钥m_public、私钥m_private，以及我们刚刚定义的oob功能prov_caps，最后还携带了一个事件回调函数prov_ect_handler。&lt;br /&gt;
&lt;br /&gt;
接下来是PB-ADV以及PB-GATT的处理过程，这个前面已经讲解过，这里不再赘述。&lt;br /&gt;
&lt;br /&gt;
最后我们调用provisionee_start()函数去开启配置。&lt;br /&gt;
[[文件:Prov-06.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
我们继续来追踪一下provisionee_start()函数，在这个函数中我们调用nrf_mesh_prov_listen()函数去开启侦听承载层的消息。这里我们需要侦听PB-ADV以及PB-GATT两个。&lt;br /&gt;
[[文件:Prov-07.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
然后在nrf_mesh_prov_listen()函数中，我们调用prov_provisionee_listen()函数携带我们刚刚传入的nrf_mesh_prov_ctx_t结构体，以及p_bearer、URI、oob_info_source等参数信息，继续向下传递。&lt;br /&gt;
[[文件:Prov-08.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
最终我们来查看下prov_provisionee_listen()函数，在这个函数中我们传递了p_bearer-&amp;gt;p_callbacks给m_prov_callbacks回调，然后调用p_bearer-&amp;gt;p_interface-&amp;gt;listen_start()函数去启动PB-ADV以及PB-GATT的侦听。&lt;br /&gt;
[[文件:Prov-09.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
当我们在侦听之后，接收到任何配置相关的消息，都将通过m_prov_callbacks()函数回调传递给我们处理，大家可以自行看下其中4个不同的功能函数，具体处理的都是什么消息。&lt;br /&gt;
[[文件:Prov-10.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
在prov_provisionee_listen()函数中，我们可以看到回调函数指针m_prov_callbacks指向的是p_bearer-&amp;gt;p_callbacks，我们一路向上查找，最终可以找到这个p_bearer-&amp;gt;p_callbacks函数就是指的prov_evt_handler()函数。所以其实我们用户可以在这一层去查看比较重要的配置事件返回，而不需要到上面的m_prov_callbacks中这样偏底层的地方去查看。&lt;br /&gt;
&lt;br /&gt;
在这个回调函数中，我们可以看到mian文件中注册的prov_device_identification_start_cb、prov_device_identification_stop_cb、provisioning_aborted_cb的返回。&lt;br /&gt;
[[文件:Prov-11.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
那么除了上方已经存在的几个回调，我们可以看到mian文件中的start()函数下的prov_start_params参数，还缺少一个配置完成的回调prov_complete_cb()。&lt;br /&gt;
&lt;br /&gt;
这个回调的返回方式比较特殊，和上方其他的都不同，是由SD（softdevice）直接从底层返回给我们的。注册这个回调的函数是NRF_SDH_STATE_OBSERVER，返回的回调函数是sd_state_evt_handler。其中触发的事件NRF_SDH_EVT_STATE_ENABLED，这个是在mian文件中的initialize()函数下的ble_stack_init()函数中就被触发的，这个大家可以自行查看一下，不太重要，因为都是nordic底层处理好的消息。&lt;br /&gt;
[[文件:Prov-13.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Prov-12.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
以上整个配置流程的处理，不太好分步展示相应的现象，这个大家可以自己使用SES在线仿真的方式去调试一下流程，这里我们直接给大家展示整个流程打印出的相应的信息，以及nRF Mesh中暂时出来的现象。&lt;br /&gt;
[[文件:Prov-16.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Prov-17.png|边框|居中|无框]]&lt;br /&gt;
&lt;br /&gt;
== ④设备信息分配 ==&lt;br /&gt;
当我们成功的配置节点设备入网之后，我们需要给节点设备主要分配如下几个信息，有关这几个要点的介绍，可以查看“BLE-Mesh技术揭秘”第3章节：&lt;br /&gt;
&lt;br /&gt;
1、节点在网络中的地址Node Address&lt;br /&gt;
&lt;br /&gt;
2、网络密钥Netkey&lt;br /&gt;
&lt;br /&gt;
3、应用密钥Appkey&lt;br /&gt;
&lt;br /&gt;
4、元素的地址Element Address&lt;br /&gt;
&lt;br /&gt;
5、绑定model的Appkey，分配Publish Address，Subscription Address&lt;br /&gt;
&lt;br /&gt;
=== Node Address ===&lt;br /&gt;
首先我们来看一下我们使用nRF Mesh成功配置我们的节点之后，打印出来的消息，开始的紫色字体显示了我们给当前节点设备分配的Node Address。&lt;br /&gt;
&lt;br /&gt;
后面紧接着还有四段黄色字体的内容（这个部分大家的工程是不会打印的，是我们修改例程打印出来给大家展示一下），这四个部分就是我们配置节点的时候，配置者设备（nRF Mesh）给节点设备的四个主要指令，这些指令的具体功能，大家可以查看mesh的协议手册。[[文件:Config-13.png|边框|居中|无框|1080x1080像素]]在代码中，我们会在mian文件的provisioning_complete_cb()回调函数中获取并打印我们的Node Address，这个回调触发的含义就是我们的节点配置已经完成了。&lt;br /&gt;
&lt;br /&gt;
具体这个回调在代码中的回调及触发流程，大家看可以查看“③入网验证”的说明。[[文件:Config-20.png|边框|居中|无框|1200x1200像素]]我们使用nRF Mesh配置好节点设备之后，app界面显示内容如下，包含了节点的信息（地址、名称、配置时间等等），元素和模型，以及Netkey和Appkey等几个重要的设备信息。[[文件:Config-01.jpg|边框|居中|无框]]&lt;br /&gt;
[[文件:Config-04.jpg|边框|居中|无框]]&lt;br /&gt;
&lt;br /&gt;
=== Netkey ===&lt;br /&gt;
我们点击查看一下Network key，可以看到里面有一个Network Key 1，这是一个16字节的网络密钥。我们可以在nRF Mesh的Settings界面去增加或者删除一个network key，用户可以自定义此密钥。[[文件:Config-02.jpg|边框|居中|无框]]在我们的代码中，我们是在pro_evt_handler回调的NRF_MESH_PROV_EVT_COMPLETE事件中获取这个netkey，这个回调函数的触发流程同样是在“③入网验证”已经有说明。[[文件:Prov-11.png|边框|居中|无框|1200x1200像素]]我们直接看下NRF_MESH_PROV_EVT_COMPLETE事件下的mesh_stack_provisioning_data_store()函数，在这个函数中我们主要的任务，就是将配置者分配给我们的node address、netkey、devkey等等信息，分别做一些存储以及功能绑定。&lt;br /&gt;
[[文件:Netkey-01.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
=== Appkey ===&lt;br /&gt;
同样的我们打开Application Key，可以看到其中包含的3个密钥，这里我们只选用了Application Key 1作为我们的密钥。大家同样可以在nRF Mesh的Settings界面去增加或者删除一个App key，用户可以自定义此密钥。[[文件:Config-03.jpg|边框|居中|无框]]appkey这里和netkey不同，netkey是我们网络中的节点设备以及我们配置者设备共有的一个密钥，只有拥有这个密钥，我们才能在当前网络中通信，所以当我们的节点设备配置入网后，会立刻得到netkey（必须要分配给我的节点设备才行）。&lt;br /&gt;
&lt;br /&gt;
而appkey只是我们的某个模型用于通信的密钥，所以我们称它为应用密钥，这个appkey的分配，是在我们的config server下。这个config_server_init的初始化流程，是在我们的mesh_stack_init()函数下，就去初始化配置好的。最终当我们有信息配置的时候，会触发opcode_handlers，我们可以从这个操作码列表中查找相应的信息处理。[[文件:Config-22.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Config-23.png|边框|居中|无框|1200x1200像素]]我们来看下config server的opcode_handlers[]操作码处理程序列表，使用黄色标记给大家圈了几个操作码指令，这几个是我们配置一个最简单的server节点设备，都需要使用到的，包含了：&lt;br /&gt;
&lt;br /&gt;
1、appkey的添加，CONFIG_OPCODE_APPKEY_ADD&lt;br /&gt;
&lt;br /&gt;
2、publish发布地址，CONFIG_OPCODE_MODEL_PUBLICATION_VIRTUAL_ADDRESS_SET&lt;br /&gt;
&lt;br /&gt;
3、subscription订阅地址，CONFIG_OPCODE_MODEL_SUBSCRIPTION_ADD&lt;br /&gt;
&lt;br /&gt;
4、模型appkey的绑定，CONFIG_OPCODE_MODEL_APP_BIND&lt;br /&gt;
&lt;br /&gt;
5、netkey的更新，CONFIG_OPCODE_NETKEY_ADD[[文件:Config-21.png|边框|居中|无框|1200x1200像素]]我们可以看到当接收到CONFIG_OPCODE_APPKEY_ADD操作码的数据，会调用handle_appkey_add()函数去处理，在这个函数中我们最终会调用dsm_appkey_add()函数去存储我们的appkey。[[文件:Appkey-01.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
== ⑤Element元素 ==&lt;br /&gt;
元素简单的说就是定义了节点的功能，一些具有复杂功能的节点设备，会包含多个元素。一个节点设备最少需要包含一个元素，我们称之为主元素。&lt;br /&gt;
&lt;br /&gt;
从主元素开始（如果有多个元素，则第一个元素就是主元素），我们会给它分配相应的元素地址。主元素的地址就是节点的地址，第二个元素的地址就是主元素的地址基础上加上1，依次类推。&lt;br /&gt;
&lt;br /&gt;
可以看到在我们的主元素Element：0x0002中包含了3个不同的服务，这3个服务就是我们的模型Model，其中Configuration Server与Health Server这两个服务，是每一个节点都必须携带的模型。Configuration Server（配置服务）是用于配置节点设备信息，Health Server（健康服务）是用于节点进行网络内的心跳联系。剩下的Generic On Off Server服务，这个就是我们用于控制一个开关量的模型（在例程中我们通过控制LED的IO口高低电平来展示）。[[文件:Config-05.jpg|边框|居中|无框]]在我们的代码中，我们首先需要在nrf_mesh_config_app.h中定义好我们节点设备的最大元素数量，可以看到我们定义#define ACCESS_ELEMENT_COUNT (1)，也就是说我们当前的例程，只含有一个主元素。&lt;br /&gt;
[[文件:Element-01.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
而这个ACCESS_ELEMENT_COUNT ，是在我们开启节点配置的地方被调用的，这个mesh_provsionionee_prov_start()函数在前面的内容中有多次讲到，这里就不再说明了。&lt;br /&gt;
[[文件:Element-02.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
== ⑥model模型 ==&lt;br /&gt;
&lt;br /&gt;
=== Configure Server ===&lt;br /&gt;
&lt;br /&gt;
=== Health Server ===&lt;br /&gt;
&lt;br /&gt;
=== Generic On Off Server ===&lt;br /&gt;
我们来看下Generic On Off Server的[[文件:Config-14.png|边框|居中|无框|1040x1040像素]]&lt;br /&gt;
[[文件:Config-06.jpg|边框|居中|无框]]&lt;br /&gt;
[[文件:Config-12.jpg|边框|居中|无框]]&lt;br /&gt;
&lt;br /&gt;
==== model bind appkey ====&lt;br /&gt;
[[文件:Config-07.jpg|边框|居中|无框]]&lt;br /&gt;
[[文件:Config-08.jpg|边框|居中|无框]]&lt;br /&gt;
&lt;br /&gt;
==== model publish address ====&lt;br /&gt;
[[文件:Config-09.jpg|边框|居中|无框]]&lt;br /&gt;
[[文件:Config-10.jpg|边框|居中|无框]]&lt;br /&gt;
&lt;br /&gt;
==== model subscription address ====&lt;br /&gt;
[[文件:Config-15.png|边框|居中|无框|1040x1040像素]]&lt;br /&gt;
[[文件:Config-11.jpg|边框|居中|无框]]&lt;br /&gt;
[[分类:NRF52832DK]]&lt;br /&gt;
[[分类:实验手册]]&lt;/div&gt;</summary>
		<author><name>Jinx</name></author>
		
	</entry>
	<entry>
		<id>http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:Element-02.png&amp;diff=2784</id>
		<title>文件:Element-02.png</title>
		<link rel="alternate" type="text/html" href="http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:Element-02.png&amp;diff=2784"/>
		<updated>2020-03-31T06:36:16Z</updated>

		<summary type="html">&lt;p&gt;Jinx：&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Element-02&lt;/div&gt;</summary>
		<author><name>Jinx</name></author>
		
	</entry>
	<entry>
		<id>http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:Element-01.png&amp;diff=2783</id>
		<title>文件:Element-01.png</title>
		<link rel="alternate" type="text/html" href="http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:Element-01.png&amp;diff=2783"/>
		<updated>2020-03-31T06:32:48Z</updated>

		<summary type="html">&lt;p&gt;Jinx：Jinx上传文件:Element-01.png的新版本&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Element-01&lt;/div&gt;</summary>
		<author><name>Jinx</name></author>
		
	</entry>
	<entry>
		<id>http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:Element-01.png&amp;diff=2782</id>
		<title>文件:Element-01.png</title>
		<link rel="alternate" type="text/html" href="http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:Element-01.png&amp;diff=2782"/>
		<updated>2020-03-31T06:31:42Z</updated>

		<summary type="html">&lt;p&gt;Jinx：&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Element-01&lt;/div&gt;</summary>
		<author><name>Jinx</name></author>
		
	</entry>
	<entry>
		<id>http://doc.iotxx.com/index.php?title=NRF52832DK-Mesh%E7%BB%84%E7%BD%91%E5%AE%9E%E9%AA%8C&amp;diff=2781</id>
		<title>NRF52832DK-Mesh组网实验</title>
		<link rel="alternate" type="text/html" href="http://doc.iotxx.com/index.php?title=NRF52832DK-Mesh%E7%BB%84%E7%BD%91%E5%AE%9E%E9%AA%8C&amp;diff=2781"/>
		<updated>2020-03-31T06:21:54Z</updated>

		<summary type="html">&lt;p&gt;Jinx：/* Node Address */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;蓝牙Mesh和BLE是并行发展的一个独立分支，虽然底层共用Physical Layer和Link Layer，但是上层协议并不相同。因此，nRF52832的Mesh例程，并不在BLE协议栈sdk中，而是安装一个扩展包。&lt;br /&gt;
&lt;br /&gt;
本文介绍蓝牙Mesh相关的环境搭建和实验介绍。&lt;br /&gt;
&lt;br /&gt;
== Mesh开发环境 ==&lt;br /&gt;
&lt;br /&gt;
=== 蓝牙Mesh SDK ===&lt;br /&gt;
SDK位置：&amp;lt;code&amp;gt;归档资料/2-协议栈SDK/nRF5_SDK_Mesh_v310.zip&amp;lt;/code&amp;gt;，注意文件名中的Mesh字样。&lt;br /&gt;
&lt;br /&gt;
和BLE协议栈一样，将该压缩包直接解压到BLE协议栈同级目录即可使用，解压后的SDK路径如下：&amp;lt;code&amp;gt;E:\project-nordic\nRF5_SDK_Mesh_v310&amp;lt;/code&amp;gt;&lt;br /&gt;
[[文件:NRF52832DK-MESH-SDK-INSTALL.gif|居中|无框|750x750像素]]&lt;br /&gt;
&lt;br /&gt;
=== 集成开发软件SES ===&lt;br /&gt;
SES时是Segger Embedded Studio编译器的缩写，用来编译蓝牙Mesh代码，SES功能与IAR或Keil类似。&lt;br /&gt;
&lt;br /&gt;
备注：Nordic为何使用SES而不是IAR，原因不得而知。&lt;br /&gt;
&lt;br /&gt;
==== SES安装 ====&lt;br /&gt;
SES软件位置：&amp;lt;code&amp;gt;归档资料/7-编译器SES/Setup_EmbeddedStudio_ARM_v410_win_x64.exe&amp;lt;/code&amp;gt;。&lt;br /&gt;
&lt;br /&gt;
我们双击打开安装界面，按照如下动图所示方式进行安装（可以全部默认安装）。[[文件:Ses install.gif|边框|居中|无框|556x556像素]]&lt;br /&gt;
&lt;br /&gt;
==== SES许可证激活 ====&lt;br /&gt;
我们双击打开安装好的SES，选择编译自带测试例程，此时弹出需要激活的界面。&lt;br /&gt;
[[文件:Licensed1.jpg|边框|居中|无框|1205x1205像素]]&lt;br /&gt;
由于我们准备使用SES编译nordic的NRF52832的SDK，所以这里我们选择Activate Your Free License。这里因为nordic公司已经帮客户想SEGGER公司买下了使用的版权，所以是免费的许可证。&lt;br /&gt;
[[文件:Licensed2.jpg|边框|居中|无框|736x736像素]]&lt;br /&gt;
我们按照要求填写好信息，主要是邮箱地址（用于接收SEGGER公司发给我们的许可证），填写完成之后点击Request License。下面是我们接收的邮件信息，我们将红色框内的内容复制下来。&lt;br /&gt;
[[文件:Ses install3.png|边框|居中|无框|1301x1301像素]]&lt;br /&gt;
打开SES，选择Tools\License Manager...，我们选择Activate Embedded Studio，打开如下界面，并将我们的许可证粘贴进去，然后点击Install License。&lt;br /&gt;
[[文件:Licensed4.png|边框|居中|无框|481x481像素]]&lt;br /&gt;
安装好许可证之后，跳转到如下的界面。&lt;br /&gt;
[[文件:Licensed5.png|边框|居中|无框|481x481像素]]&lt;br /&gt;
此时我们的许可证激活完毕（有时候需要等待一段时间，才会显示激活成功）。&lt;br /&gt;
[[文件:Licensed6.png|边框|居中|无框|515x515像素]]&lt;br /&gt;
&lt;br /&gt;
== Light_switch ==&lt;br /&gt;
&lt;br /&gt;
=== 实验简介 ===&lt;br /&gt;
此示例演示了包含充当两个角色的设备的网状生态系统：配置角色（Provisioner role）和节点角色（Node role、provisionee role）。 它还通过在应用程序中使用[Generic OnOff model]来演示了如何使用Mesh模型。&lt;br /&gt;
&lt;br /&gt;
该示例由三个较小的示例组成：&lt;br /&gt;
&lt;br /&gt;
Light switch server：一个实现了[Generic OnOff server model]的简约服务器，用于接收状态数据并控制板上LED 1的状态。&lt;br /&gt;
&lt;br /&gt;
Light switch client：一个实现了[Generic OnOff client model]的简约客户端。当用户按下任意按钮时，OnOff Set消息将发送到配置的目标地址。&lt;br /&gt;
&lt;br /&gt;
Mesh Provisioner：一个简单的静态配置设备应用，用于建立演示网络，该配置设备在一个网状网络中配置所有节点。 此外，配置设备还可以在这些节点上配置网格模型[Generic OnOff model]实例的绑定以及发布和订阅设置，以使它们能够相互通信。&lt;br /&gt;
&lt;br /&gt;
[Generic OnOff Client/Server]模型用于操纵打开/关闭状态。请注意，当服务器设置了发布地址（如本例所示）时，服务器会将其状态更改的任何操作披露到其发布地址。&lt;br /&gt;
&lt;br /&gt;
下图给出了将由静态供应商设置的网状网络的整体视图， 括号中的数字表示预配置程序分配给这些节点的地址。&lt;br /&gt;
[[文件:Mesh network.png|边框|居中|无框|727x727像素]]&lt;br /&gt;
灯开关服务器和灯开关客户端示例均具有预配方角色。它们支持通过广告承载（PB-ADV）和GATT承载（PB-GATT）进行配置，并且还支持网状代理服务器（Proxy Service），但是不支持代理客户端（Proxy Client）。&lt;br /&gt;
&lt;br /&gt;
=== 硬件说明 ===&lt;br /&gt;
完成这个实验，我们最少需要两个开发板硬件用做我们的节点设备（Node）：&lt;br /&gt;
* 一个开发板用做client&lt;br /&gt;
* 一个或者多个开发板用做server&lt;br /&gt;
此外，我们还需要以下之一作为我们的配置设备（Provisioner）：&lt;br /&gt;
* 如果您决定使用静态预配器示例，则多准备一个开发板&lt;br /&gt;
* 如果您决定使用app程序进行设置，则需要安装手机app@link_nrf_mesh_app（@link_nrf_mesh_app_ios或@link_nrf_mesh_app_android）。&lt;br /&gt;
{{Note|text=默认我们使用3个开发板完成我们的实验，3个开发板分别对应client、server以及provisioner三个类型。|type=warning}}&lt;br /&gt;
&lt;br /&gt;
=== 软件说明 ===&lt;br /&gt;
1、我们使用NrfGo上位机，依次对三个开发板进行擦除及softdevice的烧写&lt;br /&gt;
[[文件:Lightswitch nrfgo.png|边框|居中|无框|1013x1013像素]]&lt;br /&gt;
2、使用SEGGER Embedded Studio for ARM编译器分别打开我们`&amp;lt;InstallFolder&amp;gt;/examples/light_switch`路径下的3个实验，并完成编译&lt;br /&gt;
&lt;br /&gt;
●\client\light_switch_client_nrf52832_xxAA_s132_6_1_0.emProject&lt;br /&gt;
&lt;br /&gt;
●\server\light_switch_server_nrf52832_xxAA_s132_6_1_0.emProject&lt;br /&gt;
&lt;br /&gt;
●\provisioner\light_switch_provisioner_nrf52832_xxAA_s132_6_1_0.emProject&lt;br /&gt;
[[文件:Lightswitch SES build.png|边框|居中|无框|1013x1013像素]]&lt;br /&gt;
3、此时我们对3个开发板分别进行固件的烧写，分别烧写client、server、provisioner（建议大家给3个开发板贴上标签，方便后续区分）&lt;br /&gt;
[[文件:Lightswitch SES download.png.gif|边框|居中|无框|1009x1009像素]]&lt;br /&gt;
&lt;br /&gt;
=== 实验现象 ===&lt;br /&gt;
{{Note|text=注意：保证我们的3个设备都是刚按照软件说明部分的步骤配置完成，并且没有进行过任何硬件控制（按键操作）。目的是保证所有设备均未被我们人为配置过，没有进行过网络配置，否则可能有任意异常（不同网络配置，导致的不同现象）|type=danger}}&lt;br /&gt;
1、首先我们分别给client和provisioner供电，然后我们按下provisioner的按键S1：&lt;br /&gt;
&lt;br /&gt;
●provisoner：LED灯点亮，代表正在配置查找设备，配置组网&lt;br /&gt;
&lt;br /&gt;
●client：首先LED3和LED4闪烁，代表正在组网；LED1-LED4四个灯一起闪烁，代表组网完成&lt;br /&gt;
&lt;br /&gt;
我们如果打开RTT检测log打印，可以看到如下的信息，这个时候我们已经给client设备分配了Node Address为0x0100。provisioner信息（上），client信息（下）。&lt;br /&gt;
[[文件:Lightswitch rtt provisoner1.png|边框|无框|1080x1080像素|居中]]&lt;br /&gt;
[[文件:Lightswitch_rtt_node_client.png|边框|居中|无框|656x656像素]]&lt;br /&gt;
&lt;br /&gt;
2、使用provisioner配置好client之后，我们给server开发板上电，这个时候provisioner将会配置server入网：&lt;br /&gt;
&lt;br /&gt;
●provisoner：LED1点亮代表正在配置网络，LED2点亮代表网络配置完成&lt;br /&gt;
&lt;br /&gt;
●server：首先LED3和LED4闪烁，代表正在组网；LED1-LED4四个灯一起闪烁，代表组网完成&lt;br /&gt;
&lt;br /&gt;
我们打开RTT检测log打印，可以看到server已经配置入网，并且被分配了地址Node Address为0x0104。provisioner信息（上），server信息（下）。&lt;br /&gt;
[[文件:Lightswitch rtt provisoner2.png|边框|无框|710x710像素|居中]]&lt;br /&gt;
[[文件:Lightswitch rtt node server.png|边框|无框|656x656像素|居中]]&lt;br /&gt;
&lt;br /&gt;
3、此时整个网络的配置已经完成，这个时候我们可以按下client上的按键来控制server上的LED点亮或者熄灭。&lt;br /&gt;
&lt;br /&gt;
由于我们的server被provisioner分配的Node Address是0x0104（偶数），所以我们通过client的S3控制server的LED1点亮，通过client的S4控制server的LED1熄灭。&lt;br /&gt;
{{Note|text=client设备对于server设备的控制，通过Node Address的奇偶位不同，分成了两个组。&lt;br /&gt;
client的S1和S2分别控制Node Address为奇数的server设备的LED1点亮和熄灭。&lt;br /&gt;
&lt;br /&gt;
client的S3和S4分别控制Node Address为偶数的server设备的LED1点亮和熄灭。|type=warning}}&lt;br /&gt;
我们打开RTT检测log打印，可以看到client分别按下button2（S3）以及button3（S4），分别会设置server的GPIO输出高低电平。client信息（上），server信息（下）。&lt;br /&gt;
[[文件:Lightswitch rtt node client2.png|边框|无框|656x656像素|居中]]&lt;br /&gt;
[[文件:Lightswitch rtt node server2.png|边框|无框|656x656像素|居中]]&lt;br /&gt;
&lt;br /&gt;
4、至此我们的mesh组网的lightswitch实验测试完成。&lt;br /&gt;
&lt;br /&gt;
=== 源码详解 ===&lt;br /&gt;
&lt;br /&gt;
== Beaconing example ==&lt;br /&gt;
&lt;br /&gt;
=== 实验简介 ===&lt;br /&gt;
&lt;br /&gt;
=== 硬件说明 ===&lt;br /&gt;
&lt;br /&gt;
=== 软件说明 ===&lt;br /&gt;
&lt;br /&gt;
=== 实验现象 ===&lt;br /&gt;
&lt;br /&gt;
=== 源码详解 ===&lt;br /&gt;
&lt;br /&gt;
== Coexistence example ==&lt;br /&gt;
&lt;br /&gt;
=== 实验简介 ===&lt;br /&gt;
&lt;br /&gt;
=== 硬件说明 ===&lt;br /&gt;
&lt;br /&gt;
=== 软件说明 ===&lt;br /&gt;
&lt;br /&gt;
==== 将Mesh集成到nRF5 SDK示例中 ====&lt;br /&gt;
用于网格的nRF5 SDK与Nordic的nRF5 SDK兼容。 这使您可以将nRF5 SDK中的资源包括在现有的Mesh项目中，也可以将nRF5 SDK中的Mesh功能包括在nRF5 SDK示例中。&lt;br /&gt;
&lt;br /&gt;
请参阅构建网格堆栈和示例，以获取有关如何下载和安装nRF5 SDK的信息。 查看共存示例，以了解如何将nRF5 SDK功能与用于网格的nRF5 SDK同时使用。&lt;br /&gt;
&lt;br /&gt;
===== 动态记忆 =====&lt;br /&gt;
在将nRF5 SDK功能与nRF5 Mesh SDK功能一起使用时（如并存示例所示），您可能会遇到以下情况：应用程序注意事项可能需要更改动态内存的分配方式和可分配的动态内存量。 网格堆栈使用网格内存管理器界面进行动态内存分配。 默认的后端mesh_mem_stdlib.c使用标准库malloc（），该库需要定义足够大的堆大小。 可以通过用另一个内存管理器替换后端来更改此行为。&lt;br /&gt;
&lt;br /&gt;
如果使用Segger Embedded Studio构建应用程序，请在“项目选项”&amp;gt;“代码”&amp;gt;“运行时内存区域”设置中将“堆大小”设置为8192字节。&lt;br /&gt;
&lt;br /&gt;
===== 并发的SoftDevice和Mesh活动 =====&lt;br /&gt;
同时运行SoftDevice和Mesh时，最大的性能问题通常来自无线电时间争用。虽然SoftDevice通常在预定的短脉冲中运行，但Mesh会尝试尽可能多地使用无线电。只要SoftDevice没有无线电活动，Mesh就会连续扫描和通告。 SoftDevice活动将减少Mesh广播的时间，并且为了保持一致的Mesh性能，必须在不影响用户体验的情况下尽可能保守地设置SoftDevice无线电参数。&lt;br /&gt;
&lt;br /&gt;
在使用SoftDevice进行广告时，请尝试使用您的使用情况可以容许的最大广告间隔。如果可能，请在不需要时关闭SoftDevice广告商，仅在您希望收到连接请求时才激活它。如果您只需要发送不可连接的，不可扫描的广告（例如，针对第三方信标协议），请使用Mesh Advertiser API，因为它与Mesh一起使用时可以进行最少的上下文切换。&lt;br /&gt;
&lt;br /&gt;
当SoftDevice在连接中运行时：&lt;br /&gt;
&lt;br /&gt;
尝试协商应用程序可以允许的最大连接间隔。如果需要通过连接的高吞吐量，则在每个连接事件中发送更多的数据要比减少连接间隔更好，因为大部分开销来自上下文切换。&lt;br /&gt;
&lt;br /&gt;
如果网状设备在其SoftDevice连接中充当外围设备（从设备），则还可以增加“从设备等待时间”，这应使SoftDevice跳过连接事件，而不会增加通过链接的任何传出数据传输的等待时间。&lt;br /&gt;
&lt;br /&gt;
与广告一样，建议仅在需要时才保持连接活动。空闲的软设备连接对网状网性能的影响几乎与具有大量流量的连接一样多，特别是在从属延迟较低的情况下。&lt;br /&gt;
&lt;br /&gt;
基于SoftDevice的扫描对所有SoftDevice活动的Mesh性能影响最大。当SoftDevice正在扫描时，Mesh无法接收数据包，因此每个SoftDevice扫描窗口都会替换Mesh扫描。仅在尝试建立连接或需要活动扫描时才应使用SoftDevice扫描。如果需要常规的被动BLE扫描（用于侦听信标或其他第三方活动），请通过使用nrf_mesh_rx_cb_set函数设置RX回调来挂接到Mesh扫描器。如果您的应用程序需要主动扫描或需要启动连接，则应尽可能保守地设置扫描参数。较长的扫描间隔和较短的扫描窗口将使Mesh拥有更多的时间进行自身的无线电活动。类似地，建立上下文时在短时间内执行连续扫描可能比执行长时间运行的占空比扫描有利，因为上下文切换会导致很多不必要的开销。最后，强烈建议为SoftDevice连接启动调用设置超时，以避免长时间的空闲扫描。&lt;br /&gt;
&lt;br /&gt;
由于Mesh无法主动阻止SoftDevice无线电活动，因此减少同时运行SoftDevice活动的设备上的Mesh活动不会直接影响SoftDevice的性能。但是，附近的其他Mesh设备会干扰广告渠道中的SoftDevice活动，这可能会使连接启动花费更长的时间。&lt;br /&gt;
&lt;br /&gt;
通常，Mesh必须在SoftDevice活动之间执行其所有无线电操作，因此，如果设备在执行SoftDevice无线电操作时正在发送大量Mesh数据包，则它将在此上花费大部分Mesh时间，而不是接收传入的数据。为了解决这个问题，请根据可用的无线电时间调整输出数据包的数量，以减少Mesh数据包的发送。如果可能，请通过在CORE_TX_ROLE_RELAY角色上调用mesh_opt_core_adv_set来暂停耗时的SoftDevice操作期间的Mesh数据包中继。&lt;br /&gt;
&lt;br /&gt;
===== 在用于网格示例的nRF5 SDK中包括nRF5 SDK =====&lt;br /&gt;
根据您的工具链：&lt;br /&gt;
&lt;br /&gt;
使用Segger Embedded Studio时，请添加代码文件并包括相应SES项目文件的路径。&lt;br /&gt;
&lt;br /&gt;
使用CMake构建用于网格堆栈的nRF5 SDK时，添加代码文件并包括指向相应CMakeLists.txt文件的路径。 SDK_ROOT根符号用于引用nRF5 SDK安装文件夹（例如，请参阅Light Switch服务器示例中的CMakeLists.txt）。&lt;br /&gt;
&lt;br /&gt;
网格示例项目已经在其include /目录中包含sdk_config.h文件。 这些文件是默认SDK配置文件的副本，并且网格示例所需的所有更改都包含在示例目录中的include / app_config.h文件中。&lt;br /&gt;
&lt;br /&gt;
注意，&lt;br /&gt;
&lt;br /&gt;
必须先在SDK配置文件中显式启用某些SDK功能，然后才能使用它们。 有关详细信息，请参见SDK文档页面SDK配置头文件。&lt;br /&gt;
&lt;br /&gt;
===== 在nRF5 SDK示例中包括用于网格功能的nRF5 SDK =====&lt;br /&gt;
在nRF5 SDK示例的项目文件中包括来自nRF5 SDK for Mesh的以下源文件：&lt;br /&gt;
* All C files in &amp;lt;code&amp;gt;mesh/core/src&amp;lt;/code&amp;gt;&lt;br /&gt;
* All C files in &amp;lt;code&amp;gt;mesh/bearer/src&amp;lt;/code&amp;gt;&lt;br /&gt;
* All C files in &amp;lt;code&amp;gt;mesh/prov/src&amp;lt;/code&amp;gt; except nrf_mesh_prov_bearer_gatt.c&lt;br /&gt;
* All C files in &amp;lt;code&amp;gt;mesh/access/src&amp;lt;/code&amp;gt;&lt;br /&gt;
* All C files in &amp;lt;code&amp;gt;mesh/dfu/src&amp;lt;/code&amp;gt;&lt;br /&gt;
* All C files in &amp;lt;code&amp;gt;mesh/stack/src&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;models/foundation/config/src/config_server.c&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;models/foundation/config/src/composition_data.c&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;models/foundation/config/src/packed_index_list.c&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;models/foundation/health/src/health_server.c&amp;lt;/code&amp;gt;&lt;br /&gt;
* Any other mesh models that are used in your application&lt;br /&gt;
* &amp;lt;code&amp;gt;external/micro-ecc/uECC.c&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;examples/common/src/assertion_handler_weak.c&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;examples/common/src/mesh_provisionee.c&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
注意&lt;br /&gt;
&lt;br /&gt;
如果不需要各种网格特征（例如DFU），则可以从项目文件中省略相应的文件。但是，在其位置添加examples / nrf_mesh_weak.c以提供缺少的API函数的存根。&lt;br /&gt;
&lt;br /&gt;
将以下文件夹添加到nRF5 SDK示例的项目包含路径：&lt;br /&gt;
* &amp;lt;code&amp;gt;mesh/core/api&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;mesh/core/include&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;mesh/bearer/api&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;mesh/bearer/include&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;mesh/prov/api&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;mesh/prov/include&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;mesh/access/api&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;mesh/access/include&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;mesh/dfu/api&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;mesh/dfu/include&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;mesh/stack/api&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;models/foundation/config/include&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;models/foundation/health/include&amp;lt;/code&amp;gt;&lt;br /&gt;
* Path to include folder of any other mesh models that are used in your application&lt;br /&gt;
* &amp;lt;code&amp;gt;external/micro-ecc&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;examples/common/include&amp;lt;/code&amp;gt;&lt;br /&gt;
* Path to any other resources in the mesh examples that are used in your application&lt;br /&gt;
将以下预处理器符号添加到nRF5 SDK示例的项目文件中：&lt;br /&gt;
* &amp;lt;code&amp;gt;NRF52_SERIES&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;NRF_MESH_LOG_ENABLE=NRF_LOG_USES_RTT&amp;lt;/code&amp;gt; (because logging in the mesh stack relies on RTT)&lt;br /&gt;
* &amp;lt;code&amp;gt;CONFIG_APP_IN_CORE&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== 可选变更 =====&lt;br /&gt;
此外，您可能需要应用以下一项或多项更改：&lt;br /&gt;
&lt;br /&gt;
如果与nRF5 SDK集成在一起，则可能需要更新在网格堆栈中使用simple_hal模块的示例以使用Nordic nRF5 SDK bsp模块。可以同时使用两者，但是在这种情况下，必须从其中之一删除GPIOTE_IRQHandler，并且只有一个模块可以注册回调函数。&lt;br /&gt;
&lt;br /&gt;
如果原始的Nordic nRF5 SDK示例使用了SoftDevice，请确保在启用SoftDevice之后初始化并启用了网格堆栈。在这种情况下，必须将SoftDevice事件转发到网格堆栈。将以下代码添加到您的应用程序：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;#&amp;lt;/nowiki&amp;gt;include“ nrf_sdh_soc.h”&lt;br /&gt;
&lt;br /&gt;
＃定义MESH_SOC_OBSERVER_PRIO 0&lt;br /&gt;
&lt;br /&gt;
静态void mesh_soc_evt_handler（uint32_t evt_id，void * p_context）&lt;br /&gt;
&lt;br /&gt;
{&lt;br /&gt;
&lt;br /&gt;
    nrf_mesh_on_sd_evt（evt_id）;&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
NRF_SDH_SOC_OBSERVER（m_mesh_soc_observer，MESH_SOC_OBSERVER_PRIO，mesh_soc_evt_handler，NULL）;&lt;br /&gt;
&lt;br /&gt;
如果您有多个SOC观察者，请确保一次仅从其中一个观察者将SOC观察者事件转发到网格堆栈。&lt;br /&gt;
&lt;br /&gt;
默认情况下，在网格堆栈以及某些Nordic nRF5 SDK应用程序中启用网络配置的闪存存储。用于此目的的闪光区域可能会重叠并导致错误。为了使闪存存储模块Flash管理器与网状堆栈中的闪存管理器以及Nordic nRF5 SDK中的闪存存储模块安全地共存，请在nrf_mesh_config_app.h中添加以下代码块：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;#&amp;lt;/nowiki&amp;gt;include“ fds.h”&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;#&amp;lt;/nowiki&amp;gt;include“ fds_internal_defs.h”&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;#&amp;lt;/nowiki&amp;gt;define FLASH_MANAGER_RECOVERY_PAGE_OFFSET_PAGES FDS_PHY_PAGES&lt;br /&gt;
&lt;br /&gt;
如果要添加自己的网格功能，而不是使用现有的网格示例，则还需要添加文件nrf_mesh_config_app.h。将其从网格堆栈存储库中的examples / templates文件夹复制到您的项目文件夹中，并删除文件顶部的#error消息。对文件内容进行其他适当的更改，例如将ACCESS_ELEMENT_COUNT和ACCESS_MODEL_COUNT调整为所需数量的元素和模型。&lt;br /&gt;
&lt;br /&gt;
===== Flash放置项目文件 =====&lt;br /&gt;
Segger Embedded Studio项目旁边都有一个flash_placement.xml文件，该文件充当链接器的输入。在flash_placement.xml文件中，nRF5 SDK配置了一组ProgramSection列表，用于放置某些变量。除了nRF5 SDK组件所需的所有ProgramSections外，网格还需要另外两个部分，即nrf_mesh_flash和nrf_mesh_ram。&lt;br /&gt;
&lt;br /&gt;
必须将其他与网格相关的部分添加到flash_placement.xml文件中：&lt;br /&gt;
&lt;br /&gt;
将以下行添加到标记为&amp;lt;MemorySegment name =“ FLASH” ...&amp;gt;的内存段中：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ProgramSection alignment =“ 4” keep =“是” load =“是” name =“。nrf_mesh_flash”输入节=“ *（SORT（.nrf_mesh_flash。*））”“ address_symbol =” __ start_nrf_mesh_flash“ end_symbol =” __ stop_nrf_mesh_flash“ /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
将以下行添加到标记为&amp;lt;MemorySegment name =“ RAM” ...&amp;gt;的内存段中：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ProgramSection alignment =“ 4” keep =“是” load =“否” name =“。nrf_mesh_ram” inputsections =“ *（SORT（.nrf_mesh_ram。*））”“ address_symbol =” __ start_nrf_mesh_ram“ end_symbol =” __ stop_nrf_mesh_ram“ /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
注意&lt;br /&gt;
&lt;br /&gt;
对于共存示例已经进行了此更改。在编辑任何现有nRF5 SDK示例的Flash放置文件时，可以在这些示例中使用flash_placement.xml文件作为参考。&lt;br /&gt;
&lt;br /&gt;
=== 实验现象 ===&lt;br /&gt;
&lt;br /&gt;
=== 源码详解 ===&lt;br /&gt;
&lt;br /&gt;
== Coexistence Uart example ==&lt;br /&gt;
&lt;br /&gt;
=== 实验简介 ===&lt;br /&gt;
&lt;br /&gt;
=== 硬件说明 ===&lt;br /&gt;
&lt;br /&gt;
=== 软件说明 ===&lt;br /&gt;
&lt;br /&gt;
==== SDK UART共存示例 ====&lt;br /&gt;
本示例演示了如何同时使用适用于Mesh的nRF5 SDK和nRF5 SDK示例。它围绕两个示例构建，分为两个部分：&lt;br /&gt;
&lt;br /&gt;
此示例的网格部分从nRF5 SDK for Mesh实现了灯开关客户端示例。&lt;br /&gt;
&lt;br /&gt;
该示例的BLE部分实现了nRF5 SDK的ble_app_uart示例，但以下更改除外：&lt;br /&gt;
* BSP事件SLEEP，DISCONNECT和WHITELIST_OFF被忽略。这使得板子按钮仅控制应用程序的网格部分&lt;br /&gt;
* 增加广告间隔以为网格堆栈留出更多时间&lt;br /&gt;
* 可以通过发送要模拟的灯光开关客户端按钮编号，通过BLE UART来控制灯光开关服务器&lt;br /&gt;
运行此示例的结果是，您将能够使用网状网络，该示例中的网状网络可以代替电灯开关客户端示例。&lt;br /&gt;
&lt;br /&gt;
在开始测试此共存示例之前，请参阅以下页面：&lt;br /&gt;
&lt;br /&gt;
将Mesh集成到nRF5 SDK示例中&lt;br /&gt;
&lt;br /&gt;
灯光开关示例和灯光开关客户端详细信息以及Mesh API&lt;br /&gt;
&lt;br /&gt;
===== 测试示例 =====&lt;br /&gt;
将ble_app_uart_coexist文件夹复制到nRF5 SDK安装路径下的examples / ble_peripheral文件夹中。&lt;br /&gt;
&lt;br /&gt;
在ble_app_uart_coexist / pca10040 / s132 / ses / ble_app_uart_pca10040_s132.emProject中打开Segger Embedded Studio项目。&lt;br /&gt;
&lt;br /&gt;
将MESH_ROOT添加到您的Segger Embedded Studio全局宏列表：&lt;br /&gt;
&lt;br /&gt;
在SES菜单栏中，单击工具&amp;gt;选项...。&lt;br /&gt;
&lt;br /&gt;
在左列中，单击建筑物。&lt;br /&gt;
&lt;br /&gt;
在右列中，双击“全局宏”。&lt;br /&gt;
&lt;br /&gt;
在新行中添加网格根目录：MESH_ROOT = &amp;lt;网格安装路径&amp;gt;。&lt;br /&gt;
&lt;br /&gt;
如灯光开关示例中所述对灯光开关示例设备进行编程，但以下情况除外：&lt;br /&gt;
&lt;br /&gt;
用examples / ble_peripheral / ble_app_uart_coexist / pca10040 / s132 / ses / Output / Release / Exe / ble_app_uart_pca10040_s132.hex替换电灯开关客户端。&lt;br /&gt;
&lt;br /&gt;
现在，您可以并行或顺序运行两个并存的示例：&lt;br /&gt;
&lt;br /&gt;
按照电灯开关示例中的说明运行电灯开关示例。&lt;br /&gt;
&lt;br /&gt;
按照nRF5 SDK文档中的描述运行ble_app_uart示例。&lt;br /&gt;
&lt;br /&gt;
编写与UART RX特性中的数字之一（1、2、3或4）等效的十六进制ASCII码，以模拟按钮按下。&lt;br /&gt;
&lt;br /&gt;
=== 实验现象 ===&lt;br /&gt;
&lt;br /&gt;
=== 源码详解 ===&lt;br /&gt;
&lt;br /&gt;
== mesh工程文件简介 ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
!分组&lt;br /&gt;
!文件&lt;br /&gt;
!说明&lt;br /&gt;
|-&lt;br /&gt;
!Access&lt;br /&gt;
| colspan=&amp;quot;2&amp;quot; |'''访问层：'''&lt;br /&gt;
'''访问层定义高层应用程序如何使用上层传输层。 它定义了应用程序数据的格式； 它定义并控制在上层传输层执行的应用程序数据加密和解密； 并检查是否已在正确的网络和应用程序密钥的上下文中接收到传入的应用程序数据，然后再将其转发到更高层。'''&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
|access&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
|access_loopback&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
|access_publish&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
|access_publish_retransmissior&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
|access_reliable&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
|device_state_manager&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
!Application&lt;br /&gt;
! colspan=&amp;quot;2&amp;quot; |&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
|app_error_weak&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
|app_onoff&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
|assertion_handler_weak&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
|ble_softdevice_support&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
|main&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
|mesh_adv&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
|mesh_app_utils&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
|mesh_provisionee&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
|nrf_mesh_weak&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
|rtt_input&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
|sdk_config&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
|simple_hal&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== ***************************************正式整理章节************************************** ==&lt;br /&gt;
&lt;br /&gt;
== mesh实验学习流程概论 ==&lt;br /&gt;
1、首先大家一定要先查看一下我们资料中的手册“BLE-Mesh技术揭秘”，这个是很重要的一个mesh基本概念的介绍，只有看完这个文档，我们才能明白mesh组网是什么，是怎么完成的，后续的我们实验内容，也会在这个文档中查到相应的对照。&lt;br /&gt;
&lt;br /&gt;
2、按照Light_switch测试实验的实验说明，跑通测试流程（可以固件清零重新烧写多测试几遍，有助于增强我们的流程熟练度，利于后面的开发）。light_switch实验是现在nordic提供给我们的唯一的真正意义上的mesh组网通信例程。&lt;br /&gt;
&lt;br /&gt;
3、根据我们下面拆分的组网流程，对照依次的实验说明，进行mesh组网流程的分步学习。前期实验部分我们主要讲解节点设备，配置者设备功能我们使用手机app完成，目的是减少大家的学习难度，配置者设备我们会放在最后一个实验给大家做介绍。&lt;br /&gt;
&lt;br /&gt;
①广播（通信TX）——了解节点设备的广播功能，分为三个重要部分：PB-ADV和Unprov Beacon，PB-GATT的广播，以及模型的Publish用于发送数据。&lt;br /&gt;
&lt;br /&gt;
②扫描（通信RX）——了解节点设备的扫描功能，分为两个重要部分：普通的BLE SCAN功能，以及模块的Subscription用于接收数据。&lt;br /&gt;
&lt;br /&gt;
③入网验证——了解节点设备接入网络的验证过程，实验中使用的是PB-GATT去配置的，包含了：交换公共密钥，完成验证（是否有带外OOB）。&lt;br /&gt;
&lt;br /&gt;
④设备信息分配——启动配置数据分配，给节点分配如下数据：网络key（Netkey）、应用key（Appkey）以及节点设备地址（nodeAddr）。&lt;br /&gt;
&lt;br /&gt;
⑤元素——了解节点设备的元素以及模型获取以及index的分配。&lt;br /&gt;
&lt;br /&gt;
⑥模型——了解模型的功能和创建方法，模型的订阅subscription和发布publish功能。&lt;br /&gt;
&lt;br /&gt;
== Light_switch测试实验 ==&lt;br /&gt;
=== 实验简介 ===&lt;br /&gt;
此示例演示了包含充当两个角色的设备的网状生态系统：配置角色（Provisioner role）和节点角色（Node role、provisionee role）。 它还通过在应用程序中使用[Generic OnOff model]来演示了如何使用Mesh模型。&lt;br /&gt;
&lt;br /&gt;
该示例由三个较小的示例组成：&lt;br /&gt;
&lt;br /&gt;
Light switch server：一个实现了[Generic OnOff server model]的简约服务器，用于接收状态数据并控制板上LED 1的状态。&lt;br /&gt;
&lt;br /&gt;
Light switch client：一个实现了[Generic OnOff client model]的简约客户端。当用户按下任意按钮时，OnOff Set消息将发送到配置的目标地址。&lt;br /&gt;
&lt;br /&gt;
Mesh Provisioner：一个简单的静态配置设备应用，用于建立演示网络，该配置设备在一个网状网络中配置所有节点。 此外，配置设备还可以在这些节点上配置网格模型[Generic OnOff model]实例的绑定以及发布和订阅设置，以使它们能够相互通信。&lt;br /&gt;
&lt;br /&gt;
[Generic OnOff Client/Server]模型用于操纵打开/关闭状态。请注意，当服务器设置了发布地址（如本例所示）时，服务器会将其状态更改的任何操作披露到其发布地址。&lt;br /&gt;
&lt;br /&gt;
下图给出了将由静态供应商设置的网状网络的整体视图， 括号中的数字表示预配置程序分配给这些节点的地址。&lt;br /&gt;
[[文件:Mesh network.png|边框|居中|无框|727x727像素]]&lt;br /&gt;
灯开关服务器和灯开关客户端示例均具有预配方角色。它们支持通过广告承载（PB-ADV）和GATT承载（PB-GATT）进行配置，并且还支持网状代理服务器（Proxy Service），但是不支持代理客户端（Proxy Client）。&lt;br /&gt;
&lt;br /&gt;
=== 硬件说明 ===&lt;br /&gt;
完成这个实验，我们最少需要两个开发板硬件用做我们的节点设备（Node）：&lt;br /&gt;
* 一个开发板用做client&lt;br /&gt;
* 一个或者多个开发板用做server&lt;br /&gt;
此外，我们还需要以下之一作为我们的配置设备（Provisioner）：&lt;br /&gt;
* 如果您决定使用静态预配器示例，则多准备一个开发板&lt;br /&gt;
* 如果您决定使用app程序进行设置，则需要安装手机app@link_nrf_mesh_app（@link_nrf_mesh_app_ios或@link_nrf_mesh_app_android）。&lt;br /&gt;
{{Note|text=默认我们使用3个开发板完成我们的实验，3个开发板分别对应client、server以及provisioner三个类型。|type=warning}}&lt;br /&gt;
&lt;br /&gt;
=== 软件说明 ===&lt;br /&gt;
1、我们使用NrfGo上位机，依次对三个开发板进行擦除及softdevice的烧写&lt;br /&gt;
[[文件:Lightswitch nrfgo.png|边框|居中|无框|1013x1013像素]]&lt;br /&gt;
2、使用SEGGER Embedded Studio for ARM编译器分别打开我们`&amp;lt;InstallFolder&amp;gt;/examples/light_switch`路径下的3个实验，并完成编译&lt;br /&gt;
&lt;br /&gt;
●\client\light_switch_client_nrf52832_xxAA_s132_6_1_0.emProject&lt;br /&gt;
&lt;br /&gt;
●\server\light_switch_server_nrf52832_xxAA_s132_6_1_0.emProject&lt;br /&gt;
&lt;br /&gt;
●\provisioner\light_switch_provisioner_nrf52832_xxAA_s132_6_1_0.emProject&lt;br /&gt;
[[文件:Lightswitch SES build.png|边框|居中|无框|1013x1013像素]]&lt;br /&gt;
3、此时我们对3个开发板分别进行固件的烧写，分别烧写client、server、provisioner（建议大家给3个开发板贴上标签，方便后续区分）&lt;br /&gt;
[[文件:Lightswitch SES download.png.gif|边框|居中|无框|1009x1009像素]]&lt;br /&gt;
&lt;br /&gt;
=== 实验现象 ===&lt;br /&gt;
{{Note|text=注意：保证我们的3个设备都是刚按照软件说明部分的步骤配置完成，并且没有进行过任何硬件控制（按键操作）。目的是保证所有设备均未被我们人为配置过，没有进行过网络配置，否则可能有任意异常（不同网络配置，导致的不同现象）|type=danger}}&lt;br /&gt;
1、首先我们分别给client和provisioner供电，然后我们按下provisioner的按键S1：&lt;br /&gt;
&lt;br /&gt;
●provisoner：LED灯点亮，代表正在配置查找设备，配置组网&lt;br /&gt;
&lt;br /&gt;
●client：首先LED3和LED4闪烁，代表正在组网；LED1-LED4四个灯一起闪烁，代表组网完成&lt;br /&gt;
&lt;br /&gt;
我们如果打开RTT检测log打印，可以看到如下的信息，这个时候我们已经给client设备分配了Node Address为0x0100。provisioner信息（上），client信息（下）。&lt;br /&gt;
[[文件:Lightswitch rtt provisoner1.png|边框|无框|1080x1080像素|居中]]&lt;br /&gt;
[[文件:Lightswitch_rtt_node_client.png|边框|居中|无框|656x656像素]]&lt;br /&gt;
&lt;br /&gt;
2、使用provisioner配置好client之后，我们给server开发板上电，这个时候provisioner将会配置server入网：&lt;br /&gt;
&lt;br /&gt;
●provisoner：LED1点亮代表正在配置网络，LED2点亮代表网络配置完成&lt;br /&gt;
&lt;br /&gt;
●server：首先LED3和LED4闪烁，代表正在组网；LED1-LED4四个灯一起闪烁，代表组网完成&lt;br /&gt;
&lt;br /&gt;
我们打开RTT检测log打印，可以看到server已经配置入网，并且被分配了地址Node Address为0x0104。provisioner信息（上），server信息（下）。&lt;br /&gt;
[[文件:Lightswitch rtt provisoner2.png|边框|无框|710x710像素|居中]]&lt;br /&gt;
[[文件:Lightswitch rtt node server.png|边框|无框|656x656像素|居中]]&lt;br /&gt;
&lt;br /&gt;
3、此时整个网络的配置已经完成，这个时候我们可以按下client上的按键来控制server上的LED点亮或者熄灭。&lt;br /&gt;
&lt;br /&gt;
由于我们的server被provisioner分配的Node Address是0x0104（偶数），所以我们通过client的S3控制server的LED1点亮，通过client的S4控制server的LED1熄灭。&lt;br /&gt;
{{Note|text=client设备对于server设备的控制，通过Node Address的奇偶位不同，分成了两个组。&lt;br /&gt;
client的S1和S2分别控制Node Address为奇数的server设备的LED1点亮和熄灭。&lt;br /&gt;
&lt;br /&gt;
client的S3和S4分别控制Node Address为偶数的server设备的LED1点亮和熄灭。|type=warning}}&lt;br /&gt;
我们打开RTT检测log打印，可以看到client分别按下button2（S3）以及button3（S4），分别会设置server的GPIO输出高低电平。client信息（上），server信息（下）。&lt;br /&gt;
[[文件:Lightswitch rtt node client2.png|边框|无框|656x656像素|居中]]&lt;br /&gt;
[[文件:Lightswitch rtt node server2.png|边框|无框|656x656像素|居中]]&lt;br /&gt;
&lt;br /&gt;
4、至此我们的mesh组网的lightswitch实验测试完成。&lt;br /&gt;
&lt;br /&gt;
== ①广播（通信TX） ==&lt;br /&gt;
有关广播部分，我们需要给大家讲解三个方面，分别如下：&lt;br /&gt;
&lt;br /&gt;
1、PB-ADV，这个是mesh中的广播承载层，用于我们启动配置过程（暂时的例程和手机app都是用的PB-GATT来配置的，但是源码中PB-ADV的流程是包含的）。&lt;br /&gt;
&lt;br /&gt;
2、Unprov Beacon，这个是广播一个未被配置的beacon，就是一个特殊形式的广播数据包，用于声明自己是未经配置的设备。&lt;br /&gt;
&lt;br /&gt;
3、PB-GATT，这个是mesh中的GATT承载层，用于我们启动配置过程（当前手机app就是使用PB-GATT，也就是连接之后，通过GATT服务去发送配置信息）。&lt;br /&gt;
&lt;br /&gt;
4、Publish，这个是我们用于模型发布数据的部分，我们将需要发送的数据通过advData广播出去。&lt;br /&gt;
&lt;br /&gt;
=== PB-ADV ===&lt;br /&gt;
PB-ADV说明请查看“BLE-Mesh技术揭秘”第8.1章节。&lt;br /&gt;
&lt;br /&gt;
我们来看下PB-ADV整个的流程，首先在mian文件中，我们的start()函数下，我们调用了mesh_provisionee_prov_start()函数去启动节点设备的等待配置。&lt;br /&gt;
[[文件:PB-ADV-01.png|边框|居中|无框|1200x1200px]]我们继续追踪mesh_provisionee_prov_start()，可以看到在使能宏定义MESH_FEATURE_PB_ADV_ENABLED的情况下，可以追踪到nrf_mesh_prov_bearer_adv_interface_get()函数。&lt;br /&gt;
[[文件:PB-ADV-02.png|边框|居中|无框|1200x1200px]]在nrf_mesh_prov_bearer_adv_interface_get()函数中，我们可以看到prov_bearer_interface_t结构体，这个结构体下面提供了我们配置承载层需要的API函数。&lt;br /&gt;
[[文件:PB-ADV-03.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
=== Unprov Beacon ===&lt;br /&gt;
Unprov Beacon说明请查看“BLE-Mesh技术揭秘”第5.5及5.5.1章节。&lt;br /&gt;
&lt;br /&gt;
上面我们有说到PB-ADV是承载层用于配置节点的功能，而Unprov Beacon则是我们的节点设备用于声明自己未经配置，所以这两个其实是一脉相承的。&lt;br /&gt;
&lt;br /&gt;
紧接着上面的nrf_mesh_prov_bearer_adv_interface_get()函数，其中的prov_bearer_interface_t结构体包含的是处理承载层的API函数，在这个结构体当中有一个.listen_start = prov_bearer_adv_listen，是用于开始监听传入的配置链接的功能，在这个函数中我们可以看到send_unprov_beacon()函数。&lt;br /&gt;
[[文件:PB-ADV-04.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
send_unprov_beacon()函数是用于发送Unprov Beacon数据的函数，在这个函数中我们调用prov_beacon_unprov_build函数去创建用于广播的Unprov Beacon数据包，然后调用advertiser_interval_set函数去设置广播的间隔，最后调用advertiser_packet_send函数去将我们的数据包发送出去。&lt;br /&gt;
&lt;br /&gt;
默认的Unprov Beacon广播间隔是NRF_MESH_PROV_BEARER_ADV_UNPROV_BEACON_INTERVAL_MS 2000。&lt;br /&gt;
&lt;br /&gt;
最终调用的是advertiser_packet_send(0函数去将广播数据发送出去的。&lt;br /&gt;
[[文件:PB-ADV-05.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Publish-12.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
这里我们再最后看一下prov_beacon_unprov_build()函数，这个函数是生成我们的Unprov Beacon的数据包。&lt;br /&gt;
&lt;br /&gt;
其中标识位是0x2B，这个flag是beacon的意思。data的第一个字节0x00代表是我们的BEACON_TYPE_UNPROV（未配置的节点设备），接下来的16个字节是device uuid（这个是寄存器FICR中的device id生成的），接下来2字节是oob info（这里是00 00，代表没有使能OOB），最后的4个字节是URI通过AES-CMAC算法计算出来的值。&lt;br /&gt;
[[文件:PB-GATT-11.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:PB-GATT-12.png|边框|居中|无框]]&lt;br /&gt;
&lt;br /&gt;
=== PB-GATT ===&lt;br /&gt;
PB-GATT说明请查看“BLE-Mesh技术揭秘”第8.1章节。&lt;br /&gt;
&lt;br /&gt;
和PB-ADV一样的，我们从main文件中的start()函数，追踪到mesh_provisionee_prov_start()，可以看到在使能宏定义MESH_FEATURE_PB_GATT_ENABLED的情况下，可以追踪到nrf_mesh_prov_bearer_gatt_init()以及nrf_mesh_prov_bearer_gatt_interface_get()函数。&lt;br /&gt;
[[文件:PB-GATT-01.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
首先我们看一下nrf_mesh_prov_bearer_gatt_init()函数，这个是初始化并设置PB-GATT服务（通过Mesh GATT接口）的函数。对比我们使用nrf master control panel手机APP连接之后获取到的服务，可以看到其中的PB-GATT的服务以及特征值。&lt;br /&gt;
&lt;br /&gt;
再往下的mesh_gatt_init()函数，这个就不给大家分析了，是和我们普通的BLE一样的方式去注册一个服务。&lt;br /&gt;
[[文件:PB-GATT-04.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:PB-GATT-03.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:PB-GATT-02.png|边框|居中|无框]]&lt;br /&gt;
在nrf_mesh_prov_bearer_gatt_interface_get()函数中，我们可以看到prov_bearer_interface_t结构体，这个结构体下面提供了我们配置承载层需要的API函数（这个是和上方的PB-ADV是一样的）。&lt;br /&gt;
[[文件:PB-GATT-05.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
和Unprov Beacon类似的，当我们开始监听配置链接的时候，会触发.listen_start = listen_start_cb的回调，在这个回调中我们触发FSM（有限状态机）的事件E_LISTEN_START，然后调用link_evt_send()去post顺序承载事件。&lt;br /&gt;
[[文件:PB-GATT-06.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
然后我们来到状态机列表m_pb_gatt_fsm_transition_table[]，可以看到E_LISTEN_START事件ID代表的动作ID是A_LISTEN_START。而最终我们的A_LISTEN_START动作，执行的是a_listen_start函数功能（这其中涉及的代码逻辑，暂时先不说明，大家可以自己查看源码的引用）。&lt;br /&gt;
[[文件:PB-GATT-07.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:PB-GATT-08.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
然后我们继续追踪a_listen_start()，可以看到其中调用了mesh_adv_params_set函数设置了我们广播的超时时间MESH_ADV_TIMEOUT_INFINITE以及广播间隔NRF_MESH_PROV_BEARER_GATT_UNPROV_BEACON_INTERVAL_MS，调用mesh_adv_data_set函数设置了我们的广播数据内容，最后调用mesh_adv_start函数启动广播。&lt;br /&gt;
&lt;br /&gt;
默认的PB-GATT的广播数据的广播周期是NRF_MESH_PROV_BEARER_GATT_UNPROV_BEACON_INTERVAL_MS 200。&lt;br /&gt;
[[文件:PB-GATT-09.png|边框|居中|无框|1200x1200像素]]我们同样的看一下mesh_adv_data_set()函数最终配置生成的广播数据，可以看到广播数据的flag是0x06，服务的UUID就是我们上面提到的PB-GATT服务的UUID，接下来16字节UUID是和PB-ADV一样的FICR寄存器的device id值，最后的扫描回调数据携带的是设备的全名“nRF5x Mesh Light”。&lt;br /&gt;
[[文件:PB-GATT-13.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:PB-GATT-14.png|边框|居中|无框]]最后是我们的mesh_adv_start()函数去启动广播，可以看到调用是我们ble stack sotfdevice当中的API函数sd_ble_gap_adv_start()去启动的。&lt;br /&gt;
[[文件:PB-GATT-15.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
=== Publish（模型发布） ===&lt;br /&gt;
这一章节我们只给大家讲解模块publish的数据发布流程，暂时不说明模型的创建方法，以及如何使用，这个放在下面单独的模型章节进行说明。&lt;br /&gt;
&lt;br /&gt;
以generic_onoff_server模型为例，我们找到access_model_publish以及access_model_reply函数，这两个函数的功能类似的，都是用于我们用户的模型发送数据的。&lt;br /&gt;
&lt;br /&gt;
其中access_model_publish是用于模型主动发送，而access_model_reply是用于模型给对应的Opcode回应数据。&lt;br /&gt;
&lt;br /&gt;
这里我们以access_model_publish函数为主，来看下发布数据的代码流程。&lt;br /&gt;
[[文件:Publish-01.png|边框|居中|无框|1200x1200像素]]查看一下access_model_publish()函数，我们可以看到packet_alloc_and_tx()函数，这个是我们继续发送数据的函数。下面还有一个蓝牙方框中的内容，是我们定义的重传机制（mesh数据为了保证传输的成功率，默认都是会重传数据包的，默认重传次数是4次）。[[文件:Publish-02.png|边框|居中|无框|1200x1200像素]]然后我们继续看一下packet_alloc_and_tx()函数，在这个函数中，我们最终会调用packet_tx()函数将数据发送出去。[[文件:Publish-03.png|边框|居中|无框|1200x1200像素]]继续追踪packet_tx()函数，这个函数的内容比较多，我们只截取了其中数据发送的部分，也就是我们的nrf_mesh_packet_send()函数，我们在他的tx_params中赋值好我们需要发送出去的数据内容。[[文件:Publish-04.png|边框|居中|无框|1199x1199像素]]nrf_mesh_packet_send()函数最终调用的是transport_tx()函数发送数据。而transport_tx()函数，最终是调用upper_transport_tx()函数进行的数据发送。[[文件:Publish-05.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Publish-06.png|边框|居中|无框|1200x1200像素]]我们再继续追踪upper_transport_tx()函数，可以看到我们会根据p_metadata-&amp;gt;segmented来判断数据是否要分包发送。&lt;br /&gt;
[[文件:Publish-13.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
接下里我们以不分包发送函数unsegmented_packet_tx()为例，来继续说明数据传输流程。&lt;br /&gt;
&lt;br /&gt;
我们可以看到我们的unsegmented_packet_tx()函数中，最终是调用的network_packet_send()函数。（分包的segmented_packet_tx()函数最终也是调用的network_packet_send()，大家可以自己追踪代码看一下）&lt;br /&gt;
[[文件:Publish-14.png|边框|居中|无框|1200x1200像素]]继续追踪我们的network_packet_send()函数，可以看到我们的net_packet_encrypt()加密函数，这个是将我们的明文数据，做好加密工作。&lt;br /&gt;
&lt;br /&gt;
然后调用core_tx_packet_send()函数发送出去。[[文件:Publish-15.png|边框|居中|无框|1200x1200像素]]最后我们追踪到core_tx_packet_send()函数，可以看到调用的是p_bearer-&amp;gt;p_interface-&amp;gt;packet_send()函数去发送的。所以接下来我们要找到我们的packet_send()功能是在哪边实现的。[[文件:Publish-07.png|边框|居中|无框|1200x1200像素]]可以看到我们是在core_tx_bearer_add函数中去获得的p_interface这个功能。[[文件:Publish-08.png|边框|居中|无框|1200x1200像素]]我们追踪core_tx_bearer_add()函数的引用路径，可以看到是在core_tx_adv_init()当中。（除了此处，还有另一处地方也有引用，功能是类似的，大家可以自行了解）。&lt;br /&gt;
&lt;br /&gt;
我们看下参数m_interface，可以看到m_interface-&amp;gt;packet_send功能，就是对应我们在core_tx_packet_send()函数调用的p_bearer-&amp;gt;p_interface-&amp;gt;packet_send()。[[文件:Publish-09.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Publish-10.png|边框|居中|无框|1200x1200像素]]所以我们接下来来看下m_interface结构体当中的packet_send()函数实现的是什么功能，可以看到最终调用的是advertiser_packet_send()函数将数据发送出去。看到这边我们就很熟悉了，和上面的几个广播功能，最后调用的都是相同的广播包发送函数。[[文件:Publish-11.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Publish-12.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
== ②扫描（通信RX） ==&lt;br /&gt;
&lt;br /&gt;
=== 标准BLE SCAN ===&lt;br /&gt;
看过了我们的广播功能之后，我们来看下扫描的部分。在ble mesh中，我们的扫描功能scan，就是用于获取数据的，类似于数据通信的RX。&lt;br /&gt;
&lt;br /&gt;
我们从main文件中的initialize()函数开始，首先是我们mesh_init()函数，最终我们会调用mesh_stack_init()函数。&lt;br /&gt;
[[文件:SCAN-01.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
在mesh_satck_init函数中，我们可以看到调用了nrf_mesh_init函数去初始化core的参数。&lt;br /&gt;
[[文件:SCAN-02.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
这一段是nrf_mesh_init函数部分，由于这个初始化函数太长，所以我们只截取了scan相关的部分。&lt;br /&gt;
&lt;br /&gt;
首先是第一点scanner_init(scanner_packet_process_cb)函数，在这个函数中，我们回去初始化扫描的参数配置，并且会开启扫描功能。最终我们可以从scanner_packet_process_cb这个回调中得到我们扫描到的数据（获得所有的BLE广播数据）。&lt;br /&gt;
&lt;br /&gt;
然后是第二点ad_listener_subscribe(&amp;amp;m_nrf_mesh_listener)，这个是用来侦听订阅的消息的（从获得的所有BLE广播数据中，筛选出的订阅的消息）。&lt;br /&gt;
[[文件:SCAN-03.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
然后我们继续看一下scanner_init的扫描初始化函数，在这个函数中我们首先调用scanner_config_reset()函数去配置扫描的默认参数。其中包含了：&lt;br /&gt;
&lt;br /&gt;
1、扫描的通道SCANNER_CHANNELS_DEFAULT，也就是BLE的｛37，38，39｝三个channels都去扫描。&lt;br /&gt;
&lt;br /&gt;
2、规范定义的非连接状态访问地址BEARER_ACCESS_ADDR_DEFAULT。&lt;br /&gt;
&lt;br /&gt;
3、配置物理层协议是1Mbit模式。&lt;br /&gt;
&lt;br /&gt;
4、扫描的间隔时间BEARER_SCAN_INT_DEFAULT_MS，扫描的窗口时间BEARER_SCAN_WINDOW_DEFAULT_MS&lt;br /&gt;
&lt;br /&gt;
5、射频的发射功率RADIO_POWER_NRF_0DBM。&lt;br /&gt;
&lt;br /&gt;
6、BLE广播包最大长度RADIO_CONFIG_ADV_MAX_PAYLOAD_SIZE。&lt;br /&gt;
&lt;br /&gt;
最后我们会在nrf_mesh_enable()函数中调用scanner_enable()函数去启动扫描，这个大家可以自己查看一下代码，比较容易理解。&lt;br /&gt;
[[文件:SCAN-04.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:SCAN-05.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
最后我们来看一下scanner_packet_process_cb()回调函数，其中我们可以直接拿到的扫描的数据包是rx_data，包含了数据的类型、数据内容以及数据长度等等信息。到这边我们的扫描功能是已经完成的，但是我们还需要再看下下面一部分的ad_listener的流程。&lt;br /&gt;
&lt;br /&gt;
=== Subscription（模型订阅） ===&lt;br /&gt;
我们需要注意下ad_listener_process()函数，这个函数是将我们扫描到的订阅数据（不符合标准的BLE广播数据，则认为是侦听到的订阅数据）传递到订阅者进行处理（订阅者指的是带订阅功能的模型）。这个是和我们上方说的关注点二ad_listener_subscribe()函数是一脉相承的。&lt;br /&gt;
[[文件:SCAN-06.png|边框|居中|无框|1200x1200像素]]然后我们来对照着看一下ad_listener_subscribe()函数以及ad_listener_process()函数。这两个函数一个是定义了侦听订阅，一个是将扫描获取到的数据传递给订阅者。这两者间的运作关系，大家有兴趣的可以继续查阅他的底层代码逻辑处理，一个是向list中添加订阅者信息，一个是将数据通过p_listener-&amp;gt;handler()将数据传递给订阅者处理。&lt;br /&gt;
[[文件:SCAN-07.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:SCAN-08.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
最终当我们扫描到订阅消息，并调用ad_listener_process()函数传递时，会触发我们注册的回调nrf_mesh_listen（调用ad_listener_subscribe(&amp;amp;m_nrf_mesh_listener)时，在m_nrf_mesh_listener中）。我们来看下nrf_mesh_listen函数中的处理，其中我们先从数据包中解析了数据的类型type，然后根据不同类型的数据进行相应的处理。&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+&lt;br /&gt;
|AD_TYPE_MESH &lt;br /&gt;
|蓝牙网格物体的AD类型&lt;br /&gt;
|-&lt;br /&gt;
|AD_TYPE_BEACON &lt;br /&gt;
|蓝牙网状信标的AD类型&lt;br /&gt;
|-&lt;br /&gt;
|AD_TYPE_PB_ADV &lt;br /&gt;
|PB-ADV消息的AD类型&lt;br /&gt;
|-&lt;br /&gt;
|AD_TYPE_DFU &lt;br /&gt;
|nRF OpenMesh消息的AD类型&lt;br /&gt;
|}&lt;br /&gt;
[[文件:SCAN-09.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
== ③入网验证 ==&lt;br /&gt;
经过前两章的学习，我们对于BLE MESH的广播及扫描的代码流程已经有了一定的了解，也就是已经大概的明白了数据的发送和接收的流程。&lt;br /&gt;
&lt;br /&gt;
那么从这一章节开始，我们将会给大家展示，如何配置一个节点设备入网。配置者设备，我们使用的是手机APP：nRF Mesh。&lt;br /&gt;
&lt;br /&gt;
我们从main文件中的start()函数开始，来看一下入网的代码处理及其流程。在start()函数中我们最后是调用的mesh_provisionee_prov_start()函数来启动的节点配置功能，我们来查看一下它所携带的参数prov_start_params。这部分的参数都是比较重要的，有携带的是设备的信息的、有携带认证密码的，有回调返回的，所以我们会挨个介绍一下。&lt;br /&gt;
[[文件:Prov-01.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
首先携带的是static_auth_data，这个是我们带外OOB认证的密码。如果我们在使用nRF Mesh去进行身份验证的时候，选择使用Static OOB，那么当我们在进行PB-GATT的连接过程中，会弹出一个新的窗口，要求我们去输入这个静态的128bit的OOB密钥。&lt;br /&gt;
[[文件:Prov-02.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Prov-14.png|边框|居中|无框]]&lt;br /&gt;
[[文件:Prov-15.png|边框|居中|无框]]&lt;br /&gt;
接下来携带的是provisioning_complete_cb()回调函数，这个函数处理的是节点设备成功的被配置入网，在这里我们可以获取到我们的节点设备被分配的node_Address。&lt;br /&gt;
[[文件:Prov-03.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
然后携带是device_identification_start_cb()回调函数，这个回调函数是用于通知开发者，我们的节点设备当前已经开始配置。&lt;br /&gt;
[[文件:Prov-04.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
再然后携带的是URI【统一资源标识符（Uniform Resource Identifier，URI)】，这个是用于标识我们的设备类型的，这个数据会通过AES-CMAC算法加密后添加到我们的Unprov Beacon数据的末尾。&lt;br /&gt;
&lt;br /&gt;
这里我们是light switch的server设备，所以我们定义URI是EX_URI_LS_SERVER。&lt;br /&gt;
[[文件:Prov-05.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
分析完携带的参数之后，我们继续来查看mesh_provisionee_prov_start()函数。在这个函数中，我们首先是定义了一下prov_caps，也就是配置支持的OOB功能。然后我们调用nrf_mesh_generate_keys()函数去生成有效的密钥对等待使用。&lt;br /&gt;
&lt;br /&gt;
再然后是调用nrf_mesh_prov_init()函数去初始化prov配置信息的结构体参数，里面有结构体本身m_pro_ctx，需要配置的参数公钥m_public、私钥m_private，以及我们刚刚定义的oob功能prov_caps，最后还携带了一个事件回调函数prov_ect_handler。&lt;br /&gt;
&lt;br /&gt;
接下来是PB-ADV以及PB-GATT的处理过程，这个前面已经讲解过，这里不再赘述。&lt;br /&gt;
&lt;br /&gt;
最后我们调用provisionee_start()函数去开启配置。&lt;br /&gt;
[[文件:Prov-06.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
我们继续来追踪一下provisionee_start()函数，在这个函数中我们调用nrf_mesh_prov_listen()函数去开启侦听承载层的消息。这里我们需要侦听PB-ADV以及PB-GATT两个。&lt;br /&gt;
[[文件:Prov-07.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
然后在nrf_mesh_prov_listen()函数中，我们调用prov_provisionee_listen()函数携带我们刚刚传入的nrf_mesh_prov_ctx_t结构体，以及p_bearer、URI、oob_info_source等参数信息，继续向下传递。&lt;br /&gt;
[[文件:Prov-08.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
最终我们来查看下prov_provisionee_listen()函数，在这个函数中我们传递了p_bearer-&amp;gt;p_callbacks给m_prov_callbacks回调，然后调用p_bearer-&amp;gt;p_interface-&amp;gt;listen_start()函数去启动PB-ADV以及PB-GATT的侦听。&lt;br /&gt;
[[文件:Prov-09.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
当我们在侦听之后，接收到任何配置相关的消息，都将通过m_prov_callbacks()函数回调传递给我们处理，大家可以自行看下其中4个不同的功能函数，具体处理的都是什么消息。&lt;br /&gt;
[[文件:Prov-10.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
在prov_provisionee_listen()函数中，我们可以看到回调函数指针m_prov_callbacks指向的是p_bearer-&amp;gt;p_callbacks，我们一路向上查找，最终可以找到这个p_bearer-&amp;gt;p_callbacks函数就是指的prov_evt_handler()函数。所以其实我们用户可以在这一层去查看比较重要的配置事件返回，而不需要到上面的m_prov_callbacks中这样偏底层的地方去查看。&lt;br /&gt;
&lt;br /&gt;
在这个回调函数中，我们可以看到mian文件中注册的prov_device_identification_start_cb、prov_device_identification_stop_cb、provisioning_aborted_cb的返回。&lt;br /&gt;
[[文件:Prov-11.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
那么除了上方已经存在的几个回调，我们可以看到mian文件中的start()函数下的prov_start_params参数，还缺少一个配置完成的回调prov_complete_cb()。&lt;br /&gt;
&lt;br /&gt;
这个回调的返回方式比较特殊，和上方其他的都不同，是由SD（softdevice）直接从底层返回给我们的。注册这个回调的函数是NRF_SDH_STATE_OBSERVER，返回的回调函数是sd_state_evt_handler。其中触发的事件NRF_SDH_EVT_STATE_ENABLED，这个是在mian文件中的initialize()函数下的ble_stack_init()函数中就被触发的，这个大家可以自行查看一下，不太重要，因为都是nordic底层处理好的消息。&lt;br /&gt;
[[文件:Prov-13.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Prov-12.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
以上整个配置流程的处理，不太好分步展示相应的现象，这个大家可以自己使用SES在线仿真的方式去调试一下流程，这里我们直接给大家展示整个流程打印出的相应的信息，以及nRF Mesh中暂时出来的现象。&lt;br /&gt;
[[文件:Prov-16.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Prov-17.png|边框|居中|无框]]&lt;br /&gt;
&lt;br /&gt;
== ④设备信息分配 ==&lt;br /&gt;
当我们成功的配置节点设备入网之后，我们需要给节点设备主要分配如下几个信息，有关这几个要点的介绍，可以查看“BLE-Mesh技术揭秘”第3章节：&lt;br /&gt;
&lt;br /&gt;
1、节点在网络中的地址Node Address&lt;br /&gt;
&lt;br /&gt;
2、网络密钥Netkey&lt;br /&gt;
&lt;br /&gt;
3、应用密钥Appkey&lt;br /&gt;
&lt;br /&gt;
4、元素的地址Element Address&lt;br /&gt;
&lt;br /&gt;
5、绑定model的Appkey，分配Publish Address，Subscription Address&lt;br /&gt;
&lt;br /&gt;
=== Node Address ===&lt;br /&gt;
首先我们来看一下我们使用nRF Mesh成功配置我们的节点之后，打印出来的消息，开始的紫色字体显示了我们给当前节点设备分配的Node Address。&lt;br /&gt;
&lt;br /&gt;
后面紧接着还有四段黄色字体的内容（这个部分大家的工程是不会打印的，是我们修改例程打印出来给大家展示一下），这四个部分就是我们配置节点的时候，配置者设备（nRF Mesh）给节点设备的四个主要指令，这些指令的具体功能，大家可以查看mesh的协议手册。[[文件:Config-13.png|边框|居中|无框|1080x1080像素]]在代码中，我们会在mian文件的provisioning_complete_cb()回调函数中获取并打印我们的Node Address，这个回调触发的含义就是我们的节点配置已经完成了。&lt;br /&gt;
&lt;br /&gt;
具体这个回调在代码中的回调及触发流程，大家看可以查看“③入网验证”的说明。[[文件:Config-20.png|边框|居中|无框|1200x1200像素]]我们使用nRF Mesh配置好节点设备之后，app界面显示内容如下，包含了节点的信息（地址、名称、配置时间等等），元素和模型，以及Netkey和Appkey等几个重要的设备信息。[[文件:Config-01.jpg|边框|居中|无框]]&lt;br /&gt;
[[文件:Config-04.jpg|边框|居中|无框]]&lt;br /&gt;
&lt;br /&gt;
=== Netkey ===&lt;br /&gt;
我们点击查看一下Network key，可以看到里面有一个Network Key 1，这是一个16字节的网络密钥。我们可以在nRF Mesh的Settings界面去增加或者删除一个network key，用户可以自定义此密钥。[[文件:Config-02.jpg|边框|居中|无框]]在我们的代码中，我们是在pro_evt_handler回调的NRF_MESH_PROV_EVT_COMPLETE事件中获取这个netkey，这个回调函数的触发流程同样是在“③入网验证”已经有说明。[[文件:Prov-11.png|边框|居中|无框|1200x1200像素]]我们直接看下NRF_MESH_PROV_EVT_COMPLETE事件下的mesh_stack_provisioning_data_store()函数，在这个函数中我们主要的任务，就是将配置者分配给我们的node address、netkey、devkey等等信息，分别做一些存储以及功能绑定。&lt;br /&gt;
[[文件:Netkey-01.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
=== Appkey ===&lt;br /&gt;
同样的我们打开Application Key，可以看到其中包含的3个密钥，这里我们只选用了Application Key 1作为我们的密钥。大家同样可以在nRF Mesh的Settings界面去增加或者删除一个App key，用户可以自定义此密钥。[[文件:Config-03.jpg|边框|居中|无框]]appkey这里和netkey不同，netkey是我们网络中的节点设备以及我们配置者设备共有的一个密钥，只有拥有这个密钥，我们才能在当前网络中通信，所以当我们的节点设备配置入网后，会立刻得到netkey（必须要分配给我的节点设备才行）。&lt;br /&gt;
&lt;br /&gt;
而appkey只是我们的某个模型用于通信的密钥，所以我们称它为应用密钥，这个appkey的分配，是在我们的config server下。这个config_server_init的初始化流程，是在我们的mesh_stack_init()函数下，就去初始化配置好的。最终当我们有信息配置的时候，会触发opcode_handlers，我们可以从这个操作码列表中查找相应的信息处理。[[文件:Config-22.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Config-23.png|边框|居中|无框|1200x1200像素]]我们来看下config server的opcode_handlers[]操作码处理程序列表，使用黄色标记给大家圈了几个操作码指令，这几个是我们配置一个最简单的server节点设备，都需要使用到的，包含了：&lt;br /&gt;
&lt;br /&gt;
1、appkey的添加，CONFIG_OPCODE_APPKEY_ADD&lt;br /&gt;
&lt;br /&gt;
2、publish发布地址，CONFIG_OPCODE_MODEL_PUBLICATION_VIRTUAL_ADDRESS_SET&lt;br /&gt;
&lt;br /&gt;
3、subscription订阅地址，CONFIG_OPCODE_MODEL_SUBSCRIPTION_ADD&lt;br /&gt;
&lt;br /&gt;
4、模型appkey的绑定，CONFIG_OPCODE_MODEL_APP_BIND&lt;br /&gt;
&lt;br /&gt;
5、netkey的更新，CONFIG_OPCODE_NETKEY_ADD[[文件:Config-21.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
[[文件:Appkey-01.png|边框|居中|无框|1200x1200像素]]&lt;br /&gt;
&lt;br /&gt;
=== Element ===&lt;br /&gt;
元素简单的说就是定义了节点的功能，一些具有复杂功能的节点设备，会包含多个元素。一个节点设备最少需要包含一个元素，我们称之为主元素。&lt;br /&gt;
&lt;br /&gt;
从主元素开始（如果有多个元素，则第一个元素就是主元素），我们会给它分配相应的元素地址。主元素的地址就是节点的地址，第二个元素的地址就是主元素的地址基础上加上1，依次类推。&lt;br /&gt;
&lt;br /&gt;
可以看到在我们的主元素Element：0x0002中包含了3个不同的服务，这3个服务就是我们的模型Model，其中Configuration Server与Health Server这两个服务，是每一个节点都必须携带的模型。Configuration Server（配置服务）是用于配置节点设备信息，Health Server（健康服务）是用于节点进行网络内的心跳联系。&lt;br /&gt;
&lt;br /&gt;
剩下的Generic On Off Server服务，这个就是我们用于控制一个开关量的模型（在例程中我们通过控制LED的IO口高低电平来展示）。[[文件:Config-05.jpg|边框|居中|无框]]&lt;br /&gt;
&lt;br /&gt;
=== model ===&lt;br /&gt;
&lt;br /&gt;
我们来看下Generic On Off Server的[[文件:Config-14.png|边框|居中|无框|1040x1040像素]]&lt;br /&gt;
[[文件:Config-06.jpg|边框|居中|无框]]&lt;br /&gt;
[[文件:Config-12.jpg|边框|居中|无框]]&lt;br /&gt;
&lt;br /&gt;
==== model bind appkey ====&lt;br /&gt;
[[文件:Config-07.jpg|边框|居中|无框]]&lt;br /&gt;
[[文件:Config-08.jpg|边框|居中|无框]]&lt;br /&gt;
&lt;br /&gt;
==== model publish address ====&lt;br /&gt;
[[文件:Config-09.jpg|边框|居中|无框]]&lt;br /&gt;
[[文件:Config-10.jpg|边框|居中|无框]]&lt;br /&gt;
&lt;br /&gt;
==== model subscription address ====&lt;br /&gt;
[[文件:Config-15.png|边框|居中|无框|1040x1040像素]]&lt;br /&gt;
[[文件:Config-11.jpg|边框|居中|无框]]&lt;br /&gt;
[[分类:NRF52832DK]]&lt;br /&gt;
[[分类:实验手册]]&lt;/div&gt;</summary>
		<author><name>Jinx</name></author>
		
	</entry>
	<entry>
		<id>http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:Appkey-01.png&amp;diff=2780</id>
		<title>文件:Appkey-01.png</title>
		<link rel="alternate" type="text/html" href="http://doc.iotxx.com/index.php?title=%E6%96%87%E4%BB%B6:Appkey-01.png&amp;diff=2780"/>
		<updated>2020-03-31T06:21:23Z</updated>

		<summary type="html">&lt;p&gt;Jinx：&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Appkey-01&lt;/div&gt;</summary>
		<author><name>Jinx</name></author>
		
	</entry>
</feed>