企业🤖AI Agent构建引擎,智能编排和调试,一键部署,支持私有化部署方案 广告
# 概述 SDK 给用户提供基于 SIG_mesh 协议应用开发的 demo code,用户可以在这些 demo code 基础上开发自己的应用程序 # SDK 的文件架构 SDK 文件架构分为 app 应用层和 BLE&SIG_mesh 协议层。有几个主要的顶层文件夹:boot,common,drivers,homekit_src ,proj_lib,stack,vendor 。 * [ ] boot:提供芯片的 bootloader,即 MCU 上电启动或 deepsleep 唤醒后的汇编处理过程,为 后面 C 语言程序的运行搭建好环境。 * [ ] drivers:提供与 MCU 紧密相关的硬件设置和外设驱动程序,如 clock、flash、i2c、usb、 gpio、uart 等。 * [ ] proj:提供 MCU 相关的外设驱动程序,如 flash,i2c,usb,gpio, uart 等。 * [ ] proj_lib: 提供 MCU 运行所必需的库文件,包括 BLE 协议栈、RF 驱动、PM 驱动等,这部分是以库文件形式提供的,用户无法看到源文件,如 liblt_8258_mesh.a 为蓝牙协议栈的库文件,libsig_mesh.a 为 SIG_mesh 普通节点的库文件,libsig_mesh_LPN.a 为 SIG_mesh中的低功耗节点的库文件,libsig\_mesh\_prov.a为SIG\_mesh中的provision节点的库文件。 * [ ] stack:存放 BLE 协议栈相关的头文件。源文件被编译到库文件里面,对于用户是不可见的。 * [ ] vendor:用于存放用户应用层代码。目前 vendor 目录下有: ——common:主要包含了 mesh/mesh_lpn/mesh_provision/mesh_switch 等共用的模块, 例如 SIG mesh model 的处理,led 部分,出厂初始化 ,测试命令等模块。 ——mesh/mesh_gw_node_homekit/mesh_lpn/mesh_provision/mesh_switch/ spirit_lpn 这几个文件夹的结构一样,每个文件夹对应一个应用类型,都包含了 app.c、app.h、app_att.c、app_config.h、main.c。app.c/app.h 主要是初始化和底层回调功能;app_att.c 是蓝牙 att 表的描述以及接口函数的说明;app_config.h 是定义工程中对应的宏和声明;main.c是主函数和中断函数的入口。 ### 1.1.1 main.c 包括 main 函数入口,系统初始化的相关函数,以及无限循环 while(1)的写法,建议不要对 此文件进行任何修改,直接使用固有写法。 int main (void) { FLASH\_ADDRESS\_CONFIG; #if PINGPONG\_OTA\_DISABLE ota\_fw\_check\_over\_write(); // 非 pingpong OTA 的 firmware copy #endif blc\_pm\_select\_internal\_32k\_crystal(); //选择内部 32k rc 作为 32k counter 时钟源 cpu\_wakeup\_init();//MCU 最基本的硬件初始化 int deepRetWakeUp = pm\_is\_MCU\_deepRetentionWakeup(); //判断是否从 deep retention 唤醒 rf\_drv\_init(RF\_MODE\_BLE\_1M); //RF 初始化 gpio\_init(!deepRetWakeUp); //gpio 初始化,user 在 app\_config.h 中配置相关参数 clock\_init(SYS\_CLK\_16M\_Crystal); if( deepRetWakeUp ) { user\_init\_deepRetn ();//deep retention 醒来的快速初始化 } else{ user\_init\_normal ();//ble 初始化,整个系统初始化,user 进行设定 } irq\_enable(); //开全局中断 while (1) { #if (MODULE\_WATCHDOG\_ENABLE) wd\_clear(); //clear watch dog #endif main\_loop (); //包括 ble 收发处理、低功耗管理、mesh 和 user 的任务 } } ### 1.1.2 app\_config.h 用户配置文件,用于对整个系统的相关参数进行配置,包括 BLE 相关参数、GPIO 的配置、PM 低功耗管理的相关配置等。后面介绍各个模块时会对 app\_config.h 中的各个参数的含义进行详细说明。 ### 1.1.3 BLE stack entry Telink BLE SDK 中 BLE stack 部分 code 的入口函数有两个: 1) main.c 文件 irq\_handler 函数中 BLE 相关中断的处理入口 irq\_blt\_sdk\_handler。 \_attribute\_ram\_code\_ void irq\_handler(void) { …… irq\_blt\_sdk\_handler (); …… } 2) application file main\_loop 中 BLE 逻辑和数据处理的函数入口 blt\_sdk\_main\_loop。 void main\_loop (void) { mesh\_loop\_proc\_prior();//优先处理,主要是跳过 mailoop 8269 的 10ms interval ///////////////////// BLE entry //////////////////////////// blt\_sdk\_main\_loop(); ////////////////////// UI entry //////////////////////////// factory\_reset\_cnt\_check();//五次上电执行 factory reset 的实现 mesh\_loop\_process();//mesh 相关的 loop 函数 …… ////////////////////// PM configuration //////////////////// …… } ## 1.2 Demo Project TeLink SIG MESH SDK 给用户提供了多个 BLE demo。 用户通过软硬件 demo 的运行,可以观察到直观的效果。用户也可以在 demo code 上进行 修改,完成自己的应用开发。 mesh 的 demo 及区别如下表所示。 ![](https://img.kancloud.cn/98/3e/983e059d33df8dc8116517dc1ebd7b04_704x526.png) * [ ] “8258\_mesh”:是普通 SIG MESH 节点的编译工程,可以被 provisioner 配置网络,有 relay,friend,proxy 功能,无 provision 功能。 * [ ] “8258\_mesh\_LPN:是 LPN MESH 节点的编译工程,通过 friend ship 接收 message,无 relay,friend,proxy,provision 功能。 * [ ] “8258\_mesh\_gw”:是 gateway provisioner 节点的编译工 程,有 adv provisioner 功能,可以配置其他节点,同时也有 relay,friend 功能。 * [ ] “8258\_mesh\_switch”:是普通遥控器(switch) MESH 节点的编译工程。该遥控器组网后,为了实现更低的功耗需求,基本上只发命令,不接收命令。无 relay,friend 功能, * [ ] “8258\_gw\_node\_homekit”:具有 gateway adv provisioner + mesh node + homekit 这三种模式的功能。 * [ ] “8258\_spirit\_LPN” 编译选项:是天猫精灵自定义 LPN 模式。 ## 1.3 LIGHT\_TYPE\_SEL 介绍 该宏用于选择预先配置好的一些常用的产品类型。 #define LIGHT\_TYPE\_NONE 0 #define LIGHT\_TYPE\_CT 1 #define LIGHT\_TYPE\_HSL 2 #define LIGHT\_TYPE\_XYL 3 #define LIGHT\_TYPE\_POWER 4 #define LIGHT\_TYPE\_CT\_HSL 5 #define LIGHT\_TYPE\_DIM 6 // only single PWM #define LIGHT\_TYPE\_PANEL 7 // only ONOFF model #define LIGHT\_TYPE\_LPN\_ONOFF\_LEVEL 8 // only ONOFF , LEVEL model LIGHT\_TYPE\_CT CT 是色温灯的简写,对应的产品类型是色温灯,包含色温相关的 model,比如 Light CTL Server,Light CTL Setup Server,Light CTL Temperature Server 以及对应的 extend model,比如:Generic OnOff Server,Generic Level Server,Light Lightness Server 等。 LIGHT\_TYPE\_HSL 对应的产品类型是彩色灯(HSL 灯),包含 Light HSL Server,Light HSL Hue Server,Light HSLSaturation Server,Light HSL Setup Server,以及对应的 extend model,比如:Generic OnOff Server,Generic Level Server,Light Lightness Server 等。 LIGHT\_TYPE\_XYL 对应的产品类型是 XYL 灯,包含 Light xyL Server,Light xyL Setup Server,以及对应的 extend model,比如:Generic OnOff Server,Generic Level Server,Light Lightness Server 等。 LIGHT\_TYPE\_POWER 对应的产品类型是 power adapter 等,包含 Generic Power Level Server,Generic Power Level Setup Server,以及对应的 extend model,比如:Generic OnOff Server,Generic Level Server 等LIGHT\_TYPE\_CT\_HSL对应的产品类型是 色温灯+彩色灯(HSL 灯),包含色温以及 HSL 对应的 model,以及 GenericOnOff Server,Generic Level Server,Light Lightness Server 等,其中色温灯珠和 HSL 灯珠共用一个 lightness 和 onoff 值。另外,同一时间只有一种灯珠在点亮。 LIGHT\_TYPE\_DIM 对应的产品类型是调光灯,包含 Light Lightness Server,Light Lightness Setup Server 以 及对应的 extend model,比如:Generic OnOff Server,Generic Level Server 等。 LIGHT\_TYPE\_PANEL 对应的产品类型是开关面板,该面板处于server角色,也就是被app等设备控制并执行onoff 切换。包含 Generic OnOff Server model。默认开关个数是 3 个(由 LIGHT\_CNT 定义)。 LIGHT\_TYPE\_LPN\_ONOFF\_LEVEL 对应的产品类型是 LPN 设备,默认包含 Generic OnOff Server model 等,mesh OTA model 关闭。主要用于 demo LPN 的功能。 Note:开发 LPN 设备时,要注意 825x retention RAM 的使用不能超过 32K。 Note:Node 最终包含的所有 model,都会显示在 composition data 中(全局变 量:model\_sig\_cfg\_s\_cps)。 ## 1.4 产品版本号(VID)产品类型(PID)设置 配置文件:\\vendor\\common\\version.h,该文件在汇编代码中也会使用到。 示例如下: #define MESH\_PID\_SEL (LIGHT\_TYPE\_SEL) #define MESH\_VID (VERSION\_GET(0x33, 0x30)) #define FW\_VERSION\_TELINK\_RELEASE (VERSION\_GET(0x33, 0x30)) 1) composition data 中的 PID 和 VID 分别取自这里的 MESH\_PID\_SEL,MESH\_VID。 2) firmware 文件的第 3-6 字节分别表示这里的 PID 和 VID ![](https://img.kancloud.cn/1b/8d/1b8d9676a05ab4ed416e903c99587c5d_672x93.png) 3) 在通用 APP 的 ATT 页面的显示如下: ![](https://img.kancloud.cn/8a/7a/8a7a533afd6095263246f374591a7c38_318x332.png) MESH\_PID\_SEL(PID): 因为通用 APP 上显示是以 ASCII 码解析,所以字符”0x00 0x01”这两个字符不可见。客户按实际需求修改为自己的 PID。 MESH\_VID(VID):“0x33, 0x30”按 ASCII 显示为“30”。 客户按实际需求改为自己的 PID。 FW\_VERSION\_TELINK\_RELEASE:“0x33, 0x30”按 ASCII 显示为“30”。这个是 telink release SDK 时的版本号,客户不应该修改 4) 开发者按实际需求,修改 MESH\_PID\_SEL 和 MESH\_VID 的值即可。 ## 1.5 Mesh 应用收发包处理 ### 1.5.1 发包函数 节点之间发包 调用 mesh\_tx\_cmd2normal\_primary(),该函数具体用法见后面介绍。此方式发送的数据遵循 SIG mesh 协议。access\_cmd\_onoff()等命令是对 mesh\_tx\_cmd2normal\_primary()进行封装而来。 开发者可以启用 sim\_tx\_cmd\_node2node()来进行发送命令的演示(该函数默认是屏蔽的), 效果是:上电后,每隔 3 秒自动交替发送 ON/OFF 命令。详见后续对该函数的说明。 直连节点发给 master调用 bls\_att\_pushNotifyData(),具体用法请参考<AN\_17092701\_Telink 826x BLE SDK Developer Handbook> 3.4.3.10 节,此方式用于发送客户任意自定义格式的数据。但是没有 mesh 功能,一般不用。 注意:需要新增 UUID,然后才能用该方式,否则可能会和当前 UUID 的协议冲突。新增 UUID的方法,可以在 my\_Attributes\_provision\[\]/my\_Attributes\_proxy\[\]/my\_Attributes\[\]定义,自己订制 BLE service ### 1.5.2 发包流程图简介 注:红色字体为 library 函数。 ![](https://img.kancloud.cn/54/20/54202e0dabaaae0b485b4b697b9968a5_527x731.png) ### 1.5.3 收包流程图简介 ![](https://img.kancloud.cn/88/5a/885a6c093fe680ea0c26a46c46fee607_574x566.png) ### 1.5.4 收包回调函数的介绍 generic model generic model 的接口在文件 vendor/common/generic\_model.c,回调函数见结构体 mesh\_cmd\_sig\_func\[\]。比如 generic on message,收到这个命令后,按上面介绍的“收包流程图”,走到 mesh\_cmd\_sig\_g\_onoff\_set()这个函数,用户在这个函数里面实现开关灯或设置渐变参数,渐变效果在 light\_transition\_proc 处理,处理间隔为 LIGHT\_ADJUST\_INTERVAL(20ms)。 vendor model vendor model的 接 口 在 文 件vendor/common/vendor\_model.c 。参考mesh\_cmd\_vd\_func\[\],用户可以自行添加需要的 vendor command,收到这样的命令,按 SIG mesh spec 流程,会走到对应的 callback 函数,比如 cb\_vd\_key\_report()。 ### 1.5.5 SIG\_mesh 信道 SIG\_mesh 的通信信道分为两种: 一种是 adv-bearer,是通过 adv 机制实现相互通信的,通信双方不需要建立 BLE 的连接, 通讯 channel 是标准的 37/38/39; 另外一种是通过 gatt-bearer,通过建立 BLE 连接实现通信, 通讯 channel 是 1-36。 # 2. MCU 基础模块 ### 2.1.1 Flash map 介绍(以 512K flash 为例) ![](https://img.kancloud.cn/43/22/4322c230cd2ceb71817013870acf9326_395x615.png) ### 2.1.2 RAM map(以 825x 64K 为例) 对应的配置文档,详见 ./boot.link ![](https://img.kancloud.cn/47/79/477983d9e329a3b120cd21e1b60eaedb_205x535.png) * [ ] data(retention):包含有\_attribute\_data\_retention\_前缀的变量,以及所有初始化值 不等于 0 的全局变量,默认为 retention data 属性; * [ ] bss(retention):包含有\_attribute\_bss\_retention\_前缀的变量,以及所有初始化值 等 于 0 的全局变量,默认为 retention bss 属性; * [ ] data(no retention):有 \_attribute\_no\_retention\_data\_ 前缀的全局变量 * [ ] bss(no retention):有 \_attribute\_no\_retention\_bss\_ 前缀的全局变量。并且包含 irq\_stack,其 size 由 IRQ\_STK\_SIZE 定义,默认 0x300,demo SDK 使用量小于 0x200, * [ ] normal stack:bss 之后,64KRAM 之前都属于 normal stack。初始化值为 0xffffffff, 目前为了加快 RAM 初始化速度,只初始化 3K 的 RAM。 注意:\_attribute\_bss\_retention\_和\_attribute\_no\_retention\_bss\_这两个前缀,对于初始化不为0 的变量不能使用。否则初始化值无效,默认为 0。 ### 2.1.3 堆栈(stack) 溢出的判断 首先,要确保,bss(no retention)的末尾地址要小于 RAM 的末尾地址,也就是要留有空间 给 normal stack。End of bss(no retention) 可以通过 tdebug Tool 对变量按地址进行排序,最后一个变量之后的地址,就是末尾地址。 另外也可以通过 编译生成的 \*.lst 文档计算: 然后,把所有的功能都测试一遍,然后查看 normal stack 和 irq\_stack 是否有溢出。 Normal stack:初始化值为 0xffffffff。demo SDK 需要 stack 2K 左右,因为目前使用的 stack 都小于 3K,所以为了加快 RAM 初始化速度,只初始化 3K 的 RAM。通过读取 RAM 查看,如果发现 61K 位置即 0x84F400--0x84F403 这 4 个 byte 不是 0xFFFFFFFF,则表示堆栈使用已经超过 3K,如果超过,请联系我们做进一步的分析确认。irq\_stack:初始化值为 0x00000000,通过 tdebug 查看 irq\_stk\[\]变量,观察使用情况; 如果发现 irq\_stk\[0--3\] 这 4 个 byte 非 0,则表示已经溢出。 ## 2.2 时钟模块 MCU 的 clock 由 CLOCK\_SYS\_CLOCK\_HZ 定义,825x 默认是 16MHz. ### 2.2.1 System clock & System Timer 系统时钟(system clock)是 MCU 执行程序的时钟。 系统定时器(System Timer)是一个只读的定时器,为 BLE 的时序控制提供时间基准,同 时也可以提供给用户使用 在 Telink 上一代 IC(826x 系列)上,System Timer 的时钟来源于 system clock,而 8x5x IC 上,System Timer 和 system clock 是独立分开的。如下图所示,System Timer 是由外部 24M Crystal Oscillator 经 3/2 分频得到的 16M。 ![](https://img.kancloud.cn/d7/27/d7276bd6d5c4334404fe3ac65314016e_700x352.png) 图上可以看到,system clock 可以由外部 24M 晶体振荡器经"doubler”电路倍频到 48M 后 再分频得到 16M、24M、32M、48M 等,这一类 clock 我们称为 crystal clock(如 16M crystal system clock、24M crystal system clock);也可以由 IC 内部 24M RC Oscillitor 处理后得到 24M RC clock、 32M RC clock、48M RC clock 等。这一类我们称为 RC clock(BLE SDK 不支持 RC clock)。 在 BLE SDK 中,我们推荐使用 crystal clock。 初始化时调用下面的 API 配置 system clock,在枚举变量 SYS\_CLK\_TYPEDEF 定义中选择时钟对应的 clock 即可。 void clock\_init(SYS\_CLK\_TYPEDEF SYS\_CLK) 由于 8x5x System Timer 与 system clock 不一样,用户需要了解 MCU 上各硬件模块的 clock是来源于 system clock 还是 System Timer。我们以 system clock 为 24M crystal 的情况来进行说明,此时 system clock 为 24M,而 System Timer 是 16M。 在文件 app\_config.h 中 system clock 以及对应的 S、mS、uS 的定义如下: #define CLOCK\_SYS\_CLOCK\_HZ 24000000 enum{ CLOCK\_SYS\_CLOCK\_1S = CLOCK\_SYS\_CLOCK\_HZ, CLOCK\_SYS\_CLOCK\_1MS = (CLOCK\_SYS\_CLOCK\_1S / 1000), CLOCK\_SYS\_CLOCK\_1US = (CLOCK\_SYS\_CLOCK\_1S / 1000000), }; 所有时钟源为 system clock 的硬件模块,在设置模块的 clock 时,只能使用上面 CLOCK\_SYS\_CLOCK\_HZ、CLOCK\_SYS\_CLOCK\_1S 等;换言之,如果用户看到模块中 clock 的设置使用的是以上几个定义,说明该模块的时钟源为 system clock。 如 PWM 驱动中 PWM 周期和占空的设置如下,说明 PWM 的时钟源是 system clock。 pwm\_set\_cycle\_and\_duty(PWM0\_ID, (u16) (1000 \* CLOCK\_SYS\_CLOCK\_1US), (u16) (500 \*CLOCK\_SYS\_CLOCK\_1US) ); System Timer 是固定的 16M,所以对于这个 timer,SDK code 中使用如下的数值来表示 S、mS 和 uS。 //system timer clock source is constant 16M, never change enum{ CLOCK\_16M\_SYS\_TIMER\_CLK\_1S = 16000000, CLOCK\_16M\_SYS\_TIMER\_CLK\_1MS = 16000, CLOCK\_16M\_SYS\_TIMER\_CLK\_1US = 16, }; SDK 中以下几个 API 都是跟 System Timer 相关的一些操作,所以涉及到这些 API 操作时, 都使用上面类似”CLOCK\_16M\_SYS\_TIMER\_CLK\_xxx”的方式来表示时间。 void sleep\_us (unsigned long microsec); unsigned int clock\_time(void); int clock\_time\_exceed(unsigned int ref, unsigned int span\_us); #define ClockTime clock\_time #define WaitUs sleep\_us #define WaitMs(t) sleep\_us((t)\*1000) 由于 System Timer 是 BLE 计时的基准,SDK 中所有 BLE 时间相关的参数和变量,在涉及 到时间的表达时,都是用”CLOCK\_16M\_SYS\_TIMER\_CLK\_xxx”的方式。 ### 2.2.2 System Timer 的使用 Main 函数中 cpu\_wakeup\_init 初始化完成后后,System Timer 就开始工作,用户可以读 取 System Timer 计数器的值(简称 System Timer tick)。 System Timer tick 每一个时钟周期加一,其长度为 32bit,即每 1/16 us 加 1,最小值 0x00000000,最大值 0xffffffff。System Timer 刚启动的时候,tick 值为 0,到最大值 0xffffffff 需要的时间为:(1/16) us \* (2^32) 约等于 268 S,每过 268 S System Timer tick 转一圈。 MCU 在运行程序过程中 system tick 不会停止。 System Timer tick 的读取可以通过 clock\_time()函数获得: u32 current\_tick = clock\_time() 该 BLE SDK 整个 BLE 时序都是基于 System Timer tick 设计的,程序中也大量使用这个 System Timer tick 来完成各种计时和超时判断,强烈推荐 user 使用这个 System Timer tick 来实现一些简单的定时和超时判断。 比如要实现一个简单的软件定时。软件定时器的实现基于查询机制,由于是通过查询来实现,不能保证非常好的实时性和准确性,一般用于对误差要求不是特别苛刻的应用。实现方法: 1) 启动计时:设置一个 u32 的变量,读取并记录当前 System Timer tick。 u32 start\_tick = clock\_time(); // clock\_time()返回 System Timer tick 值。 2) 在程序的某处不断查询当前 System Timer tick 和 start\_tick 的差值是否超过需要定时的时间值。若超过,认为定时器触发,执行相应的操作,并根据实际的需求清除计时器或启动新一轮的定时。 假设需要定时的时间为 100 ms,查询计时是否到达的写法为: if( (u32) ( clock\_time() - start\_tick) > 100 \* CLOCK\_16M\_SYS\_TIMER\_CLK\_1MS) 由于将差值转化为 u32 型,解决了 system clock tick 从 0xffffffff 到 0 这个极限情况。 实际上 SDK 中为了解决系统时钟不同导致和 u32 转换的问题,提供了统一的调用函数,不管系统时钟多少,都可以下面函数进行查询判断: if( clock\_time\_exceed(start\_tick, 100 \* 1000)) //第二个参数单位为 us 需要注意的是:由于 16M 时钟转一圈为 268 S,这个查询函数只适用于 268 S 以内的定时。如果超过 268 S,需要在软件上加计数器累计实现(这里不介绍)。 应用举例:A 条件触发(只会触发一次)的 2 S 后,程序进行 B()操作。 u32 a\_trig\_tick; int a\_trig\_flg = 0; while(1) { if(A) { a\_trig\_tick = clock\_time(); a\_trig\_flg = 1; } if(a\_trig\_flg &&clock\_time\_exceed(a\_trig\_tick,2 \*1000 \* 1000)) { a\_trig\_flg = 0; B(); } } # 3. MESH spec 中的常用概念介绍 ## 3.1 Layered architecture ![](https://img.kancloud.cn/de/b0/deb06d6ba324acffeacba3cfb9252aa3_297x388.png) 3.1.1 Model layer model 定义了一个节点支持的功能,每一个 model 都定义了自己的 op code,以及 status。 比如 generic onoff model,定义了 Generic ON/OFF/GET/STATUS。 Provisioner 在组网的时候,会通过 get composition data 命令去获取节点支持的所有 model id,然后 provisioner 就能知道节点具体支持什么功能了。只有这个节点支持了对应的 model 之后,才应该给它发送该 model 定义的 op code。 Model 又分为 server model 和 client model。server model:简单的说,他是一个被控制的 角色,有自己的状态,可以被别的节点改变和获取,比如 onoff server model,可以接收 onoff set/ get 命令,可以回复 onoff status 命令,但是不能发送 onoff set/get 命令,也不会处理onoff status 命令。 client model:是一个控制 server 节点的角色,没有自己的状态。比如 onoff client model, 可以发送 onoff set/ get 命令,可以处理收到的 onoff status 命令,但是不能发送 onoff status命令,也不会处理 onoff set/get 命令。 ### 3.1.2 Foundation Model layer Foundation Model 的模式和 model 基本一样,是基础 model,包含 Configuration Server model,Configuration Client model,Health Server model,Health Client model。 对于被配网节点都必须包含 Configuration Server model, provisioner 节点必须包含 Configuration Client model。这两个 model 包含的常用 op code 是 subscription add/delete(即组号添加/删除)等,并且这两个 model 的 access layer 层的加密都使用 device key,所以一般来说只有 provisioner 节点才能发送 configuration model 的 set/get 命令。 ### 3.1.3 access layer 把 op code 和 parameter 按规定的格式组合在一起。 ### 3.1.4 transport layer 使用 app key 或者 device key(configuration model 使用)进行加解密。判断并确认是否需 要执行 分包和组包协议。目前为了兼容 BLE4.2 等不支持长广播包的设备,所以都统一设定 adv 的最大 payload 为31byte。 ### 3.1.5 Network layer 对于发送流程:主要包含,对数据包添加 sequence number,等,并使用 network key,iv index 对数据进行加密。发送完成后 sequence number 会执行“加 1”的操作。 对于接收流程:主要包含,使用 network key,iv index 对数据进行解密,解密后判断 sequence number 是否有效(即是否大于已经接收过的值),如果无效则直接丢弃。 ### 3.1.6 Bearer layer 把已经执行过加密的数据包通过type为 LL\_TYPE\_ADV\_NONCONN\_IND(0x02) 的广播包 发送到 mesh 网络中。 ## 3.2 Architectural concepts ### 3.2.1 States 一个节点的状态,比如 onoff States,lightness States ### 3.2.2 Bound states 两个相互关联的 states,比如 onoff 和 lightness。比如,当 onoff 的值由 1 变为 0 的时候, lightness 的值也会变成 0;当 onoff 的值由 0 变为 1 的时候,lightness 的值也会由 0 变为关灯之前的 lightness 的值.同理,当 lightness 的值在 0 和非 0 之间变化时,onoff 的值也会由 0 变为 1。 ### 3.2.3 Messages 就是把加密完成后,发送到 mesh 网络中的 packet,我们也叫做 mesh packet、mesh command。 ### 3.2.4 Node & Elements Node 表示一个完整的节点或者蓝牙模块,Element 表示 Node 里面的某个操作元素。 Node address 有且只有一个,element address 可以是一个或者多个,当 element address 是多个的情况,这些 address 都会是连续的。第一个 element address 又叫做 primary address,Node address 的值和 primary address 相同。 有多个 element 的原因是,当一个 node 有多个相同的 states 的时候,就需要用到了。比 如一个插座产品,插座上有 3 个插孔,要使用 Generic ONOFF 命令控制插孔的 onoff 状态,如果只有一个 address 的话,当节点收到命令后,是没办法确定要控制哪一个插孔的,所以为了能指定控制某一个插孔,就需要使用多个 element address。 另外,色温灯(CT Light)的 element 个数是 2 个,虽然色温灯只需要一个 onoff states,但 是他需要两个 generic level model,一个是和 lightness 对应,另一个是和 Temp 对应,所以就需要 2 个 element。 同理,HSL 灯(RGB 灯)的 element 个数是 3,因为 HSL 灯需要 3 个 generic level model, 分别和 lightness、Hue 以及 Sat 对应。 在组网的时候,Node 会在 provision flow 的交互信息里面上报 element 的个数,比如 2, provisioner 会分配一个地址给 Node,比如是 0x0002。Node 收到后,会按顺序分配 0x0002 给 element 1, 0x0003 给 element 2。Provisioner 在对下一个节点进行组网的时候,会从 0x0004 开始分配。 ### 3.2.5 Models 参考 3.3.1 “Model layer”的介绍。 ### 3.2.6 Publish & subscribe * [ ] Publish:publish 就是 Element 主动发送 status 的过程,可以通过 Config Model Publication Set 命令配置 publish address,以及设置周期 publish 参数。当配置了 publish address 后,只要状态发生变化,Node 都会自动执行 publish status 的动作。是否需要周 期发送,就要看周期 publish 的参数。 * [ ] Subscribe:Subscribe (即订阅)说的是:节点接收到别的节点 publish 出来的 status message(比如 generic onoff status),或者 control message(比如 generic onoff)后,根 据 model 的 Subscribe list\[\]来判断是否处理该 message。Subscribe list\[\]里面是 group address 或者 virtual address,不能是 unicast address,也不能是 0xffff。可以通过 Config Model Subscription Add,Config Model Subscription Virtual Address Add 等命令添加。 判断是否接收并处理的依据是: 1) 当收到 message 的 destination address 为非 unicast address,再判断是否能在对应 model 的 Subscribe list 的 address 里面匹配到。 2) 当 destination address 为 unicast address,则直接判断和自身的 element address 是否匹 配。 3) 当 destination address 为 0xffff,则表示符合判断条件。 ### 3.2.7 Security 加解密时需要使用的要素包含:Network key,IV index,App key 或者 device key。 一个 message 需要进行两层加密,分别是使用 app key 或者 device key 对整个 access layer(包含 op code,parameters)进行加密,以及使用 network key + iv index 对 network layer 进行加密,network layer 就是发送到 mesh 网络中的 packet,包含 source address、destination address 和 sequence number 等。 对 access layer 加密的时候,如果 op code 对应的 model 是 config model,则使用 device key,否则使用 app key。 对于需要分包的 message,因为 access layer 的 payload 在加密的时候是整个 payload 进 行加密的,所以需要接收到所有的 packet 后,才能完成解密和校验。 我们的SDK 默认支持 2 个network key (NET\_KEY\_MAX)和两个 app key(APP\_KEY\_MAX)。 多个 network key 可用于管理多个 network。 多个 app key 可以用于管理不同安全级别的产品。比如 mesh 网络里面既有灯,也有门锁, 如果需要门锁的安全级别更高的话,只有某些人能控制,此时可以通过单独给门锁分配一个独立 的 app key,并且保证这个 app key 只有某些手机 app(provisioner)知道,在进行网络分享的时 候,也不会分享出去。这样就可以提高安全级别。 ### 3.2.8 存储 Sequence Number 的处理 前面”3.1.5 Network layer”章节有提到,mesh message 的 Sequence Number(SNO)每发完一次命令都会进行累加,接收端在进行 SNO 判断的时候,要求大于已经收到过的值,如果小于等于则认为是无效的 message。这个就要求发送端每发送一个命令后,都要把 SNO 存储到flash 中,这个存储频率太高了,特别是在需要分包发送的时候。所以我们做了一个处理:SNO每增加 MESH\_CMD\_SNO\_SAVE\_DELTA(默认 0x80),才存储一次,此时,为了能保证在断电并重新上电后的 SNO 大于已经使用过的值,在上电初始化时,把读取到的 SNO 加上MESH\_CMD\_SNO\_SAVE\_DELTA。具体实现部分,请参考 mesh\_flash\_save\_check()和 mesh\_misc\_retrieve()关于 MESH\_CMD\_SNO\_SAVE\_DELTA 的处理。 ### 3.2.9 Friendship Friend ship 是指 Friend Node(FN)和 Low Power Node(LPN)之间通过规定的 establish friend ship flow 建立的关系。在 friend ship 建立成功后,LPN 在 sleep 期间,当有节点发命令 给 LPN 时,FN 会先把这个 message 先保存下来。当 LPN 唤醒后,会发送 POLL 的查询命令给 FN,此时 FN 就会把保存的 message 发送给 LPN。这样就能达到较低的功耗,缺点是需要网络 中有 friend node 的存在,以及命令的接收和响应有一定的延迟。 更详细的部分请参考 spec 以及后续 LPN 的章节。 ### 3.2.10 Features Mesh features 主要有: * [ ] Relay feature ---- 节点收到有效的 network message 后,会把 message 的 TTL 值减 1, 然后 relay 发送出去,如果收到的 TTL 的值小于等于 1,则不进行 relay。这样可以使 mesh 网络传输距离更远。引入 TTL,主要是控制最后收到该 message 的节点的 delay 时间在可 控范围内。我们 SDK 的TTL 的值默认是TTL\_DEFAULT(0x0A),可以修改这个宏,provisioner 也可以通过 Config Default TTL Set 配置,最大值是 127。 * [ ] Proxy feature ---- proxy 是用于手机 APP 接入 mesh 网络的协议。在 mesh 网络中,APP 也是一个独立的节点,有自己的 Node address。引入 proxy,是因为目前大部分手机不能 完全自定义发送广播包,以及不能一直处于监听 mesh 网络的状态(中间要切换到 WiFi 等), 所以手机 APP 需要通过 BLE GATT 连接一个节点,直连节点收到 APP 发过来的数据后会转 发出去,当直连节点收到 mesh 网络中回复给 app 的 message 后,会先通过 GATT 按 proxy 协议回复给手机 APP。 * [ ] APP 下发 message 给直连节点用的是 ATT\_OP\_WRITE\_CMD(0x52),直连节点回复 message 给 APP 用的是 notify,即 ATT\_OP\_HANDLE\_VALUE\_NOTI(0x1B)。 * [ ] Low Power feature ---- 参考 3.2.9 的“Friendship”的介绍。 * [ ] Friend feature ----参考 3.2.9 的“Friendship”的介绍。 ### 3.2.11 Mesh Topology ![](https://img.kancloud.cn/2c/4f/2c4f0112457e02ed3af3ee6e724ed96c_687x312.png) 我们 SDK,默认常供电节点都支持 Relay,Friend. 所有节点都支持通过 GATT proxy 和 ADV组网。 ## 3.3 Mesh networking ### 3.3.1 Network layer Address ![](https://img.kancloud.cn/3a/2e/3a2e8198b63639a259d37eaaeb33a410_594x144.png) * [ ] Unassigned address:0 表示 Unassigned address * [ ] Unicast address:用于表示 element address * [ ] Group address:组号地址,用于组控以及 publish----subscribe 机制。 * [ ] Virtual address:需要结合 16BYTE 的 lable UUID 使用,Virtual address 是这个 UUID 经过 hash 算法后生成的值。当 group address(共 16384 个)不够使用的时候,可以进行扩展。 目前的应用暂时用不到。 Network PDU ![](https://img.kancloud.cn/14/3e/143ef015183091f33d0eee273a215807_689x623.png) * [ ] IVI: iv index(即 SDK 的 iv\_idx\_st.tx\[3\]这个 byte 的最低 bit, 目前 SDK 的 iv index 按 big endian 存储)。 * [ ] NID:和 network key 相关 * [ ] CTL:标记是否是 control message。 Network transmit count/interval (重传次数和重传间隔的定义) network transmit count 是指发送一个命令,需要重传的次数,这些重传的 rf packet 是一 模一样的,包括 sno 等。重传的目的是为了提升接收成功率。假如当网络中只有两个 mesh 节点,只发送一次的成功率是 80%,即丢包率是 20%,那么,理论上,发送 6 次的丢包率是 20%的 6 次方,即 0.0064%,即收包成功率是 99.993%,当然这个是理论分析。和 RF 环境等还有一定的关系。 我们的 SDK 协议栈默认重发 5 次,即 TRANSMIT\_CNT\_DEF(5),总共发发送次数是 n+1 = 6 个。 network transmit interval 是指重传的两个包之间的发送间隔,我们 SDK 默认在 30-40ms 之间,由 TRANSMIT\_INVL\_STEPS\_DEF(2) 决定,计算方式是 ((TRANSMIT\_INVL\_STEPS\_DEF + 1)\*10 + (0----10))ms。” network transmit count、transmit interval 还可以通过 SIG 定义的标准 config 命令 CFG\_NW\_TRANSMIT\_SET 来配置。 综上所述,我们 SDK 默认发送一个 network packet,比如 generic ONOFF no ack 命令(该 命令不需要分包),需要的时间大概是 40 \* 6 = 240ms。 Reliable retry(发包重试次数)Reliable retry 是指应用层的重试,用于有 status 回复的命令,比如 generic ONOFF。当发送一个 network packet 后(包含 network transmit),会判断是否收到 status,如果没有收到,我们会进行 retry,retry 的时候,network packet 里面的 sequence number 会变化。我们默认最多重试两次。 ### 3.3.2 Access layer ![](https://img.kancloud.cn/e1/40/e1403e2592dbc24f4124aee1db92d083_550x370.png) op code 有三种类型,1byte,2byte 和 3byte。1byte,2byte 是 SIG 定义的命令。3byte 是 vendor 自定义的命令,其中 2 个 byte 是 vendor ID(CID),整个 mesh 网络中,一个 vendor id 最多支持 64 个 vendor opcode。Access layer 包含 op code 和 parameter,最大支持 380 byte。 ### 3.3.3 transport layer 目前为了兼容 BLE4.2 等不支持长广播包的设备,所以都统一设定 adv 的最大 payload 为 31byte。去掉一些数据包的通讯协议需要占用的部分,单包的有效 payload 是 11byte,所以当 Access layer 超过 11byte 后,就需要分包,所以对于 vendor op code 来说,当 parameters 大 于 8 个 byte(8=11-3),mesh 协议栈就会自动执行分包发送。用户不需要介入。 ### 3.3.4 mesh beacon 下图是 unprovisioned device beacon 的 PDU。 ![](https://img.kancloud.cn/73/82/73826352d482585a233284f5e542aed4_699x76.png) Device UUID 可以唯一识别 node。因为有些手机,比如 IOS 不能获取 mac,以及在未来的 remote provision 中无法获得 mac,所以在 SIG mesh 中是通过 Device UUID 来唯一识别 node, 而不是通过 mac。unprovisioned beacon 通过 non-connectable ADV 的 packet 来发送,用于 PB-ADV provision 模式。 Oob info 及 URI Hash 请参考 spec 《3.9.2 Unprovisioned Device beacon》 在未组网时,会发送 unprovision beacon,通过 unprov\_beacon\_send()来发送。发送周期 由”beacon\_send.inter = MAX\_BEACON\_SEND\_INTERVAL”来定义,默认是 2 秒。 在组网后,会发送 security beacon,通过 mesh\_tx\_sec\_nw\_beacon()来发送。另外还可以 通过 CFG\_BEACON\_SET 命令打开或者关闭这个发送使能开关。请参考本文章节《4.4 控制对应 的节点》““SecNwBc”按钮”的操作。发送周期由 SEC\_NW\_BC\_INV\_DEF\_100MS 来定义,默 认是 10 秒。 ### 3.3.5 iv update folw 即 iv index 的update flow。network layer和 access layer 的加解密过程都需要用到 iv index。 前面提到,mesh网络要求 network PDU的 sequence number 要一直累加,而 sequence number 是 3byte 表示。当使用很长一段时间后,sequence number 接近最大值的时候,就需要考虑更 新 iv index,否则 sequence number 就会归 0,导致接收端认为是一个无效的 message。从某 种程度上,可以理解 iv index 为 sequence number 的扩展位。 iv index update flow 是节点自行发起,自行 update 的过程。目前 SDK,当节点检测自己 的 sequence number 超过了 IV\_UPDATE\_START\_SNO (0xC00000) 后,会主动发起 iv update folw。每次 Iv update 后,iv index 会加 1. 详细的 iv update flow 请查阅 spe 相关章节: SPEC V1.0 《3.10.5 IV Update procedure》。 ### 3.3.6 heartbeat mesh 的心跳包,可以配置周期发送,所以可以用于做在线离线检测(周期 publish 机制也可 以做在线离线检测),以及 hops 的计算,即计算 heartbeat message 经过多少跳之后,才被接 收到。经过统计一定时间内收到的 heartbeat 的个数(count),并计算得到每个 heartbeat 的 hops 的 值,得到 min hops 和 max hops,进而了解整个网络的布局,以及每一个节点的 message 传输的可靠程度。不过每次配置 heartbeat subscription 只能监听和统计一个节点的状态。 hops 计算方式是 hops = InitTTL - RxTTL +1。 * [ ] InitTTL:是 heartbeat publish set 里面的 TTL 参数。 * [ ] RxTTL:是收到的 message 的 network PDU 里面的 TTL。 节点默认不发送 heartbeat,具体配置方式,详见“5.6 Heartbeat 的演示”章节 ### 3.3.7 Health Health model 相关的 message 是用来反映节点的 warning 或者 error 状态。比如电量的 warning 和 error 指示等.