SIG Mesh SDK
SDK概述
SDK给用户提供基于SIG_mesh协议应用开发的示例代码demo code,用户可以在这些demo code基础上开发自己的应用程序。
SDK开发包下载方式:
访问Telink Wiki下载最新的蓝牙Mesh SDK,或者点击此链接直接下载sig_mesh_sdk.zip。
目前工程适用的IC部分为825x/8278/b91m/8269。泰凌微提供了上位机工具(sig_mesh_tool.exe), 通过USB与master dongle连接实现provisioner的功能, master dongle目前仅支持8269芯片型号。
如果需要快速了解和演示基本的功能,请参考:
SDK的文件架构
SDK文件架构分为app应用层和BLE&SIG_mesh协议层。当导入工程文件后,(工程导入方法请参考《AN_IDEUG-E1_Telink IDE User Guide.pdf》,新建工程参考文档《AN_16063000-E1_Guide For Adding New Project On Existing SDK.pdf》),显示的文件架构如下图所示。有几个主要的顶层文件夹:boot,common,drivers,homekit_src,proj_lib,stack,vendor。
注意:
B91m工程会在编译器的预编译宏里面 使能这个宏 __TLSR_RISCV_EN__。
-
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目录下有:
-
8267_master_kma_dongle:上位机测试使用,配合GATT模式上位机工具可以作为一个provisioner的角色,用于演示和debug。
-
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是主函数和中断函数的入口。
-
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的任务
}
}
app_config.h
用户配置文件,用于对整个系统的相关参数进行配置,包括BLE相关参数、GPIO的配置、PM低功耗管理的相关配置等。
后面介绍各个模块时会对app_config.h中的各个参数的含义进行详细说明。
BLE stack entry
Telink BLE SDK中BLE stack部分code的入口函数有两个:
a) main.c文件irq_handler函数中BLE相关中断的处理入口irq_blt_sdk_handler。
_attribute_ram_code_ void irq_handler(void)
{
……
irq_blt_sdk_handler ();
……
}
b) 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 ////////////////////
……
}
工程示例
TeLink SIG Mesh SDK给用户提供了多个BLE工程示例。
用户通过软硬件运行工程示例,可以观察到直观的效果。用户也可以在示例代码上进行修改,完成自己的应用开发。
Mesh SDK的工程示例及区别如下表所示。
Demo | Vendor folder | Application | Mesh Feature |
---|---|---|---|
8258_mesh/ 8269_mesh |
.\mesh | CT/HSL light and etc | Relay, friend, proxy |
8258_mesh_LPN\ 8269_mesh_LPN |
.\mesh_lpn | LPN | LPN, proxy |
8258_mesh_gw\ 8269_mesh_gw |
.\mesh_provision | Gateway provisioner | adv provisioner,Relay, friend |
8258_mesh_gw_node | .\mesh_provision | Gateway+light node | adv provisioner, Relay, friend, proxy |
8258_mesh_switch\ 8269_mesh_switch |
.\mesh_switch | 遥控器应用 | proxy |
8258_spirit_LPN | .\spirit_lpn | 天猫精灵自定义LPN模式 | proxy |
8269_mesh_master_dongle | .\8267_master_kma_dongle | Tool | GATT provisioner |
-
“8269_mesh_master_dongle”编译选项:有GATT provisioner功能,无relay,friend功能。上位机测试使用,配合GATT模式上位机工具可以作为一个provisioner的角色,用于演示和debug。
-
“8258_mesh”,“8269_mesh”编译选项:是普通SIG MESH节点的编译工程,可以被provisioner配置网络,有relay,friend,proxy功能,无provision功能。
-
“8258_mesh_LPN”,“8269_mesh_LPN”编译选项:是LPN MESH节点的编译工程,通过friend ship接收message,无relay,friend,provision功能,有 proxy 功能,但只是在 GATT master 和 LPN之间通讯,不会转发app发过来的命令给别的节点。
-
“8258_mesh_gw”,“8269_mesh_gw”编译选项:是gateway provisioner节点的编译工程,有adv provisioner功能,可以配置其他节点,同时也有relay,friend功能。
-
“8258_mesh_switch”,“8269_mesh_switch”编译选项:是普通遥控器(switch) MESH节点的编译工程。该遥控器组网后,为了实现更低的功耗需求,基本上只发命令,不接收命令。无relay,friend功能,
-
“8258_gw_node”,编译选项:具有gateway adv provisioner + mesh node 这两个角色的功能,可以自己建立网络,把别的未组网节点添加进来。也可以被别人组网。
-
“8258_spirit_LPN” 编译选项:是天猫精灵自定义LPN模式。
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 ON/OFF model
#define LIGHT_TYPE_LPN_ON_OFF_LEVEL 8 // only ON/OFF , LEVEL model
#define TYPE_TOOTH_BRUSH 9
#define LIGHT_TYPE_NLC_CTRL_CLIENT 10
//#define LIGHT_TYPE_NLC_BLC // set NLCP_BLC_EN to 1 to enable Basic Lightness Controller Mesh Profile.
#define LIGHT_TYPE_NLC_SENSOR 11
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 HSL Saturation 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,以及Generic OnOff 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 的功能。
注意:
- 开发LPN设备时,要注意825x retention RAM的使用不能超过32K。
- Node最终包含的所有model,都会显示在composition data中(全局变量:model_sig_cfg_s_cps)。
TYPE_TOOTH_BRUSH:
对应的产品类型是客户定制的产品。
LIGHT_TYPE_NLC_CTRL_CLIENT:
对应的产品类型是Mesh V1.1 的 NLC feature,详见此章节 DICNLCP。
LIGHT_TYPE_NLC_SENSOR:
对应的产品类型是Mesh V1.1 的 NLC feature,详见此章节 OCSSNLCP, ALSNLCP 和 ENMNLCP。
产品版本号(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
3) 在通用APP的ATT页面的显示如下:
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的值即可。
手机App介绍
App安装方法
(1) Android App
在SDK开发包中获取安装包:
\telink_sig_mesh_sdk\app\android\TelinkBleMesh\TelinkBleMeshDemo-V4.1.0.0-20231113.apk
或者通过 “Telink Apps” 这个App下载。
(2) iOS App
在App Store通过搜索telinksigmesh获取。或者通过 “Telink Apps” 这个 App 下载。
(3) App操作说明文档
参考章节33 Android and iOS APP使用说明。
Mesh应用收发包处理
发包函数
节点之间发包:
调用mesh_tx_cmd2normal_primary(),该函数具体用法见后面介绍。此方式发送的数据遵循SIG mesh协议。access_cmd_onoff()等命令是对mesh_tx_cmd2normal_primary()进行封装而来。
开发者可以启用sim_tx_cmd_node2node()来进行发送命令的演示(该函数默认是屏蔽的),效果是:上电后,每隔3秒自动交替发送ON/OFF命令。详见后续对该函数的说明。
注意判断发送是否成功:
(1) 执行完发包函数后,需要判断函数返回值,如果为 0,则表示成功,否则失败,对应的error code,详见SDK里面的tx_errno_e:
enum tx_errno_e{
TX_ERRNO_SUCCESS = 0,
TX_ERRNO_DEV_OR_APP_KEY_NOT_FOUND = 1,/* device key or app key not found */
TX_ERRNO_GET_UT_TX_BUF_FAIL = 2,/* get the upper layer tx buffer fail */
TX_ERRNO_ADDRESS_INVALID = 3,/* source address or destination address invalid */
TX_ERRNO_PAR_LEN_OVER_FLOW = 4,/* parameters length > 378 */
TX_ERRNO_TX_BUSY = 5,/* segment busy, reliable busy,... */
TX_ERRNO_TX_FIFO_FULL = 6,/* tx fifo full: mesh_adv_cmd_fifo_(normal message) or mesh_adv_fifo_fn2lpn_(message from friend to LPN)*/
TX_ERRNO_PAR_LEN_LPN_CTL = 7,/* All transport control messages originated by a Low Power node shall be sent as Unsegmented */
TX_ERRNO_IV_INVALID = 8,/* have not get iv index after import JSON */
TX_ERRNO_ALL_OTHER_ERR = -1,/* default error */
};
(2) 如果是发送的segment包,如果在发包中途失败,发送失败的回调是"mesh_seg_block_ack_cb()",错误类型见“st_block_ack_t”。比如 “ST_BLOCK_ACK_BUSY” 表示接收端正在接收另一个节点发过来的分包,并且还未结束。此时客户可以根据情况,进行延时一段时间后,应用层做重发处理。
typedef enum{
ST_BLOCK_ACK_RX_ALL = 0, // RX node has received all segments.
ST_BLOCK_ACK_MISSING = 1, // RX node only received some segments.
ST_BLOCK_ACK_BUSY = 2, // RX node is receiving another segment flow, so current tx segment flow should be stopped and retry later.
ST_BLOCK_ACK_TIMEOUT = 3, // tx segment flow timeout.
ST_BLOCK_ACK_UNKNOW = 4, //
}st_block_ack_t;
(3) 发包前可以先判断下,当前是否处于tx busy状态。使用 is_busy_mesh_tx_cmd() 判断即可。也可以先直接调用发包接口,再看返回值。
直连节点发给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。
发包流程图简介
注:红色字体为library函数。
收包流程图简介
收包回调函数的介绍
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()。
SIG_mesh信道
SIG_mesh的通信信道分为两种:
一种是adv-bearer,是通过adv机制实现相互通信的,通信双方不需要建立BLE的连接,通讯channel是标准的37/38/39;
另外一种是通过gatt-bearer,通过建立BLE连接实现通信, 通讯channel是1-36。
Telink debug方法介绍
Tdebug Tool调试
此方式可以稳定可靠的进行调试,并且不影响MCU运行,能实时查看和修改全局变量,也能查看函数的运行状态等,函数是否执行可以在函数里面定义一个全局变量,然后再执行计数操作,然后通过Tdebug查看这个全局变量即可。比如:
Tdebug简介如下,详见BDT工具的“Help” -> ”User guide”的文档说明。
注意:
下图第8个step通过右键弹出该菜单。
第9个step按name排序后找到 tick_loop,(因为tick_loop是static变量,在代码中可能会重名,所以编译器增加了后缀.12397做区分),然后按右键,点击Refresh,就会读取所有的全局变量并刷新,tick_loop的值也会被刷新。
如果需要修改全局变量,在value一栏修改值,然后点回车即可。回读的话,按右键,点击Refresh即可。
大于4 byte,小于1K byte的结构体变量或者数组,通过以下方式读取,点击1处的“…”,读到的值会显示在 2 的位置:
大于1K byte的,会自动生成文件存放在BDT工具的Telink Burning and Debugging Tool -> config -> user -> Read.bin
Log打印调试 Log Print Debugging
在SDK2.9版本后提供了通过GPIO模拟uart输出的方式进行log打印, 默认是1Mbps。此方法需要留意的是, 为了保证log输出不出错,输出log期间默认是执行irq_disable(),所以log数据过多有可能会影响MCU以及RF收包处理的速度等,也有可能会造成mesh的不稳定,反而不利于查找问题的.因此在使用时候尽可能的减少log的信息输出.调试完功能后把能关闭的log尽量都关闭。
Log可以设置print level(TL_LOG_LEVEL) 以及 print module(TL_LOG_SEL_VAL)。
print level:为了不把过多的log默认编译到firmware里面,TL_LOG_LEVEL默认设置为TL_LOG_LEVEL_ERROR,print level需要小于等于TL_LOG_LEVEL_ERROR才会打印。TL_LOG_LEVEL_LIB 用于library里面的code,或者用于打印非library code的重要 log。TL_LOG_LEVEL_USER 是供用户使用的类型,在library里面不会使用到。所以建议客户在打印的时候,使用LOG_USER_MSG_INFO() 来打印。如果需要使用LOG_MSG_INFO()等接口,需要修改TL_LOG_LEVEL。
Print module(即TL_LOG_SEL_VAL):需要包含对应的module比如TL_LOG_USER,该module才会打印。
只有print level 和 Print module 都符合条件的情况下,对应的log才会打印。
注意
打印数据长度有最大长度的限制,当超过时,数据会被截断。如果想不被截断,可以把 log_dst[] 这个数组的size改大即可。
Log打印设置步骤:
Step 1 设置HCI_LOG_FW_EN的值设为 1
PRINT_DEBUG_INFO的功能是:使用 GPIO 口模拟UART来输出log,该模式仅支持TX不支持RX,专门用于输出log.
Step 2 设置print pin
Step 3 设置baud rate. 默认是1Mbps.
因为在IO模拟UART输出的时候,是关闭中断了的(SIMU_UART_IRQ_EN 等于1),所以,在IO模拟UART的模式下,log 速度越快越好。所以不建议降低 baud rate。
注意:
可能有一些USB转串口的板子在1M速率下会有严重的误报,可以适当降低uart速率(但是速度降低会影响log的打印速度),或者更换USB转串口工具,比如型号为CH340G。
Step 4 选择log module
在当前的分级log定义中,除了本章节开头提到的print level外,还有log module的设置。要输出log,需要满足print level以及log module设定才会输出。
Demo SDK为了使log更简洁,TL_LOG_SEL_VAL我们默认只打开TL_LOG_USER,其他 log 都是关闭的。
TL_LOG_USER默认是没有任何地方调用的。用户需要增加打印的时候,使用如下方式即可:
LOG_USER_MSG_INFO(pbuf, len, format,...),其中,
-
pbuf:需要把某个buffer转换成字符打印出来时使用。如无,请写0.
-
len:pbuf的长度。
如需要使用其他API,比如LOG_MSG_INFO,请先确认 TL_LOG_LEVEL是否满足(默认是不满足的)。
Step 5 如果需要打开我们预设的一些调试log,设置TL_LOG_SEL_VAL的值即可,比如:
#define TL_LOG_SEL_VAL (BIT(TL_LOG_USER)|BIT(TL_LOG_PROVISION)|BIT(TL_LOG_FRIEND)|BIT(TL_LOG_NODE_SDK)|BIT(TL_LOG_NODE_BASIC))
MCU基础模块
本文主要介绍和mesh相关的部分,详细部分请参考《AN_19011501-C2_Telink Kite BLE SDK Developer Handbook》的“MCU基础模块”章节。
Flash and RAM map
Flash map介绍
以Demo SDK 默认的FlashMapB85m512K和FlashMapB91m1M为例:
对应的flash map文档,也可参考sdk/doc/SIGMeshFlashmap_yyyymmdd.xlsx
"Sig Mesh Parameters 1" 的详细信息,请查看SDK的FLASH_ADR_MESH_KEY, FLASH_ADR_MD_CFG_S ......
"Sig Mesh Parameters 2" 的详细信息,请查看SDK的FLASH_ADR_MD_VD_LIGHT, FLASH_ADR_MD_SCENE ......
"Sig Mesh Parameters 1" 的详细信息,请查看SDK的FLASH_ADR_MESH_KEY, FLASH_ADR_MD_CFG_S ......
"Sig Mesh Parameters 2" 的详细信息,请查看SDK的FLASH_ADR_MISC, FLASH_ADR_RESET_CNT ......
RAM map(以8258 64K为例)
对应的配置文档,详见 ./boot.link。
-
data(retention):包含有_attribute_data_retention_前缀的变量,以及所有初始化值不等于0的全局变量,默认为retention data属性;(在 B85m SIG mesh SDK的 list 文件中显示的是 data,而不是 retention_data)
-
bss(retention):包含有_attribute_bss_retention_前缀的变量,以及所有初始化值等于0的全局变量,默认为retention bss属性;(在 B85m SIG mesh SDK的 list 文件中显示的是 bss,而不是 retention_bss)
-
data(no retention):有_attribute_no_retention_data_前缀的全局变量。(注意,由于某种原因,在 B85m SIG mesh SDK的 list 文件中显示的是 retention_data,而不是 no_retention_data)
-
bss(no retention):有_attribute_no_retention_bss_前缀的全局变量。并且包含irq_stack,其size由IRQ_STK_SIZE定义,默认0x300, demo SDK使用量小于0x200。(注意,由于某种原因,在 B85m SIG mesh SDK的 list 文件中显示的是 retention_bss,而不是 no_retention_bss)
-
normal stack:bss之后,64KRAM之前都属于normal stack。初始化值为0xffffffff,目前为了加快RAM初始化速度,只初始化3K的RAM。
注意:
- "retention bss" 末尾地址需要小于 0x848000,当工作在retention sleep 模式的时候。因为retention RAM 最大32K。
- _attribute_bss_retention_和_attribute_no_retention_bss_这两个前缀,对于初始化不为0的变量不能使用。否则初始化值无效,默认为0。
堆栈(stack) 溢出和retention RAM溢出的判断
以B85m为例介绍如下:
堆栈溢出判断方法概括
为了堆栈不溢出,首先要确保,no retention bss 的末尾地址要小于RAM的末尾地址,也就是要留有空间给normal stack。
no retention bss 的末尾地址可以通过tdebug Tool对变量按地址进行排序,最后一个变量之后的地址,就是末尾地址。
另外也可以通过编译生成的*.lst 文档计算:
然后,把所有的功能都测试一遍,然后查看normal stack和irq_stack是否有溢出。
(1) Normal stack溢出检查方法
初始化值为0xffffffff。Demo SDK需要stack 2.5K左右,因为目前使用的stack都小于3K,所以为了加快RAM初始化速度,只初始化3K的RAM。通过读取RAM查看,如果发现61K位置即 0x84F400--0x84F403这4个byte不是0xFFFFFFFF,则表示堆栈使用已经超过3K,有可能 Normal stack 溢出。为了进一步确认是否溢出,可以按以下方式修改,初始化整个 normal stack:
其中 no_retention_bss_end 和 下图的 sdk_version 的 VMA 地址是一样的。通过读取RAM查看,如果发现_no_retention_bss_end_位置的4个byte不是0xFFFFFFFF,则表示Normal stack 溢出。
(2) irq_stack溢出检查方法
初始化值为0x00000000,通过tdebug查看irq_stk[]变量,观察使用情况; 如果发现irq_stk[0--3] 这4个byte非0,则表示已经溢出。
RAM剩余大小分析
根据编译器生成的light_8258.lst文件(和bin文件在同一个目录) 分析如下:
(注意,由于某种原因,在 B85m SIG mesh SDK的 list 文件中显示的 retention_data 是指 no_retention_data,retention_bss 是指 no_retention_bss)
Idx Name: 段名称
Size:本段所占字节大小
VMA:实际运行地址
LMA:在flash中的存储地址
剩余RAM = RAM的末尾地址 - no retention bss的最后一个字节的地址。(如果有retention_data 或者 retention_bss段,则以 retention_bss 的末尾地址来计算)
825X RAM 起始地址是0x840000,8258 RAM size是64K,所以RAM 结束地址是0x850000。
bss的最后一个字节的地址获取方式:
有sdk_version section的 SDK,bss的最后一个字节的地址 等于 sdk_version的首地址。sdk_version段,不实际占用 RAM 空间,因为在 cstartup 里面也是没有从flash 加载对应的 内容到 RAM 。
没有sdk_version section的 SDK,可以按以下两个方式中的一个获取:
(1) bss VMA (即bss start address) + bss size, 比如上图的是:(0x8428d0 + 0x1869) = 0x844139
如果有retention_data 或者 retention_bss段,则以 retention_bss 的末尾地址来计算,即retention_bss VMA (即retention_bss start address) + retention_bss size。
备注:这里的retention_data和retention_bss实际上是 no retention RAM区域,只是 boot.link里面由于某个原因,需要命名 data 和 bss 为 retention 区,以及no retention_data 和 noretention_bss命名为retention_data和retention_bss 区。
(2) 通过BDT工具对变量按address 排序,最后一个变量的地址 + 这个变量的size,如下图,0x844138 + 1 = 0x844139,(在BDT工具中,高位的‘8’ 已省略)
根据上图分析,这个firmware的剩余RAM = 0x850000 – (0x8428d0 + 0x1869) = 0xBEC7 = 47.7K
需要注意的是剩余RAM,并不都能直接进行使用,这段剩余RAM还要分配一段区间给normal stack使用(对于B85m SIG mesh的堆栈要求预留量要不小于2.5k, 对于B91m SIG mesh的堆栈要求预留量要不小于 4k, 对于私有mesh的堆栈要求预留量要不小于512)。以上图的B85m SIG mesh为例,也就是说在这个firmware中,真正能使用的RAM约为47.7-2.5 = 45.2K。
以8258为例判断堆栈是否溢出的过程
RAM溢出的判断
RAM溢出的原因是堆栈的使用超过了剩余RAM区域,而越界使用了BSS区或者其他RAM区导致系统崩溃。
在本系统中,有两个堆栈区,一个是normal stack,另一个是irq stack。堆栈的使用是不确定的,主要取决于实际运行过的函数调用的深度和函数中使用局部变量的大小。
(1) 判断normal stack是否溢出
a) 一个简单方式是:剩余RAM size(计算方式请参考1.1),SIG mesh SDK要大于2.5k, 私有mesh要大于512, 这个是经验值判断,不进行实际读取确认。
b) 另一个方式是:把所有的功能都测试一遍,(不能包含有触发重启的功能),然后再查看normal stack是否有溢出,即在查看bss末尾地址之后是否还有连续的0xFF区域(normal stack初始值为0xFF),如果有,则表示RAM没有溢出。查看bss末尾地址之后的地址,比如查看最后 4K,(3K之前也就是0x84F400之前的没有使用到的RAM ,为了加快初始化速度,没有执行初始化,所以会是随机数)。但是我们的堆栈应该都不会超过3K,所以只看3K即可。之所以读取4K主要是为了地址4K对齐,也就是在读取到的文档里面的地址的低12bit和实际的地址是一样,这样更方便查看。查看的方式如下:(在“84f000”的控件里面按下“Tab”键即可),生成的文件在BDT工具所在目录 "./BDT/release_v5.4.4/config/user/",例如:"./BDT/release_v5.4.4/config/user/log_17_17_48_addr0x0084f000.bin", 因为BDT工具有做判断,当读取数据的长度大于等于1K的时候,自动存成文件,文件名格式是 “log + 时间戳 + start address”。
读取的文件,举例如下,其中84F000+0xF0C 就是当前运行情况下的堆栈最大使用位置。stack 使用约为 0x100。我们有看到还有很多连续的“0xFFFFFFFF”, 所以堆栈没有溢出。
(2) 判断irq stack是否溢出
Irq stack是中断函数使用的,目前配置的大小是IRQ_STK_SIZE, 0x300,(这个size不建议客户改小),所有的中断回调函数也都将使用这个堆栈。所以客户应避免在中断回调函数中定义很大的局部变量。
判断irq stack和判断normal stack的第二个方法类似,把所有的功能都测试一遍,(不能包含有触发重启的功能),然后在BDT中查看irq_stk这个buf是否还有连续的0x00区域(irq stack初始值为0x00),如果有,则表示RAM没有溢出。如下图:
Retention RAM的size计算
对于SIG mesh SDK,如果打开了retention功能,即打开了PM_DEEPSLEEP_RETENTION_ENABLE这个宏,那编译的时候,boot.link文件会检查:如果retention使用超过32K,则会报错。如下图:
根据list文档,Retention RAM的size计算方法:
Retention区域包含 vectors,ramcode,data,bss段。
需要注意的是,在mesh SDK(注意,仅mesh SDK),由于data,bss是默认放在retention区域的,也就是定义的时候,可以不加_attribute_data_retention_ 或者 attribute_bss_retention 前缀,以及其他一些原因,上图显示的retention data ,retention bss其实是非retention的,只是借用这个名字。
所以retention 区域的末尾地址的计算方法是:.bss VMA (即bss start address) + bss size, 比如上图的是:(0x843940 + 0x243a) = 0x845d7a;Retention size是: 0x845d7a – 0x84000 = 0x5d7a = 23.4k.
启动文件cstartup.s和链接文件boot.link
和BLE基础SDK有点不一样,mesh SDK里面cstartup.S和boot.link都统一只用一份。 以B85m为例, B85m只使用 cstartup_8258_RET_16K.S,该文件适用于16K retention和32K retention的配置,因为里面已经做了自动识别大小,retention data的末尾地址之后,就当做普通的no retention RAM来使用,用于存放 no retention data/bss等。
时钟模块
MCU的clock由CLOCK_SYS_CLOCK_HZ 定义,B85m mesh工程默认是32MHz,B85m gateway工程默认是48MHz;B91m mesh和gateway工程默认都是48MHz。
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。
图上可以看到,system clock可以由外部24M晶体振荡器经"doubler”电路倍频到48M后再分频得到16M、24M、32M、48M等,这一类clock我们称为crystal clock(如16M crystal system clock、24M crystal system clock);也可以由IC内部24M RC Oscillator处理后得到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”的方式。
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) 约等于268s,每过268s 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时钟转一圈为268s,这个查询函数只适用于268s以内的定时。如果超过268s,需要在软件上加计数器累计实现(这里不介绍)。
应用举例:A条件触发(只会触发一次)的2s后,程序进行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();
}
}
常用概念介绍
Mesh相关的spec下载链接详见 mesh_1.1_feature汇总 的介绍。
以下介绍的顺序,按《MshPRFv1.0.1.pdf》的标题顺序进行,本文主要做大概的介绍,详细的介绍,请参考SIG MESH SPEC的相关章节。
Layered architecture
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命令。
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命令。
Access layer
把op code和parameter按规定的格式组合在一起。
Transport layer
使用app key或者device key(configuration model使用)进行加解密。判断并确认是否需要执行 分包和组包协议。
目前为了兼容BLE4.2等不支持长广播包的设备,所以都统一设定adv的最大payload为31byte。
Network layer
对于发送流程:主要包含,对数据包添加sequence number等,并使用network key,iv index对数据进行加密。发送完成后sequence number会执行“加1”的操作。
对于接收流程:主要包含,使用network key,iv index对数据进行解密,解密后判断sequence number是否有效(即是否大于已经接收过的值),如果无效则直接丢弃。
Bearer layer
把已经执行过加密的数据包通过type为LL_TYPE_ADV_NONCONN_IND(0x02)的广播包发送到mesh网络中。
Architectural concepts
States
一个节点的状态,比如onoff States,lightness States。
Bound states
两个相互关联的states,比如onoff 和 lightness。比如,当onoff的值由1变为0的时候,lightness的值也会变成0;当onoff的值由0变为1的时候,lightness的值也会由0变为关灯之前的lightness的值。同理,当lightness的值在0和非0之间变化时,onoff的值也会由0变为1。
Messages
就是把加密完成后,发送到mesh网络中的packet,我们也叫做mesh packet、mesh command。
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开始分配。
Models
参考3.3.1 “Model layer”的介绍。
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,则表示符合判断条件。
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)知道,在进行网络分享的时候,也不会分享出去。这样就可以提高安全级别。
存储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的处理。
Friendship
Friendship是指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的存在,以及命令的接收和响应有一定的延迟。
更详细的部分请参考mesh spec以及后续LPN的章节。
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”的介绍。
Mesh Topology
我们SDK,默认常供电节点都支持 Relay,Friend. 所有节点都支持通过GATT proxy和ADV组网。
Mesh networking
Network layer
Address:
-
Unassigned address:0 表示 Unassigned address
-
Unicast address:用于表示element address
-
Group address:组号地址,用于组控以及publish - subscribe机制。
-
Virtual address:需要结合16BYTE的label UUID使用,Virtual address是这个UUID经过hash算法后生成的值。当group address(共16384个)不够使用的时候,可以进行扩展。目前的应用暂时用不到。
Network PDU:
-
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会变化。我们默认最多重试两次。
Access layer
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。
Transport layer
目前为了兼容BLE4.2等不支持长广播包的设备,所以都统一设定adv的最大payload为31byte。去掉一些数据包的通讯协议需要占用的部分,单包的有效payload是11byte,所以当Access layer超过11byte后,就需要分包,所以对于vendor op code来说,当parameters大于8个byte(8=11-3),mesh 协议栈就会自动执行分包发送。用户不需要介入。
Mesh beacon
下图是unprovisioned device beacon的PDU。
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秒。
IV update flow
即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 flow。每次IV update后,IV index会加1.
详细的IV update flow请查阅spe相关章节: SPEC V1.0 《3.10.5 IV Update procedure》。
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,具体配置方式,详见“heartbeat的演示”章节
Health
Health model相关的message是用来反映节点的warning或者error状态。比如电量的warning和error指示等,详见spec 4.2.15.1 Current Fault,如下表:
Value | Description |
---|---|
0x00 | No Fault |
0x01 | Battery Low Warning |
0x02 | Battery Low Error |
0x03 | Supply Voltage Too Low Warning |
0x04 | Supply Voltage Too Low Error |
0x05 | Supply Voltage Too High Warning |
0x06 | Supply Voltage Too High Error |
0x07 | Power Supply Interrupted Warning |
0x08 | Power Supply Interrupted Error |
0x09 | No Load Warning |
0x0A | No Load Error |
0x0B | Overload Warning |
0x0C | Overload Error |
0x0D | Overheat Warning |
0x0E | Overheat Error |
0x0F | Condensation Warning |
0x10 | Condensation Error |
0x11 | Vibration Warning |
0x12 | Vibration Error |
0x13 | Configuration Warning |
0x14 | Element Not Calibrated Warning |
0x16 | Element Not Calibrated Error |
0x17 | Memory Warning |
0x18 | Memory Error |
0x19 | Self-Test Warning |
0x1A | Self-Test Error |
0x1B | Input Too Low Warning |
0x1C | Input Too Low Error |
0x1D | Input Too High Warning |
0x1E | Input Too High Error |
0x1F | Input No Change Warning |
0x20 | Input No Change Error |
0x21 | Actuator Blocked Warning |
0x22 | Actuator Blocked Error |
0x23 | Housing Opened Warning |
0x24 | Housing Opened Error |
0x25 | Tamper Warning |
0x26 | Tamper Error |
0x27 | Device Moved Warning |
0x28 | Device Moved Error |
0x29 | Device Dropped Warning |
0x2A | Device Dropped Error |
0x2B | Overflow Warning |
0x2C | Overflow Error |
0x2D | Empty Warning |
0x2E | Empty Error |
0x2F | Internal Bus Warning |
0x30 | Internal Bus Error |
0x31 | Mechanism Jammed Warning |
0x32 | Mechanism Jammed Error |
0x33–0x7F | Reserved for Future Use |
0x80–0xFF | Vendor Specific Warning / Error |
调试工具操作说明
下载固件
关于BDT工具的详细操作说明,请参考 “Help” -> “User guide”的说明文档,以下仅介绍使用到的操作。
在操作之前,需要先下载 “8258_mesh.bin” 到每一个节点中(8258 Dongle), 然后烧录provisioner节点,provisioner目前有两个模式,即GATT连接的master dongle模式和gateway模式(ADV 模式),:
GATT模式:把“8269_mesh_master_dongle.bin”下载到8269的Master Dongle(8269 Dongle)。
Gateway模式:把“8258_mesh_gw.bin”下载到8258的gateway Dongle中。
例如,用户可以按照以下步骤下载固件到8258 light节点。
1) 硬件连接: 通过USB线连接EVK板的miniUSB接口和PC的USB口,如果EVK板子上面的指示灯闪烁一下,表示EVK板和PC是正常连接的。8258 Dongle通过USB接口来连接EVK板的USB接口。
正常情况,工具左下角会显示:。
2) 通过Telink BDT工具将“8258_mesh.bin”下载到8258 Dongle的flash。
Step 1 打开BDT,首先选择对应的型号,再点击确认EVK和8258 dongle节点 是否可以正常通讯,如果正常,会看到“Swire ok”。如果不正常,则可能是芯片处于睡眠状态,需要点击按钮进行激活唤醒,特别是低功耗设备需要这个操作,激活成功会提示“Activate OK”。Activate包含有MCU重启的操作。
Step 2 擦除8258 Dongle 板的整个 flash部分。点击按钮。
注:erase的起始地址和size可以在进行配置。
Step 3 点击“File”按钮在选择“open”选择对应的“8258_mesh.bin”文件,点击打开,然后BDT界面对应的文件:
。
Step 4 点击按钮,将选中的“8258_mesh.bin”烧写到flash从0开始的地址。
注:down load起始地址和size可以在这里配置,默认是0。
固件烧写步骤:
Step 1 点击工具栏“Tool” —> “Memory Access”,弹出对话框:
注意:
烧录light节点和gateway节点时需要此操作,GATT master dongle不需要。
按以上参数输入6个byte的MAC,输完后,在data控件栏敲下回车键执行写入操作。在Addr控件栏敲下Tab键,会执行 read操作,进行回读确认。
如果没有输入mac,8258 dongle重新上电后,检测到0x76000没有MAC,就会随机分配一个,并保存到flash 0x76000处。
Step 2 重新上电后,8258 Dongle板就能够作为一个灯节点。
以上是烧录的时候,BDT使用到的操作步骤。用户也可以通过点击“Help” -> “User guide”打开说明文档来获取其他更多的功能和操作方法。
Gateway USB模式的BLE连接和加灯
1) 打开“sig_mesh_tool.exe”工具,将烧录好8258_mesh_gw.bin的gateway dongle插到PC的USB口中。
2) 如下图所示,工具左上角的“Found”表示8258 gateway Dongle和PC工具正常连接,并且可以正常通信。此时工具会根据接入的硬件自动选择“tl_node_gateway.ini”
3) 将8258mesh节点上电。
4) 点击右上角的“Scan”按钮,工具将打开一个“ScanDev”窗口,该窗口会显示对应的MAC地址列表,包含rssi和频偏。
5) 双击“ScanDev”窗口中对应的条目,选择节点。
Gateway模式:双击后,只是选择该节点,不需要任何连接动作和命令发出。
8258 gateway Dongle模式:双击后会建立BLE连接,如果8258 gateway Dongle上的红灯亮起,表示BLE连接正常建立。“Stop”按钮用于终止当前的BLE连接,8258 gateway Dongle上的白灯亮起,表示BLE连接断开。目前只支持单个节点的BLE GATT连接。
6) 点击右下角“Prov”按钮来打开“provision”窗口。
注意:
在初始状态下,“Provision”按钮和“bind_all”按钮是被禁止的,并且用户不可同时操作这两个按钮。
“network_key”是第一次打开“provision”窗口的时候随机生成的,在点击“SetPro_internal”前可以修改。
7) 点击“SetPro_internal”用来设置网络的初始参数,在log窗口中打印“Set internal provision success” 来表示设置内部的网络参数成功。
一旦点击 “SetPro_internal”,对应的netkey等参数就不能再更改,所以“SetPro_internal”会被灰化。参数被保存在mesh_database.json文件中,下次重新开启工具,会自动读取mesh_database.json里面的参数。此时如果还需要修改network_key,需要解散整个网络,全部恢复到出厂设置。
此时“Provision”按钮使能。unicast_addr是即将分配给待provision的节点的primary address,客户可以手动修改,但是,如果没特别需求不建议修改。
8) 点击“Provision”按钮来执行SIG的provision flow,就能把对应的节点加入到网络中,连接的节点的红色LED会闪烁4下表示成功。log信息显示如下图。
Gateway模式的log:
GATT Master dongle模式的log:
9) 设置好app_key后点击“bind_all”,会先发送get composition data命令,获取所有model id,然后为所有model绑定app_key。
Bind_all成功后,“unicast_adr”会自动根据当前节点占用的element个数累加,表示对下一个节点进行provision所使用的primary address。(比如CT灯的element个数是2,则每加一个CT灯,“unicast_adr”控件的值就会加2)。
10) 绑定App_key后点击主界面Mesh按钮进去mesh界面可进行开关灯等操作。
11) 解散网络
GATT master dongle模式和Gateway模式均可按如下方式操作:
依次选择节点,然后按“4.5.3 通过UI配置某一节点的详细参数”章节里面“DelNode”按钮的操作说明,点击“DelNode”踢掉该节点。
注意:
GATT master dongle模式需要先删除非GATT直连节点,最后再删除GATT直连节点,因为删除GATT直连节点会导致当前GATT连接断开。
Gateway UART模式的BLE连接和加灯
主要是配置UART端口:
1) gateway firmware的HCI_ACCESS 选择 HCI_USE_UART,重新编译。
2) 插入串口工具到PC,tx/rx 和 gateway的rx/tx接通。
3) 打开“sig_mesh_tool.exe”。
4) 点击UART 然后选择 显示出来的COM口。
5) 点击“Connect”按钮,如果连接成功,按钮会变成“Disconnect”。现在就可以通过UART来执行gateway的功能了。
6) 剩余所有操作和USB模式都一样。请参考“4.2 Gateway USB模式的BLE连接和加灯”。
GATT master dongle模式的BLE连接和加灯
1) 打开“sig_mesh_tool.exe”工具,将烧录好程序的8269 Master Dongle插到PC的USB口中。
2) 如下图所示,工具左上角的“Found”表示8269 Master Dongle和PC工具正常连接,并且可以正常通信。此时工具会根据接入的硬件自动选择“sig_mesh_master.ini”。
3) 从第3步开始,请参考本章“Gateway模式的BLE连接和加灯”的介绍。
控制对应的节点
GATT master dongle和gateway的操作方式和UI都相同。
UI显示和单节点、所有节点的onoff控制
1) 点击主窗口右下角的“Mesh”按钮。会弹出一个“Mesh”窗口。
2) 用户能够点击“Mesh”窗口中的“Nodes”来刷新所有的灯的状态。
如果“Nodes”右边的下拉框选择reliable的时候,点击“Nodes”,是发送了lightness get命令。根据lightness status的值刷新UI。
如果“Nodes”右边的下拉框选择unreliable的时候,点击“Nodes”,是发送了lightness get noack命令。但是后续发出的onoff等命令是no ack,所以节点不回复status,所以UI无法刷新,此时一般是结合publish机制来刷新UI。
如果“Nodes”右边的下拉框选择online status的时候,点击“Nodes”,是不会发送任何命令,只是初始化UI为空,然后根据返回的online status数据刷新UI。Note:online status模式是私有模式,需要节点的firmware打开ONLINE_STATUS_EN这个宏开关。
另外,lightness显示的时候,是把SIG定义的0 - 65536的刻度转换成0 - 100的刻度之后再显示的。
3) 单个节点操作: 点击“On”/“Off” 按钮 (如图红框所示),对应的灯会控制开关的状态。对应的节点的状态会上报给工具来刷新对应的状态。
4) 表示状态“on”,表示状态“off”,表示”离线”。
5) 全开全关的控制: 点击在“All”标记旁的“On”/“Off”,MESH网络内的所有节点都会开关。
分组控制(即subscription的功能演示)
点击对应灯节点的索引号,如002,获取该灯的node address并显示在下图的位置。
此处默认是 0xffff,表示未选择任何一个节点。
用户可以左键/右键点击Group控件中对应group的“Svr”框(在Off的右边),将选中的该灯节点添加到对应的组/从对应组中删除。“Svr”框中显示√,表示节点已加入组中;“Svr”框中显示空白,表示节点已不在组中。
-
“Svr”这一列对应操作的是generic onoff server model (0x1000)。
-
“Clnt”这一列对应操作的是generic onoff client model (0x1001)。
因为一般情况下,节点只支持 server model,所以,我们一般只操作 “Svr”这一列。
group index和组地址的转换关系是:组地址 = group index + 0xC000。
用户可以点击Group控件中对应group的“On”/“Off”来控制整个组的开关。
通过UI配置某一节点的详细参数
双击下图1的位置,选中该节点,选中后,会先自动发送get group(subscription list)命令,获取该节点的group并显示在对应的UI。然后接着判断当前节点是否支持scene、time和scheduler功能。如果支持,会自动发送get命令获取scene,time和scheduler列表。
获取scene的时候,发送SCENE_REG_GET命令获取所有的有效场景序号,并显示在UI列表中。
获取time的时候,发送TIME_GET命令获取当前节点的time,并显示在UI中。如果节点刚上电,time是0,表示等待被配置时间,这种情况下,时钟会一直保持0,不会计时。配置时间的方式有两种。第一,通过app/gateway发送time set命令;第二,配置节点的time model的publish属性,刚上电的节点获取别的节点publish出来的time status。
获取scheduler的时候,会先发送SCHD_GET命令获取所有有效的scheduler index,根据返回的index的值,分别再发送SCHD_ACTION_GET获取详细的参数,并显示在UI列表中。下图所示是节点刚provision完成,还没有添加任何scheduler,所以不需要发送SCHD_ACTION_GET。
另外,因为节点是刚provision完成,所以group,scene、time和scheduler都是空的。
以下的按钮介绍都是基于当前选择的节点。
“Group_S”按钮:
点击该按钮会发送CFG_SIG_MODEL_SUB_GET命令,获取当前选中节点的onoff server model的subscription address list即组号列表,并在“Svr”这一列中显示出来。
“Group_C”按钮:
点击该按钮会发送CFG_SIG_MODEL_SUB_GET命令,获取当前选中节点的onoff client model 的subscription address list即组号列表,并在“Clnt”这一列中显示出来。
大部分的节点不支持 onoff client model,所以这个按钮一般不用。
“GrpDelAll_S”按钮:
点击该按钮会发送CFG_MODEL_SUB_DEL_ALL命令,删除当前选中节点的onoff server model的subscription address list,并把“Svr”这一列中清空。
“GrpDelAll_C”按钮:
点击该按钮会发送CFG_MODEL_SUB_DEL_ALL命令,删除当前选中节点的onoff client model的subscription address list,并把“Clnt”这一列中清空。
“GetPub_S”按钮:
点击该按钮会发送CFG_MODEL_PUB_GET命令,操作的model是 onoff server model,并把返回的值在后边的显示框中显示。功能是获取节点的publish address。
在输入框中,修改publish address,然后按键盘的“Enter”键,会发送CFG_MODEL_PUB_SET,对应的publish address是输入框里面的值。
此处配置publish参数的时候,除了publish address外,其他参数都是默认值。详见发送命令的log:
如果需要修改publish的其他参数,可以通过INI 命令cfg_pub_set_sig ,并修改参数为自己期望的参数,然后发送即可。
“SecNwBc”按钮:
点击该按钮会发送CFG_BEACON_GET命令,并把返回的值显示在右边的显示框中。该命令配置是否发送security network beacon。
在输入框中,修改值,然后按键盘的“Enter”键,会发送CFG_BEACON_SET,对应的参数是输入框里面的值。
“TTL”按钮:
点击该按钮会发送CFG_DEFAULT_TTL_GET命令,并把返回的值显示在右边的显示框中。该命令获取节点的default TTL值。SDK默认值通过TTL_DEFAULT定义。
在输入框中,修改TTL值,然后按键盘的“Enter”键,会发送CFG_BEACON_SET,对应的参数是输入框里面的值
“transmit”按钮:
点击该按钮会发送CFG_NW_TRANSMIT_GET命令,并把返回的值显示在右边的显示框中。该命令获取节点的network transmit的值。低3bit表示network transmit count,高5bit表示network transmit interval。
SDK默认值定义如下:
注意:
transmit count(5)和network transmit interval(2)组合起来就是0x15.
在输入框中,修改network transmit的值,然后按键盘的“Enter”键,会发送CFG_NW_TRANSMIT_SET,对应的参数是输入框里面的值。
“Relay”按钮:
点击该按钮会发送CFG_RELAY_GET命令,并把返回的值显示在右边的显示框中。该命令获取节点的relay使能开关的值。
在输入框中,修改该值,然后按键盘的“Enter”键,会发送CFG_RELAY_SET,对应的参数是输入框里面的值。
“Friend”按钮:
点击该按钮会发送CFG_FRIEND_GET命令,并把返回的值显示在右边的显示框中。该命令获取节点的friend feature的使能开关的值。
在输入框中,修改该值,然后按键盘的“Enter”键,会发送CFG_FRIEND_SET,对应的参数是输入框里面的值。
“Proxy”按钮:
点击该按钮会发送CFG_GATT_PROXY_GET命令,并把返回的值显示在右边的显示框中。该命令获取节点的proxy feature的使能开关的值。
在输入框中,修改该值,然后按键盘的“Enter”键,会发送CFG_GATT_PROXY_SET,对应的参数是输入框里面的值。
“Lightness”按钮:
点击该按钮会发送LIGHTNESS_GET命令,并把返回的值,由0-65535的刻度转换为0-0x64的刻度后显示在右边的显示框中。
在输入框中,修改该值,然后按键盘的“Enter”键,会发送LIGHTNESS_SET,对应的参数是输入框里面的值。
“C/T”按钮:
点击该按钮会发送LIGHT_CTL_TEMP_GET命令,并把返回的值,由800-20000的刻度转换为0-0x64的刻度后显示在右边的显示框中。
在输入框中,修改该值,然后按键盘的“Enter”键,会发送LIGHT_CTL_TEMP_SET,对应的参数是输入框里面的值。
“RFU”:
Reserve for future。
“GetCPS”按钮:
点击该按钮会发送COMPOSITION_DATA_GET命令,并把返回的值显示在log窗口中。
“DelNode”:
点击该按钮会发送NODE_RESET命令,把当前节点踢出当前网络。踢出成功后,节点的红色LED会闪烁8次,并有执行reboot的操作。
Time model的操作
1) 节点的固件端time model默认是关闭的,需要设置MD_TIME_EN为1。另外,gateway 8269默认也是关闭,需要打开;gateway 8258默认是打开的。节点和gateway等设置MD_TIME_EN为1后,编译烧录,并重新组网。
2) 双击选择要操作的节点。
3) 点击“set time”按钮,工具会把PC 当前的时间通过“TIME_SET”命令发送给节点。此时time显示如下,并且时钟会自动刷新。
备注:time set命令在发送的时候,主要的参数解析如下:
-
TAI_sec:是相对于0时区的值。
-
zone_offset:要配置为当前的时区,单位是15minute。
举个例子:北京时间 2019/1/1 09:00:00 (时区:东8区)的配置方式。
上述函数只是介绍TAI和本地时间的转换过程,一般是不会这么用的。因为,time set命令是在手机APP或者PC上发送的,这两者都有API直接获取当前的TAI_sec和zone_offset。比如我们PC的上位机获取方式如下:
(需要偏移OFFSET_1970_2000,是因为PC的基准年份是 1970年,SIG MESH定义的基准年份是2000年)
注意事项:
节点断电后,时间会丢失,即g_TAI_sec等于0,所以是不会开始计时的。 需要接收到 app或者gateway 等 的 timeset 命令,或者接收到其它未断电节点publish 的TIME_STATUS信息,此时时钟才能正常运行。 如何配置节点publish TIME_STATUS消息:Telink SIG mesh app在组网的时候,会检查composition data的model 列表里面是否有time model,有的话就自动给time model发送 publish 命令。如果是gateway,或者master dongle上位机等,则需要手动发送命令。
Scene model的操作
1) 固件端scene model默认是关闭的,需要设置MD_SCENE_EN为1。另外,gateway 8269默认也是关闭,需要打开;gateway 8258默认是打开的。
2) 双击选择要操作的节点。
3) 通过UI或者INI命令,把节点的状态调节到场景期望的配置。比如generic onoff set以及lightness set命令等。
4) 输入scene number,然后点击“Store”,就会发送场景添加命令(SCENE_STORE),把节点当前的状态设置为对应的场景ID。并在列表中列出已经配置好的场景ID,如下图“3”所示。
节点收到 SCENE_STORE 后的处理函数是: mesh_cmd_sig_scene_set().
注意:
场景添加命令(SCENE_STORE)只有scene number,没有灯的状态信息,节点在收到场景添加命令后,会自动把当前的状态信息,比如onoff ,lightness等保存起来作为该scene的状态。
5) Recall场景,即把灯的状态设置为场景定义的状态。双击下图“1”所示的序号,然后点击“Recall”按钮即可。
节点收到SCENE_RECALL后的处理函数是: mesh_cmd_sig_scene_recall().
注意:
Recall scene后,灯的状态发生改变,但是灯节点的状态没有上报,因为没有配置publish status,如果需要UI能刷新,则需要配置相关model的publish参数。
6) 修改场景。目前没有专门的修改命令,通过scene store的方式进行修改即可。
7) 删除场景。双击下图“1”所示的序号,然后点击“Delete”按钮即可。
Scheduler model的操作
1) 参数介绍(更详细的信息请参考spec):
-
Year:any表示每一年;custom表示指定某一年,base是2000年,即0表示2000年,19表示2019年……
-
Month:可以单选,多选,全选。Note:不选和全选都表示全选。
-
Day:any表示每一天;custom表示指定某一天。
-
Week:可以单选,多选,全选。Note:不选和全选都表示全选。
-
Hour:any表示每一小时;once a day表示一天的某一个小时响应一次,这个值是随机数,且每一天都重新生成这个随机数;custom表示指定某一小时。
-
Min:any表示每一分钟;every 15表示0/15/30/45响应;every 20表示0/20/40响应;once an hour表示一小时的某一个分钟响应一次,这个值是随机数,且每一小时都重新生成这个随机数;custom表示指定某一分钟。
-
Second:规则同Min。
2) 双击选择要操作的节点,如果下图“action”一栏,如果为空,则表示该节点的这个id的schedule 还未进行过配置。
3) 单击“id”所在的列,选择要配置的scheduler的id(SIG定义最多16个,取值范围是0 - 15),被选中的id会已蓝色底显示。并且该id对应的schedule参数会被刷新到上方的UI。
4) 用户根据需要修改schedule参数,然后点击“Action Set”按钮,发送SCHD_ACTION_SET进行配置。
因为UI显示区的限制,在列表里面只显示action参数,对应的是“on”, “off”, “no action”, “recall”,如果什么都不显示,表示该schedule还未进行过配置。
如果需要查看某个schedule id的详细信息,单击“id”一栏的某个值即可。
5) Schedule 删除。
SIG目前未定义专门的删除命令。可以通过设置该schedule的action为“No action”即可。
厂测模式
厂测模式的目的
厂测模式的目的是用于生产中,不需要进行组网,就能进行一些常用控制,比如ONOFF,亮度,色温控制等。目前gateway和GATT master dongle支持厂测模式。APP暂时不支持。
厂测模式的参数
-
unicast address默认是MAC的低15 bit,如果低15bit为0,则使用1作为unicast address。
-
network key,app key,device key,IV index使用编译的默认值。
厂测模式的默认能测试的命令
厂测模式能控制的model由这个数组factory_test_model_array[]定义,configure model能使用的命令由factory_test_cfg_op_array[]定义。
厂测模式下的控制,不需要组网,直接按“4.5 控制对应的节点”章节操作即可。需要注意,要使所有节点,包括gateway或者master dongle都没有进行过组网动作。
SDK常用模块
配置mesh SDK的默认feature
1) Mesh节点在composition data (model_sig_cfg_s_cps.page0.head.feature)里面描述了该节点支持的feature,SDK的初始化如下:
2) Composition data里面只是定义了是否支持,另外在处于“支持”的状态下,可以配置是否开启。详见mesh_global_var_init()里面配置的地方:model_sig_cfg_s.frid、model_sig_cfg_s.relay、 model_sig_cfg_s.gatt_proxy。
另外,在节点正常工作的过程中,支持通过命令来开启或者关闭这几个feature,分别是CFG_FRIEND_SET、CFG_RELAY_SET、CFG_GATT_PROXY_SET。
当不支持对应的feature时,model_sig_cfg_s相关的参数需要设置为SUPPORT_DISABLE。
3) 我们各个编译工程的feature的默认情况,详见第一章”SDK 概述”的“Demo Project” 章节.
常用宏定义和介绍
部分常用宏定义及 API 也可以参考 "常用api"。
LIGHT_CNT 和 ELE_CNT_EVERY_LIGHT
详见 "节点的element个数的定义" 的介绍。
ONPOWER_UP_SELECT
ONPOWER_UP_SELECT 定义灯节点断电后,重新上电时灯的状态设定,可以设置为以下几种状态中的一个:
- ONPOWER_UP_OFF: 上电后,灯被设置为 OFF 状态。
- ONPOWER_UP_DEFAULT:上电后,灯被设置为 ON 状态。
- ONPOWER_UP_STORE: 上电后,灯保持断电前的状态。
详见 mesh model spec 《MshMDL_v1.1.pdf》 的 章节 “3.1.4 Generic OnPowerUp”:
MESH_POWERUP_BASE_TIME
用于定义,节点上电经过多长时间后,再经过一个随机时间,就开始发送 lightness status 或者 onoff status,通知网关或者手机等,当前节点已经在线。
详见 mesh_vd_init() 和 system_time_run() 的处理:
void mesh_vd_init()
{
......
publish_powerup_random_ms = rand() % 1500; // 0--1500ms
STATIC_ASSERT(MESH_POWERUP_BASE_TIME >=200);
publish_powerup_random_ms += MESH_POWERUP_BASE_TIME; // 200ms: base time.
......
}
void system_time_run(){
......
if(publish_powerup_random_ms && clock_time_exceed_ms(0, publish_powerup_random_ms)){
publish_powerup_random_ms = 0;
publish_when_powerup();
}
......
}
判断节点是否已组网
is_provision_success();
节点的element个数的定义
一个节点包含的element数量可以是一个,也可能是多个。由这两个宏的值来决定:ELE_CNT_EVERY_LIGHT 和 LIGHT_CNT 来决定,每一个 element 占用一个 unicast address。
#define ELE_CNT (LIGHT_CNT * ELE_CNT_EVERY_LIGHT)
-
ELE_CNT_EVERY_LIGHT 表示 一个产品单元由几个element 来组成,比如一个色温灯由两个 element来组成。之所以要两个element,是因为色温灯有两个状态,亮度值和色温值。而亮度可以通过level set等命令来控制,色温也可以通过level set等命令来控制。如果只有一个element,那当色温灯节点收到level set命令的时候,就没办法区分是要控制亮度还是色温,所以需要两个 element。同理 HSL(RGB)灯需要3个element。
-
LIGHT_CNT:表示一个BLE mesh模组有几个产品单元。
对于支持server model的产品,比如当一个BLE模组驱动两个色温灯,此时LIGHT_CNT就需要设置为2。
对于支持client model 的产品,比如遥控器产品,即Switch工程,按键发送的控制命令的目的地址可以通过修改publish address进行修改,所以相同命令的按键,有多少个需要独立配置 publish address 的按键,LIGHT_CNT 就需要配置为多少。比如 demo SDK 的 Switch project,有 4 对发送 group 地址的按键,所以 LIGHT_CNT 就设置为 4。
分组特性及share-model的介绍
spec对应的介绍,请参考《MshMDL_v1.1.pdf》的 "4.2.4 Subscription List" 等。
(1) SIG Mesh spec定义每个model都可以独立配置组号,所以,目前每个model都有一份独立的组号数据,最大存储个数是8个(SUB_LIST_MAX), 如下图。
(2) 使用device key加解密的model的命令不支持组播地址,因为每个节点的device key是不一样的。使用device key的model有config model等,详见MODEL_ID_DEV_KEY[]:
const u32 MODEL_ID_DEV_KEY[] = {
SIG_MD_CFG_SERVER, SIG_MD_CFG_CLIENT,
SIG_MD_REMOTE_PROV_SERVER, SIG_MD_REMOTE_PROV_CLIENT, // no para
SIG_MD_DF_CFG_S, SIG_MD_DF_CFG_C,
SIG_MD_BRIDGE_CFG_SERVER, SIG_MD_BRIDGE_CFG_CLIENT,
SIG_MD_PRIVATE_BEACON_SERVER, SIG_MD_PRIVATE_BEACON_CLIENT,
SIG_MD_SAR_CFG_S, SIG_MD_SAR_CFG_C, // save in model_sig_cfg_s_t now.
SIG_MD_ON_DEMAND_PROXY_S, SIG_MD_ON_DEMAND_PROXY_C, // save in model_sig_cfg_s_t now.
SIG_MD_LARGE_CPS_S, SIG_MD_LARGE_CPS_C, // no para to save
};
(3) Sig mesh还规定,同一个element内,有状态绑定关系的,或者有model扩展关系的,要共用组号信息,比如给onoff model配置一个组号后, lightness model也会自动绑定这个组号, 所以SUBSCRIPTION_SHARE_EN默认需要打开。绑定关系图 详见 model spec 《MshMDL_v1.1.pdf》的 "Summary of xxx models" 相关章节,比如“6.7 Summary of lighting models”的 “Figure 6.12: Relationships between lighting models – Part 1”。也可以查看 sdk 里面的 sub_share_model_sig_onoff_server_extend[], 这个数组里面包含了和onoff model有扩展关系的所有 model。
(4) 有些model之间是没有状态绑定关系的,比如 onoff model,vendor model,sensor model 三者之间,就需要分别对这3个model 发送配置组号的命令。在某些应用里面,有一些私有需求,即对某一个model绑定组号的时候,还希望对没有扩展model或者状态绑定关系之间的model也自动做组号绑定,减少组号配置时间。这个时候,可以打开 SHARE_ALL_LIGHT_STATE_MODEL_EN 来实现。需要注意的是,这个是自定义规则。打开这个宏开关后,把需要一起自动绑定的组号的model id放在指定数组里面就可以了。如果是 SIG model,则放在sub_share_model_sig_onoff_server_extend[]这个数组,如果是 vendor model,则放在sub_share_model_vendor_server_extend[]。
其他细节,请参考SUBSCRIPTION_SHARE_EN 和 SHARE_ALL_LIGHT_STATE_MODEL_EN 这两个宏对应的 code。
节点获取自己所属的组号的方法
- 通过全局变量获取
每一个model都有一个组号列表,以onoff model为例,通过 model_sig_g_onoff_level.onoff_srv[i].com.sub_list[] 获取。
在BDT工具里面双击model_sig_g_onoff_level对应的value即可获取它的信息,在获取到信息后,参考model_sig_g_onoff_level的结构体定义,找到sub_list的位置,即可看到组号。
- 通过model id获取组号
通过此函数 mesh_find_ele_resource_in_model() 返回的 p_model 指针,然后 通过 p_model->sub_list[] 即可获取。
- 获取所有的model id的组号
通过遍历MeshSigModelResource[]里面的所有 model 全局变量,然后通过第一个方法“通过全局变量获取” 来获取所有model_id的组号。
Heartbeat的演示
heartbeat功能详见 "heartbeat" 章节。
默认情况下,节点不发送heartbeat message,通过接收到 app 等发送过来的HEARTBEAT_PUB_SET配置命令,节点就会开始发送heartbeat message。以下是示例:每2秒发送一次heartbeat message,对应的INI命令:
CMD-cfg_hb_pub_set_sig
=a3 ff 00 00 00 00 00 00 02 00 80 39 01 00 ff 02 05 07 00 00 00
该命令配置的参数解析如下:
-
80 39:op code
-
01 00:即表示heart beat的destination address是0x0001。
-
ff:CountLog,0xff表示数量无穷大。
-
02:PeriodLog,周期等于 2的(02-1)次方即2秒。
-
05:InitTTL,用于发送heartbeat message的时候,赋值给network layer 的 TTL。该值可以自定义,不要求等于 model_sig_cfg_s.ttl_def,因为这取决于用户想让多少跳范围内的节点才能接收到该heartbeat message。
-
07 00:features,只要relay,friend,proxy feature这3个中的任意一个的状态有变化(在enable和disable之间切换)就会立即上报heartbeat message。
-
00 00:NetKeyIndex。
在上位机工具中可以看到心跳包。
-
0a:heartbeat 的opcode,注意heartbeat 是control message。
-
05:InitTTL, 值和heartbeat set message里的参数相同。
-
07:Features, 值和heartbeat set message里的参数相同。
接收端收到heartbeat message后,会执行回调mesh_process_hb_sub(),在里面可以得到heartbeat message access layer参数区的InitTTL值和network layer的ttl值,两个相减即可知道该heartbeat 是经过了多少跳才到达当前节点的。
以收到的heartbeat的network layer的ttl值等于 2 为例,具体计算是:hops = p_hb->iniTTL- (p_bear->nw.ttl) + 1 = 5 - 2 + 1 = 4;也就是 heartbeat 是经过了 4 跳才到当前节点。
Mesh ADV发送计时方式
SDK user_init初始化时调用bls_set_advertise_prepare (app_advertise_prepare_handler)注册广播包发送回调函数,发广播前允许用户访问和修改广播包内容,在 app_advertise_prepare_handler(rf_packet_adv_t * p) 里面,p指针指向要发送的数据区,修改p指向的内容即可修改即将要发送的内容。
注册app_advertise_prepare_handler()后,该函数默认10ms调用一次去广播包,该10ms时间由 ADV_INTERVAL_MIN 定义。定义10ms的原因是,network transmit interval的单位是 10ms,以network transmit interval等于2为例,发送network message 的时候,mesh_tx_cmd_busy_cnt 初始值设置为 network transmit interval,每当 app_advertise_prepare_handler()被调用一次,mesh_tx_cmd_busy_cnt就会减1,减到0后,再做 0~10ms的延时,即实现了spec定义的transmit interval的即时,然后就可以发送下一个 transmit count对应的RF包了。
Mesh ADV payload设置API
Unprovisioned Device beacon
未组网时发送的不可连接的广播包,供ADV provisioner发现用。对应的payload设置API是 unprov_beacon_send(),该API是把数据通过 mesh_tx_cmd_add_packet() 压到 mesh_adv_cmd_fifo 这个 fifo,然后在 app_advertise_prepare_handler() 检查 mesh_adv_cmd_fifo,看到有数据,就发送出来。数据格式详见 V1.1 spec的这个章节“3.10.2 Unprovisioned Device beacon”。sample data详见 “8.4 Beacon sample data” 章节。
Mesh Provisioning Service advertising
未组网时发送的可连接的广播包,供GATT provisioner发现用。对应的payload设置API是 set_adv_provision()。数据格式详见 V1.1 spec的“7.1.2.2.1 Advertising”这个章节。 sample data详见 “8.5 Provisioning Service sample data”。
Mesh Secure Network beacon
组网成功后,发送的不可连接的广播包,主要用来广播 iv index,以及 iv 更新,和 用于 key refresh 流程。对应的 payload 设置 API 是 mesh_tx_sec_private_beacon_proc()。数据格式详见 V1.1 spec的这个章节 “3.10.3 Secure Network beacon”。sample data详见 “8.4 Beacon sample data” 章节。
Mesh Proxy ADV
组网成功后,发送的可连接的广播包,供GATT proxy client发现和连接用。有包含network ID 和node identity两种内容。对应的 payload 设置 API 是 set_adv_proxy()。数据格式详见 V1.1 spec的这个章节 “7.2.2.2.1 Advertising”。sample data详见 “8.6 Mesh Proxy Service sample data”章节。
Mesh接收和发送自定义广播包
自定义广播包的发送:
当需要发送非mesh spec定义的beacon时,比如ibeacon等, 设置BEACON_ENABLE等于1即可。详见 BEACON_ENABLE 相关的 code。
SDK初始化时调用bls_set_advertise_prepare (app_advertise_prepare_handler)注册广播包发送回调函数,发广播前允许用户访问和修改广播包内容,sdk默认10ms调用一次广播包发送函数。如果用户想发送自定义的广播包,在gatt_adv_prepare_handler里参照mesh可连接广播包的发送即可。通过clock_time_exceed软件计时方式控制广播包间隔,rf_packet_adv_t * p指向待发送的广播包,用户根据广播包格式修改p指向的广播包内容(可参考set_adv_provision()),最后置返回值ret为1即可,表示要发送广播包。
接收和过滤可连接广播包:
SDK在rf rx中断调用adv_filter_proc()函数对收到的广播包进行过滤,函数返回0表示丢弃收到的广播包,返回1表示保留该广播包(接收并压入blt_rxfifo)。默认情况下,过滤所有可连接广播包。如果用户想接收可连接广播包,打开宏USER_ADV_FILTER_EN,在user_adv_filter_proc()函数里对用户关心的广播包进行判断并返回1,不建议所有的可连接广播包都返回1,因为此方式不对广播包做任何过滤,所有广播包都会压到blt_rxfifo里,包括周围非mesh的其他BLE产品发出的广播包也会被接收进来,我们的接收buffer可能会不能存储那么多数据,导致需要关心的mesh message丢失,进而影响mesh的收包效果。
blt_sdk_main_loop ()会检查blt_rxfifo,如果检测到有数据需要处理,会调用app_event_handle(),用户在此回调函数的if(LL_TYPE_ADV_NONCONN_IND != (pa->event_type & 0x0F))分支下处理收到的可连接广播包即可。如下图:
修改mesh网络最大节点数的方法
Mesh产品在设计阶段,需要先设定最大节点数,否则在发送命令获取所有节点的某个状态的时候,比如Lightness Get All,会出现cache buffer不够位置存储,导致某个Lightness Get All会被重复处理。
最大节点数 MESH_NODE_MAX_NUM (默认 105)。
#if WIN32
#define MESH_NODE_MAX_NUM 1000 // 1000
#elif (FEATURE_LOWPOWER_EN)
#define MESH_NODE_MAX_NUM 105 // no need to many for LPN to save retention RAM.
#elif DEBUG_CFG_CMD_GROUP_AK_EN
#define MESH_NODE_MAX_NUM 305
#else
#define MESH_NODE_MAX_NUM 105 // gateway and node should keep the same, because of mesh command cache..
#endif
修改网络节点数量的方法: MESH_NODE_MAX_NUM (默认 105) 修改为所需要的值。 注意网关和节点要配置成一样的值。
每增加一个节点,对应的RAM开销,如下表:
cache_buf 是用来缓存 sequence number 之类的。要缓存所有节点的 sequence number等。
注意:网关设置MESH_NODE_MAX_NUM超过200后,编译的时候,这里会提示报错:
STATIC_ASSERT(ARRAY_SIZE(gw_node_info) <= (FLASH_ADR_VC_NODE_INFO_END - FLASH_ADR_VC_NODE_INFO)/sizeof(VC_node_info_t)); // make sure enough flash area to save
因为默认的4KB flash 扇区已经不能存储那么多的节点的信息,所以需要用户找一块 连续的 flash 区域来存储。然后对应修改FLASH_ADR_VC_NODE_INFO和FLASH_ADR_VC_NODE_INFO_END即可。
Telink自定义的通过扩展广播包_extend_adv_发送mesh消息的模式
扩展广播包: extend ADV
功能介绍
B85芯片和协议栈支持发送extend ADV, 但是SIG mesh spec目前暂未定义通过extend ADV发送mesh消息。由于在某些场景需要使用extend ADV发送消息,提高发送效率,比如传输压缩图片的数据,和做mesh OTA等。所以我们定义了通过extend ADV发送消息的模式。
该模式指定采用BLE spec定义的extend ADV的其中一种格式,如下图:
并且规定ADV 的payload由原来的最大31 bytes,增加到最大 245 bytes (ADV_EXTEND_PAYLOAD_MAX), 也就是network PDU的长度增加了214bytes (CONST_DELTA_EXTEND_AND_NORMAL), 其他发包逻辑没有变化,包括transmit interval和 transmit count等都保持不变。当发送的access layer,即 (opcode + parameters) 超过(11+214 = 225) bytes时,也会执行segment 分包组包流程。
综上所述,满负荷发送时,发包速度提升到约为原来的225/11 = 20倍。
测试方法
(1) 节点的配置
Firmware SDK的EXTENDED_ADV_ENABLE置为 1
使能 EXTENDED_ADV_ENABLE 后,firmware SDK 默认也是仅对 mesh OTA 命令按 extend ADV 格式发送,比如 FW_UPDATE_START、BLOB_CHUNK_TRANSFER 和 BLOB_BLOCK_STATUS。其他数据包还是保持以前的短包,也就是segment 分包流程,因为要考虑常用命令还能和其他厂家互联互通。详见函数:is_not_use_extend_adv();的处理。 (注意:V3.3.3之前的旧版本是 is_extend_unseg2short_unseg())。
如有需要对所有access layer小于225bytes(含op code)的命令都不分包,在 is_not_use_extend_adv()里面直接return 0,即可。
如果是增加规则:所有的vendor op code都按extend ADV格式发送,那判断 (IS_VENDOR_OP(op)) 返回1的时候,在is_not_use_extend_adv()里面return 0,即可。
(2) Provisioner的配置
- sig_mesh_tool.exe 上位机的配置
需要按以下方式修改 “ExtendAdv”控件的值:
对应的函数处理,请参考 is_not_use_extend_adv() 里面的处理。
a) 选择 None,
b) 选择 OTA only, 此时上位机仅仅将 is_not_use_extend_adv() 里面默认的 FW_UPDATE_START,BLOB_CHUNK_TRANSFER,BLOB_BLOCK_STATUS,BLOB_PARTIAL_BLOCK_REPORT 等几个op用extend ADV发送,目的是仅需要加快mesh OTA速度,其他命令能兼容和不支持extend ADV 的节点之间能相互控制。
c) 选择all,表示上位机将所有的 access layer的长度 (opcode + parameters) 小于225bytes的命令,都用单包的 extend ADV格式发送。
-
手机 App 的配置
请打开扩展长包选项:
setting -- setting -- Extend Bearer Mode 选择 "Extend GATT & ADV"
(3) 注意事项
目前仅B85 和 B91 支持 extend ADV 功能。 其他芯片型号暂不支持。
Soft_timer的应用
Soft timer软件定时器的介绍
(在这里只是介绍如何使用,详细信息可以在B85单连接handbook中有介绍)
为了方便user一些简单的定时器任务,Telink BLE SDK提供了blt software timer demo,并且全部源码提供。user可以在理解了该timer的设计思路后直接使用,也可以自己做一些修改设计。
Soft timer特别适用于在低功耗的应用中增加定时任务,即使在睡眠状态下,也可以定时唤醒,完成定时任务。 当然,在非低功耗的应用中同样可以使用 soft timer 做定时任务。
源代码全部在 vendor/common/blt_soft_timer.c 和 blt_soft_timer.h 文件中,若需要使用,先将下面宏改为1:
#define BLT_SOFTWARE_TIMER_ENABLE 0 //enable or disable
blt soft timer 是基于 system tick 设计的查询式 timer,其准确度无法达到硬件 timer 那么准,且需要保证在main_loop 中一直被查询。
我们预定:blt soft timer 的使用场景为定时时间大于 5ms、且对于时间误差要求不是特别高的情况。blt soft timer 的最大特点是不仅在 main_loop 中会被查询,也能确保在进入 suspend 后能够及时唤醒并执行 timer 的任务,该设计是基于低功耗唤醒部分介绍的“应用层定时唤醒”实现的。目前设计上最多同时支持4个timer运行,实际user可以修改下面的宏来实现更多的或者更少的 timer:
#define MAX_TIMER_NUM 4 //timer max number
Soft timer 初始化
调用下面的 API 进行初始化:
void blt_soft_timer_init(void):
可以看到源码上初始化只是将 blt_soft_timer_process 注册为应用层提前唤醒的回调函数。
void blt_soft_timer_init(void){
bls_pm_registerAppWakeupLowPowerCb(blt_soft_timer_process);
}
Soft timer的查询处理
blt soft timer的查询处理使⽤ blt_soft_timer_process 函数来实现:
void blt_soft_timer_process(int type):
blt_soft_timer_process 的参数中type有如下两种情况:0 表示在 main_loop 中查询进入,1 表示发生了timer提前唤醒时进入该函数。
#define MAIN_LOOP_ENTRY 0
#define CALLBACK_ENTRY 1
Soft timer任务配置
如果用户想要使用定时器实现某些功能,可以使用如下API添加定时器任务,使用方式为():
(1) 定义自己的soft timer函数:这个函数的功能是周期发送ALL ON命令(只是一个例子)
int soft_timer_switch_send_all_on(void)
{
access_cmd_onoff(ADR_ALL_NODES, 0, G_ON, CMD_NO_ACK, 0);
LOG_USER_MSG_INFO(0, 0, "%s", __func__);
return 0;
}
(2) 添加定时器任务
使用如下API添加
int blt_soft_timer_add(blt_timer_callback_t func, u32 interval_us):
func 为定期执行的任务函数;interval_us 为定时时间,单位为 us。
定时任务 func 的 int 返回值三种处理方法为:
返回值小于 0,则该任务执行后被自动删除。可以使用这个特性来控制定时器执行的次数。
返回 0,则一直使用之前的 interval_us 来定时。
返回值大于 0,则使用该返回值做为新的定时周期,单位 us。
Soft timer任务删除
除了使用上面返回值小于 0 来自动删除定时器任务,还可以使用下面 API 来指定要删除的定时器任务。
int blt_soft_timer_delete(blt_timer_callback_t func):
Soft_timer周期发送命令示例
以下示例实现基于 8258_mesh_switch 工程。
(1) 打开 BLT_SOFTWARE_TIMER_ENABLE。
(2) 在 执行blt_soft_timer_init();之后添加 blt_soft_timer_add()即可。示例代码如下:
以下代码第一次按按键 RC_KEY_R 开启了一个soft timer任务,这个任务是每500ms调用一次soft_timer_switch_send_all_on() 来发送一次命令。时间没到的时候,节点处于休眠状态。
再次按按键RC_KEY_R则关闭这个任务,停止发送命令。
void mesh_proc_keyboard ()
{
......
else if (kb_event.keycode[0] == RC_KEY_R){// will enter here once when RC_KEY_R is pressed and release.
static u32 press_cnt = 0;
press_cnt++;
if(press_cnt & 0x01){
blt_soft_timer_add(soft_timer_switch_send_all_on, 500 * 1000); //
}else{
blt_soft_timer_delete(soft_timer_switch_send_all_on);
}
}
}
soft_timer_switch_send_all_on() 返回值的定义请 参考 blt_soft_timer_add 函数的 参数“func” 的介绍。
长休眠接口的使用
230秒内的睡眠,都不建议使用长休眠接口。因为唤醒后的计时方法需要修改,并且计时精度也会降低一点。另外不应使用长休眠的 SUSPEND_MODE 模式,因为功耗偏高。
使用长休眠接口,可以设置睡眠时间最大为 37 小时。
函数名
/**
* @brief This function servers to wake up the cpu from sleep mode.
* @param[in] sleep_mode - sleep mode type select.
* @param[in] wakeup_src - wake up source select.
* @param[in] wakeup_tick - the time of sleep.unit is 31.25us,1ms = 32.
* @return indicate whether the cpu is wake up successful.
*/
int cpu_long_sleep_wakeup(SleepMode_TypeDef sleep_mode, SleepWakeupSrc_TypeDef wakeup_src, unsigned int wakeup_tick);
使用方法
- 打开 MESH_LONG_SLEEP_WAKEUP_EN
- 如果有需要 deep retetion模式,则需要打开 PM_DEEPSLEEP_RETENTION_ENABLE。
- 调用 cpu_long_sleep_wakeup
测试举例:
1) 在main loop中调用cpu_long_sleep_wakeup(SUSPEND_MODE, PM_WAKEUP_TIMER, 40 * 32 * 1000); 表示设置睡眠20s后唤醒,在运行长睡眠函数之前添加log记录当前时间
LOG_USER_MSG_INFO(0, 0, "system_time_s: %d, system_time_100ms: %d, system_time_ms: %d!", system_time_s, system_time_100ms, system_time_ms);
cpu_long_sleep_wakeup(DEEPSLEEP_MODE_RET_SRAM_LOW32K, PM_WAKEUP_TIMER, 40 * 32 * 1000);
2) 观察log打印的时间结果与睡眠时间是否相同
3) 观察system_time_100ms,system_time_s 是否准确。
4) 修改步骤 1 的 40秒改为 600s,重复测试步骤 2,3. 亦符合预期。
唤醒源识别接口介绍
唤醒源识别接口可以获取到当前的唤醒源,唤醒源总共有四个
enum{
CPU_POWER_RESET, // 上电复位唤醒
CPU_WATCHDOG_RESET, // 看门狗复位唤醒
CPU_PAD_WAKEUP, // 按键唤醒
CPU_TIMER_WAKEUP, // 定时唤醒
};
api 函数名
/**
* @brief This function server to get cpu wakeup source
* @return CPU_WATCHDOG_RESET: watchdog reset.
* CPU_PAD_WAKEUP: gpio wakeup.
* CPU_TIMER_WAKEUP: timer wakeup.
* CPU_POWER_RESET: power reset.
* @note function called must be after "cpu_wakeup init()" and before wakeup io setting(if exist).
*/
int get_cpu_wakeup_source()
{
if(read_reg8(0x72) & BIT(0)){
write_reg8(0x72, BIT(0)); // manual clear watchdog reset flag after read.
return CPU_WATCHDOG_RESET;
}
u8 val = analog_read(0x44);
if((val & WAKEUP_STATUS_TIMER_PAD ) == WAKEUP_STATUS_PAD){
return CPU_PAD_WAKEUP;
}
else if((val & WAKEUP_STATUS_TIMER_CORE ) == WAKEUP_STATUS_TIMER_CORE){
return CPU_TIMER_WAKEUP;
}
return CPU_POWER_RESET;
}
使用方法
1) 在需要获取唤醒源的地方调用int get_cpu_wakeup_source(),根据返回值得到唤醒源
2) 在调用唤醒源测试接口后,要将数字寄存器0x72的bit(0)清零,否则下次调用该函数会默认识别唤醒源为看门狗唤醒
注意:
唤醒源识别接口要在cpu_wakeup init()之后才能调用,因为cpu_wakeup init()执行之前,MCU还无法执行analog_read()读取模拟寄存器。
按键扫描
Demo sdk 打开 UI_KEYBOARD_ENABLE 即可使能按键扫描功能,检测矩阵键盘按键 或者 按钮(Button)的输入。
键盘检测详细说明详见 该 文档 《AN-21112301-C_Telink B85m BLE Single Connection SDK Developer Handbook.pdf》的 “按键扫描” 章节。下载链接是:
中文版:
AN-21112301-C_Telink B85m BLE Single Connection SDK Developer Handbook.pdf
英文版:
AN-21112300-E_Telink B85m BLE Single Connection SDK Developer Handbook.pdf
其中,SDK已经默认给常用的 8258 dongle, 8258 开发板 和 8258 Switch PCBA 做了适配。如果客户重做 PCBA, 主要关注以下几个方面即可:
矩阵键盘模式
(1) KB_LINE_MODE 设置为 0
(2) KB_LINE_HIGH_VALID 有效电平设置, 0 表示 低有效,1 表示 高有效
(3) KB_DRIVE_PINS, KB_SCAN_PINS 按新的 PCBA 修改。
(4) 配置 KB_DRIVE_PINS, KB_SCAN_PINS 对应的 GPIO 的 FUNCTION, INPUT, 上下拉属性。
(5) 如果有需要修改 KEY map,修改 KB_MAP_NORMAL即可。
Button模式
(1) KB_LINE_MODE 设置为 1
(2) KB_LINE_HIGH_VALID 有效电平设置, 0 表示 低有效,1 表示 高有效
(3) KB_DRIVE_PINS 没有实际意义,设置为 0 或者 scan pin 的第一个GPIO即可, KB_SCAN_PINS 按新的 PCBA 修改。
(4) 配置 KB_SCAN_PINS 对应的 GPIO 的 FUNCTION, INPUT, 上下拉属性。
(5) 如果有需要修改 KEY map,修改 KB_MAP_NORMAL即可。
Vendor model的使用
添加vendor model说明
一般来说没必要增加model,目前的SIG model已经做好,vendor model可以直接使用已经添加的vendor model:VENDOR_MD_LIGHT_C, VENDOR_MD_LIGHT_S,添加op code(即下一章的vendor) 即可。
除非是要在现有的vendor model 上publish 多个status,才有需要考虑增加model。目前不建议添加。如果需要添加,请参考目前的结构添加model,比如参考MD_SCENE_EN的宏括起来的相关代码,或者联系我们。
添加vendor命令的注册说明
vendor_opcode介绍
本文描述的Command 与opcode是相同概念,op code(1BYTE) + vendor id(2BYTE)。
Vendor model的op code,总共只有64个。注意,不是每一个产品类型各有64个,而是整个网络同一个vendor id的所有产品总共只有64个。当MESH_USER_DEFINE_MODE选择MESH_NORMAL_MODE时,telink需要预留使用32个,即0xC0 --- 0xDF,客户使用从0xE0 --- 0xFF。所以请注意节省使用,建议做子命令的方式。如果确实使用超过32个,在这种情况下,telink某些自定义的功能可能就无法打开,所以请联系我们。
另外vendor 命令的参数区,最大是377 byte,但是大于8 byte,SIG mesh底层就会自动采用分包的方式发送,效率会降低。所以,如果是经常使用的控制命令,不建议大于8 byte。
添加vendor opcode步骤的介绍
VENDOR_OP_MODE_SEL 设置为默认的 VENDOR_OP_MODE_DEFAULT。
为了方便用户快速添加vendor opcode,用户可以在不新增opcode的情况下,直接使用VENDOR_OP_USER_DEMO_EN括起来的opcode demo,用户可以直接使用这四个opcode,只需要更改对应的回调函数cb_vd_user_demo_set(), cb_vd_user_demo_get()为预期的功能,就可以快速使用。另外,用户新增vendor opcode也可以参考VENDOR_OP_USER_DEMO_EN括起来的opcode demo,添加更多的 vendor op code。
#if (VENDOR_OP_USER_DEMO_EN)
CMD_NO_STR(VD_MESH_USER_DEMO_SET, 0, VENDOR_MD_LIGHT_C, VENDOR_MD_LIGHT_S, cb_vd_user_demo_set, VD_MESH_USER_DEMO_STATUS),
CMD_NO_STR(VD_MESH_USER_DEMO_GET, 0, VENDOR_MD_LIGHT_C, VENDOR_MD_LIGHT_S, cb_vd_user_demo_get, VD_MESH_USER_DEMO_STATUS),
CMD_NO_STR(VD_MESH_USER_DEMO_SET_NOACK, 0, VENDOR_MD_LIGHT_C, VENDOR_MD_LIGHT_S, cb_vd_user_demo_set, STATUS_NONE),
CMD_NO_STR(VD_MESH_USER_DEMO_STATUS, 1, VENDOR_MD_LIGHT_S, VENDOR_MD_LIGHT_C, cb_vd_user_demo_status, STATUS_NONE),
#endif
另外,如果有数据需要存储,在 cb_vd_user_demo_set() 里面 调用mesh_common_store() 即可。
mesh_common_store(FLASH_ADR_MD_VD_LIGHT);
即可把 model_vd_light_t 里面增加的参数 sno_vd_user_demo 存储到 flash 的这个扇区 FLASH_ADR_MD_VD_LIGHT。重新上电后,sdk 已经实现了 通过 mesh_flash_retrieve() 读取该 扇区的内容到 对应的 全局变量。需要留意的是,OTA 前后 model_vd_light_t 里面的结构和大小不能有变化,否则OTA后,数据会读取异常。
本文档以VD_GROUP_G_SET / VD_GROUP_G_GET / VD_GROUP_G_SET_NOACK / VD_GROUP_G_STATUS 这一组命令为例介绍如下:
(1) 添加vendor opcode的定义
// op cmd 11xxxxxx yyyyyyyy yyyyyyyy (vendor)
// ---------------------------------from 0xC0 to 0xFF
#if (VENDOR_OP_MODE_SEL == VENDOR_OP_MODE_SPIRIT)
......
#elif(VENDOR_OP_MODE_SEL == VENDOR_OP_MODE_DEFAULT)
// ------ 0xC0 to 0xDF for telink used
......
#define VD_GROUP_G_GET 0xC1
#define VD_GROUP_G_SET 0xC2
#define VD_GROUP_G_SET_NOACK 0xC3
#define VD_GROUP_G_STATUS 0xC4
......
#endif
(2) 添加vendor opcode的注册
Vendor model的注册参考代码vendor_model.c的mesh_cmd_sig_func_t const mesh_cmd_vd_func[] = {… …}的实现:
const mesh_cmd_sig_func_t mesh_cmd_vd_func[] = {
#if (VENDOR_OP_MODE_SEL == VENDOR_OP_MODE_SPIRIT)
......
#elif(VENDOR_OP_MODE_SEL == VENDOR_OP_MODE_DEFAULT)
......
CMD_NO_STR(VD_GROUP_G_SET, 0, VENDOR_MD_LIGHT_C, VENDOR_MD_LIGHT_S, cb_vd_group_g_set, VD_GROUP_G_STATUS),
CMD_NO_STR(VD_GROUP_G_GET, 0, VENDOR_MD_LIGHT_C, VENDOR_MD_LIGHT_S, cb_vd_group_g_get, VD_GROUP_G_STATUS),
CMD_NO_STR(VD_GROUP_G_SET_NOACK, 0, VENDOR_MD_LIGHT_C, VENDOR_MD_LIGHT_S, cb_vd_group_g_set, STATUS_NONE),
CMD_NO_STR(VD_GROUP_G_STATUS, 1, VENDOR_MD_LIGHT_S, VENDOR_MD_LIGHT_C, cb_vd_group_g_status, STATUS_NONE),
#endif
......
};
注意:
mesh_cmd_vd_func[]是const类型的数组,所以这个数组是只读, 不可改写, 可以节省RAM的空间。
(3) mesh_cmd_sig_func_t 的介绍
typedef struct{
u16 op;
u16 status_cmd;
u32 model_id_tx;
u32 model_id_rx;
cb_cmd_sig2_t cb;
u32 op_rsp;
}mesh_cmd_sig_func_t;
-
op:新增命令的opcode,不管是SIG还是vendor命令,都统一用u16表示,其中vendor命令不需要填写vendor id这两个byte,library底层会自动添加。
-
status_cmd:如果该opcode是对应某个acknowledge request command的status command,比如VD_LIGHT_ONOFF_STATUS,则该值写1;否则写0。当model_id_rx 是client model的时候,status_cmd都会是1。这个status_cmd flag在Library会使用到。
-
model_id_tx:发送该命令对应的model ID。比如进行publish status的时候,需要根据op查表mesh_cmd_vd_func[],得到这个model_id_tx,然后通过mesh_find_ele_resource_in_model()获取到model对应的全局变量资源,比如model_sig_g_onoff_level.onoff_srv,然后再进一步获取这个model里面的model_sig_g_onoff_level.onoff_srv -> com. pub_adr以及其它publish参数,然后才能执行publish status操作。
-
model_id_rx:接收该命令对应的model ID,如果该node的composition data没有对应的model id,则不处理这个opcode。
当支持这个model的时候,还需要根据这个model里面的参数判断 是否已经绑定了对应的app key,当destination address是group address的时候,是否有订阅对应的group等。
-
cb:收到该命令时,调用的回调处理函数mesh_rc_data_layer_access_cb() -> p_res -> cb(),客户在这个回调函数里处理自己的应用即可。
-
op_rsp:如果该opcode是request command,需要status回复,则该处写对应的status op code,否则写STATUS_NONE。发送request command的一端,当发送命令后,用来判断是否收到对应的 status response。
(4) 添加命令回调
添加VD_GROUP_G_SET命令的回调函数
(5) 添加TID的注册
一般情况下,不需要添加 TID。TID 的用途介绍,请参考此章节:“增加acknowledge-command的示例”。
如果添加的命令码需要TID字段(),则还需要另外在is_cmd_with_tid_vendor()里面注册,详见“增加acknowledge-command的示例”的举例说明部分。
(6) 增加acknowledge-command的示例
acknowledge-command 即 request command with status response。
以VD_GROUP_G_SET为例:
1) 在mesh_cmd_vd_func[]中添加:
(VD_GROUP_G_SET, 0, VENDOR_MD_LIGHT_C, VENDOR_MD_LIGHT_S, cb_vd_group_g_set, VD_GROUP_G_STATUS)
2) 该命令需要TID字段(transmit ID),所以需要在is_cmd_with_tid_vendor()中添加对应的分支,以及标识TID字段在access payload中对应的位置。
在library里面会有一个统一管理TID的全局变量mesh_tid,当发送命令的时候,会自动加一,并拷贝到命令参数区的TID字段。使用举例请参考[Vendor model 命令格式]。
TID用途:如果在一定时间内(目前默认是6秒),收到重复的TID,则不执行对应的动作,但是response会回复。这个识别的动作在library完成,上层应用直接判断cb_par -> retransaction这个flag即可。
TID一般是控制灯的状态的命令,才会使用。因为TID,是为了避免在一定的时间内收到retry的时候,会重复执行,导致渐变时间不对,比如当节点收到OFF命令,要求延时1秒钟到达OFF状态,当渐变执行到0.8秒的时候,收到了retry命令,如果执行这个命令的话,渐变时间需要重新计时1秒,这样看到的现象是1.8秒后节点才能处于OFF的状态。另外也可能会导致灯的闪烁,比如,同一时间有两个app对同一个节点进行控制,其中一个执行generic on,另外一个执行generic off,并且有执行retry动作(message的sequence number不相同,但是TID相同),这个时候,我们预期是,light只执行一次on和一次off,最终状态是on还是off由最后收到的命令决定。如果有TID的识别的话,可以实现预期的需求。但是如果没有TID的识别,灯可能会执行了几次onoff动作,这样就会有闪烁的问题。
SIG的标准命令里面也只有类似onoff set,level set 、lightness set,CT set等命令的时候,才会使用TID。
像一些lightness get等命令,以及config model 里面的config set/get命令都是不需要使用的。
对于vendor命令,没必要添加的时候,尽量不要添加,因为本来有效字节就比较少,没必要再浪费一个byte。
3) 编写cb_vd_group_g_set()函数,调用light_onoff_idx()执行开关灯动作。
4) 因为该命令是需要ack回复的命令,所以编写对应的ack函数vd_light_tx_cmd_onoff_st(),该函数里面调用mesh_tx_cmd(VD_GROUP_G_STATUS,……)来进行ack回复。
注意:
- 收到命令后回复status,需要调用 mesh_tx_cmd_rsp()来进行回复,而不是调用mesh_tx_cmd2normal(),因为在多个network key,app key的网络中,收包的时候,用什么key进行解密,回复status的时候就必须用对应的key进行加密。所以使用mesh_tx_cmd_rsp()。
- mesh_tx_cmd2normal_primary()是默认都统一用第一个key进行发送,一般用于主动发送命令,所以回复status的时候,不能直接使用这个函数。
5) 封装发送VD_GROUP_G_SET命令的接口vd_cmd_onoff();
rsp_max表示需要回复的节点的个数。设置方法:
- 当adr_dst为unicast时,该值设为1或0都可以(建议设置为1);
- 当adr_dst为group时,根据APP数据库中的记录,设置为group拥有的element个数。
增加Unacknowledge command的示例
以VD_GROUP_G_SET_NOACK为例:
1) 在mesh_cmd_vd_func[]中添加:
(VD_GROUP_G_SET_NOACK, 0, VENDOR_MD_LIGHT_C, VENDOR_MD_LIGHT_S, cb_vd_group_g_set, STATUS_NONE)
2) 该命令需要TID字段(transmit ID),参考acknowledge command的添加方法。
3) 编写cb_vd_group_g_set()函数(和VD_GROUP_G_SET共用),参考acknowledge command的添加方法。
4) 该命令不需要ack回复。
5) 封装发送VD_GROUP_G_SET_NOACK命令的接口vd_cmd_onoff();
参考acknowledge command的添加方法。
Publish函数注册
一般情况下不需要 vendor model的publish 功能。
如果需要,可通过CFG_MODEL_PUB_SET命令设置light节点的model的publish参数,该model就有了publish功能,当model status变化的时候会自动publish一个status message到publish参数配置的publish address,另外publish 参数还可以配置周期发送参数等,详见spec关于publish命令的参数定义[4.3.2.16 Config Model Publication Set]。
为了实现上述自动publish的功能,需要为该model注册发送status message的publish函数,如下图:
当状态变化,或者publish周期时间到之后,mesh stack会回调该函数发送status message。
添加vendor opcode子命令
“vendor_opcode_介绍” 介绍了,Vendor 子命令的用途。客户可根据需要,选着是否使用子命令的形式。以下是 vendor opcode 子命令的使用介绍。
Vendor子命令区间
Vendor子命令的 sub opcode占一个byte,总共只有 256 个值。0x00 to 0x7f 预留给 Telink,客户的区间是 0x80 to 0xff,详见 sdk 的 vd_group_g_func[] 的注释。
添加vendor子命令步骤的介绍
为了方便用户快速添加vendor 子命令,用户可以在不新增vendor子命令的情况下,直接使用VENDOR_SUB_OP_USER_DEMO_EN括起来的demo,用户可以直接使用这个子命令,只需要更改对应的回调函数vd_rx_group_g_sub_op_user_demo_set(),vd_rx_group_g_sub_op_user_demo_st() 就可以快速使用。另外,用户新增子命令也可以参考VENDOR_SUB_OP_USER_DEMO_EN括起来的demo增加更多的子命令。
#if VENDOR_SUB_OP_USER_DEMO_EN
{VD_GROUP_G_SUB_OP_USER_DEMO, vd_rx_group_g_sub_op_user_demo_set, vd_rx_group_g_sub_op_user_demo_st}
#endif
另外,如果有数据需要存储,在 vd_rx_group_g_sub_op_user_demo_set() 里面 调用
mesh_common_store(FLASH_ADR_MD_VD_LIGHT);
即可把model_vd_light_t里面增加的参数 sno_vd_sno_sub_op_user_demouser_demo 存储到 flash 的这个扇区 FLASH_ADR_MD_VD_LIGHT。重新上电后,sdk 已经实现了 通过 mesh_flash_retrieve() 读取该 扇区的内容到 对应的 全局变量。需要留意的是,OTA 前后 model_vd_light_t 里面的结构和大小不能有变化,否则OTA后,数据会读取异常。
以下介绍给 VD_GROUP_G_SET / VD_GROUP_G_GET / VD_GROUP_G_SET_NOACK / VD_GROUP_G_STATUS 这一组命令增加子命令的示例。
(1) 添加vendor子命令的定义
Demo SDK 把 vendor 的 on 和 off 分开定义为两个,主要是为了兼容以前旧版本的写法。 如果客户添加此类命令的话,只需要添加 一个 子命令就可以,然后使用参数区的另外一个字节来表示 on 或者 off 就可以了。所以以下只介绍 VD_GROUP_G_ON。
enum{/*vendor generic group, op code include C1-C4*/
......
VD_GROUP_G_ON = 1, // compatible with legacy
......
};
(2) 添加vendor子命令的注册
Vendor子命令的注册参考代码 vendor_model.c 的 vd_group_g_func_t vd_group_g_func = {......} 的实现:
vd_group_g_func_t vd_group_g_func[] = {
/* telink use sub op from 0x00 to 0x7f*/
......
{VD_GROUP_G_ON, vd_group_g_light_onoff, vd_light_tx_cmd_onoff_st},
......
};
注意:
vd_group_g_func是 const 类型的数组,所以这个数组是只读, 不可改写. 可以节省 RAM 的空间.另外,如果添加的命令码需要 TID 字段,则还需要另外在 is_cmd_with_tid_vendor() ⾥⾯注册,详⻅“增加acknowledge-command”的举例说明部分。
(3) vd_group_g_func_t介绍
typedef struct{
u32 sub_op;
cb_vd_group_g_sub_set cb_set;
cb_vd_group_g_sub_tx_st cb_tx_st;
//cb_vd_group_g_sub_rx_status cb_rx_status; // TBD, only client may use.
}vd_group_g_func_t;
- sub_op: 子命令
- cb_set:设置收到该命令调用的处理函数
- cb_tx_st:设置需要回复的发送函数
- cb_vd_group_g_sub_rx_status:网关设备收到该子命令的status命令的处理函数,目前暂不启用。
(4) 添加子命令回调函数
添加VD_GROUP_G_ON子命令的回调函数
int vd_group_g_light_onoff(u8 *par, int par_len, mesh_cb_fun_par_t *cb_par)
{
int pub_flag = 0;
vd_light_onoff_set_t *p_set = (vd_light_onoff_set_t *)par;
int light_idx = cb_par->model_idx;
int on = !!p_set->sub_op; // make sure bool
light_onoff_all(on);
if(vd_onoff_state[light_idx] != on){
vd_onoff_state[light_idx] = on;
pub_flag = 1;
}else{
}
return pub_flag;
}
增加acknowledge类型的子命令
acknowledge-command 即 request command with status response,表示该命令需要有status回复。
以 VD_GROUP_G_ON 为例
子命令需要回复需要满足两个条件:大的命令需要是 VD_GROUP_G_SET 或者 VD_GROUP_G_GET,并且vd_group_g_func[] 里面的 成员变量 cb_tx_st 不为 NULL。
1) 在vd_group_g_func[]中对应位置添加 vd_light_tx_cmd_onoff_st:
vd_group_g_func_t vd_group_g_func[] = {
/* telink use sub op from 0x00 to 0x7f*/
......
{VD_GROUP_G_ON, vd_group_g_light_onoff, vd_light_tx_cmd_onoff_st},
......
};
2) 编写 vd_light_tx_cmd_onoff_st 函数
int vd_light_tx_cmd_onoff_st(u8 light_idx, u8 sub_op, u16 ele_adr, u16 dst_adr, u8 *uuid, model_common_t *pub_md)
{
vd_light_onoff_st_t rsp;
rsp.sub_op = !!vd_onoff_state[light_idx];
return mesh_tx_cmd_rsp(VD_GROUP_G_STATUS, (u8 *)&rsp, sizeof(rsp), ele_adr, dst_adr, uuid, pub_md);
}
增加Unacknowledge类型的子命令
以VD_GROUP_G_ON 为例
子命令不需要回复,满足以下条件中的一个即可:大的命令使用 VD_GROUP_G_SET_NOACK 或者 vd_group_g_func[] 里面的 成员变量 cb_tx_st 为 NULL。
编写发送 VD_GROUP_G_ON 命令的API
vd_cmd_onoff()
增加vendor子命令空示例
Vendor子命令空示例即回调函数为空的示例。因此增加 vendor 子命令空示例用户只需要执行两个步骤即可。
1) 增加vendor子命令定义
enum{/*vendor generic group, op code include C1-C4*/
......
VD_GROUP_LOOP_ON = 1, // compatible with legacy
......
};
2) 增加 vendor 子命令的注册
Vendor子命令的注册参考代码 vendor_model.c 的 vd_group_g_func_t vd_group_g_func = {......} 的实现:
vd_group_g_func_t vd_group_g_func[] = {
/* telink use sub op from 0x00 to 0x7f*/
......
{VD_GROUP_LOOP_ON, NULL, NULL},
......
};
全局配置文件说明
mesh_config.h
PROXY_HCI_SEL:
开发调试用,开发者不需要关注,默认选择PROXY_HCI_GATT即可。
DEBUG_VENDOR_CMD_EN:
开关vendor model的debug命令。默认打开。
FAST_PROVISION_ENABLE:
Fast provision是私有模式,可对多节点同时进行组网,执行快速批量以及支持relay组网。默认不打开。
MESH_USER_DEFINE_MODE:
定义provision时的认证模式, MESH_NORMAL_MODE为no OOB模式,其他为static OOB模式,详细参考平台接入章节的介绍。
SUBSCRIPTION_BOUND_STATE_SHARE_EN:
目的是在收到对onoff model设定组号的命令后,固件端自动对sub_share_model_sig[]和sub_share_model_vendor[]里面列出来的model也添加该组号。因为有状态绑定关系的model 之间是要共享组号信息的, 比如 Onoff model 和 lightness model。
另外,私有模式时,没有状态绑定关系的model 之间也可以配置为要共享组号信息,在 sub_share_model_sig[]和sub_share_model_vendor[] 里面添加对应的组号即可。
PROVISION_FLOW_SIMPLE_EN:
和标准组网流程一样,也是依次对每个节点进行配网,即同一时间只有一个节点在配网。只是在node收到app key add后,自动对每一个model都执行key bind的动作。Provisioner端不再需要发送key bind命令。简化组网流程,减少组网时间。
AIS_ENABLE / MI_API_ENABLE:
请参考本文件平台接入章节。
LIGHT_TYPE_SEL:
选择灯的类型,目前几种类型的灯是互斥的关系。请参考1.3 LIGHT_TYPE_SEL介绍章节。
以下控制model开关的宏,比如MD_LIGHTNESS_EN,使能时打开的是client还是server model,或者是两者都打开,取决于MD_SERVER_EN,MD_CLIENT_EN,以及MD_CLIENT_VENDOR_EN这3个开关宏。具体规则详见本章节这三个宏的介绍。
LIGHT_TYPE_CT_EN:
色温灯相关model的开关,包含Light CTL Server,Light CTL Setup Server,Light CTL Temperature Server,Light CTL Client。
LIGHT_TYPE_HSL_EN:
彩色灯(HSL)相关model的开关,包含Light HSL Server,Light HSL Hue Server,Light HSL Saturation Server,Light HSL Setup Server,Light HSL Client。
MD_LIGHT_CONTROL_EN:
Lighting Control相关model的开关,默认关闭。包含Light LC Server,Light LC Setup Server,Light LC Client。
MD_LIGHTNESS_EN:
Lightness相关model的开关,包含Light Lightness Server,Light Lightness Setup Server,Light Lightness Client。
MD_LEVEL_EN:
Level相关model的开关。每一个状态量都可以对应有一个level model。
MD_MESH_OTA_EN:
Mesh OTA相关model的开关,默认关闭。
MD_ONOFF_EN:
Generic OnOff相关model的开关,默认开启。
MD_DEF_TRANSIT_TIME_EN:
Generic Default Transition Time相关model的开关,默认支持,
MD_POWER_ONOFF_EN:
Generic Power OnOff相关model的开关,默认支持,和MD_DEF_TRANSIT_TIME_EN同时关闭或者打开,因为这两个model的参数存在同一个flash扇区。
MD_TIME_EN:
Time Model相关model的开关,默认关闭。
MD_SCENE_EN:
Scene Model相关model的开关,默认关闭。
MD_SCHEDULE_EN:
Schedule相关model的开关,默认关闭,和MD_TIME_EN同时关闭或者打开,因为schedule 依赖于time。
MD_PROPERTY_EN:
Property相关model的开关,包含Generic User Property Server,Generic Admin Property Server,Generic Manufacturer Property Server,Generic Client Property Server,Generic Property Client。
MD_LOCATION_EN:
Location相关model的开关,包含Generic Location Server,Generic Location Setup Server,Generic Location Client。
MD_SENSOR_EN:
Sensor相关model的开关,包含Sensor Server,Sensor Setup Server,Sensor Client。
MD_BATTERY_EN:
Battery相关model的开关,包含Generic Battery Server,Generic Battery Client。
MD_SERVER_EN:
等于1时,打开上述介绍的处于使能状态的server model包含SIG和vendor的,比如lightness server和 VENDOR_MD_LIGHT_S。
MD_REMOTE_PROV:
Remote provision相关model的开关,默认关闭。
MD_CLIENT_EN:
等于1时,打开上述介绍的处于使能状态的client SIG model。比如 lightness client。
MD_CLIENT_VENDOR_EN:
等于1时,打开client vendor model:VENDOR_MD_LIGHT_C。
MD_VENDOR_2ND_EN:
等于1时,打开第二个vendor server model。VENDOR_MD_LIGHT_S2。一般vendor model只要一个就够了。
注意:
Client model的开关。对于节点一般是不需要client model,所以为了节省Ram,灯端默认关闭。Client model由MD_CLIENT_EN,MD_CLIENT_VENDOR_EN这两个宏开关分别控制,是因为有些灯节点需要打开vendor的client model。但是SIG的client model不需要打开。
FACTORY_TEST_MODE_ENABLE:
产测模式,默认开启。为方便产线测试,未provision情况下可以对节点使用默认key进行开关,调亮度等简单操作。
MANUAL_FACTORY_RESET_TX_STATUS_EN:
通过5次上电的复位操作执行后,是否发送NODE_RESET_STATUS指令通知gateway,或者app。
KEEP_ONOFF_STATE_AFTER_OTA:
该宏开关用于决定OTA重启后,是否要保持重启前的灯的onoff状态。
ELE_CNT_EVERY_LIGHT:
每一个light的element个数。比如一个色温灯需要两个element,大部分的model都会放在第一个element上,只有Light CTL Temperature Server以及对应的level model存在于第二个element上。
需要两个element的原因是因为lightness和CTL Temperature都可以通过level model相关的命令来控制,如果只有一个element address,在接收到 level set命令后,就没办法知道是要控制 lightness还是CTL Temperature。
要注意和LIGHT_CNT,ELE_CNT的区别。
LIGHT_CNT是说一个蓝牙模组上同时存在几个相同的灯珠,比如两个色温灯。
ELE_CNT = ELE_CNT_EVERY_LIGHT * LIGHT_CNT,表示该node的总element个数。在配网时,占用的element address个数也等于ELE_CNT。
FEATURE_FRIEND_EN:
设置是否支持Friend feature。
FEATURE_LOWPOWER_EN:
设置是否支持Low Power feature。
FEATURE_PROV_EN:
Provision开关,需打开。
FEATURE_RELAY_EN:
设置是否支持Relay feature。
FEATURE_PROXY_EN:
设置是否支持Proxy feature。
MAX_LPN_NUM:
设置一个friend节点最大能支持几个low power节点,目前为2,如需修改,建议小于10(我们测试的最大数量是10)。不建议改太大,主要是考虑LPN节点太多时,friend给多个LPN回复response时,发包窗口可能产生冲突的概率增加,导致回复有延时,LPN节点功耗会增加。另外RAM的开销也会增加。
USER_DEFINE_SET_CCC_ENABLE:
必须打开。用于给APP 控制slave 节点是否上报notify或indication。
SEND_STATUS_WHEN_POWER_ON:
设置上电时是否发亮度状态message,默认发送地址是0xffff。
mesh_node.h
SUB_LIST_MAX:
每个model最多支持的订阅地址个数(即组号个数)。V3.3.0之前的版本(不含),用户不可修改,因为在library中有使用该宏,之后的版本可以修改。当修改后的个数大于8,为了节省 RAM 和 flash参数存储区,默认关闭了虚拟地址的订阅,即VIRTUAL_ADDR_ENABLE等于0。一般来说,也用不到 虚拟地址,如果需要打开,把VIRTUAL_ADDR_ENABLE设置为1即可。
BIND_KEY_MAX:
每个model能bind key的个数的最大值。目前用户不能修改,因为在library中有使用该宏。
SCENE_CNT_MAX:
能配置的最大的scene数量。用户可以修改。
app_mesh.h
宏设定介绍
TRANSMIT_CNT_DEF:
设置message默认的 transmit cnt,即每个命令的重传次数,
次数 = TRANSMIT_CNT_DEF + 1.
TRANSMIT_INVL_STEPS_DEF:
设置message默认的 transmit interval,即重传的间隔。
重传的间隔 = (TRANSMIT_INVL_STEPS_DEF + 1) * 10ms + (0--10) ms.
TRANSMIT_CNT_LPN_ACCESS_CMD:
对于LPN节点,control 命令的的transmit cnt 由TRANSMIT_CNT_DEF定义,比如friend request,friend poll等,对于其他message则由TRANSMIT_CNT_LPN_ACCESS_CMD定义,比如onoff status.
TRANSMIT_CNT_DEF_RELAY,TRANSMIT_INVL_STEPS_DEF_RELAY:
Relay的重传次数和间隔。
MESH_ADV_CMD_BUF_CNT:
设置发送message的buffer size,不包含relay message。
MESH_ADV_BUF_RELAY_CNT:
设置relay message的buffer size。
SEC_NW_BC_INV_DEF_100MS:
设置组网成功后,发送security beacon的interval,单位100ms。
函数介绍
mesh_tx_cmd(material_tx_cmd_t *p)
通用的发送命令的函数
1) 参数介绍:
type typedef struct{
union{ //指向参数地址
u8 *par;
u8 *p_ac;
};
union{ //参数的长度
u32 par_len;
u32 len_ac;
};
u16 adr_src; //源地址
u16 adr_dst;//目的地址
u8* uuid; //指向虚拟地址
model_common_t *pub_md; // 指向model参数
u32 rsp_max; //需要回复的节点的个数
u16 op; // 命令码
u16 nk_array_idx; // network_key index
u16 ak_array_idx; // app_key index
u8 retry_cnt; // retry 次数
}material_tx_cmd_t;
参数值由调用它的函数mesh_tx_cmd2normal_primary (u16 op, u8 *par, u32 par_len, u16 adr_dst, int rsp_max)带的参数决定。
2) 返回值
返回值返回0表示命令执行成功;
返回非0表示发送失败,比如当前还在发送命令,不能接受新的命令(即busy状态),以及参数非法等。
int mesh_tx_cmd_primary(u16 op, u8 *par, u32 par_len, u16 adr_dst, int rsp_max)
该函数是把adr_src固定为ele_adr_primary,然后对mesh_tx_cmd()进行封装。
app_provision.c
u8 is_provision_success():
获取节点是否处于配网成功状态。
u8 is_provision_working():
获取节点是否处于正在配网过程。
mesh_node.c
is_own_ele():
判断adr是否是本身的element address。
mesh_common.c文件介绍
HCI fifo:
hci_tx_fifo,hci_rx_fifo是定义和外设传输数据的fifo,比如gateway节点和gateway上位机的USB通讯。
mesh_get_proxy_hci_type():
定义proxy的类型,默认是PROXY_HCI_GATT。PROXY_HCI_USB是开发debug模式,用户不需要使用。
mesh_tid_save():
TID保存函数,比如generic on/off等命令需要用到tid,不执行deep sleep模式的情况下是不用保存的,上电初始化为0即可。有deep模式的,比如switch等,因为每次按键都会初始化所有变量,包括tid,所以需要保存。
adv_filter_proc():
IRQ RX收到校验正确的广播包后,通过这个函数进行过滤,比如可连接广播包直接丢弃。详见code。
const u16 sub_share_model[]:
const u16 sub_share_mode= {
SIG_MD_G_ONOFF_S, SIG_MD_G_LEVEL_S, SIG_MD_LIGHTNESS_S, SIG_MD_LIGHTNESS_SETUP_S,
SIG_MD_LIGHT_CTL_S, SIG_MD_LIGHT_CTL_SETUP_S, SIG_MD_LIGHT_CTL_TEMP_S,
SIG_MD_LIGHT_HSL_S, SIG_MD_LIGHT_HSL_SETUP_S, SIG_MD_LIGHT_HSL_HUE_S, SIG_MD_LIGHT_HSL_SAT_S,
SIG_MD_SCENE_S, };
参考mesh_config.h章节里面对SUBSCRIPTION_SHARE_EN的介绍。
Model默认绑定关系,在设置订阅地址(分组)的时候,这几个model是同时生效的。
具体的调用流程:
收到CFG_MODEL_SUB_ADD -> mesh_rc_data_layer_access_cb() -> mesh_cmd_sig_cfg_model_sub_set() -> share_model_sub_by_rx_cmd() -> share_model_sub()。
entry_ota_mode():
当收到OTA start命令后,会回调此函数。
ota_condition_enable():
允许GATT OTA的条件。当GATT连接成功,并且收到set proxy filter后,pair_login_ok会被置为1。(注意:收发set proxy filter需要使用network key进行加解密)
proc_telink_mesh_to_sig_mesh():
检测ota前的firmware是SIG mesh还是其他已经定义的SDK,比如telink mesh如果不是SIG mesh,则表示进行产品切换,需要执行参数初始化。
mesh_ota_reboot_proc():
Mesh ota完成后,延时1.5秒再重启。
调用方式:main_loop() -> mesh_loop_process() -> mesh_ota_reboot_proc()
mesh_ble_connect_cb:
GATT connect成功之后,会回调此函数。
mesh_ble_disconnect_cb:
GATT disconnect之后,会回调此函数。
update_para_change_MTU():
在连接成功后,适当的时间申请更改BLE连接参数请求,主要避免在discovery和组网期间发起连接参数更新。
gatt_adv_prepare_handler():
1) relay_adv_prepare_handler()
relay buffer是独立的,和主动发包用的是不相同的fifo,因为relay buffer可以在主动发包transmit interval间隙发包。
发包优先级: 主动发包 > relay > 可连接广播包。
2) 其他是GATT相关的广播包。
app_advertise_prepare_handler ():
当BLE协议栈底层允许发广播包时,会回调此函数,当前任务中如果有需要发送广播包(包括可连接广播包,beacon包等),对参数p指向结构体赋值即可。
发包优先级:Friend Node收到LPN的poll后回复给LPN的message > 主动发送的network message > relay > 可连接广播包。
1) get_adv_cmd():
返回值为等待发送的mesh message数据包指针,非零表示有数据包需要发送:包括MESH_ADV_TYPE_MESSAGE、SECURE_BEACON 类型的MESH_ADV_TYPE_BEACON。
2) mesh_adv_cmd_set()
把等待发送的数据包拷贝给BLE协议栈。
3) p_bear -> trans_par_val:
即spec中提到的retransmit的概念。包含transmit count和transmit interval。
4) mesh_rsp_random_delay_step
节点收到目标地址是group的地址时,在回复response时需要增加让Random delay,详见mesh_rc_data_layer_access_cb()的介绍。
5) adv_retry_flag
用于一些需要取消 network transmit interval,连续发包的情景,比如LPN建立friend ship后发送的poll等。
app_l2cap_packet_receive ():
当BLE协议栈收到有payload的时候,回调此函数,然后调用blc_l2cap_packet_receive()函数进行数据分析。调试时,开发者可以把该数据打印出来,便于分析。
chn_conn_update_dispatch():
暂未使用。
sim_tx_cmd_node2node():
每隔3秒发送unreliable的开关灯命令。做demo演示用。
usb_id_init():
USB ID配置,当多个dongle接在同一台PC的时候,必须设置不同的ID,PC才能识别是不同的设备。
ble_mac_init():
当MAC的参数位置,低4byte为0xFFFFFFFF,会随机生成一个MAC并保存起来。
mesh_scan_rsp_init():
初始化的时候填充scan rsp的固定字段,比如mac address。因为在建立数据库的时候,需要用到mac,并且IOS系统不能直接从广播包数据的AdvA字段获取,所以需要在scan response的内容里面标识出来。
mesh_scan_rsp_update_adr_primary():
当primary adr有变更(比如进行provision时),以及上电初始化时,会回调该函数更新scan rsp中的adr_primary字段。
publish_when_powerup():
上电,经过一个random时间(publish_powerup_random_ms)之后发送相应的status,通知app或者gateway节点上线。
mesh_vd_init():
多个project的关于mesh相关的公共处理部分,在mesh_init_all()调用。
mesh_global_var_init():
全局结构体变量初始化函数,在读取存储在flash里面的相关参数前运行。用于设定编译默认值。如果flash里面有相关参数,则以flash里面的为准。
model_sig_cfg_s_cps:
即composition data,相关定义请参考model_sig_cfg_s_cps结构体定义,以及spec相关资料。
set_unprov_beacon_para():
-
p_uuid:为设置uuid的指针,长度为16字节。
-
p_info:为设置oob_info的指针,长度为2字节。
-
p_hash:为设置URI的hash值的指针,长度为4字节。
-
uri_para:为uri连接的指针,最大长度为40 字节。
-
uri_len:为实际用到的uri部分的数据的长度,最大长度不能超过40。
以上参数用来配置未provision节点的beacon包。
set_provision_adv_data():
-
p_uuid:为设置uuid的指针,长度为16字节。
-
oob_info:为设置oob_info的指针,长度为2字节。
以上参数用来配置未完成provision时的广播包部分的参数。
set_proxy_adv_pkt():
-
p_hash:为设置hash的指针,长度为8字节。
-
p_random:为设置random的指针,长度为8字节。
-
node_identity表示的是广播包的类型。
如果node_identity为0,表示发送的广播包类型为advertising with Network ID,此时p_hash和p_random参数均不生效。
如果node_identity为1,表示发送的广播包类型为advertising with Node Identity,p_hash和p_random参数的设置才能生效。
以上参数用来配置已经完成provision之后,发送proxy连接的广播包。
uart_drv_init()/usb_bulk_drv_init():
串口和USB初始化,通过宏HCI_ACCESS选择串口或USB,blc_register_hci_handler注册回调函数。用户可通过调用my_fifo_push_hci_tx_fifo往hci_tx_fifo推送需要上报的数据。
set_material_tx_cmd():
设置发包的参数:
1) op:vendor op code,只需要输入一个byte,vendor id不需要输入,会自动填充。
2) rsp_max:只对有status回复的command有效,用于判断是否收到了足够的status个数。当destination address是unicast时,该值是0或1(0或1的作用相同,因为是收到status的时候才进行判断),当destination address是group时,该值是属于这个group的节点个数。
3) retry_cnt:只对有status回复的command有效,当命令发出一段时间后,未收到指定rsp_max个数的status,则触发retry flow,retry的次数由retry_cnt决定。
4) uuid:当需要发送的destination address是virtual address时,需要输入uuid,否则为0。
5) nk_array_idx:因为mesh支持多个netkey,此处是设定使用netkey 数组里面的key的数组索引值,注意不是 组网时分配的global netkey index。
6) ak_array_idx:因为mesh支持多个appkey,此处是设定使用appkey 数组里面的key的数组索引值,注意不是 组网时分配的global appkey index。
7) pub_md:当需要执行publish status时,需要设置为model的指针,因为在发送message时需要使用pub_md -> pub_par里面的mesh_model_pub_par_t参数,比如ttl,network transmit,network count,appkey index,而不是使用这些参数对应的默认值。当不是执行publish status时,pub_md设为0。
mesh_tx_cmd2normal_primary():
节点端主动发送命令调用API。参数区详见 set_material_tx_cmd()的相关介绍。
SendOpParaDebug_vendor():
当是WIN32 模式时,包括gateway,app,par_tmp[2:3]的解析,详见ini格式的解析。
is_need_response_to_self():
当自己收到从自己发过来的需要status回复的命令时,该函数用来设定是否需要回复status。
mesh_rc_data_layer_access_cb():
当节点端收到一个发给自己的命令(条件:model支持,destination address 匹配),将会回调该函数。
1) Vendor Op code在VD_OP_RESERVE_FOR_TELINK_START和VD_OP_RESERVE_FOR_TELINK_END之间的,是预留给telink的opcode,客户不应该使用。
2) mesh_need_random_delay :当节点收到目标地址是group的地址时,在回复response时需要增加让Random delay,尽量避免多节点在同一时刻回复。
3) p_res->cb: 这个是每个op code对应的回调函数,对应的是mesh_cmd_sig_func[] -> cb以及mesh_cmd_vd_func[] -> cb。
mesh_rsp_handle_cb():
用于上报gateway收到的status message给上位机。上报的方式包括USB或UART。
hci_send_data_user():
Hci tx fifo的buffer data区,前面两个byte是len,第三个是data type,用于识别数据是哪种类型。此处类型为HCI_RSP_USER,其他类型请参考 hci_type_t。
mesh_tx_reliable_stop_report():
Gateway在发送reliable命令,达到结束条件后的回调函数。
app_hci_cmd_from_usb():
在blt_sdk_main_loop()函数中,回调app_hci_cmd_from_usb()处理从上位机收到的命令,并通过app_hci_cmd_from_usb_handle()进行命令解析和执行。
app_hci_cmd_from_usb_handle ():
Buff对应的是ini格式的数据,详见ini章节的详细介绍。
cmd_interface.h文件介绍
access_cmd_get_level():
获取element的level值。该函数是把opcode设为G_LEVEL_GET,然后对mesh_tx_cmd()进行封装。
access_cmd_set_level():
设置element的level值。该函数是对mesh_tx_cmd()进行封装。当ack为1时,opcode设为G_LEVEL_GET;当为0时,opcode设为G_LEVEL_SET_NOACK。该命令用到的tid参数由内部统一进行管理实现,上层开发不需要关心。
access_set_lum():
通过输入lum的方式(范围是0~100)来设置level值。该函数是对access_cmd_set_level ()进行封装。
access_cmd_onoff():
设置element的onoff值。该函数是对mesh_tx_cmd()进行封装。当ack为1时,opcode设为G_ONOFF_GET;当为0时,opcode设为G_ONOFF_SET_NOACK。该命令用到的tid参数由内部统一进行管理实现,上层开发不需要关心。
vendor_model.c文件介绍
该文件介绍vendor model对应的命令码的发送和收到该命令码后执行的相应回调函数等。
注意事项:一般来说对于节点端(非provisioner角色)只会使用一个vendor id,并且会在composition data里面显示出来, Vendor model的op code,总共只有64个。注意,这个不是每一个产品类型各有64个,而是整个网络同一个vendor id的所有产品的所有vendor model的op code加起来总共只有64个。所以请注意节省使用,能做成子命令的时候,尽量做成子命令。
另外,因为telink也会有需求使用 vendor op code来实现一些自定义的feature,所以目前暂定 0xC0—0xDF是预留给telink使用,0xE0—0xFF是留个客户使用。
vendor命令码的注册:
详见3.2章节的介绍。
mesh_search_model_id_by_op_vendor():
通过opcode在mesh_cmd_vd_func[]数组里面查找对应的资源。用户了解即可,不需要更改该函数。
vd_cmd_key_report():
上报按键事件。在8258_mesh_switch工程中使用。
int SendOpParaDebug(u16 adr_dst, u8 rsp_max, u16 op, u8 *par, int len);可以认为是和mesh_tx_cmd_primary()是等效的。
is_cmd_with_tid_vendor():
该函数判断并返回该opcode是否需要带tid,如果有则返回1,并把tid在参数区的位置通过tid_pos_out来返回。否则返回0。
mesh_test_cmd.c文件介绍
该文件用于存放测试命令的实现。
8258 MESH工程介绍
app_config_8258.h
PCBA_8258_SEL:
选择PCBA,默认选择48pin的dongle板。另外两个是开发板。
FLASH_1M_ENABLE:
当芯片内置是1M flash的时候,需要把该宏打开,因为flash map不一样,另外,出厂的时候,MAC默认放在0Xff000,而512K是放在0x76000。
HCI_ACCESS:
设置hci的接口。
- 等于HCI_USE_USB时使用USB,
- 等于HCI_USE_UART时使用UART。
默认不需要使用HCI收发数据。
UART_GPIO_SEL:
设置uart的IO。
HCI_LOG_FW_EN:
Firmware默认不打开调试信息,如有需要打开调试信息用户可打开,打开方式参考log输出章节。
ADC_ENABLE:
设置是否使能ADC。
ONLINE_STATUS_EN:
私有mesh SDK的online status功能。实时发送状态数据,实时性能比 publish的机制更优化。
DUAL_MODE_ADAPT_EN:
SIG mesh + ZigBee双模。
DUAL_MODE_WITH_TLK_MESH_EN:
SIG mesh + 私有mesh SDK双模。
TRANSITION_TIME_DEFAULT_VAL:
默认的渐变参数。包括上电的时候的渐变参数,以及当收到支持渐变参数的命令,比如generic onoff,但是收到的命令又没有渐变参数时,灯端会按照TRANSITION_TIME_DEFAULT_VAL来执行渐变动作。默认渐变时间1秒,如需关闭渐变,把TRANSITION_TIME_DEFAULT_VAL设置为0即可,具体解析格式请参考trans_time_t。
SW1_GPIO/SW2_GPIO:
Dongle板的2个按键,做测试button的IO,默认没有使用。
如果使用的时候,需要改IO,则改完IO后,还需要同步修改对应的PULL_WAKEUP_SRC_XXX和XXX_INPUT_ENABLE。
PWM_R/ PWM_G/ PWM_B/ PWM_W:
设置PWM对应的IO。
GPIO_LED:
定义led指示灯的IO,比如组网完成,恢复出厂设置等,进行闪灯动作的IO定义。
app.c文件介绍
广播包以及广播响应包的定制
广播包:
可连接广播包:目前可连接广播包的格式已经由SIG MESH的spec定义完所有字段,具体定义格式,请参考PB_GATT_ADV_DAT 结构体,或者spec相关资料。
广播响应包:
用户可通过mesh_scan_rsp_init()修改u8 tbl_scanRsp [] = {} 这个数组来定制广播响应包,广播响应包最长有31个BYTE,目前只使用了一部分,客户可在mesh_scan_rsp_init()里面对rsv字段赋值,填充自己想要的信息。
typedef struct{
u8 len;
u8 type;
u8 mac_adr[6];
u16 adr_primary;
u8 rsv_telink [10]; // not for user
u8 rsv_user[11];
}mesh_scan_rsp_t;
FIFO部分配置
MYFIFO_INIT(blt_rxfifo, 64, 16);
MYFIFO_INIT(blt_txfifo, 40, 32);
配置BLE协议栈底层的收包buff和发包buff。如果有RAM不足时,可修改,但一般不建议更改。
app_event_handler ()
回调处理函数:当BLE stack收到:adv (包含可连接广告包,beacon包等)、connect request包、BLE连接参数更新包、BLE连接断开等,事件后会回调该函数进行处理。
目前SIG MESH通讯用的所有beacon,都是在(subcode == HCI_SUB_EVT_LE_ADVERTISING_REPORT) 这个分支里面处理。
其他事件的处理,请参考对应的code,目前仅做简单的LED指示灯处理。用户有需要,可以添加相关功能。
HCI_SUB_EVT_LE_ADVERTISING_REPORT:
接收到广播包的处理分支,客户可以在该分支下添加对应的事件以及处理的函数。
HCI_SUB_EVT_LE_CONNECTION_COMPLETE:
蓝牙连接上之后产生的事件回调,在蓝牙连接上之后,BLE协议栈底层会回调到这个分支。
HCI_CMD_DISCONNECTION_COMPLETE:
蓝牙连接断开之后,BLE协议栈底层会回调到这个分支。
main_loop ()
-
mesh_loop_proc_prior():实时性要求相对较高的函数。
-
blt_SDK_main_loop ():BLE协议栈的main_loop函数。
-
proc_led():led指示灯事件处理函数。
-
factory_reset_cnt_check():factory reset处理函数。支持上电5次的方式进行复位。具体请参考factory reset章节。
-
mesh_loop_process():SIG mesh相关的loop函数,包括reliable 命令的retry机制、segment ack超时回复、TID 超时检测机制等。
-
sim_tx_cmd_node2node():提供一个定时发送开关命令的demo 演示接口。
user_init()
-
proc_telink_mesh_to_sig_mesh():sig mesh和telink mesh相互OTA时,需要对参数进行初始化,因为两者间的参数格式是不兼容的。目前的做法是,当检测到mesh类型变化时,会把新mesh用到的参数区都进行清除。
-
bls_ll_setAdvParam():广播包间隔等参数定义,目前暂时不建议用户修改。
-
blc_ll_setAdvCustomedChannel():广播channel定制,sig mesh要求使用标准的37/38/39,但是测试过程中,可以更改该channel,方便调试。
-
bls_ll_setAdvEnable(1):使能广播包的发送。
-
rf_set_power_level_index (MY_RF_POWER_INDEX ):默认设置发送功率为3dbm。如果需要修改发射功率,修改MY_RF_POWER_INDEX这个宏即可。如果中途有动态修改,还原时调用rf_set_power_level_index (my_rf_power_index);即可。
-
mesh_init_all():sig mesh相关的初始化。
void proc_ui()
该函数主要是做些UI方面的处理,如button检测函数,以及对应的测试代码。
app_att.c文件介绍
pb_gatt_provision_out_ccc_cb():
使能provision out部分的发送使能。只有将provision_Out_ccc设置为01 00才能使得mesh节点能够正常的返回指令。
pb_gatt_Write ():
对应的uuid为my_pb_gatt_in_UUID的回调函数,用于处理provision命令的回调函数。
proxy_gatt_Write():
处理proxy命令的回调函数,其中proxy的指令分为三种,分别为MSG_PROXY_CONFIG,MSG_MESH_BEACON,MSG_NETWORK_PDU命令头。
-
MSG_PROXY_CONFIG:用于配置proxy通信的白名单和黑名单。
-
MSG_MESH_BEACON:控制beacon指令(notify)的接收。
-
MSG_NETWORK_PDU:用于控制开关灯的指令。
attribute_t my_Attributes[]:
SIG_mesh中的服务列表,里面包含基本的att,以及关于SIG_mesh部分的att的内容。
light.c文件介绍
修改IO口:
修改PWM_R / PWM_G / PWM_B / PWM_W对应的IO口即可。
#define PWM_R GPIO_PC2 //red
#define PWM_G GPIO_PC3 //green
#define PWM_B GPIO_PB6 //blue
#define PWM_W GPIO_PB4 //white
typedef struct{
u32 gpio;
u8 id; // pwm id
u8 invert; // pwm的翻转属性
u8 func; // 采用第一功能PWM 还是 第二功能PWM
u8 rsv[1];
}light_res_hw_t;
light_res_hw_t light_res_hw[LIGHT_CNT][4];
light_res_hw定义PWM IO的属性。
func:有3个PWM 管脚有两种PWM输出,包括GPIO_PC1,GPIO_PC4,GPIO_PD5。此处定义是使用第一功能还是第二功能。
定义PWM的宏的时候,我们默认把dongle板或者开发板上的四个led都列出来了,也就是RES_HW_PWM_R/ RES_HW_PWM_G/ RES_HW_PWM_B/ RES_HW_PWM_W,但是针对不同类型的灯类型,会选用某几路就可以了,不需要都用完,这个由light_res_hw来设置。比如:
LIGHT_TYPE_CT:选用RES_HW_PWM_R和RES_HW_PWM_G,其中红色代表暖色灯珠,绿色代表冷色灯珠。
LIGHT_TYPE_HSL:RES_HW_PWM_R, RES_HW_PWM_G, RES_HW_PWM_B分别对应RGB灯珠,在调节灯光的时候,会在dim_refresh()中把HSL转换为RGB值,来驱动LED。
LIGHT_TYPE_LPN_ONOFF_LEVEL:使用RES_HW_PWM_R,由于retention 低功耗的特性,目前只能控制onoff。
LIGHT_TYPE_PANEL:默认LIGHT_CNT等于3,占用3个element address,有3个onoff server model,3个onoff model分别对应RES_HW_PWM_R/ RES_HW_PWM_G/ RES_HW_PWM_B这三个灯珠。
PWM频率设定:
修改PWM_FREQ即可。
注:如果编译的时候,STATIC_ASSERT(PWM_MAX_TICK < 0x10000)报错,是因为pwm的tick溢出了。因为pwm的tick是16bit的,PWM时钟默认是PLL时钟,当PWM_FREQ设置过小时,pwm的tick会溢出,此时可以设置PWM分频,即设置PWM_CLK_DIV_LIGHT。一般情况下,不需要配置。
ct_flag:
ct_flag只有在LIGHT_TYPE_CT_HSL模式的时候才有作用。该模式有CT和HSL两种灯珠,但是同一时间只有一种灯珠在点亮。ct_flag等于1表示,当前是CT灯的状态,ct_flag等于0表示,当前是HSL灯的状态。
light_res_sw_save:
这个变量包含了所有和灯的状态相关,且需要保存的参数。比如lightness,色温,等。注意,保存的格式都是转换为generic level之后才保存的。(取值范围是 -32768 ~ 32767).
采用level保存的原因是:
(1) 所有的状态值都可以和level转换。
(2) level的刻度是最精细的。
(3) 同一个状态量只能存储一个参数,比如色温值,不能同时存储 色温值,又同时存储色温转换为level之后的值。因为有多份数据可能会出现数据不同步的问题。
综上考虑,都是按level来存储。否则会有精度丢失的问题。
亮度值和PWM值的非线性对应关系:
开发者可根据实际的灯的特性,修改rgb_lumen_map[]数组。
// 0-100% (pwm's value index: this is pwm compare value, and the pwm cycle is 255*256)
const u16 rgb_lumen_map[101] = {}
mesh_global_var_init_light_sw():
第一次上电的时候,设置的初始值,相当于编译默认值。当flash有存储对应的参数后,则以flash存储的为准。
light_res_sw_load():
从flash加载灯的状态的参数。
light_pwm_init():
读取灯的状态参数数后,调用该函数。初始化PWM register 后,先设置为OFF的状态,然后再看是否需要打开,以及是否需要渐变参数。
light_par_save_proc():
为了避免频繁的写flash,灯的状态改变3秒后才会存储到flash。
light_dim_set_hw():
设置PWM的输出,当节点收到G_LEVEL_SET,G_LEVEL_SET_NOACK 等命令后,会执行。
Idx和idx2对应light_res_hw[idx][idx2]。
idx: 对应light count的索引值,比如,一个蓝牙模组上有两个色温灯模组时。
Idx2:灯珠的索引值。
light_dim_refresh ():
当灯的状态发生变化后,调用该函数来刷新PWM 输出值。在该函数里面,用户能直接获取具体的 lightness,色温值等,客户可以根据这个值按自己的调光算法来输出PWM值。
默认的算法是,把标准的lightness,色温值换算为 0—100的刻度,然后从rgb_lumen_map[101]查表获取到对应的PWM输出值。
get_light_pub_list():
当灯的状态发生变化后,查询所有需要publish的status。
temp_to_temp100():
把800—20000的色温值转换为0—100的刻度。
temp100_to_temp():
把0—100 的刻度转换为800—20000的色温值
light_g_level_set_idx_with_trans():
typedef struct{
s32 step_1p32768; // (1 / 32768 level unit)
u32 remain_t_ms; // unit ms: max 26bit: 38400*1000ms
u16 delay_ms; // unit ms
s16 present; // all value transfer into level, include CT.
s16 present_1p32768;// (1 / 32768 level unit)
s16 target;
}st_transition_t;
收到level set/lightness set等命令时,如果有渐变需求,则通过该函数来设置渐变参数。st_transition_t里面的 “1p32768”表示把一个level刻度划分为32768份,也就是该值的unit是1/32768,此处的做法主要是避免浮点运算。 提高运算效率。
step_1p32768:表示渐变过程中,每经过LIGHT_ADJUST_INTERVAL时间,变化的量。
remain_t_ms:命令中remain的值转换为ms。
delay_ms:命令中delay_ms的值转换为ms。
present:渐变过程中,level的实时值。
present_1p32768:present的余数部分,单位是1/32768 level刻度。
target:目标level。
light_transition_proc():
渐变轮询处理函数。当渐变结束,即当前level达到目标level时,如果该渐变是由scene load触发,则会调用 scene_target_complete_check(i); 来标记场景已生效。
另外,当渐变结束时,还要检查并发送publish status。
led_onoff_gpio():
当处于deep retention sleep或者deep sleep模式时,如果需要在sleep期间保持输出,只能通过设置上下拉的方式来输出。因为此时pwm已停止工作,gpio function,gpio output enable,gpio output等数字register已经不工作。只有设置上下拉等 analog register在工作。
proc_led():
LED指示灯事件轮询处理函数。
rf_link_light_event_callback ():
LED指示灯注册事件,用该方式,闪灯是在main_loop中执行,不影响其他事件的处理。
当处于LPN模式时,因为要进入deep模式,不能一直轮询执行proc_led(),导致闪烁很慢,不符合预期。所以,目前暂时的做法是,需要led指示时,需要设置较快的闪烁参数,然后一直轮询proc_led(),当闪烁结束后,再执行其他函数。一般的LPN产品是不需要LED的,只是开发时使用。如果有需要,还需要另行处理。
主要闪灯事件介绍:
LGT_CMD_SET_MESH_INFO(LGT_CMD_PROV_SUC_EVE):表示light节点端,provision成功后的闪灯事件。
LGT_CMD_FRIEND_SHIP_OK:LPN和friend建立friend ship完成,LPN会产生该事件。
LGT_CMD_SET_SUBSCRIPTION:收到修改subscription address的message。
LGT_CMD_BLE_ADV:BLE连接断开事件。仅Debug模式使用,默认不打开。
LGT_CMD_BLE_CONN:BLE连接成功事件。仅Debug模式使用,默认不打开。
LGT_CMD_SWITCH_POWERON:switch上电闪灯一次。
LGT_CMD_SWITCH_PROVISION:switch进入配网模式。
LGT_CMD_SWITCH_CMD:switch发送按键命令。
PROV_START_LED_CMD:gateway开始对节点发起provision flow。
PROV_END_LED_CMD:gateway对节点发起provision flow已完成。
LGT_CMD_DUAL_MODE_MESH:dual mode模式执行切换mode。
show_ota_result():
OTA结束后的闪灯指示处理函数。
show_factory_reset():
执行恢复出厂设置后的闪灯指示处理函数。
如何导入客户自己的调光算法:
默认的调光算法:参考前面light_dim_refresh()的介绍。
客户需要修改调光算法的话,修改light_dim_refresh()即可,即在里面获取到标准的值,然后调用自己的调光算法即可:
Lightness:
st_transition_t *p_trans = P_ST_TRANS(idx, ST_TRANS_LIGHTNESS);
u16 lightness = get_lightness_from_level(p_trans->present);
CT value:
u16 temp = light_ctl_temp_prensent_get(idx);
HSL(RGB) value:
参考light_dim_refresh()里面的获取方法,根据调光算法需求,取得HSL.h/HSL.s/HSL.l 或者RGB.r/ RGB.g/ RGB.b。
Onoff:onoff是通过lightness来决定的。
Provisioner(Gateway)工程介绍
Provisioner的功能介绍
adv-bearer和gatt-bearer
Provisioning是将一个未分配的节点加入到mesh网络中的,provisioner能够将未分配的节点加入到mesh中成为mesh网络中的节点。在provision的过程中主要分配network key、IV index、unicast adr,其中network key和IV index是决定是否是处于同一个网络的关键参数,unicast adr为在网络中分配的地址。provisioner可以通过网络参数和unicast adr去访问对应的节点进行相应的控制,例如开关灯,调节亮度之类的。
Provision的链接信道分为两种:
-
一种是通过广播包实现adv-bearer信道上的通信,本章节主要介绍adv-bearer部分的内容。
-
另外一种是采用BLE连接实现,通过gatt-bearer实现,gatt-bearer的实现方式详见7.2章节中的BLE连接和加灯,以及SIG_mesh对应Android和iOS的app可实现对应的功能。
Provisioner的原理
Provisioner的命令交互
Provisioner通过adv-bearer将unprov node(未配对节点)加入到网络中,采用蓝牙的37,38,39信道和unprov node进行通信,通过相互交互random,key之类的参数(详见Mesh_v1.0.pdf中5.3)来交换网络参数和地址的分配最终达到将unprov node加入到网络中。
adv provisioner的时序图
在provision_dat指令中包含network key、IV index、unicast adr这3个网络参数,实现组网的功能。
adv-provision的函数发包部分的函数关系调用图:
adv-provision的函数收包部分的函数关系调用图:
GATT provisioner的时序图
通过gatt-provision的方式能够更加快速的实现provision的功能,其中int pb_gatt_Write (void *p)为gatt_provision部分的入口函数。
gatt_provision的发包函数入口:
gatt_provision的收包函数入口:
app.c文件介绍
目前在provisioner工程中需要定制修改的内容暂时仅仅只限于app.c文件,需要对app.c中的内容做定制化的修改。
广播包以及广播响应包的定制:
参考《8258 MESH工程介绍》。
FIFO部分配置:
参考《8258 MESH工程介绍》。
HCI(USB/UART)上报数据:
my_fifo_push_hci_tx_fifo (u8 p, u16 n, u8 head, u8 head_len)
p:指向所发数据地址
n:数据长度
head:标识头
head_len:标识头长度(没有则填0)
调用my_fifo_push_hci_tx_fifo (u8 p, u16 n, u8 head, u8 head_len)往hci_tx_fifo送数据,在发送回调函数里会把hci_tx_fifo的数据送出去。回调函数已在user_init里注册:
-
UART: blc_register_hci_handler (blc_rx_from_uart, blc_hci_tx_to_uart);
-
USB: blc_register_hci_handler (app_hci_cmd_from_usb, blc_hci_tx_to_usb);
app_event_handler ():
参考《8258 MESH工程介绍》。
main_loop ():
参考《8258 MESH工程介绍》。
user_init():
参考《8258 MESH工程介绍》。
proc_ui():
在proc_ui函数中配置每40ms扫描下IO口检测IO的变化。最终是通过这个接口access_cmd_onoff函数来发送开关灯的指令。每间隔100ms发送一条开关灯的指令,按SW1发送开灯的指令,SW0发送关灯的指令。
网关(provisioner)操作和接口
对于网关,协议栈是在网关端运行的。入网的节点信息都存在网关flash地址为FLASH_ADR_VC_NODE_INFO(0x3f000)的位置,由于一个sector为4K,目前最多保存200个节点信息。如果超过200个节点无法继续将对应的节点正确的加入到网络中,需要手动删除一些离线节点,或者扩大存储区域,扩大方法,请参考 "修改mesh网络最大节点数的方法"。
SIG_MESH_TOOL ini文件格式说明
Gateway的操作会用到“SIG_MESH_TOOL”,用户可以通过点击界面上的button控制,或双击主界面左边的命令列表,或编辑edit控件后按enter键下发命令。网关收到后会在app_hci_cmd_from_usb_handle的相应分支作处理。
ini文件里命令格式分为SIG model和vendor model。
SIG model命令格式g_all_on为例
前2个字节是标识符,为telink 私有化定义,是通信的包头识别,gateway为0xE8FF, app(包含手机app及kma dongle上位机)为0xA3FF。
后面参数结构如下:
typedef struct{
u16 nk_idx; //netkey index
u16 ak_idx; //app_key index
u8 retry_cnt; // 应用层retry的次数,未收到rsp_max个回复时应用层会retry
u8 rsp_max; // 表示期望收到的回复个数
u16 adr_dst; // 目标地址
u8 op; //操作码,op code的第一个byte
u8 par[MESH_CMD_ACCESS_LEN_MAX]; //op code的剩余byte以及parameter
}mesh_bulk_cmd_par_t;
其中,retry_cnt 在VC tool中,如果设置为0,则表示使用“retry”控件的值,默认是2,。VC tool会自动把INI 数据中retry_cnt的值改为2。如果设置为0xFF,则表示不需要retry,即VC tool会自动把INI 数据中retry_cnt的值改为0。
另外,nk_idx,ak_idx, rsp_max更详细的介绍,请参考本文档set_material_tx_cmd()的介绍。
注意:
TID,如果值为0时,由协议栈来管理和维护。如果TID的值不为0,则使用这个值作为TID,这样客户就可以自己维护TID。
Vendor model 命令格式
以CMD-vendor_on为例
与SIG model不同的是,多了op_rsp和tid_pos,这2个参数只是给上位机本身使用的,是一个伪参数,并不会下发到设备端。
op_rsp:设置op对应的response opcode(不需要包含vendor id)。
tid_pos:设置tid的位置(值为0表示没有tid字段,1表示tid在para[0]位置,2表示在para[1]位置…),op_rsp和tid_pos的设置要与设备端统一。
增加这两个参数的目的,是因为provisioner端需要支持多种vendor id的op code,以及vendor op随时会添加,所以无法把这些信息都编译进程序中。所以需要通过ini命令的方式添加到里面。
节点的烧录
烧录2个8258 dongle:1个8258 provisioner节点(8258_mesh_gw.bin)、1个dongle节点(8258_mesh.bin)。
烧录步骤,请参考4 调试工具操作说明的“下载固件”章节。
Provisioner加灯
网关配合SIG_mesh_tool工具的加灯流程和相应命令格式(16进制表示)。
1) Provisioner dongle插到USB口。打开“SIG_MESH_TOOL”选择tl_node_gateway.ini,标题栏显示Found说明找到gateway设备(provisioner)。
工具会自动获取网关的uuid和mac地址,发送的命令格式为:
HCI_CMD_GATEWAY_CTL+ HCI_GATEWAY_CMD_GET_UUID_MAC。
即e9 ff + 10
网关收到后会上报uuid和mac,格式为:
TSCRIPT_GATEWAY_DIR_RSP+HCI_GATEWAY_CMD_SEND_UUID+uuid(16 bytes)+mac(6 bytes),即:91+99+uuid(16 bytes)+mac(6 bytes)。
2) 8258 dongle上电,然后点击scan搜索设备。
scan控件对应的命令为HCI_CMD_GATEWAY_CTL + HCI_GATEWAY_CMD_START:e9 ff + 00
stop控件对应的命令为HCI_CMD_GATEWAY_CTL + HCI_GATEWAY_CMD_STOP:e9 ff + 01
3) 点击scan控件后网关会把收到的unprovision beacon上报上来,上报格式为:
TSCRIPT_GATEWAY_DIR_RSP+ HCI_GATEWAY_CMD_UPDATE_MAC+unprovision beacon。即:91+88+mac(6 bytes)+ unprovision beacon。
然后在设备列表显示出来,双击选择需要provision的设备,双击对应的命令为:
HCI_CMD_GATEWAY_CTL+HCI_GATEWAY_CMD_SET_ADV_FILTER+6字节mac地址。
即:e9 ff + 08 + mac(6 bytes)。
4) 点击Prov进入provision界面
a. Provision控件对应的命令为:HCI_GATEWAY_CMD_GET_PRO_SELF_STS。
即“e9 ff 0c”。
b. 网关收到此命令后后会返回是否已有配置信息和网关本身的element个数,相应的命令格式为:TSCRIPT_GATEWAY_DIR_RSP+ HCI_GATEWAY_CMD_PRO_STS_RSP+provision_flag+ pro_net_info。
即:91 8b + provision_flag+ pro_net_info。pro_net_info为25字节的provision data。格式如下:
typedef struct{
u8 net_work_key[16]; //network key
u16 key_index; //network key index
union{
mesh_ctl_fri_update_flag_t prov_flags;
u8 flags; // iv update flag
};
u8 iv_index[4]; // iv index
u16 unicast_address;
}provision_net_info_str;
TSCRIPT_GATEWAY_DIR_RSP+HCI_GATEWAY_CMD_SEND_ELE_CNT+total element:即91+8c+ total element。
c. 如果provision flag标记为0,说明网关没有配置信息,SetPro Interna控件使能,在provision界面填好pro_net_info相关参数后点击SetPro_interval设置网关配置信息。然后会自动发出两条命令:
- HCI_CMD_GATEWAY_CTL+HCI_GATEWAY_CMD_SET_PRO_PARA + pro_net_info。(第一条命令)
即:e9 ff + 09 + pro_net_info。
- HCI_CMD_GATEWAY_CTL + HCI_GATEWAY_CMD_SET_DEV_KEY + unicast address + device key:(第二条命令)
即:e9 ff + 0d +gateway address+device key
如果provision flag标记为1,说明gateway已有配置信息,SetPro Interna控件禁止,跳过此步骤即可。
5) 点击Provision按钮进行provision。provision过程中主界面的会打印相关log,provision成功后bind_all按钮使能。
a. Provision控件对应的命令为:HCI_CMD_GATEWAY_CTL+HCI_GATEWAY_CMD_SET_NODE_PARA+ pro_net_info 。
即:e9 ff+0a+ ro_net_info。
b. Provision过程中会上报分配给设备的地址,格式为:
TSCRIPT_GATEWAY_DIR_RSP +HCI_GATEWAY_RSP_UNICAST+unicast addr,
即:91 80+unicast address。
c. provision完成后会上报设备的information。格式为:
TSCRIPT_GATEWAY_DIR_RSP+ HCI_GATEWAY_CMD_SEND_NODE_INFO+ VC_node_info_t。
即:91+8d+ VC_node_info_t。
VC_node_info_t定义如下:
typedef struct{
u16 node_adr; // primary address
u8 element_cnt;
u8 rsv;
u8 dev_key[16];
}VC_node_info_t;
d. Provision完成后会上报设备provision状态。格式为:
TSCRIPT_GATEWAY_DIR_RSP+HCI_GATEWAY_CMD_PROVISION_EVT+ gateway_prov_event_t。
即:91 + 89 + gateway_prov_event_t。
gateway_prov_event_t定义如下:
typedef struct{
u8 eve;//1表示成功
u16 adr;
u8 mac[6];
u8 uuid[16];
}gateway_prov_event_t;
绑定app_key
Provision完成后,还需要为model绑定app_key。点击bind_all为model绑定app_key。
a. bind_all对应的命令为:HCI_CMD_GATEWAY_CTL+ HCI_GATEWAY_CMD_START_KEYBIND + fast_bind + +app_key index(2 byte)+app_key(16 bytes)。
即:e9 ff + 0b + fast_bind + app_key index(2 byte)+app_key(16 bytes)。
fast_bind为1时:网关只会下发appkey add,被provision的设备需打开默认绑定功能(PROVISION_FLOW_SIMPLE_EN设置为1)。
fast_bind为0时:网关默认绑定全部model id,为了节省时间,用户可选择需要绑定的model id。网关端打开宏MD_BIND_WHITE_LIST_EN,需要绑定的model id详见Mesh_common.c文件中master_filter_list[],用户可根据需要自行修改。
b. App_key bind过程中gateway会调用u8 gateway_model_cmd_rsp(u8 *para,u8 len )返回绑定model的状态信息,格式为:TSCRIPT_GATEWAY_DIR_RSP + HCI_GATEWAY_RSP_OP_CODE +参数。 即:91 + 81 + appkey bind status
c. App_key bind完成之后会返回HCI_GATEWAY_CMD_KEY_BIND_EVT表示成功还是time_out。格式为:
TSCRIPT_GATEWAY_DIR_RSP + HCI_GATEWAY_CMD_KEY_BIND_EVT +result。
即:91 + 8a + result。(1:success 2:time_out)。
控制灯的开关
绑定app_key后点击Mesh进入mesh界面、或者双击主界面左边的INI命令:g_all_on/g_all_off进行开关灯操作。
发送onoff命令后,对应的节点上报状态的格式为:
Status Rsp______________: 04 00 01 00 82 04 00 01 0a
对应的结构体为:
typedef struct{
u16 len; // length
u16 src; // source address
u16 dst; // destination address
u8 data[ACCESS_WITH_MIC_LEN_MAX]; // access layer(op code, parameters)
}mesh_rc_rsp_t;
Provisioner的控制流程图
一键组网(Smart Provision)
一键配网在代码里面对应Smart Provision。
一键组网功能是基于gateway工程,然后去掉上位机,只需要 gateway dongle 即可进行组网。适用于简单的网络场景,后期也不需要通过UI界面对网络节点进行过多的配置,主要是使用常用的控制功能。
(1) 一键组网和普通组网的区别
-
Gateway普通组网模式:需要上位机或者app才能进行组网,组网完成后,可以通过上位机对网络节点进行各种配置。
-
一键组网模式:不需要上位机就可以进行组网,只需要gateway dongle即可。
另外,为了简化组网流程,节省组网时间,节点端需要打开PROVISION_FLOW_SIMPLE_EN,这样在组网的时候gateway dongle发送 App key add之后,结束组网流程,不再发送 app Key bind命令,被组网节点收到 App key add后,自动触发对自己的 app Key bind。
如果节点端不打开PROVISION_FLOW_SIMPLE_EN,则需要
a) 修改prov_uuid_fastbind_mode(),在函数里面直接返回 1.
b) 把GATEWAY_APPKEY_ADD_HEAD 由
{(u8)HCI_CMD_GATEWAY_CTL, HCI_CMD_GATEWAY_CTL>>8, HCI_GATEWAY_CMD_START_KEYBIND, 1}
改为
{(u8)HCI_CMD_GATEWAY_CTL, HCI_CMD_GATEWAY_CTL>>8, HCI_GATEWAY_CMD_START_KEYBIND, 0}
(2) 原理说明
一键配网的配网流程使用的也是sig标准的配网方式,只是把上位机配网的命令交互流程移到网关端的dongle firmware的应用层处理。按下配网按键后,主流程mesh_smart_provision_proc()处理配网状态,模拟上位机往hci rx fifo里压命令。在上报给上位机的接口函数gateway_common_cmd_rsp()中,调用mesh_smart_provision_rsp_handle()对消息进行处理处理。
配网后网关的初始 iv index 是SMART_IV_INDEX。network key和app key都是随机值。具体内容可以查看smart_gateway_provision_data_set()。
组网的时候,如果只想添加符合一定条件的节点,可在mesh_smart_provision_rsp_handle()函数的HCI_GATEWAY_CMD_UPDATE_MAC分支的函数prov_uuid_fastbind_mode()里面修改过滤规则。
(3) 函数说明
一键配网过程中也会和正常组网一样,网关会发出invite,start等命令。如:mesh_adv_prov_send_invite()发送invite命令,mesh_adv_prov_send_start_cmd()发送start命令。
(4) 测试流程
a) SDK设置
网关:需要打开SMART_PROVISION_ENABLE
节点:需要打开PROVISION_FLOW_SIMPLE_EN
节点端如果不希望打开 PROVISION_FLOW_SIMPLE_EN,请参考 “一键组网和普通组网的区别” 里面的介绍进行配置。
b) 初始组网
网关一键配网功能,使能此功能把8258_mesh_gw.bin文件烧录到8258 dongle后按配网键SW2,网关自动把一跳范围内的未配网节点加入到网络。30秒内搜不到未配网节点退出配网流程,按SW1可控制网络内节点的开关。
c) 网络组建成功并运行一段时间后,再次添加节点的步骤
再次按配网键SW2即可。
Mesh LPN工程介绍
LPN节点的概念和实现方式介绍
注:本节图片均来自于Sig Mesh spec。
LPN和friend的概念
低功耗(Low-Power)特性:能够以明显较低的接收端占空比在mesh网络中运行。通过将无线电接收器启用时间最小化可实现节点功耗的降低,只有在绝对必要时才启动接收器。低功耗节点(LPN,low power node)通过与好友节点(FN,friend node)建立友谊(friendship)关系来实现这一点。
对于LPN节点,只能和一个FN建立友谊;对于FN节点,可以和多个LPN节点建立友谊。
当建立friendship后,如果LPN节点之前有和另一个FN节点建立过friendship,则 LPN 会通过 friend request 命令,告知新FN节点,FN节点会调用friend_cmd_send_clear()发送 clear 命令,通知旧FN节点清除和该LPN对应的friendship。低功耗节点会以一个比较长的周期,比如 2秒(FRI_POLL_INTERVAL_MS)或者更长,对好友节点进行轮询(Poll)。查看是否有新消息,如果有,则获取该消息。建立友谊后,LPN节点会上报当前的订阅列表(即所有组号信息)给FN节点。然后如果 FN 收到目的地址和这些组号或者LPN的element address匹配的消息时,就会缓存该消息,然后在收到LPN发送的Poll命令后,把缓存的消息发送给LPN。FN和LPN在交互 Poll 和 update 命令时,里面有 iv index 信息,可以进行 iv update flow。
好友(Friend)特性:通过存储发往LPN的消息,仅在LPN明确发出请求时才进行转发来帮助LPN运行的能力。
友谊(Friendship)参数
低功耗节点需要找到好友节点,与其建立“友谊”关系。所涉及的流程称为“友谊建立”。我们稍后会探讨此流程。在此之前,先介绍一些有关对LPN行为进行管理的关键参数,这些参数被设定于友谊建立过程中。
1) ReceiveDelay是从LPN向好友节点发送请求,到其开始收听响应中间经过的时间。这让好友节点有时间做好响应的准备,并将响应发回。由 LPN 通过 这个宏 FRI_REC_DELAY_MS 指定,并通过 friend request 命令告知 FN。
2) ReceiveWindow 是LPN用于收听响应的时间。由 FN 通过 这个宏 FRI_REC_WIN_MS 指定,并通过 friend offer 命令告知 LPN。下图描述了涉及ReceiveDelay和ReceiveWindow的时序。
3) PollTimeout设定了LPN发送给其好友节点的两个连续请求之间可能经过的最长时间。由 LPN 通过这个宏 LPN_POLL_TIMEOUT_100MS 指定,并通过 friend request 命令告知 FN。 如果在PollTimeout计时器到时之前,好友节点未能收到LPN的请求,则友谊关系将被终止。
“友谊”建立
蓝牙mesh网络中“友谊”的建立,需要经过以下步骤。
Step 1 LPN发布一个“好友请求”(Friend Request)消息。该消息不会被中继,因此只有处于直接无线电范围内的好友节点才能处理该消息。不具有“好友”特性的节点会将消息丢弃。“好友请求”消息包括LPN的ReceiveDelay、ReceiveWindow和PollTimeout参数。
Step 2 附近的好友节点若支持“好友请求”消息中特定的要求,将准备一个“Friend Offer”消息,并将其发送回LPN。该消息包括各种参数,包括支持的ReceiveWindow大小、可用的消息队列大小、可用的订阅列表(Subscription List)大小、以及由好友节点测量的RSSI值。
Step 3 LPN接收到“Friend Offer”消息时,通过当前节点采用的算法来选择合适的好友节点。该算法可能会考虑到各种各样的情况。某些设备可能会优先考虑ReceiveWindow大小,以尽可能减少功耗;而有些设备则可能会更加关注RSSI值,以确保能够与好友节点保持高质量的链路。所采用的精确算法由产品开发者决定。
Step 4 选择好友节点之后,LPN将向好友节点发送一个“Friend Poll”轮询消息。
Step 5 从LPN收到“好友轮询”(Friend Poll)消息后,好友节点会回复一个“Friend Update”更新消息,完成“好友”建立流程并提供安全参数。此时“友谊”得以建立。
友谊(Friendship)消息传送
友谊建立之后,好友节点将LPN的所有消息存储在“好友队列”(Friend Queue)中,这些消息就是我们所说的“被存储的消息”。下图所示为好友节点和关联LPN之间的消息交换。
当好友节点收到一个寻址到该节点的LPN的消息时,好友节点会缓冲此消息,将其存储在称为“好友队列”的区域中。在上图中,我们可以看到,好友节点为LPN存储了消息1和2。
LPN会周期性地启用其收发器(transceiver),并向好友节点发送 “好友轮询” 消息,询问是否存储有任何为其缓冲的消息。
好友节点会先将一个被存储的消息发回至LPN作为对“好友轮询”(Friend Poll)的响应。
在每次接收到来自好友节点的消息之后,LPN将会继续发送“好友轮询”消息,直到收到一条“MD(MD =更多数据)”字段设置为0的“好友更新”消息为止。这意味着已经没有为LPN缓冲的更多消息了。此时,LPN停止对好友节点的轮询。
安全性
主安全资料(Master Security Material):由网络密钥(NetKey)派生,可被同一网络中的其他节点使用。使用主安全资料加密的消息可被同一网络中的任何节点解码。
好友安全资料(Friend Security Material):由网络密钥(NetKey)、以及由低功耗节点(LPN)和好友节点生成的额外计数器号码派生而来。使用好友安全资料加密的消息只能由处理该消息的好友节点和LPN解码。
使用好友安全材料加密的相应友谊消息:好友轮询(Friend Poll)、好友更新(Friend Update)、好友订阅列表(Friend Subscription List)。
使用主安全资料加密的相应友谊消息:好友清除(Friend Clear)、好友清除确认(Friend Clear Confirm)。
其它从LPN发送至好友节点的非控制消息将根据应用设置对应model publish 参数里面的credential_flag,来确定是通过主安全资料或好友安全资料进行加密。credential_flag默认值是0,也就是使用主安全资料。
“友谊”终止
如果在PollTimeout计时结束之前,好友节点未收到“好友轮询”、“好友订阅列表添加”或“好友订阅列表删除”消息,则友谊终止。
LPN可以通过将“好友清除”消息发送给好友节点,以启动友谊终止程序,“友谊”就会被好友节点终止。
Friendship休眠和工作机制介绍
FN收包处理接口
void mesh_friend_ship_proc_FN(u8 *bear)
- 其中当 bear 非空 时,表示收到 friendship 相关命令。
- 其中当 bear 为空 时,表示是 在 main_loop() 里面轮询调用,实现计时事件和超时事件的检测和处理等。
void mesh_friend_ship_proc_FN(u8 *bear)
{
foreach(i,g_max_lpn_num){ // a friend node may establish friendship with many LPN, so check all LPN.
mesh_fri_ship_proc_fn_t *proc_fn = &fri_ship_proc_fn[i];
if(!bear){
if(proc_fn->status){ // (FRI_ST_IDLE != proc_fn->status)
if(FRI_ST_OFFER == proc_fn->status){
if(clock_time_exceed(proc_fn->offer_tick, proc_fn->offer_delay*1000)){
......
// send friend offer and set to state of receiving friend poll after received friend request.
friend_cmd_send_fn(i, CMD_CTL_OFFER);
......
mesh_friend_ship_set_st_fn(i, FRI_ST_POLL);
}
}else if(FRI_ST_POLL == proc_fn->status){
// add 500ms, because handling response of POLL was delay some ten ms.
if(clock_time_exceed(proc_fn->offer_tick, (500+FRI_ESTABLISH_OFFER_MS)*1000)){
// timeout to receive friend poll from LPN after send friend offer to LPN,
// means that LPN did not receive offer, or LPN did not select current FN as friend node,
// or FN did not receive the friend poll from LPN.
mesh_friend_ship_proc_init_fn(i);
}
}else if(FRI_ST_TIMEOUT_CHECK == proc_fn->status){
if(clock_time_exceed_100ms(proc_fn->poll_tick, (u32)(fn_req[i].PollTimeout))){
// timeout to receive friend poll from LPN, then will disconnect this friendship.
friend_ship_disconnect_fn(i, FS_DISCONNECT_TYPE_POLL_TIMEOUT);
}
}
}
if(proc_fn->clear_poll){ // clear by other FN
if(clock_time_exceed_100ms(proc_fn->clear_start_tick, (u32)(fn_req[i].PollTimeout)*2)){
// when the timeout expires, even if the clear response has not been received yet, the clear command will stop being sent.
mesh_stop_clear_cmd(i);
}else{
if(clock_time_exceed_100ms(proc_fn->clear_cmd_tick, proc_fn->clear_int_100ms)){
......
// Gradually reduce the frequency of sending clear commands.
// please refer to mesh V1.1 spec "Figure 3.24: Friend Clear procedure example" of "3.6.6.3.1 Friend establishment".
friend_cmd_send_fn(i, CMD_CTL_CLEAR);
}
}
}
if(proc_fn->clear_by_lpn_tick && clock_time_exceed(proc_fn->clear_by_lpn_tick, 5*1000*1000)){
// when received friend clear, should not clear at once, and need to delay some time to clear Friend ship.
// because LPN may retry sending friend clear command when not receive clear confirm.
friend_ship_disconnect_fn(i, FS_DISCONNECT_TYPE_CLEAR);
}
}else{
...... // to process packet received
}
}
}
FN发包给LPN的处理接口
mesh_friend_response_delay_proc_fn()
当FN需要发包给LPN时,比如 发送 friend update,当poll delay 到最后,需要尽快发包,而不能像其他 普通的 network PDU 一样,可以等待 bls_ll_setAdvParam()设定的 adv interval (10ms) 到之后,才去发包。所以这里通过 mesh_friend_response_delay_proc_fn() 轮询 tick,时间到后,调用 fn_quick_send_adv() 立即发包。另外,由于发送给LPN的消息还需要再检查以下,是否需要更新消息,比如 segment block ack是否需要更新等(参考 get_cache_buf_for_poll() 的处理),所以需要等更新完后,再进行network layer的加密,再发送。详见以下mesh_friend_response_delay_proc_fn()的处理:
void mesh_friend_response_delay_proc_fn(u8 lpn_idx)
{
fn_ctl_rsp_delay_t *p_delay = &fn_ctl_rsp_delay[lpn_idx];
int print_cache_flag = 0;
if(p_delay->delay_type && clock_time_exceed(p_delay->tick, fn_req[lpn_idx].RecDelay * 1000 - 1800)){ // 1800us: encryption pkt time
if(DELAY_POLL == p_delay->delay_type){
if(p_delay->poll_rsp){
if(fn_other_par[lpn_idx].cache_overwrite){
p_delay->poll_rsp = get_cache_buf_for_poll(lpn_idx, 1, 1); // cache_overwrite will be clear inside.
}
......
if(bear_tx_len <= MESH_BEAR_SIZE){
......
// no encryption before, because need to check buffer in mesh_fri_cmd2cache_(), then to set cache_overwrite or not.
mesh_sec_msg_enc_nw_rf_buf((u8 *)(&bear_temp->nw), mesh_lt_len_get_by_bear(bear_temp), FRIENDSHIP, lpn_idx,0,fn_other_par[lpn_idx].nk_sel_dec_fn, 0);
......
mesh_tx_cmd_add_packet_fn2lpn((u8 *)bear_temp);
}
......
}
mesh_fri_ship_proc_fn_t *proc_fn = &fri_ship_proc_fn[lpn_idx];
if(proc_fn->clear_delay_cnt){
proc_fn->clear_delay_cnt--;
if(0 == proc_fn->clear_delay_cnt){ // make sure establish friend ship success
friend_cmd_send_fn(lpn_idx, CMD_CTL_CLEAR); // use normal fifo, not mesh_adv_fifo_fn2lpn_
......
}
}
}else if(DELAY_SUBSC_LIST == p_delay->delay_type){
friend_cmd_send_subsc_conf(p_delay->adr_dst, (u8)p_delay->par_val);
}else if(DELAY_CLEAR_CONF == p_delay->delay_type){
......
friend_cmd_send_clear_conf(clear.LPNAdr, (u8 *)&clear, sizeof(mesh_ctl_fri_clear_t));
}
p_delay->delay_type = 0;
}
if(my_fifo_data_cnt_get(&mesh_adv_fifo_fn2lpn)){
fn_quick_send_adv(); // "poll rsp" may be delay when in BLE_S window, so quickly send here again. and also "send_subsc_conf /send_clear_conf" need quick send.
}
}
LPN收包处理接口
void mesh_friend_ship_proc_LPN(u8 *bear)
- 其中当 bear 非空 时,表示收到 friendship 相关命令。
- 其中当 bear 为空 时,表示是 在 main_loop() 里面轮询调用,实现计时事件的检测等。
void mesh_friend_ship_proc_LPN(u8 *bear)
{
......
if(!bear && is_mesh_adv_cmd_fifo_empty()){
if(fri_ship_proc_lpn.poll_retry && clock_time_exceed(fri_ship_proc_lpn.poll_tick, poll_retry_interval_ms*1000)){
fri_ship_proc_lpn.poll_retry--;
if(0 == fri_ship_proc_lpn.poll_retry){
...... // poll 发出后,超时未收到FN回复的处理逻辑
}
}
else if(subsc_list_retry.retry_cnt && clock_time_exceed(subsc_list_retry.tick, timeout_ms)){
subsc_list_retry.tick = clock_time(); // also refresh when send_subsc
subsc_list_retry.retry_cnt--;
...... // friendship 刚建立成功,LPN 上报 subscription list 后,超时未收到FN回复的处理逻辑
}
}
mesh_cmd_bear_t *p_bear = (mesh_cmd_bear_t *)bear;
//mesh_cmd_nw_t *p_nw = &p_bear->nw;
mesh_cmd_lt_ctl_unseg_t *p_lt_ctl_unseg = &p_bear->lt_ctl_unseg;
u8 op = -1;
if(bear){
op = p_lt_ctl_unseg->opcode;
}
if(0 == fri_ship_proc_lpn.status){ // LPN 在 friendship建立成功后的处理分支,或者未开始发送 friend request前的处理分支。
if(bear){
if(CMD_CTL_SUBS_LIST_CONF == op){
...... // 发送 Friend Subscription List Add后,收到 Friend Subscription List Confirm的处理分支
// 详见 3.6.5.7 Friend Subscription List Add
}else if(CMD_CTL_UPDATE == op){
......
// 收到 Friend update的处理分支。包含 iv index 的处理等。
iv_update_key_refresh_rx_handle(&p_update->flag, p_update->IVIndex);
}
}else{
if(is_friend_ship_link_ok_lpn() && is_mesh_adv_cmd_fifo_empty() && clock_time_exceed(fri_ship_proc_lpn.poll_tick, get_lpn_poll_interval_ms() * 1000)){
// 当LPN在某段时间不需要睡眠,此时就不能通过 user_init_deepRetn()里面的 mesh_friend_ship_start_poll() 来执行周期发送 friend poll的事件。所以在这里增加了再次检查是否需要发送 friend poll的处理。如果有retention 唤醒,那这里的处理是不会执行到。
mesh_friend_ship_start_poll();
}
}
}else{
switch(fri_ship_proc_lpn.status){ // Be true only during establishing friend ship.
case FRI_ST_REQUEST:
if(is_mesh_adv_cmd_fifo_empty() && clock_time_exceed(fri_ship_proc_lpn.req_tick, FRI_REQ_TIMEOUT_MS * 1000)){
......
friend_cmd_send_request();
......
mesh_friend_ship_set_st_lpn(FRI_ST_OFFER); // 发送完 request 后,进入 等待接收 offer 的状态
}
break;
case FRI_ST_OFFER:
if(bear){
if(CMD_CTL_OFFER == p_lt_ctl_unseg->opcode){
if(0 != lpn_rx_offer_handle(bear)){ // 包含比较选择一个最佳的 FN
break;
}
}
}else{
if(clock_time_exceed(fri_ship_proc_lpn.req_tick, FRI_ESTABLISH_PERIOD_MS*1000)){
if(mesh_lpn_par.FriAdr){
mesh_lpn_par.link_ok = 1;
mesh_friend_key_update_all_nk(0, 0); // 在 1秒后,确定最佳的FN,然后更新对应的 friend key
......
}
mesh_friend_ship_set_st_lpn(FRI_ST_POLL);// 进入发送 Friend Poll 的状态
}
}
break;
case FRI_ST_POLL:
if(is_friend_ship_link_ok_lpn()){
if(is_mesh_adv_cmd_fifo_empty()){
mesh_lpn_par.poll.FSN = 0; // init
// send poll
fri_ship_proc_lpn.poll_retry = FRI_GET_UPDATE_RETRY_MAX + 1;
friend_cmd_send_poll(); // 把 Friend poll 压入发包fifo,在 mesh_friend_ship_proc_LPN()的最前面检查时间到后,再发包。
t_rec_delay_and_win = mesh_lpn_par.req.RecDelay + mesh_lpn_par.offer.RecWin;
mesh_friend_ship_set_st_lpn(FRI_ST_UPDATE);// 进入 等待接收 friend update 的状态
}
}else{
lpn_no_offer_handle(); // 检查如果在等待接收offer 阶段没有收到任何 offer,则建立 friendship失败,启动重新发送 Friend request
}
break;
case FRI_ST_UPDATE:
if(bear){ // current state is establishing friend ship
if(CMD_CTL_UPDATE == p_lt_ctl_unseg->opcode){
// 收到 Friend update,Friendship建立完成
//friend ship establish done
mesh_lpn_par.req.PreAdr = mesh_lpn_par.FriAdr;
iv_update_key_refresh_rx_handle(&p_update->flag, p_update->IVIndex);
mesh_friend_ship_proc_init_lpn();
friend_ship_establish_ok_cb_lpn();
}
}else{
if(clock_time_exceed(fri_ship_proc_lpn.poll_tick, t_rec_delay_and_win*1000)){
// 超时未收到 Friend update,则回到 FRI_ST_POLL 阶段,重新发送 Friend Poll
mesh_friend_ship_retry();
}
}
break;
default:
break;
}
}
}
Friendship休眠机制
定时事件,包括定时唤醒发包等,都是基于soft timer机制来完成,soft timer相关内容,请参考此章节 “soft_timer的应用”。
mesh_lpn_adv_interval_update()根据LPN的不同状态来刷新LPN的广播(唤醒)间隔,从而改变friend request/poll命令的间隔。
Friendship工作机制
LPN 节点通过打开 BLE_REMOTE_PM_ENABLE 使能了 BLE 的低功耗管理机制,该机制详情可以参考 BLE 的 handbook,比如 “AN-21112301-C_Telink B85m BLE Single Connection SDK Developer Handbook.pdf” 的 “低功耗管理(PM)”章节。简单的说是,该机制通过 soft timer 实现:
- user init 里面 通过 blc_ll_initPowerManagement_module() 注册了 睡眠管理模块 ,包含 ll_module_pm_cb 等。
- 在 ADV 状态时,定义了 ADV interval, 通过 soft timer实现 每个 interval 发送 一次广播包,然后main loop执行到 blt_sdk_main_loop() 里面的 睡眠管理单元 ll_module_pm_cb(),此时soft timer 根据 ADV interval 设定 下次唤醒的时间点,然后进入睡眠。然后时间到后,MCU 唤醒,执行 user_init_deepRetn(),然后发送下一个广播包......。
- 在 GATT connected 状态时,interval 变成 connected interval,其他 机制是一样的。
LPN的工作机制如下:
1) 刚开始时,处于未组网状态,user_init()-->user_init_peripheral-->mesh_lpn_adv_interval_update(),以可连接广播包的 interval,作为 soft timer 事件,周期唤醒和发送可连接广播包。默认支持 PB-ADV 和 PB-GATT 两种方式组网,所以user_init_peripheral() 里面,判断,如果处于 未组网状态,调用 bls_pm_setSuspendMask (SUSPEND_DISABLE) 关闭休眠机制。
2) lpn节点在组网分配完成 netkey等信息后,provisioner 开始进行 key bind 流程,由于key bind流程时间不确定,LPN 是通过在mesh_lpn_state_proc()里面判断,当连续 3秒(LPN_START_REQUEST_AFTER_BIND_MS)没有收到 key bind命令,认为key bind已完成,整个组网流程也完成。然后就调用mesh_friend_ship_set_st_lpn(FRI_ST_REQUEST)接口进入FRI_ST_REQUESTt状态。并在mesh_friend_ship_set_st_lpn()里面调用 mesh_lpn_adv_interval_update() 设定 soft timer 周期事件的 interval 为 FRI_REQ_TIMEOUT_MS。
void mesh_lpn_state_proc()
{
......
if(lpn_provision_ok){
......
}else{
if(!is_provision_success()){
......
}else{
if((!lpn_provision_ok) && node_binding_tick && clock_time_exceed(node_binding_tick, LPN_START_REQUEST_AFTER_BIND_MS*1000)){
lpn_provision_ok = 1;// provison and key bind finish
gatt_adv_send_flag = GATT_LPN_EN;
mesh_friend_ship_set_st_lpn(FRI_ST_REQUEST);
if(BLS_LINK_STATE_CONN == blt_state){
bls_ll_terminateConnection(0x13); // disconnect to establish friendship
}
}
}
}
......
}
或者在断电重新上电后,在 proc_ui()里面 调用 mesh_friend_ship_set_st_lpn(FRI_ST_REQUEST);
3) 之后在mesh_friend_ship_proc_LPN()发送Friend Request进入friendship的建立流程。
Note:LPN每发送一条mesh消息,会调用mesh_lpn_sleep_prepare(u16 op)函数设置PM,并通过soft timer更新下一次任务的回调函数和时间点。
发送 Friend Request的 函数 friend_cmd_send_request()里面有执行 mesh_lpn_sleep_prepare() 设定 下次的唤醒点在 FRI_ESTABLISH_REC_DELAY_MS 之后,并且接着执行 lpn_quick_tx() 立即把包发出去。
4) 发出 friend request 后,会等待 FRI_ESTABLISH_PERIOD_MS(默认是 1.1秒),1.1秒内,如果没有收到 任何 friend offer,则 MCU 会在 blt_sdk_main_loop() 里面的 睡眠管理单元 ll_module_pm_cb() 根据 soft timer 设置下次唤醒时间点,然后进入睡眠。时间到后,会唤醒继续发送 friend request。
5) 如果收到 friend offer,则执行收包处理和建立Friend ship的过程,详见LPN收包处理接口的介绍。
6) 当 Friend ship建立完成后,会执行mesh_lpn_adv_interval_update() 更新 基准唤醒周期为 poll interval。除了这个 基准唤醒周期,还有每次发包事件等 调用 mesh_lpn_sleep_prepare()增加的 timer 事件。
7) 发送 friend poll 是通过 mesh_friend_ship_start_poll()来触发。目前有三个地方调用: - user_init_deepRetn()->mesh_friend_ship_start_poll() 这个是正常的发送,即每次 poll interval 唤醒后,再调用一次来实现周期发送 poll。 - mesh_lpn_poll_md_wakeup()->mesh_friend_ship_start_poll() 这个是当检测到 Friend Node 的cache还有数据待取时的调用。 - mesh_friend_ship_proc_LPN()->mesh_friend_ship_start_poll() 这个是特殊情况下,才会触发到。即某一个时间段,不进入sleep时,就没办法通过 user_init_deepRetn()->mesh_friend_ship_start_poll() 来触发发送 poll,所以在这里触发。
LPN接收目的地址是组号的机制
- 每次建立 friend ship 的时候,LPN 会发送 subscription list add 命令(CMD_CTL_SUBS_LIST_ADD)
- FN 节点收到后把组号list存储下来,详见 friend_subsc_list_add_adr()的处理,
- 后续,FN收到其他节点发过来的命令的目的地址符合该组号list里面的组号的时候,就会帮助 LPN 缓存这些信息,并在接收到LPN的poll命令后,发送给 LPN 处理。测试 的时候, 给 LPN 配置 一个 组号。 然后 每次 LPN 建立 friend ship的 时候,LPN 就会 自动 发出 CMD_CTL_SUBS_LIST_ADD。
LPN常用参数配置
以下 FN 表示 Friend Node, LPN 表示 Low Power Node。
Friend Node
- FN_CACHE_SIZE_LOG: 给 LPN 缓存的最大消息数量为 2 的 FN_CACHE_SIZE_LOG 次方。
- FRI_REC_WIN_MS:FN要求LPN的最小接收窗口,默认为 20ms。表示 LPN 发送 Poll后,监听广播包的时间,如果超时,则表示没收到 Friend node的回复。然后会重发 Poll 命令。FRI_REC_WIN_MS 不能设置为太小,因为 广播包发送有 3 个channel,以及 FN 有可能在处理别的优先级更高的事情,导致 FN 回复 LPN 的时间点没有那么精准。
Low Power Node
- FRI_REQ_TIMEOUT_MS: 配置 发送 friend request 的 interval,默认是 2 秒。如果产品定义要求功耗比较低,可以按实际情况改大。
- FRI_ESTABLISH_WIN_MS:表示发送 Friend offer 后,等待接收 Friend offer 的最大时间,spec 规定该时间是 1秒,因为要尽可能收到更多 FN 节点发过来的 offer,然后选择一个最佳的 FN。一般不建议修改这个值。但是如果产品要求功耗很低,只修改 FRI_REQ_TIMEOUT_MS 不能满足需求,则可以考虑把 FRI_ESTABLISH_WIN_MS 改小一点。
- FRI_POLL_INTERVAL_MS:friend poll 的 interval,默认是 2 秒。默认时间比较短,主要是考虑用于单火开关灯低功耗设备,命令响应时间要求不能太长。如果产品定义要求功耗比较低,可以按实际情况改大。
- FRI_POLL_RETRY_MAX:LPN 发送 Poll 命令后未收到 FN 的回复,当次数超过该值时,则 翻转一次 poll 里面的 FSN 的值,然后再发送 Poll,如果还是 连续 FRI_POLL_RETRY_MAX 次 没有收到 FN 的回复,则认为 FN 离线。此时 LPN 就会断开当前 friendship,然后开始发送 friend request,尝试和其他 friend node建立 friend ship。
- LPN_SCAN_PROVISION_START_TIMEOUT_MS: 表示 LPN 发送 Friend request后,一直没有收到 FN 节点回复 的 offer,如果超过该时间,节点为了节省电量,LPN 会进入 睡眠状态,需要按键唤醒后,才会重新开始发送 friend request。改时间默认为 60秒。
LPN使用演示
硬件准备
此演示基于GATT master dongle模式进行,APP和gateway模式的操作步骤和GATT master dongle模式类似。注意,gateway模式时,gateway节点本身也支持friend功能。
一个8269 GATT master dongle和2个8258 mesh dongle(一个烧录8258_mesh.bin, 默认支持Friend功能。另一个烧录8258_mesh_LPN.bin,即LPN节点)。
注意事项:
- LPN 默认支持 generic ONOFF, generic Level,不使能 lightness 和 light CT。
- LPN 不接收 0xffff 的目的地址的。 只接收 单播地址和 订阅的 组号。因为空中太多 0xffff 的命令了, 如果 LPN poll 间隔比较久的话, friend 缓存的命令,很容易被刷掉了。
测试步骤
以下提到的和时间相关的宏,客户可以按实际需求更改。
Step 1 mesh friend节点(FN)上电,并用SIG_MESH_TOOL对其进行组网。
Step 2 未配网的LPN节点上电,此时LPN处于唤醒状态。
LPN节点上电后,红色LED会处于ON的状态。未配网状态下,不进入sleep状态,目的是可以支持GATT组网和ADV组网。在此状态下,如果1分钟(LPN_SCAN_PROVISION_START_TIMEOUT_MS)之后,还未开始组网流程,则进入deep sleep状态,不再发送ADV,并关闭LED,目的是节省功耗,避免长时间处于工作高功耗状态。如果已经进入deep sleep,则需要按下mesh_lpn_key_map[ ]里面定义的SW1, SW2来唤醒,唤醒后才会重新开始发送ADV和开始组网流程。
Step 3 对处于唤醒状态的未配网LPN进行组网和bind key流程。
Bind key成功,3秒钟后(LPN_START_REQUEST_AFTER_BIND_MS),LPN会自动重启,然后把lpn_provision_ok置为1,并进入LPN模式,开始每2秒(FRI_REQ_TIMEOUT_MS)发送一次friend request命令。
组网成功后,为了减少处理无效的network message,降低功耗,LPN只接收从FN通过friend ship发过来的信息。如果要接收普通network message,把mesh_lpn_rx_master_key初始化为1即可。
Step 4 当有FN存在时,会自动建立friend ship,只有建立成功(红灯闪烁3次),LPN才能接收message。
FN收到friend request后,会自动回复friend offer,然后按flow建立friendship,如果建立成功,会回调friend_ship_establish_ok_cb_lpn(),并在里面执行红灯会闪烁3次(LGT_CMD_FRIEND_SHIP_OK)。然后开始以2秒周期(FRI_POLL_INTERVAL_MS)发送friend POLL。当FN收到POLL后,如果有需要发送给LPN的cache message,则会把这个message发送给LPN。Cache message(network PDU)的默认最大个数是 4 个(2 ^ FN_CACHE_SIZE_LOG)。
假如暂时没有FN响应POLL,LPN会一直按2秒周期发送friend request。
Step 5 “mesh”窗口显示LPN节点及ONOFF操作。
首先打开“mesh”窗口,点击“LPN_get_level”INI命令,下图左下角出现a3 ff 00 00 00 00 00 00 04 00 82 05,其中04 00是LPN的unicast address(如不正确,需要修改),按回车键,发送该命令,收到LPN的level status回复后,在UI中就会显示LPN节点,然后可以对LPN发起ONOFF操作等,如下图。
因为LPN是不接收destination address为 0xffff的message,所以需要按unicast的方式发送命令。如果已经对LPN配置了组号,也可以按group的方式发送命令。
另外留意,点击了“Nodes”按钮或者重新打开“Mesh”窗口后,VC tool会把所有的节点置为离线状态,然后发送lightness get all(destination address为 0xffff)的命令,重新获取节点状态,但是没有单独给LPN节点按unicast destination address发送get命令,所以需要手动再点“LPN_get_level”命令或者点击mesh 窗口里面的ON/OFF命令才会显示在线状态,否则为离线状态。
Step 6 group操作,和普通节点的操作一样,请参考“4.5.2 分组控制(即subscription的功能演示)”。
Step 7 LPN检测到FN断电,自动重新寻找新的FN。
当FN断电,LPN retry 8次(FRI_POLL_RETRY_MAX)POLL命令,其中POLL的间隔是170ms(FRI_REC_DELAY_MS + FRI_REC_WIN_MS),如果LPN还是没有收到FN的回复,则认为FN已断电,会断开friend ship并回调friend_ship_disconnect_cb_lpn(),如果需要执行led闪烁操作,也请在该回调函数里面添加。然后重新发送friend request寻找新的friend节点。
Step 8 目前demo SDK 1个friend node默认最多同时和两个LPN建立friend ship,如需修改,设置MAX_LPN_NUM 即可,最大值是16。
当LPN断电后,FN会检测10秒钟(LPN_POLL_TIMEOUT_100MS),如果还未收到POLL命令,则认为节点已断电,此时FN会清除该LPN的信息。
Step 9 按键发送ALL ON/OFF命令。
当LPN处于retention sleep状态的时候,按下按键SW2(MESH_LPN_CMD_KEY)唤醒LPN,然后通过suspend_handle_next_poll_interval() –> mesh_lpn_wakeup_key_io_get()检测到该按键,然后执行test_cmd_wakeup_lpn()函数交替发送ALL ON/OFF命令。LPN主动发送access layer命令默认使用主安全资料加密。
Step 10 恢复出厂设置。
长按按键SW1 (MESH_LPN_FACTORY_RESET_KEY) 3秒钟(LONG_PRESS_TRIGGER_MS) 触发factory reset.
app.c文件介绍
广播包以及广播响应包的定制:
参考《8258 MESH工程介绍》。
fifo部分配置:
参考《8258 MESH工程介绍》。
app_event_handler ():
参考《8258 MESH工程介绍》。
main_loop ():
参考《8258 MESH工程介绍》。
user_init():
参考《8258 MESH工程介绍》。
proc_ui():
该函数主要是做些UI方面的处理,比如button检测函数,以及对应的测试代码。当LPN处于非GATT ota模式时,会发送friend request请求建立friend关系。按下按键SW2(KEY_),会交替发送ON/OFF命令;长按按键SW1 (KEY_RESET) 3秒钟(LONG_PRESS_TRIGGER_MS) 触发factory reset.
test_cmd_wakeup_lpn():
当命令按键按下时(目前demo dongle是SW2),会唤醒程序,并执行test_cmd_wakeup_lpn(),test_cmd_wakeup_lpn()里面会发出ON OFF命令。命令发送完成后进入sleep。该功能仅做demo演示用。
mesh_lpn_state_proc():
该函数主要是LPN节点工作状态的处理:
(1) 处于LPN_MODE_NORMAL模式时,设置LED闪灯
(2) LPN已配网且连续60秒未进入PM,回到FRI_ST_REQUEST状态.
(3) LPN未配网且上电60秒(LPN_SCAN_PROVISION_START_TIMEOUT_MS*1000)时间内没有被组网,进入睡眠。
(4) LPN绑定appkey 3秒后,进入FRI_ST_REQUEST状态。
mesh_lpn_pm_proc():
该函数主要管理LPN节点的功能,用户可在这个函数处理一些PM状态。比如LPN节点已组网且处于连接态时,总是使能ENABLE_SUSPEND_MASK来节省功耗。用户按下按键时,4秒内不进入PM的demo。
mesh_lpn.c文件介绍
mesh_lpn_sleep_prepare ():
该函数处理LPN的睡眠处理函数。lpn_sleep.op表示因为什么命令或者事件需要进入睡眠,并在唤醒后处理该事件的后续动作。
比如当lpn_sleep.op等于CMD_CTL_POLL,表示刚刚发送了POLL message,然后需要进入时间为receive delay的retention sleep,然后唤醒进入receive window,如下图:
其他的自定义事件有:
CMD_ST_SLEEP:一个friend ship的交互周期完成后,进入2秒(friend request interval或者poll interval)的retention sleep状态,然后唤醒进入下一个周期的交互。
CMD_ST_NORMAL_TX: 主动发送mesh消息后,设置下次进入mesh_lpn_poll_md_wakeup的时间。
CMD_ST_POLL_MD:当发送POLL后,收到FN回复MD(more data)为1,则睡眠100ms(FRI_POLL_DELAY_FOR_MD_MS)后,唤醒,并继续发送POLL接收剩余的message。
mesh_feature_set_lpn():
LPN的一些可配置参数的初始化。主要是配置 LPN_POLL_TIMEOUT_100MS,默认是10秒。
Switch工程介绍
Switch的功能介绍
Switch的功能主要是为了在mesh节点中加入遥控器的功能。使得switch能够控制mesh网络中的节点。switch的节点需要先被provisioner加入网络中,然后可以通过switch上的按键来控制mesh网络中的节点。
Switch的原理介绍
Switch是控制mesh的遥控器,由于遥控器是低功耗节点,必须触发配对模式才能进行provision,加入网络中之后,能够控制mesh中的节点。
app.c文件介绍
广播包以及广播响应包的定制:
参考《8258 MESH工程介绍》。
fifo部分配置:
参考《8258 MESH工程介绍》。
app_event_handler ():
参考《8258 MESH工程介绍》。
main_loop ():
参考《8258 MESH工程介绍》。
user_init():
参考《8258 MESH工程介绍》。
proc_ui ():
每隔4ms扫描一次按键部分。mesh_proc_keyboard为处理按键部分的接口函数。
- 当keycode为RC_KEY_A_ON时,switch发送all_on(打开网络中所有的灯)的指令。
- 当keycode为RC_KEY_A_OFF时,switch发送all_off(关闭网络里所有的灯)指令。
proc_led():
首先通过cfg_led_event配置LED闪烁的频率和时间,如cfg_led_event(LED_EVENT_FLASH_1HZ_4S)表示配置LED以1Hz频率闪烁4s。然后通过proc_led控制LED闪烁部分的处理。
mesh_switch_init():
mesh_switch_init中有两部分的设置:
上面那部分是switch部分的唤醒IO部分的代码设置,以及打开唤醒使能标记位。
下面那部分是LED的IO部分的设置,默认将led管脚设置为GPIO模式,100kohm下拉电阻,并上电闪烁4下。
proc_rc_ui_suspend():
休眠函数部分的处理函数为proc_rc_ui_suspend()。
休眠部分的处理目前设定为广播状态下,如果不发包直接进入deep状态,按键唤醒后,发完包之后继续进入deep状态。触发配对模式之后30s之后进入deep状态,链接状态暂时不进入deep状态。
休眠部分的处理流程详见本章的 ”休眠部分的处理流程图”小节。
kb_scan_key ():
kb_scan_key为矩阵键盘扫描部分的接口,numlock_status目前默认为0,表示全键盘中的numlock,read_key为读取的key值。
按键事件检测流程介绍
代码框图
void mesh_proc_keyboard ()
{
static u32 tick_key_pressed, tick_key_repeat;
static u8 kb_last[2];
int det_key = kb_scan_key (0, 1);
......
///////////////////////////////////////////////////////////////////////////////////////
// key change:pressed or released
///////////////////////////////////////////////////////////////////////////////////////
if (det_key) {
......
if(kb_event.cnt)
{
...... // key was detected pressed. MCU run the code here one time for one press action.
}
/////////////////////////// key released ///////////////////////////////////////
else {
...... // key was released . MCU run the code here one time for one release action.
rc_repeat_key = 0;
key_released = 1;
}
......
}
//////////////////////////////////////////////////////////////////////////////////////////
// no key change event
//////////////////////////////////////////////////////////////////////////////////////////
else if (kb_last[0])
{
// long pressed // key was detected in a continuously pressed state. for each main_loop, MCU run the code here until the key is released.
if (clock_time_exceed(tick_key_pressed, 2000000)) // long pressed // 2000000 is the threshold for long press detection
{
if ((kb_last[0] == RC_KEY_A_ON && kb_last[1] == RC_KEY_1_OFF) ||
(kb_last[1] == RC_KEY_A_ON && kb_last[0] == RC_KEY_1_OFF))
{
if(SWITCH_MODE_NORMAL == switch_mode){ // long pressed event
switch_mode_set(SWITCH_MODE_GATT);
}
}
}
......
}else{
...... // no key was detected.
key_released = 1;
}
......
}
按键事件介绍:
- 按键按下:在注释为 “key was detected pressed” 的地方,表示检测到按键按下
- 按键释放:在注释为 “key was released” 的地方,表示检测到按键释放
- 按键长按:在注释为 “// long pressed // 2000000 is the threshold for long press detection” 的地方,表示检测到按键长按事件
- 无按键事件:在注释为 “no key was detected.” 的地方,表示检测到按键按下
开发者可以在上述地方添加自己的按键功能。
Switch工程长按处理逻辑
判断当前按键被按下且用clock_time_exceed从按键被按下开始计时,当达到设定的时间时,则触发长按按键的处理
举例:按键RC_KEY_A_ON与RC_KEY_1_OFF长按两秒触发switch进入GATT模式。
else if (kb_last[0])
{
// long pressed // key was detected in a continuously pressed state. for each main_loop, MCU run the code here until the key is released.
if (clock_time_exceed(tick_key_pressed, 2000000)) // long pressed // 2000000 is the threshold for long press detection
{
if ((kb_last[0] == RC_KEY_A_ON && kb_last[1] == RC_KEY_1_OFF) ||
(kb_last[1] == RC_KEY_A_ON && kb_last[0] == RC_KEY_1_OFF))
{
if(SWITCH_MODE_NORMAL == switch_mode){ // long pressed event
switch_mode_set(SWITCH_MODE_GATT);
}
}
}
......
}
使用soft_timer周期发送命令示例
使用soft_timer周期发送命令示例请参考此章节"soft_timer周期发送命令示例"。
Switch部分的配置
key table
#define KB_MAP_NORMAL {\
{RC_KEY_1_OFF, RC_KEY_2_OFF, RC_KEY_1_ON}, \
{RC_KEY_3_ON, RC_KEY_3_OFF, RC_KEY_2_ON}, \
{RC_KEY_4_ON, RC_KEY_4_OFF, RC_KEY_R}, \
{RC_KEY_A_OFF, RC_KEY_A_ON, RC_KEY_UP}, \
{RC_KEY_L, RC_KEY_DN, RC_KEY_M}, }
用户可以根据实际驱动脚的pin的数量和扫描脚的数量来配置实际的key_table部分的内容。驱动脚对应列数,扫描脚对应行数。
配置驱动脚和扫描脚的IO
#define KB_DRIVE_PINS {GPIO_PB4, GPIO_PB5, GPIO_PB6}
#define KB_SCAN_PINS {GPIO_PE3, GPIO_PE2, GPIO_PE1, GPIO_PE0, GPIO_PD3}
根据实际用到的管脚来修改KB_DRIVE_PINS和KB_SCAN_PINS对应的宏定义。
其次根据对应的PIN定制drive pin和scan pin的IO特性:
drive pin对应的IO属性设置:
#define PB4_FUNC AS_GPIO
#define PB5_FUNC AS_GPIO
#define PB6_FUNC AS_GPIO
#define PULL_WAKEUP_SRC_PB4 MATRIX_ROW_PULL
#define PULL_WAKEUP_SRC_PB5 MATRIX_ROW_PULL
#define PULL_WAKEUP_SRC_PB6 MATRIX_ROW_PULL
#define PB4_INPUT_ENABLE 1
#define PB5_INPUT_ENABLE 1
#define PB6_INPUT_ENABLE 1
scan pin对应的IO属性设置:
#define PE3_FUNC AS_GPIO
#define PE2_FUNC AS_GPIO
#define PE1_FUNC AS_GPIO
#define PE0_FUNC AS_GPIO
#define PD3_FUNC AS_GPIO
#define PULL_WAKEUP_SRC_PD3 MATRIX_COL_PULL
#define PULL_WAKEUP_SRC_PE0 MATRIX_COL_PULL
#define PULL_WAKEUP_SRC_PE1 MATRIX_COL_PULL
#define PULL_WAKEUP_SRC_PE2 MATRIX_COL_PULL
#define PULL_WAKEUP_SRC_PE3 MATRIX_COL_PULL
#define PE3_INPUT_ENABLE 1
#define PE2_INPUT_ENABLE 1
#define PE1_INPUT_ENABLE 1
#define PE0_INPUT_ENABLE 1
#define PD3_INPUT_ENABLE 1
假如在驱动管脚部分将GPIO_PB6修改为GPIO_PB7,则需要修改几个部分。
1). #define PB6_FUNC AS_GPIO----->>>#define PB7_FUNC AS_GPIO
2). #define PULL_WAKEUP_SRC_PB6 MATRIX_ROW_PULL-------->>
#define PULL_WAKEUP_SRC_PB7 MATRIX_ROW_PULL
3). #define PB6_INPUT_ENABLE 1 ----------->>
#define PB7_INPUT_ENABLE 1
Switch开关灯
根据不同的键值发送不同的命令以及做出相应的处理。
参考具体的按键处理程序mesh_proc_keyboard()。switch在加入网络前,无法控制网络中灯的开关。可以通过同时按键RC_KEY_A_ON和RC_KEY_1_OFF超过2s,之后触发配对模式,通过provisioner将switch加入网络,之后就可以通过RC_KEY_A_ON和RC_KEY_A_OFF来全开/全关网络中的灯。详见本章”switch操作” 小节。
Switch操作
继续10.4中关于provision的操作之后,烧录switch固件。Switch烧录部分的连接图如下所示:
Switch按键的示意图为:
将switch节点的设备上电,上电之后由于switch属于低功耗节点,所以必须先触发配对模式,使得switch通过provisioner加入到mesh网络中。触发配对模式的方式为同时按压RC_KEY_A_ON和RC_KEY_1_OFF超过2s。
Switch上的led灯连续闪烁4次,表示进入配对模式,provisioner节点上电,等待15s左右,switch上的led连续闪烁4次,表示switch已经加入到网络。
Switch就可以通过RC_KEY_A_ON和RC_KEY_A_OFF正常控制网络中的灯节点全开/全关。
遥控器的运行流程图
休眠处理的流程图
修改按键发送命令的目的地址
下图所示的 4 组按键支持修改命令的目的地址:
- 1_ON / 1_OFF :按键默认功能是发送 目的地址为 0xC000 的 onoff 命令。
- 2_ON / 2_OFF :按键默认功能是发送 目的地址为 0xC001 的 onoff 命令。
- 3_ON / 3_OFF :按键默认功能是发送 目的地址为 0xC002 的 onoff 命令。
- 4_ON / 4_OFF :按键默认功能是发送 目的地址为 0xC003 的 onoff 命令。
如需修改按键发送命令的的地址,比如把 “1_ON / 1_OFF” 的目的地址由 0xC000 改为 0xD000, 可以通过上位机的 INI 命令发送 publish set 命令配置示例如下(示例遥控器节点的 primary address,也就是 node address 是 0x0025):
cfg_pub_set_sig0025 =e8 ff 00 00 00 00 00 00 25 00 03 25 00 00 0D 00 00 ff 00 15 00 10
把 “2_ON / 2_OFF” 的目的地址由 0xC001 改为 0xD001, 示例如下:
cfg_pub_set_sig0026 =e8 ff 00 00 00 00 00 00 25 00 03 26 00 01 0D 00 00 ff 00 15 00 10
publish set的命令参数是:
详见 mesh spec V1.1 的 "4.3.2.16 Config Model Publication Set" 小节。
如果是通过手机 App 进行设置,请参考33 Android and iOS APP使用说明的 “Device Setting(Switch 设备)” 章节。
如果希望再增加一组 可以配置 onoff publish 地址的按键,比如 “5_ON / 5_OFF”, 则修改 ELE_CNT_EVERY_LIGHT 的值为 5,然后配置 (primary address + 4) 的 onoff client model的 publish address 即可。
ELE_CNT_EVERY_LIGHT 的介绍,请参考 此章节 “节点的element个数的定义” 。
Switch的IV index更新模式
- 情景1:Switch在每96小时唤醒一次,发送 一次 security beacon,然后进入scan adv 状态,扫描 security beacon,如果扫描到 任何一个有效的 security beacon,则进入休眠。如果超时(SWITCH_IV_RCV_WINDOW_S)未收到,也会进入睡眠。
详见switch_trigger_iv_search_mode(int force) 的处理。
- 情景2:断电,然后重新上电后,也会调用 switch_trigger_iv_search_mode(1) 去执行 情景 1 的发送和扫描security beacon动作。
平台接入
当需要接入某个平台的时候,需要配置一些选项,特别是provision的方式。通过配置MESH_USER_DEFINE_MODE进行选择。
Normal模式
No OOB provision模式
配置方式:
Provision采用MESH_NO_OOB模式。
VENDOR_ID是0x0211
测试的时候,直接使用我们的手机app或者上位机工具组网即可。
Static OOB provision模式
(1) Light节点烧录Static oob
在烧录firmware的时候,直接写16byte在 flash 固定位置FLASH_ADR_STATIC_OOB(例如0x77800, 以实际code为准)即可。如果没有烧录(全为0xff),则表示使用no oob模式。如果需要修改flash address,修改FLASH_ADR_STATIC_OOB这个宏即可。
(2) Light节点的Device uuid获取方法
Device uuid默认是通过user_prov_multi_device_uuid() -> uuid_create_by_mac(tbl_mac,prov_para.device_uuid)生成的,可通过以下几种方法获取:
a) 通过BDT tool读取prov_para.device_uuid
b) 通过通用APP获取unprovision广播包
c) 通过TI sniffer获取unprovision广播包
d) GATT provision的时候,在连接成功的时候,会打印出device uuid
e) Gateway provision的时候,选择某个scan得到的节点的时候,会打印出device uuid
(3) 用户定制uuid的方法
如果用户想定制device uuid,把NORMAL_MODE_DEV_UUID_CUSTOMIZE_EN 置为1即可。
(4) Provisioner static oob数据库
Provisioner端需要填写节点的oob数据到oob数据库文件(oob_database.txt)中:
oob数据库数据格式如下:
device uuid(16byte) + oob(16byte)
例如: 1d4d89b32765103d8a0b29e4103acdab ff000000000000000000000000000000
字段解析如下:
1d4d89b32765103d8a0b29e4103acdab:被组网节点的device uuid.
ff000000000000000000000000000000:被组网节点的oob数据,即Light Firmware在flash 固定位置FLASH_ADR_STATIC_OOB(例如0x76300)写入的值。
如有多个节点,换行增加即可,比如:
1d4d89b32765103d8a0b29e4103acdab ff000000000000000000000000000000
9f6f6e6e5e90943db9be6d79446a9e37 ffaa0000000000000000000000000000
(5) 测试步骤
按正常流程测试组网即可。
static oob组网成功的测试结果,使用GATT master dongle模式截图如下:
capability data,更详细的解析请参考spec以下章节:
数据解析如下:
01 02 01 00 00 01 00 00 00 00 00 00
01:Provisioning PDU Type, 01 表示Provisioning Capabilities
02:Number of Elements
01 00:Algorithms
00:Public Key Type
01:Static OOB Type
00:Output OOB Size
00 00:Output OOB Action
00:input OOB Size
00 00:Input OOB Action
阿里天猫精灵平台
配置方式
Provision采用MESH_STATIC_OOB模式。
VENDOR_ID是0x01A8
向阿里申请三元组
默认的三元组信息在是空的(con_sec_data[16]为空),具体代码在user_ali.c如下图所示:
所以客户需要从阿里获取三元组:
(共24byte: PID(4byte,小端) + MAC(6byte,大端) + secret data(16 byte)),
然后烧录到FLASH_ADR_THREE_PARA_ADR(0x78000),代码会自动读取0x78000的参数。
使用SDK默认的三元组信息
当只是为了做演示,可以使用SDK默认的三元组信息,打开方式如下:(使能user_ali.c文件里面预设的con_sec_data[])
但是,因为默认的三元组只有一个,所以测试的时候,只能用于单节点的演示。
通过天猫精灵组网
直接通过天猫精灵的语音命令组网就可以了。
通过上位机组网
因为天猫精灵模式默认只支持static oob的模式,oob和三元组是有绑定关系的,所以上位机需要知道三元组的信息才能组网。所以需要把三元组添加到上位机的这个文件:
SIG_MESH_Release_Vxxx -> tools -> telink-ble-phone -> three_para.txt
SDK默认的三元组信息已经添加在这个文件里面了。添加新的三元组信息的时候,参考这个格式添加即可。
然后直接按“4. 调试工具操作说明”章节里面的“组网部分”进行组网就可以。
同时支持static oob和no oob模式
天猫精灵模式,节点端默认只响应static oob的组网模式,如果需要同时支持no oob模式,把ENABLE_NO_OOB_IN_STATIC_OOB由0改为1,即可。
小米小爱同学平台
配置方式
Provision采用小米专有mesh配网模式。
VENDOR_ID是0x038F。
认证数据的设定
- 研发测试模式:默认采用SDK预设的认证数据,dev_cert_pri[],所以只能用于单节点的测试模式,默认有6个证书。因为这几个证书是公开的,如果已经被别人使用,会产生冲突。所以客户最好使用自己申请的证书,然后按生产模式的方式进行测试。
- 生产模式:当需要生产,或者多节点网络的测试时,需要通过写flash的方式,步骤如下:
Step 1 把MI_CER_MODE的宏定义为FLASH_CER_MODE,重新生成firmware。
Step 2 把认证数据烧录到DEV_SK_FLASH_ADR(0x7f000)的位置。
组网测试
烧录完firmware后,请确保flash的MAC地址(512K flash是在0x76000位置,1M flash是在0xFF000位置)是空的(即全为0xff),否则会提示“无法连接”或者“组网失败”。
Firmware在第一次上电启动的时候,会从证书里面把MAC提取出来,写到MAC地址扇区,并生成一些必要的参数。
节点上电后,直接通过小爱同学进行组网即可。(语音指令示例:“小爱同学,添加设备”)。
双VENDOR模式(阿里天猫精灵和小米小爱同学)
功能说明
节点在出厂的时候,会同时发送阿里和小米模式的广播包,既可以被天猫精灵组网,也可以被小爱同学组网。一旦组网成功,后续的功能都按照被选择的模式执行,包括vendor model,以及transmit count等参数,参数切换函数详见mesh_ais_global_var_set()。
未配网状态下的产测功能依照小米模式进行。
已配网成功后,执行踢灯命令,或者恢复出厂设置动作,均可恢复进入双vendor模式,重新选择。
当前处于哪一种模式,可以通过provision_mag.dual_vendor_st这个变量查看。
配置方式
配网方式以及一些必要的参数设置,请参考本章 “天猫精灵”和“小爱同学”平台的介绍。
恢复出厂配置
8258_mesh/8269_mesh节点
函数介绍
Factory reset的reset执行动作,请参考factory_reset_handle()或者kick_out():
{
irq_disable();
factory_reset(); // 执行flash的擦除动作
#if DUAL_MODE_WITH_TLK_MESH_EN
UI_resotre_TLK_4K_with_check();
#endif
show_ota_result(OTA_SUCCESS); // LED 指示
start_reboot(); // MCU重启
}
如果客户有修改flash map,或者使用了customer flash section(默认是0x7a000—0x7f000,且默认不对该区域执行erase),需要重新确认factory_reset()函数,确认是否有sector错误erase或者遗漏erase。
上电序列检测函数factory_reset_cnt_check ():
(a) 上电后, 经过VALID_POWER_ON_TIME_US(默认50ms)时间后,才开始检测上电时序。因为要过滤某些电源上电时产生的脉冲电压。
(b) clear_st等于4:
首先通过reset_cnt_get_idx ()获取断电前的序列,如果是奇数,则表示之前的上电序列不符合预期,直接清除上电序列,重新开始计数。如果是偶数,则表示符合预期。
然后,检查存储的上电序列值是否满足触发factory reset的条件,如果满足,则执行factory reset。如果不满足,则立即对序列值加1处理。
(c) clear_st等于3,通过get_reset_cnt()得到上电序列值,获取计时时间,并开始第一阶段的计时。
(d) clear_st等于2,第一阶段计时满足要求,通过get_reset_cnt()得到上电序列值,获取计时时间,并开始第二阶段的计时。
(e) clear_st等于1,如果第二阶段计时结束,还未断电,则表示上电时间不符合预期,直接清除上电序。
默认触发动作
低功耗节点,比如LPN,不能使用上电序列来触发,因为低功耗节点在睡眠的时候无法计时及判断时序。可以考虑通过按键触发等方式,调用 14.1 (1) 章节的函数即可。
非低功耗节点,可以用下面的流程来恢复出厂配置:
Step 1 上电SIG_mesh模组,等待factory_reset_serials []里面设定的最大时间,默认是30s。
因为超过30秒,就不符合factory_reset_serials[]定义的任意一个上电序列,将清除之前未知的上电记录,确保后续的操作符合第一次上电时序要求。
Step 2 SIG_mesh模组重新上电3次,要求在上电后0~3s内断电(符合factory_reset_serials[]前3个上电序列的要求)。
Step 3 SIG_mesh模组重新上电2次,要求在上电后3~30s内断电(符合factory_reset_serials[]后两个上电序列的要求)。
Step 4 重新上电SIG_mesh模组。
在user_init()里面的factory_reset_handle()会检测到之前的5次上电序列符合触发“Factory Reset”的要求,红色led指示灯以1Hz频率闪烁8s,恢复出厂配置成功。
注意:
因为某些供电模组,断电完成需要一定时间,为了确保MCU完全停止,所以要求断电之后,需要等待一段时间再上电,比如2秒等。这个时间需要根据实际的供电模组确认。
上电时序由下面数组定义:
u8 factory_reset_serials[] = { 0, 3,
0, 3,
0, 3,
3, 30,
3, 30,};
修改为其它上电序列的方法
在factory_reset_serials[]数组中修改、增加、删除对应的序列即可。要求:
-
左边的值比右边的小。
-
增加或者删除时,必须是增加或删除两个值(因为一个序列对应两个值)。
比如,要改为6个序列,改为以下方式即可。
u8 factory_reset_serials[] = { 0, 3,
0, 3,
0, 3,
3, 30,
3, 30,
3, 30,};
触发复位动作后还能恢复到之前mesh网络的功能
以上1----3介绍的模式是正常的恢复出厂设置的模式。
有些用户触发恢复出厂设置后,如果在一定时间内还未重新配网(比如30秒),希望能自动恢复到之前的网络信息。sdk中提供了实现此功能,所需要的2个API:
-
mesh_reset_network(u8 provision_enable):把ram里的网络信息恢复到默认网络状态,参数provision_enable为1时表示设备会发unprovision beacon和pb_adv,设备可被重新配网。此时只是RAM数据还原了,但是flash信息并没有改变。
-
mesh_revert_network():从flash里重新加载网络信息。
实现方式:用户触发恢复出厂设置动作进入kick_out函数时,直接调用mesh_reset_network(1)把ram里的网络信息恢复到默认网络,不调用factory_reset()和start_reboot()。如果用户想恢复网络,直接调用mesh_revert_network()从flash里重新加载mesh信息即可。
gateway节点 + 上位机
gateway进行复位,需要执行两个动作:一个是删除mesh_database.json, 另一个是把gateway dongle的flash的参数区也都清除(比如通过5次上电的复位方式)。因为有两个动作需要执行,所以为了方便操作,在上位机增加了一个“GATE_RESET”按钮, 执行这个按钮后,就会执行上述两件事,gateway dongle把自己的flash参数区清除后,会自动调用start_reboot()软重启。
注意,这个按钮,只是复位了gateway本身,并不会发命令出去把其他节点也进行RESET。
发送命令E9 FF 02, E9 FF表示的是宏HCI_CMD_GATEWAY_CTL,相关的处理流程在函数app_hci_cmd_from_usb_handle中。
else if (HCI_CMD_GATEWAY_CTL == type){
#if IS_VC_PROJECT
ret = fifo_push_vc_cmd2dongle_usb(buff, n);
#else
#if GATEWAY_ENABLE
ret = gateway_cmd_from_host_ctl(hci_data, hci_data_len);
#endif
#endif
}
opcode为02表示HCI_GATEWAY_CMD_RESET,相关的处理流程在函数gateway_cmd_from_host_ctl中。
else if (op_code == HCI_GATEWAY_CMD_RESET){
factory_reset();
light_ev_with_sleep(4, 100*1000); //10hz for about the 1s
start_reboot();
}
GATT master dongle + 上位机
master dongle的flash里面是没有存储参数的,所以只要删除 上位机的mesh_database.json文件,然后重新打开上位机工具即可。
LPN节点
低功耗节点,不能通过5次上电的方式进行复位,因为当处于sleep状态时,断电后,还需要一段时间才会把电消耗完,这种情况下,就不好确定是否断电成功。所以LPN节点一般需要使用按键等方式,触发调用本章第一小节的函数即可。
Demo LPN: 长按按键SW1(MESH_LPN_FACTORY_RESET_KEY)3秒以上(LONG_PRESS_TRIGGER_MS),会闪灯4次,提示复位成功。
Switch节点
低功耗节点,不能通过5次上电的方式进行复位。需要使用按键的方式,长按按组合键:RC_KEY_A_ON + RC_KEY_4_OFF三秒以上,会闪灯4次,提示复位成功。
Fast bind模式
功能介绍
此模式属于非标准模式,用于加快标准流程的配网速度。具体改善如下:Spec定义provision flow执行完,分配好unicast address,netkey 等信息后,还需要按顺序发送get composition data,app key add以及对获取到的composition data里面的每一个model执行key bind动作。这个在有些应用场景中不需要那么复杂,在多数情况下一个设备仅仅会有一个APPkey而已。所以我们定义了这个Fast bind模式。
Fast bind模式主要是优化key bind部分,当provisioner发送app key add后,不再发送key bind命令。节点收到app key add后,自行对自身的所有model执行key bind动作,详见PROVISION_FLOW_SIMPLE_EN这个宏括起来的code。
另外,为了更进一步的简化组网流程,我们demo app也不需要执行get composition data的命令,直接通过组网获取的device UUID里面的PID来查询数据库来获取对应的composition data的所有信息。下图是firmware端把PID写到device uuid的函数。
如果客户不希望通过查询数据库的方式获取composition data,也可以修改app的flow,增加get composition data的命令。
配置方式
节点Firmware:把PROVISION_FLOW_SIMPLE_EN设置为1即可。
功能演示
上位机的配置
APP界面的配置
APP 使能该选项:
主页 -- setting -- settings -- Enable Private Mode(Default Bound)
私有Fast provision功能
功能介绍
区别于remote provision,只能逐一节点进行配置,网络较大时,组网时间还是过长的问题,我们增加了私有的批量组网模式,即在默认key的network里面,增加vendor命令,比如VD_MESH_RESET_NETWORK等,向0xffff的目标地址发送network key,app key,iv index (只需要发送一次,整个网络都可以同时接收),然后根据mac的不同,逐一分配unicast address。这种方式可以把多跳范围内的节点都同时组网进来。device key是按一定规则根据mac地址生成,所以不需要通过mesh命令单独分配。
另外,在一个已经配置好的mesh网络内,还可以继续通过fast provision的方式把新购买的未配网设备添加进当前的网络。
配置方式
把FAST_PROVISION_ENABLE设置为1。编译8258_mesh工程,下载到3个(2个以上)8258 dongle。
目前GATT master dongle模式和APP默认支持处理fast provision功能。 Gateway V3.3.4及之后的版本支持此功能,默认关闭。
功能演示
以下demo演示了Gateway如何将多个(大于2个)8258节点批量加入现存mesh网络。
1) 将8258 mesh节点上电。
2) 把FAST_PROVISION_ENABLE设置为1,编译8258_mesh_gw编译选项得到8258_mesh_gw.bin并烧录到8258 Dongle,即Gateway。
3) 把Gateway插入USB口,打开“SIG_MESH_TOOL”选择 tl_node_gateway.ini,标题栏显⽰ Found说明找到 gateway 设备(provisioner)。工具会自动获取网关的 uuid 和 mac 地址,发送的命令格式详见"Provisioner(Gateway)工程介绍"中的"Provisioner 加灯"章节。
4) 点击右下角的“Prov”按钮,进入provision 界面,勾选Fast prov mode。设置好network参数后点击SetPro Internal配置Gateway的provision data,“Prov”和"SetPro Internal"对应的命令格式详见"Provisioner(Gateway)工程介绍"中的"Provisioner 加灯"章节。
5) 添加Gateway的appkey。由于fast provision没有绑定过程,如果之前Gateway没有添加app key,需对网关添加app key,对应的命令格式为:
HCI_CMD_GATEWAY_CMD + netkey index + appkey index + retry cnt + response max + destination + op + par。
即:e8 ff + 0x0000 + 0x0000 + 0x00 + 0x01 + gateway address + 0x00 + netkey appkey index(3 bytes) + appkey(16 bytes)。
6) 点击"Provision"按钮启动配网。
Provision控件对应的命令格式为HCI_CMD_GATEWAY_CTL + HCI_GATEWAY_CMD_FAST_PROV_START + pid(2 bytes) + new device address(2 bytes)
即:e9 ff + 0x17 + pid + new device address。
注:可通过pid指定要加进来的设备类型,如果要把所有设备类型加进来,pid设置为0xffff。
7) 网关在fast provision过程中,会把分配给设备的地址上报给上位机。格式为
TSCRIPT_GATEWAY_DIR_RSP + HCI_GATEWAY_CMD_SEND_NODE_INFO + VC_node_info_t。
即:0x91 + 0x81 + VC_node_info_t。VC_node_info_t格式详见"Provisioner(Gateway)工程介绍"中的"Provisioner 加灯"章节。
注意:由于vendor消息的有效参数长度只有8字节,设备在网关扫描时只返回6字节mac和2字节pid,网关端根据pid获取设备的element个数,详见mesh_fast_prov_get_ele_cnt_callback(u16 pid)。
8) fast provision完成后,会看到所有节点都在闪烁3下。网关向上位机上报绑定成功,格式为
TSCRIPT_GATEWAY_DIR_RSP + MESH_KEYBIND_EVE_SUC + event。
即:0x91 + 0x8a + 0x01。
9) 点击“Mesh”按钮进入mesh界面可进行开关灯等操作。
私有online status功能演示
功能介绍
目前SIG mesh spec提供的在线离线监测机制,可以通过heartbeat以及publish机制实现。节点onoff等状态信息实时监测的方式可以通过publish机制实现。
当同时实现在线离线监测以及状态信息实时监测就需要publish机制了。但是publish 机制有以下几个限制:
-
publish message一般需要设置relay,所以空中的包会比较多。
-
publish周期不能设置太短(一般需要几十秒或者更久),否则空中的包会太多,影响正常的控制。
-
有时候需要几条publish status message,才能把包含所有需要上报的status,这个时候空中的包会更多。
所以我们增加了online status机制,目的是:实现快速有效的在线离线检测,以及上报节点重要状态信息,同时还能有效的降低网络中的数据包。
配置方式
把mesh和mesh_provision这两个编译选项的app_config.h的ONLINE_STATUS_EN由0改为1.
数据包格式
Online status数据包采用ADV NON CONN IND的adv进行发送,payload的type采用自定义的MESH_ADV_TYPE_ONLINE_ST (0x62),如下图。
Payload有效字节的长度是24byte,默认每个节点需要占用6个byte,详细请参考:
每个节点的有效长度是MESH_NODE_ST_PAR_LEN(3),具体填充的data根据每个产品可以自定义。默认填充BYTE 0是亮度,BYTE 1是色温值,BYTE 2保留。针对客户需求修改device_status_update()即可。
注意,MESH_NODE_ST_PAR_LEN(3), 可以修改,但是长度越大,传输速度会相应变慢。并且这个长度没有length字段来描述,所以,需要在定义网络的时候,就要先确定好MESH_NODE_ST_PAR_LEN的值。如果网络中有节点配置的MESH_NODE_ST_PAR_LEN值不一致,就会有兼容性问题,导致数据格式解析错误。
SIG_MESH_TOOL上位机演示
Step 1 如图选择online status模式;
Step 2 如图点击“Node”按钮,点击后,从log窗口可以看出,并没有发送lightness get等类似命令,而是通过自定义的UUID直接获取直连节点里面的online status数据;
Step 3 点击Node按钮后,左边的节点显示窗口可以看到当前网络的节点信息。
Telink私有OTA协议简要测试说明
GATT master dongle对BLE直连节点进行firmware更新的OTA
此模式是点对点的BLE直连OTA。
1) 将需要OTA的BIN文件(New FW)按照7.1的说明下载到8269 master dongle的flash地址0x20000开始的位置,0x0000的位置烧录8269_mesh_master_dongle.bin。
注意:烧录New FW的差异
BDT工具,请参考以下图示步骤:
如果是wtcdb 工具,应点击“WF20000”按钮开始烧录。请参考以下图示步骤:
2) 参考第4.4小节”GATT master dongle模式的BLE连接和加灯”的(1)~(5)的步骤,建立起目标节点和工具之间的BLE连接。
3) 点击OTA按钮,启动OTA过程。如果正常完成OTA,该节点会连续闪烁8下。
4) OTA部分的命令以及协议部分详细部分,可参考《AN_17092701_Telink 826x BLE SDK Developer Handbook》的6.4章节。里面对命令和协议格式有详细的说明。
Gateway节点对自己进行固件更新的OTA
Gateway Gate OTA目的是为gateway本身固件做升级。
1) 打开BDT工具,把8258_mesh_gw.bin下载到8258dongle。
2) 工具左上角的“Found”表示8258 Dongle和PC工具正常连接,并且可以正常通信。
3) 点击右下角按钮,选择不同版本的8258_mesh_gw.bin文件;
4) 点击Gate_ota按钮,开始升级。LOG提示gateway firmware load suc表示升级成功,升级成功后,gateway会自动重启,并启用新的firmware。
网络分享
更多信息,也可参考33 Android and iOS APP使用说明的33.5.1.2 Share Export章节。
Gateway或者GATT master dongle组网后分享给APP的模式
Step 1 用VC master或gateway组网,确定8258 dongle节点能正常组网和控制;
Step 2 点击主页的 “output_db”;(不能直接复制粘贴JSON文件,因为要去掉一些添加的字段,比如iv index等)
Step 3 把输出的json文件导入TelinkSigmesh APP;
IOS APP: 步骤如下:
Step 1 将手机连接到安装了iTunes的电脑上;
Step 2 点击iTunes左上角的手机图标进入iTunes设备详情界面;
Step 3 选择iTunes左侧的“文件共享”,然后在应用中找到并点击demo APP “TelinkSigMesh”,等待iTunes加载文件;
Step 4 文件加载完成后,将电脑上的json文件拖入右侧的“TelinkSigMesh”的文稿中;
Step 5 APP点击IMPORT按钮选择刚刚的JSON文件进行加载。此步骤详见以下图片所示:
第一步:打开APP TelinkSig mesh,点击Setting按钮;
第二步:点击按钮;
第三步:点击IMPORT按钮;
第四步:选择mesh.json文件,点击IMPORT。
Android APP导入文件步骤如下:
Step 1 将手机连接到电脑上,把mesh.json文件导入手机任意文件夹,记住文件夹的路径。
第一步:打开APP TelinkSigmesh,点击Setting按钮;
第二步:点击按钮;
第三步:点击IMPORT按钮;
第四步:点击Select File选择从电脑导入手机的mesh.json文件。
第五步:点击按钮。
Step 2 导入成功后,返回主页面:APP Device 界面,此时会显示分享过来的节点,这些节点是VC工具组网的节点,APP可以受控,并且VC和APP都能控制节点。
App组网后分享给Gateway或者GATT master dongle的模式
1) 用iOS或者Android版本的TelinkSIGmesh APP进行组网,能正常控制功能。
2) 点击Setting按钮,再点击Share按钮,进入share界面,点击EXPORT按钮,生成JSON文件。界面提示JSON文件路径所在的文件夹。
3) 把master 8269 Dongle或者gateway dongle接到PC上。
4) 打开“SIG_MESH_TOOL”工具,此时会显示master 8269 Dongle和PC工具正常连接或者gateway 8258 Dongle和PC工具正常连接。
5) 点击 “input_db” 按钮,导入APP生成的mesh.json。(不能直接复制粘贴 JSON文件,因为要清除ini文件里面的node max等,否则unicast address的地址空间会产生浪费等)
Note:Gateway dongle一般来说是还没有被配置过网络的,如果已经被配置过,里面的信息会被删除,并且使用导入的数据。
6) 点击“mesh”按钮,此时“mesh”窗口会出现导入的节点,并且可以受控。
7) 至此分享完成,gateway/master dongle和APP都能控制节点。
通过INI控制节点实例讲解
设备入网
设备的入网流程在PB-GATT和PB-ADV会有一些不同,但是在界面操作上流程执行相同,都将执行如下步骤:
Step 1 扫描UNprovision_beacon广播的设备;
Step 2 根据扫描到UNprovision广播的设备的MAC进行连接;
Step 3 Provision device;
Step 4 Bind model。
此例程采用的是mast dongle进行测试。点击stop,后点击scan进入扫描模式,根据扫描到的设备进行连接,如下连接mac:112233445566的设备。
设备连接成功后log信息:
Provision参数设置与设备入网:
1) 点击prov按钮,进入provision界面,首先点击SetPro_internal将net key下发给dongle。
2) 点击provision按钮,对已连接的MAC:112233445566设备进行provision动作,provision过程中将上位机生成netkeyindex,IVindex,unicast_addr下发给设备。
3) 点击bind_all按钮对设备进行APPkey的bind(bind 过程会根据设备composition data上报的model进行bind)。
如下图截取的Provision流程交互数据进行说明如下说明:
1) 开始provision,provisioner发送;
2) public key交互;
3) check confirm;
4) send provision data(net_key/nkey_index/IV_update_flag/IV_index/unicast_addr);
5) 对proxy添加白名单。
Bind流程说明:
Bind是在provision后根据读取的设备的composition data的信息,将APP key与device的所有model进行bind的过程。这个过程会根据model的数量决定bind的时间(20s只有)。因此在天猫精灵等平台都将bind进行了优化处理,接下来的fastbind将会进行介绍。
Bind前首先需要获取device的composition data。Composition data的详细解析格式请参考
根据获取到的element和model的对应信息,后下发bind命令根据model逐个bind。
配置操作
Key add/bind操作
APPKey add命令格式解析:
CMD-cfg_appkey_add_001= a3 ff 00 00 00 00 02 00 07 00 00 00 00 00 60 96 47 71 73 4f bd 76 e3 b4 05 19 d1 d9 4a 48
命令解析:
-
Flag:为telink 私有化定义,目的为USB or UART通信的头包识别,UART为E8FF, USB为A3FF。
-
NK_idx:network key index
-
Ak_idx:APP key index
-
Reliable retry cnt:应用层retry(上位机发出命令后如果收不到回复则重发次数)
-
Reliable resp_max:设置回复的节点的个数
-
Dst:目的地址填充
-
Op:sig mesh 规范定义的标准命令码,可以参考< Mesh_v1.0>,命令码不是固定长度, 参考文档里的<4.3.4 Messages summary>。
[9:12]:传参部分,填充数据为参考文档
Key bind命令格式解析:
CMD-cfg_appkey_bind_001 = a3 ff 00 00 00 00 02 00 02 00 80 3d 02 00 00 00 00 10
命令解析:
-
Flag:为telink私有化定义,目的为USB or UART通信的头包识别,UART为E8FF, USB为A3FF。
-
NK_idx:network key index
-
Ak_idx:APP key index
-
Reliable retry cnt:应用层retry(上位机发出命令后如果收不到回复则重发次数)
-
Reliable resp_max:设置回复的节点的个数
-
Dst:目的地址填充
-
Op:sig mesh 规范定义的标准命令码,可以参考< Mesh_v1.0>,命令码不是固定长度,参考文档里的<4.3.4 Messages summary>。
[9:12]:传参部分,light HSL 控制命令填充数据为参考文档
传参解析格式参考:
订阅设置
CMD-cfg_sub_add = a3 ff 00 00 00 00 00 01 02 00 80 1b 02 00 01 c0 00 10
或者参考《4.5.2 分组控制(即subscription的功能演示)》的组号控制。
Publish设置
CMD-cfg_pub_set_sig_2s = a3 ff 00 00 00 00 00 00 02 00 03 02 00 01 00 00 00 ff 14 15 00 10
或者参考《4.5.3 通过UI配置某一节点的详细参数》的“GetPub_S”按钮控制。
Relay/Friend功能设置
Relay:a3 ff 00 00 00 00 02 01 07 00 80 27 01
Friend:a3 ff 00 00 00 00 02 01 07 00 80 10 01
Proxy:a3 ff 00 00 00 00 02 01 07 00 80 13 01
或者参考《4.5.3 通过UI配置某一节点的详细参数》的“Relay”,“Friend”,“Proxy”按钮控制。
Heartbeat设置
CMD-cfg_hb_pub_set_sig = a3 ff 00 00 00 00 00 00 02 00 80 39 01 00 ff 02 01 07 00 00 00
控制操作
控制Generic model实例
测试准备:
-
Provisionner dongle(烧录8258_mesh_gw.bin)
-
上位机工具(sig_mesh_tool.exe),选择tl_node_gateway.ini
-
Dongle_2#(烧录mesh.bin)
-
SDK无需修改
测试说明:
测试主要目的是实现对Generic model的控制实现,主要以实现G_ONOFF_SET命令测试。
并对CMD send与ACK的响应时间进行测试计算。
测试步骤:
Step 1 将dongle_1#烧录gateway bin后,插入电脑,同时打开上位机软件sig_mesh_tool.exe。
Step 2 将dongle_2#烧录mesh节点bin。
Step 3 上位机将mesh节点添加入网
Step 4 按照如下命令格式发或者自己在上位机的ini CMD栏直接双击该条命令。
CMD-g_on_03 = e8 ff 00 00 00 00 00 00 03 00 82 02 01 00
Step 5 上位机显示发送成功后,如下图所示。
Step 6 如上图所示<0011>所示,CMD发送时间与rsp时间间隔为:199 – 075 = 124ms。Gateway与node采用adv控制的方式,受网络影响命令ack回复的时间会有差异。
Status Rsp______________: 03 00 02 00 82 04 00 01 0a
对应的结构体为:
typedef struct{
u16 len; // length
u16 src; // source address
u16 dst; // destination address
u8 data[ACCESS_WITH_MIC_LEN_MAX]; // access layer(op code, parameters)
}mesh_rc_rsp_t;
Step 7 如果控制的目的地址是组播或者广播地址,则node在收到命令后回复ACK前会加随机延时,以便多设备回复消息尽可能的避让。如下图所示广播地址的时候,CMD与rsp 的时间间隔为:<0023> 12:390 – 11:752 = 538 ms
CTL model
测试准备:
-
Provisionner dongle(烧录8269_mesh_gw.bin or 8258_mesh_gw.bin)
-
上位机工具(sig_mesh_tool.exe),选择tl_node_gateway.ini
-
Dongle_2#(烧录mesh.bin)
-
SDK 需要修改 #define LIGHT_TYPE_SEL LIGHT_TYPE_CT
测试说明:
测试主要目的是实现对CTL model的控制实现,主要以实现LIGHT_CTL_SET命令测试。
测试步骤:
Step 1 将dongle_1#烧录 gateway bin后,插入电脑,同时打开上位机软件sig_mesh_tool.exe。
Step 2 将dongle_2#烧录mesh节点bin。
Step 3 上位机将mesh节点添加入网
Step 4 按照如下命令格式发或者自己在上位机的ini CMD栏直接双击该条命令。
CMD-light_ctl_set = e8 ff 00 00 00 00 00 00 ff ff 82 5e 01 00 20 4e 00 00 00
上位机显示发送成功后,如下图所示。
HSL model
Hsl model在配网后会分配3个element地址,主element用于lightness,generic model控制以及configuration model配置等,element2是hue控制,element3是saturation控制。
测试准备:
-
Provisionner dongle(烧录8269_mesh_gw.bin or 8258_mesh_gw.bin)
-
上位机工具(sig_mesh_tool.exe),选择tl_node_gateway.ini
-
Dongle_2#(烧录mesh.bin)
-
SDK 需要修改 #define LIGHT_TYPE_SEL LIGHT_TYPE_HSL
测试说明:
测试主要目的是实现对HSL model的控制实现,主要以实现LIGHT_HSL_SET命令测试。
测试步骤:
Step 1 将dongle_1#烧录gateway bin后,插入电脑,同时打开上位机软件sig_mesh_tool.exe。
Step 2 将dongle_2#烧录mesh节点bin。
Step 3 上位机将mesh节点添加入网
Step 4 按照如下命令格式发或者自己在上位机的ini CMD栏直接双击该条命令。
CMD-light_hsl_set = a3 ff 00 00 00 00 00 00 ff ff 82 76 01 00 00 50 00 80 00
上位机显示发送成功后,如下图所示。
Vendor model
自定义OP操作
测试准备:
-
Provisionner dongle(烧录8269_mesh_master_dongle.bin)
-
上位机工具(sig_mesh_tool.exe)
-
Dongle_2#(烧录8258_mesh.bin)
-
SDK需要做出修改,按照<测试说明进行配置>
SDK修改说明:
1) 在vendor_model.h里配置
#define VD_USER_ONOFF_GET 0xE1
#define VD_USER__ONOFF_SET 0xE2
#define VD_USER__ONOFF_SET_NOACK 0xE3
#define VD_USER__ONOFF_STATUS 0xE4
2) 在vendor_model.c进行声明
{VD_USER_ONOFF_SET, 0, VENDOR_MD_LIGHT_C, VENDOR_MD_LIGHT_S, cb_vd_light_onoff_set, VD_USER_ONOFF_STATUS},
{VD_USER_ONOFF_GET, 0, VENDOR_MD_LIGHT_C, VENDOR_MD_LIGHT_S, cb_vd_light_onoff_get, VD_USER_ONOFF_STATUS},
{VD_USER_ONOFF_SET_NOACK, 0, VENDOR_MD_LIGHT_C, VENDOR_MD_LIGHT_S, cb_vd_light_onoff_set, STATUS_NONE},
{VD_USER_ONOFF_STATUS, 1, VENDOR_MD_LIGHT_S, VENDOR_MD_LIGHT_C, cb_vd_light_onoff_status, STATUS_NONE},
3) main_loop里添加处理
测试说明:
Telink的sdk根据客户的使用情况做出了如下的限定值:
1) 一个friend node可以最多挂靠16个LPN,默认为2个,可以修改MAX_LPN_NUM为16个
2) Friend node缓存的1个LPN节点的数据可以支持长包,长包最大字节为41byte。(APP在发送data的时候最大传参是41byte)。
Gateway发长包到LPN
测试准备:
-
Provisionner dongle(烧录8258_mesh_gw.bin)
-
上位机工具(sig_mesh_tool.exe),选择tl_node_gateway.ini
-
Dongle_2#(烧录LPN.bin)
-
SDK需要做出修改,按照<测试说明进行配置>
测试说明:
Telink的SDK根据客户的使用情况做出了如下的限定值:
1) 一个friend node可以最多挂靠16个LPN,默认为2个,可以修改MAX_LPN_NUM为16个。
2) Friend node缓存的1个LPN节点的数据可以支持长包,长包最大字节为41byte。(APP在发送data的时候最大传参是41byte)。
为了调试方便sdk做出如下修改:
1) 调试方便需要DEBUG_SUSPEND = 1,(不进入低功耗模式)。
2) 需要在cb_vd_light_onoff_set()回调里添加如下的信息。
u8 debug_fn_reciver_data[64];
u8 debug_fn_cnt;
memset(debug_fn_reciver_data,0,sizeof(debug_fn_reciver_data));
memcpy(debug_fn_reciver_data,par,par_len);
测试步骤:
Step 1 将dongle_1#烧录gateway bin后,插入电脑,同时打开上位机软件sig_mesh_tool.exe。
Step 2 将dongle_2#烧录LPN节点bin。
Step 3 上位机将LPN添加入网
Step 4 在现有的vendor_on命令和vendor_off命令控制LPN节点,LPN可以被正常控制
Step 5 将vendor_on的命令的传参加长共计41byte的传参,然后发送
CMD-vendor_on = a3 ff 00 00 00 00 00 00 ff ff c2 11 02 c4 02 01 00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16
Step 6 上位机显示发送成功后,使用tdebug工具查看debug_fn_reciver_data[64]的内如是否正确。
mesh_1.1_feature汇总
Mesh V1.1 feature汇总内容和简介可以参考 https://www.bluetooth.com/mesh-feature-enhancements-summary/
对应的mesh spec 可以从这里下载:
https://www.bluetooth.com/specifications/specs/?types=specs-docs&keyword=mesh&filter=
该链接包含以下的spec:
(1) Mesh协议栈spec:
Mesh Protocol 1.1 Specification
mesh 协议栈,包含数据通讯格式定义,foundation models等。其中foundation models包含Configuration model,Health model,Remote Provisioning model,Directed Forwarding Configuration model,Bridge Configuration model,Mesh Private Beacon model,On-Demand Private Proxy model,SAR Configuration model,Solicitation PDU RPL Configuration model,Opcodes Aggregator model,Large Composition Data model。这些 model 在后面的章节都会介绍到。当需要查看这些 model的命令码对应的参数时,需要查阅此文档的 “4 Foundation models” 章节的 “message”小节。
(2) Mesh产品model spec:
mesh产品model ,比如 Generic OnOff model,Lightness model 等。当需要查看这些 model的命令码对应的参数时,需要查阅此文档的 每个model章节对应的 “message”小节。
(3) Mesh OTA model spec:
Mesh Binary Large Object Transfer Model Mesh Device Firmware Update Model
(4) NLC spec
NLC: Networked Lighting Control
Ambient Light Sensor NLC Profile Basic Lightness Controller NLC Profile Basic Scene Selector NLC Profile Dimming Control NLC Profile Energy Monitor NLC Profile Occupancy Sensor NLC Profile
(5) 设备属性定义
包含sensor ID ,sensor数据格式的定义,light control model 里面亮度和时间属性的格式介绍等。
(6) Mesh网络共享时导入导出文件的格式定义
Mesh Configuration Database Profile
certify_base_provision证书模式
功能介绍
基于证书认证的组网模式,可以由provisioner进行判断,只有符合要求的节点才能加入网络。
和普通的组网模式的差别是:组网的时候,provisioner 先获取未组网节点的证书,证书里面包含public key,uuid,以及身份认证信息等,provisioner拿到证书,会先判断是否合法,如果合法,则开始组网。组网的时候 provisioner 就不需要再获取对方的public key,其他流程和普通组网模式相同。证书判断是否合法,只在 provisioner端判断。
功能概述还可以参考 https://www.bluetooth.com/mesh-feature-enhancements-summary/ 这个SIG官网的描述以及https://www.bluetooth.com/bluetooth-certificate-based-provisioning-a-technical-overview/。
测试使用代码默认证书并直接编译到firmware里面的方式
代码配置
- 打开CERTIFY_BASE_ENABLE。
- CERTIFY_BASE_ENABLEcertify_base_crypto.c 里面的CERTIFY_TYPE修改为CERTIFY_OOB_BY_DEFAULT_CERT。
Note:
此时所有节点都使用相同的证书,以及相同的device UUID。(Mac地址可能会不同)
扫描界面如下:
然后按正常组网操作流程直接点击“Add”等操作后,即可组网和控制。
测试使用新生成的证书的方式
代码配置
- 打开CERTIFY_BASE_ENABLE
- certify_base_crypto.c里面的CERTIFY_TYPE保持为默认的CERTIFY_OOB_BY_READING_FLASH
安装git bash,如果当前电脑已经安装git的话,那就已经有git bash了。注意,git bash版本要大于等于2.41.0 版本。
Note:
运行bash方式:在macOS,linux命令行或window的git bash中输入./xxx.bash。
(1) 打开git_bash终端
在telink_sig_mesh_src/sig_mesh_tool/bash-certifybase目录下,打开git bash终端,打开方式如下:
鼠标右键 -- Git Bash here
(2) 生成根证书
我们的sdk中有一个默认证书,不建议重新生成root证书,建议直接跳过此步骤。 如果要一定要重新生成root证书,需要将output-root中的证书文件root.der导入到app中,并设置新导入的证书为root证书。操作流程如下:
- 运行gen-root.bash生成root(根)证书,运行命令:./gen-root.bash。
- 导入root(根)证书到APP
(3) 运行gen-intermediate.bash生成intermediate(中间)证书
该证书由根证书签名, 结果输出在output-intermediate目录中。
运行命令:./gen-intermediate.bash
(4) 配置device证书参数
更改gen-device.config中的以下参数:
CN(common name):此参数为device UUID,每次生成证书都应该先变化该参数,因为每个节点的device UUID不能相同。
BPID:此参数为PID(Product ID),必须和firmware sdk code里面的MESH_PID_SEL相等。
BCID:此参数为CID(Company ID),必须和firmware sdk code里面的MESH_VID相等。
注意,在gen-device.config文件中的CID和PID为大端字节序。
(5) 然后运行gen-device.bash生成device(设备)证书
该证书由中间证书签名, 结果输出在 output-device ⽬录中
运行命令:./gen-device.bash
(6) 烧录证书到设备的flash里面
将在output-device目录中生成的4KB bin文件烧录到设备的flash地址:
FLASH_ADR_CERTIFY_ADR(默认是0x78000)的位置
烧录完成后,重启即可执行certify base模式的组网。
(7) 以下为部分代码介绍
a) \<代码处理流程介绍,非操作步骤>程序中会调用crc16函数校验flash地址78000的位置,校验长度为f00,确认读取到的证书是否完整,是否被异常修改。
b) \<代码处理流程介绍,非操作步骤>结果与flash地址为78f00的crc值对比,预期比较结果为两者相同。
Remote provision功能介绍和开发说明
Remote provision功能介绍
SIG MESH Spec V1.0 / V1.0.1 版本里面,进行配网(Provisioning)的时候,要求Provisioner和被配网节点(Provisionee)在一跳范围内,因为unprovision beacon 包不能直接进行relay,所以配网过程中的命令交互也就不能进行relay。
为了把一跳以外的节点加入网络,SIG MESH V1.1 加入Remote Provision功能。
Remote Provision在组网的时候,也是逐个节点进行添加,但是有relay的功能,所以可以添加较远的节点进入网络。
重要应用场景:采用Remote Provision后,当主机(Provisioner)不方便移动时,也可以实现在应用中按实际场景先布置好mesh节点,然后再组网。特别是有网关的应用场景。
Remote provision除了可以做远程组网,还可以做Device Key,Node Address和 Composition Data的更新,详见 V1.1 spec 的 “3.11.8 Node Provisioning Protocol Interface procedures”。
备注:以上功能概述还可以参考SIG 官网 https://www.bluetooth.com/mesh-feature-enhancements-summary/ 的描述, 以及 https://www.bluetooth.com/mesh-remote-provisioning/ 。
Remote_provision组网交互流程介绍
(整个组网流程也是逐个节点进行组网):
(1) 先对主机(Provisioner)一跳范围内的一个或者多个节点进行组网。
(2) 通过已经组网的节点扫描更远距离(第二跳范围内)的节点发出来的unprovision beacon并上报给Provisioner。
(3) Provisioner选中某一个上报unprovision beacon的节点(比如已组网节点 A 上报扫描到了未组网节点 B )。
(4) Provisioner对节点B组网时,把要发送给节点B的消息先封装成 mesh network message,然后先发送给节点 A,然后节点A再把组网信息提取出来,以通用的provision PDU的形式发送给未组网节点B(可以是 PB-ADV, 也可以是 PB-GATT的方式)。
(5) 节点B回复给Provisioner的消息,先按照标准消息发给节点A, 节点A先封装成mesh network message再发给Provisioner。
(6) 步骤4和步骤5多次执行,直至组网完成。在此过程中,对于未组网节点 B 来说,可以认为节点 A 是不存在的,和普通的组网模式没有什么区别。
(7) 通过重复步骤2到6把第二跳范围内的节点组网完成。然后按相同的方式,再搜索和组网第三跳的节点,直至搜索不到任何未配网节点。
Remote provision opcode和流程图
Remote provision opcode(详见命令参数见 V1.1 spec 的“4.3.4 Remote Provisioning messages”)
remote_provision组网交互流程介绍 步骤2的 remote scan 流程图:(其中capa get是Remote Provisioning Scan Capabilities Get, 可以获取当前的mesh节点能够扫描到的最大未配网节点的个数以及是否支持active-scan.)
remote_provision组网交互流程介绍 步骤3/4/5的 remote provision 流程图:
使用App测试remote provision功能
测试条件
8258 dongle大于等于2个(烧录 8258_mesh.bin), Android 或者 iOS SIG Mesh App
Firmware SDK代码配置
在默认配置下,RPR功能是关闭的。如需开启该功能,则需要在mesh_config.h文件中,打开节点端的 MD_REMOTE_PROV 宏开关。如下图:
App设置
App首页点击 setting——settings——Provison Mode,选择remote provision
测试步骤
(1) App 首页点击" + "号, 进入 Remote Provision页面。然后开始自动组网。
开始自动组网前,App会判断,当前App是否是和支持Remote Provision功能的已组网节点处于GATT connected状态,如果不是,则进行普通的 PB-GATT 组网,如果是,则通过这个已组网节点对其它未组网节点进行remote provision组网。如下图,第一个(左上)是通过 普通的 PB-GATT 进行组网,其他的(右上和左下)是通过remote provision组网。
(2) 当超时还未扫描到未组网节点,则表示所有节点已组网完成,然后返回主页显示如下:
Gateway remote provision上位机开发指引
Gateway的remote provision的代码和工具参数配置
测试条件:8258 dongle 1 个(烧录 8258_mesh_gw.bin)、 8258 dongle 2 个(烧录8258_mesh.bin)
(1) MD_REMOTE_PROV打开。
(2) 如果想提高配网效率,可把宏EXTENDED_ADV_ENABLE设置为1,也就是使用扩展广播包模式发送remote provision消息。注意,该模式为私有模式。
- 打开宏开关
- 检查下方圈起来的代码是否有添加,如果没有,则加上。
- 上位机设置网关为扩展广播包模式
“Extend Adv” 控件的3个模式的处理方式,详见 is_not_use_extend_adv() 函数。
注意:
如果gateway dongle 意外断电,网关会被默认设置成 "GATT Only" 模式,需要再次点击“Extend Adv”,将网关设置成扩展广播包模式(All),或者关闭然后重新打开上位机,自动把上位机当前的 “Extend Adv” 刷新给 gateway dongle。
第一阶段以普通pb_adv方式组网一个或多个节点
当网络为空,没有已组网设备时,需要通过普通的 PB-ADV 的方式先把 gateway 一跳范围内的一个或者多个节点先组网成功。
组网步骤:通过gw dongle连接pc,打开tools工具sig_mesh_tool.exe,选择gw对应的ini文件如下图,用pb_adv方式对其中一个/多个mesh节点进行组网,具体流程参考sig mesh handle book网关工程provisioner加灯章节 "Provisioner加灯"。
组网成功后,点击“MESH” 按钮打开 mesh 控制界面,并自动发送LIGHTNESS_GET all命令获取当前所有节点(也可以发ONOFF_GET),并显示在 UI界面。如果当前已处于 mesh 控制界面,并且UI界面没有显示有支持 remote provision的节点,则需要点 “Nodes”按钮,该命令也是发出LIGHTNESS_GET all命令。
LIGHTNESS_GET all命令格式如下:
HCI_CMD_GATEWAY_CMD + netkey index(2 bytes) + appkey index(2 bytes) + retry cnt + rsp_max + gateway addr + op + par
即:e8 ff + netkey index + appkey index + retry cnt + rsp_max + gateway addr + 82 02 00 00
注:如无特别说明,本文档中ini格式均为16进制。
第二阶段Remote provision加灯
(1) 点击图中rp_scan按钮,设置好limit和timeout参数(详见 V1.1 spec "4.3.4.4 Remote Provisioning Scan Start"),
limit:Maximum number of scanned items to be reported. Value 0 indicates no limit.
timeout:Time limit for a scan (in seconds)
然后点击确认,工具会对前一步获取的节点列表下发scan start命令,格式为:
HCI_CMD_GATEWAY_CMD + netkey index(2 bytes) + appkey index(2 bytes) + retry cnt + rsp_max + scan server addr + op + par
即e8 ff + 00 00 + 00 00 + 02 + 01 + scan server addr + 80 52 + limit + timeout
节点收到scan start后会回复scan status,格式为:
len(2 bytes) + TSCRIPT_GATEWAY_DIR_RSP + HCI_GATEWAY_RSP_OP_CODE + scan server addr + gateway addr + op + par。
即: len + 91 + 81 + scan server addr + dst addr + 80 54 + par。
注意:如果点击rp_scan按钮的时候,有提示下图的错误信息:
则表示找不到已经组网的且支持remote provision的节点,没办法通过该节点对其他节点做 remote provision,有可能是上一步骤提到的 “点击'MESH' 按钮打开 mesh 控制界面” 这个动作没有做,此时,点击 'MESH' 按钮 即可。
(2) 指定的scan节点在scan期间通过REMOTE_PROV_SCAN_REPORT消息上报扫描到的未配网节点,格式为:
len(2 bytes) + TSCRIPT_GATEWAY_DIR_RSP + HCI_GATEWAY_RSP_OP_CODE + src addr + gateway addr + op + rssi + uuid + oob info(2 bytes) + uri hash(2 bytes, optional)。
即: len + 91 + 81 + src addr + gateway addr + 80 55 + rssi + uuid + oob info。
注:为了方便上位机显示mac,设备开启remote provision功能时,sdk中默认uuid的最后6字节设置为mac地址,详见uuid_create_by_mac()里面的处理。如下图所示:
(3) 在扫描的设备列表双击需要provision的设备,对应的命令为:
HCI_CMD_GATEWAY_CTL + HCI_GATEWAY_CMD_RP_LINK_OPEN + scan server addr + uuid
即:E9 FF + 1A + scan server addr + uuid。
网关收到HCI_GATEWAY_CMD_RP_LINK_OPEN 后,会向scan server节点发送Remote Provisioning Link Open指令,scan server节点收到后回复Remote Provisioning Link Status给网关,Remote Provisioning Link Status格式如下:
len(2 bytes) + TSCRIPT_GATEWAY_DIR_RSP + HCI_GATEWAY_RSP_OP_CODE + scan server addr + gateway addr + op + par。
即:len + 91 + 81 + scan server addr + gateway addr + 80 5B + par。
如下图,双击需要provision的设备:
(4) 点击Prov控件进入provision界面,Provision控件对应的命令为:
HCI_CMD_GATEWAY_CTL + HCI_GATEWAY_CMD_GET_PRO_SELF_STS。
即:e9 ff + 0c。
点击Prov进入provision界面,如下图:
网关收到此命令后后会返回是否已有配置信息和网关本身的element个数。网关配网信息命令格式为:
len(2 bytes) + TSCRIPT_GATEWAY_DIR_RSP + HCI_GATEWAY_CMD_PRO_STS_RSP + provision_flag + pro_net_info。
即: len + 91 8b + provision_flag+ pro_net_info。
provision_flag为1表示网关已配网。为0表示需设置网络信息,参考pb_adv加灯章节SetPro Interna控件对应命令。
网关本身的element个数命令格式:
len(2 bytes) + TSCRIPT_GATEWAY_DIR_RSP + HCI_GATEWAY_CMD_SEND_ELE_CNT。
即:len + 91 + 8C + gateway element count。
(5) 点击provision控件触发加灯,对应命令:
HCI_CMD_GATEWAY_CTL + HCI_GATEWAY_CMD_RP_START + provision data。
即:E9 FF + 1B + provision data。
(6) provision完成后会上报设备provision状态。格式为:
TSCRIPT_GATEWAY_DIR_RSP+HCI_GATEWAY_CMD_PROVISION_EVT+ gateway_prov_event_t。
即: 91 + 89 + gateway_prov_event_t.
typedef struct{
u8 eve;//1表示成功
u16 adr;
u8 mac[6];
u8 uuid[16];
}gateway_prov_event_t;
(7) 绑定app_key
Provision完成后,还需要为model绑定app_key。点击bind_all为model绑定app_key。该流程和非remote provision模式是一样的。
a. bind_all对应的命令为:HCI_CMD_GATEWAY_CTL+ HCI_GATEWAY_CMD_START_KEYBIND + fast_bind + app_key index(2 byte)+app_key(16 bytes)。
即: e9 ff + 0b + fast_bind + app_key index(2 byte)+app_key(16 bytes)。
fast_bind为1时:网关只会下发appkey add,被provision的设备需打开默认绑定功能(PROVISION_FLOW_SIMPLE_EN设置为1)。
fast_bind为0时:网关默认绑定全部model id,为了节省时间,用户可选择需要绑定的model id。网关端打开宏MD_BIND_WHITE_LIST_EN,需要绑定的model id详见Mesh_common.c文件中master_filter_list[],用户可根据需要自行修改。
b. App_key bind过程中gateway会调用u8 gateway_model_cmd_rsp(u8 *para,u8 len )返回绑定model的状态信息,格式为:TSCRIPT_GATEWAY_DIR_RSP + HCI_GATEWAY_RSP_OP_CODE +参数。
即: 91 + 81 + appkey bind status
c. App_key bind完成之后会返回HCI_GATEWAY_CMD_KEY_BIND_EVT表示成功还是time_out。格式为:
TSCRIPT_GATEWAY_DIR_RSP + HCI_GATEWAY_CMD_KEY_BIND_EVT +result。
即: 91 + 8a + result。(1:success 2:time_out)。
(8) 重复步骤 (1)~(7) 逐个对其他节点执行 remote provision 组网,直至把所有节点组网完成。
Mesh OTA介绍及上位机开发指引
Mesh OTA 介绍
Mesh OTA 功能和模式介绍
Mesh OTA在mesh spec里面的术语为mesh DFU(device firmware update),是给节点做固件升级的功能。该功能规范了OTA的实现方式,以及实现了给RF多跳距离以外的节点同时进行固件升级的功能。
Mesh OTA 模式和参考速率介绍
支持以下几种模式:
(1)支持通过mesh ADV relay的方式同时对多节点升级。目前Demo SDK 160k firmware 升级时间在60分钟左右,如果启用Telink的扩展广播包模式,升级时间在4分钟左右。
(2)经过Friend给LPN进行 mesh OTA 的模式:目前 Demo SDK 130k firmware 升级时间在70分钟左右,如果启用Telink的扩展广播包模式,升级时间在4分钟左右。
(3)单个节点(包含LPN节点)的GATT OTA 模式:APP会断开当前连接,去连接被升级节点,然后执行SIG MESH 的OTA,收发流程和对多节点升级的流程一样,只是收发包交互更快,OTA 时间和Telink gerneric BLE SDK 定义的 OTA 时间基本一样,升级时间在 1 分钟左右。
功能概述还可以参考 https://www.bluetooth.com/mesh-feature-enhancements-summary/ 这个SIG 官网的描述。
Mesh OTA firmware 分发方式
(1) 上位机或者App同时作为 Initiator和distributor:在分发 firmware data 给多个需要升级的节点的过程中,上位机 一直在参与。上位机要一直和节点保持连接,不能断开,该过程需要持续几十分钟。
(2) "GATT master dongle + 上位机"或者App作为Initiator,GATT直连节点作为distributor:该模式,只需要上位机在前面阶段,通过 GATT 把new firmware先下载到直连节点,然后由 直连节点作为 distributor,管理和执行后面的工作,包括分发new firmware给其它待升级节点,此时App就不需要一直和节点保持GATT连接状态。gateway暂不支持该模式,而是直接采用上位机加gateway dongle作为 Initiator和distributor的模式,因为gateway不需要和节点保持GATT连接。
Mesh OTA的三个角色介绍
(1) Initiator: SDK的DISTRIBUTOR_UPDATE_CLIENT_EN设置为1的节点,功能为:OTA 发起者,比如上位机,APP等。Initiator 会从本地或者云端通过http获取new firmware,并生成设备列表(receiver list),该设备列表指定对哪些节点发起OTA更新。然后把new firmware和receiver list发给Distributor,对应流程在mesh_ota_initiator_proc ()。
(2) Distributor:SDK的DISTRIBUTOR_UPDATE_SERVER_EN设置为1的节点,比如gateway node以及打开distributor功能的Mesh Light 节点(默认不支持,需要使能DISTRIBUTOR_UPDATE_SERVER_EN)。 功能为:接收Initiator发过来的firmware,或者通过Initiator发过来的http网址下载New firmware,并暂存。然后将new firmware分发给属于 receiver list的节点。Distributor对应发包流程在mesh_ota_master_proc (),收包流程在mesh_ota_master_rx()。
(3) Updating Node:接收OTA new firmware ,并把旧firmware更新为new firmware的节点。也即属于receiver list里面的节点。
Mesh OTA静默升级模式
Mesh OTA在分发完成firmware后,会有一条firmware update apply命令发给 被升级节点,升级节点才会应用新 firmware。App或者上位机在把 firmware 传完给被升级节点后,可以选择一个合适的时间再发送firmware update apply 命令,实现静默升级模式。
Mesh OTA的model介绍
(1) BLOB Transfer(Binary Large Object Transfer) server:该model用于接收大块数据传输,包括但不限于接收 firmware data。包含的命令如BLOB_INFO_GET,BLOB_TRANSFER_START,BLOB_BLOCK_START,BLOB_CHUNK_TRANSFER等。有Push mode和Pull mode两种模式(见MESH_OTA_TRANSFER_MODE_SEL的定义),Pull mode仅用于 LPN 节点。
- Push mode是主机主动发送firmware data给被升级节点,优点是速度快,但要求节点一直在监听mesh消息。
-
Pull mode 是被升级节点处于接收模式时,向主机请求firmware data。适用于 LPN 设备,OTA时间相对比较长,并且同一时间只有一个节点在升级。
LPN默认同时支持Push mode和Pull mode。LPN只有处于GATT连接状态时,才可以使用Push mode。
(2) Firmware update server:该model用于firmware 流程的控制,包含的命令如FW_UPDATE_METADATA_CHECK,FW_UPDATE_START,FW_UPDATE_APPLY等。
(3) Firmware Distribution server:该model主要用于接收 Initiator 传过来的 new firmware以及receiver list 。通过接收FW_DISTRIBUT_UPLOAD_START ,FW_DISTRIBUT_RECEIVERS_ADD , BLOB_TRANSFER_START 等命令把new firmware下载到本地暂存;通过接收FW_DISTRIBUT_START , BLOB_TRANSFER_START等命令把 new firmware 分发出去。
(4) BLOB Transfer client:对应server model的发送端。
(5) Firmware update client :对应server model的发送端。
(6) Firmware Distribution client :对应server model的发送端。
使用App测试mesh OTA
使用App测试 mesh OTA 的方法,请参考33 Android and iOS APP使用说明 的 33.4.4 Mesh OTA 章节。
Gateway mesh OTA
待升级节点为非 LPN 节点。
测试和命令收发流程
(1) 代码配置
测试条件:8258 dongle 1 个(烧录 8258_mesh_gw.bin)、 8258 dongle 2 个(烧录 8258_mesh.bin)
a) MD_MESH_OTA_EN打开。
b) 为缩短mesh ota时间,可把宏EXTENDED_ADV_ENABLE设置为1支持扩展广播包模式,需要注意的是该模式不是spec定义的模式。
c) 上位机可设置Extend Adv开启扩展广播包模式
(2) 组网节点
通过gw dongle连接pc,打开tools工具sig_mesh_tool.exe,选择gw对应的ini文件如下图,再依次对2个8258 dongle 进行组网。组网成功后,点击“MESH” 按钮打开 mesh 控制界面,并自动发送LIGHTNESS_GET all命令获取当前所有节点(也可以发ONOFF_GET)。
HCI_CMD_GATEWAY_CMD + netkey index(2 bytes) + appkey index(2 bytes) + retry cnt + rsp_max + gateway addr + op + par
即:e8 ff + netkey index + appkey index + retry cnt + rsp_max + gateway addr + 82 02 00 00
注:如无特别说明,本文档中ini格式均为16进制。
确认所有节点都在UI中显示,因为后续如果要通过fw_distribution_start_all进行mesh OTA,对应节点的unicast address 是根据这个UI里面获取,也就是FW_DISTRIBUT_START的参数区的address list的来源。
(3) 选择new firmware
点击图中 open file 按钮,选择目标固件,点击确认
(4) 下载new firmware到gateway dongle本地暂存
点击”GwMeshOta”按钮,上位机从目标路径加载选择的new firmware到gateway dongle本地暂存,为后续mesh OTA 做准备。按钮点击后,上位机调用OnBnClickedGatewayOta下发firmware到网关,对应的ini命令如下:
a) 设置ota类型为GATEWAY_OTA_MESH,格式为: HCI_CMD_MESH_OTA + MESH_OTA_SET_TYPE + GATEWAY_OTA_MESH。 即:eb ff + 01 + 00。
注:ota类型GATEWAY_OTA_SLEF(01)为升级网关本身
b) 发送ota start命令,等待ota区域擦除完成,默认等待5秒,格式为: HCI_CMD_GATEWAY_OTA + len + CMD_OTA_START。 即:ea ff 02 01 ff。
c) 发送固件包,格式为: HCI_CMD_GATEWAY_OTA + len + ota_index(2 bytes) + ota_payload(16 bytes) + crc16. 即:ea ff + 14 + ota_index + ota_packet(16 bytes) + crc16。其中,pkt index为ota包的索引值,每个ota payload大小为16字节,crc16值为ota_index和ota_payload的crc16校验码。
d) 固件传输完后,发送ota end命令,格式为: HCI_CMD_GATEWAY_OTA + len + CMD_OTA_END + index_max + ~(index_max)。 即:ea ff + 6 + 02 ff + index_max + ~(index_max)。 其中,index_max=(firmware_total_len + 15)/16 - 1,是ota包最大索引值,用来收到ota结束命令时是否收全。
注意:
new firmware是暂存在gateway dongle的待OTA区域(0x0000 或者 0x40000),gateway dongle 一旦重启,就会被清除,需要重新执行加载动作。否则后续无法进行mesh ota。
(5) 获取当前在网的节点的版本信息
用户可通过fw_updata_info_get查询当前在网的节点的版本信息,如下图02地址设备的版本号显示 41 00(即 4.1 版本’4’, ’1’的 ASCII 码)
HCI_CMD_GATEWAY_CMD + netkey index(2 bytes) + appkey index(2 bytes) + retry cnt + rsp_max + dst addr + op + par
即:e8 ff + netkey index + appkey index + retry cnt + rsp_max + dst addr + 83 08 00 01
(6) 发送fw_distribution_start_all命令
双击ini列表中fw_distribution_start_all命令,上位机会在vc_distribute_all_proc()会从UI 列表里面自动读取当前所有节点的unicast address,命令格式:
HCI_CMD_GATEWAY_CMD + netkey index(2 bytes) + appkey index(2 bytes) + retry cnt + rsp_max + gateway addr + op + par
即:e8 ff + 00 00 + 00 00 + 02 + 00 + gateway_addr + 83 19 + group_addr + device_addr _list。
其中,gateway_addr为网关地址,网关会把device_addr_list指定的设备添加到组group_addr后进行mesh ota。
(7) OTA进度上报
网关收到distribution_start后会启动mesh OTA状态机,mesh_cmd_sig_fw_distribut_start()->mesh_ota_master_proc(),将暂存在gateway的firmware发送给节点。
Gateway OTA进度目前通过APP_RefreshProgressBar接口以字符串格式上报。如下图:
(8) Mesh OTA完成显示页面
OTA完毕后,通过gateway_upload_mesh_ota_sts上报ota结果,格式为:
TSCRIPT_GATEWAY_DIR_RSP + HCI_GATEWAY_CMD_SEND_MESH_OTA_STS + fail_num + fail_list。
即:91 + 98 + fail_num + fail_list。
其中,fail_num为0表示升级失败的个数,为0表示全部成功,打印页面会出现mesh OTA success 如下图。
(9) 设备慢闪6秒后
所有设备会慢闪6秒,然后会自动重启,生效new firmware。重启后,可以通过以上的第5步进行查询版本,再次确认版本是否升级成功,如下图.
OTA代码流程总结
(1) 上位机加载fireware到gw节点。
(2) 上位机发送FW_DISTRIBUT_START通知gw开始ota,同时将需要ota的节点(unicast)和ota用到的组号发送给gw
(3) gw收到FW_DISTRIBUT_START回调对应函数。mesh_cmd_sig_fw_distribut_start_tlk() -> mesh_ota_master_proc() -> (开始发包)
Gateway的OTA流程图
Gateway 在mesh_ota_master_proc() 的处理流程图如下:
Mesh OTA相关命令介绍
INI命令对应的结构体是mesh_bulk_ini_vc_t:
typedef struct{
u16 nk_idx;
u16 ak_idx;
u8 retry_cnt; // only for reliable command // for op "distribute start" of gateway mesh OTA, it is reliable retry interval for LPN. // retry_intv_for_lpn_100ms
u8 rsp_max; // only for reliable command
u16 adr_dst;
u8 op;
u8 par[MESH_CMD_ACCESS_LEN_MAX];
}mesh_bulk_cmd_par_t;
typedef struct{
u16 flag;
mesh_bulk_cmd_par_t cmd;
}mesh_bulk_ini_vc_t;
以下是对应流程图 gateway的OTA流程图 里面用到的命令进行介绍。每个命令的参数介绍,也可参考 spec 《MshDFU_v1.0.pdf》 和 《MshMBT_v1.0.pdf》里面的 “messages”章节。
(1) FW_DISTRIBUT_START
Initiator(上位机)给distributor发送此命令,distributor收到后开始执行分发firmware。
注意
INI里面的这些distributor start是私有命令格式。
fw_distribution_start_all =a3 ff 00 00 00 00 00 00 01 00 83 19 00 c0
其中 opcode “83 19” 是 spec 定义的 distributor start opcode,但是当 后面的参数的前两个字节是一个 组号地址时,就按私有格式来识别了。详见 mesh_cmd_sig_fw_distribut_start() 里面 的 is_par_distribute_start_tlk() 判断方式,目的是兼容早期的命令格式。当 is_par_distribute_start_tlk() 返回 ture 时,参数区对应的解析格式是:
typedef struct{
u16 adr_group; // 发送 firmware data 的时候使用的 目的地址。
u16 update_list[MESH_OTA_UPDATE_NODE_MAX]; // 待升级节点的 unicast address list
}fw_distribut_start_tlk_t;
其中,fw_distribution_start_all 的 update_list 为空,表示需要使用上位机 “Mesh” 窗口的节点 list 来自动填充。
如果fw_distribution_start 的 update_list 不为空,示例如下:
CMD-fw_distribution_start_0002 =a3 ff 00 00 00 00 00 00 01 00 83 19 00 c0 02 00
CMD-fw_distribution_start_02_04 =a3 ff 00 00 00 00 00 00 01 00 83 19 00 c0 02 00 04 00
则表示待升级节点的unicast address list 由INI命令来指定,不需要自动填充。但需要注意的是,当填充的 地址的个数超过MESH_OTA_UPDATE_NODE_MAX默认定义的值时,需要修改MESH_OTA_UPDATE_NODE_MAX。
另外,由于给LPN做Mesh OTA的时候,同一时间只能对一个 LPN 节点进行升级,所以用的是:
LPN_fw_distrib_ota_start_04 =a3 ff 00 00 00 00 32 00 01 00 83 19 00 00 04 00
参数区对应的解析格式是也是fw_distribut_start_tlk_t,只是adr_group要设置为0,update_list[0]是LPN的unicast address。另外,reliable retry count 是“32”,对于LPN_fw_distrib_ota_start来说,此时表示的不是reliable retry count,而是 reliable retry interval,单位 100ms,0x32 表示 5000ms,该值要大于 FRI_POLL_INTERVAL_MS_MESH_OTA(默认是 400ms)。详见 mesh_bulk_cmd_par_t 的 成员 retry_cnt 的注释。
(2) FW_UPDATE_METADATA_CHECK
Distributor给待升级节点发送此命令,该命令包含firmware id,该id是读取 new firmware 的第二到第五字节的内容,对应的是 pid(product id) 和 vid(version id),详见get_fw_metadata()。当待升级节点收到该命令,检查收到的firmware id和自身的pid vid做比较,当pid相同时,则回复METADATA_CHECK status 的值为 success,提示distributor可以进行OTA,如果pid不相同,则回复不允许OTA。详见 mesh_cmd_sig_fw_update_metadata_check() 的 mesh_ota_slave_need_ota() 的处理。SDK 默认只比对 pid,不判断 vid,如需判断 vid,打开 OTA_ADOPT_RULE_CHECK_VID_EN 即可,如需修改为其他规则,请修改这个函数ota_is_valid_pid_vid()。
/**
* @brief This function check if new firmware has a valid PID(product ID) and VID(Version IS).
* @param[in] p_fw_id - firmware ID
* @param[in] gatt_flag - 1: it is GATT OTA.
* @return
* @note for both GATT and MESH ADV OTA
*/
_USER_CAN_REDEFINE_ int ota_is_valid_pid_vid(fw_id_t *p_fw_id, int gatt_flag)
{
#if (OTA_ADOPT_RULE_CHECK_PID_EN)
// user can change this policy
int accept = 0;
if(p_fw_id->pid == fw_id_local.pid){
#if OTA_ADOPT_RULE_CHECK_VID_EN
sw_version_big_endian_t *p_new = (sw_version_big_endian_t *)p_fw_id;
sw_version_big_endian_t *p_local = (sw_version_big_endian_t *)&fw_id_local.pid;
u16 ver_new_little = get_little_end_version(p_fw_id->pid);
u16 ver_local_little = get_little_end_version(fw_id_local.pid);
if(ver_new_little > ver_local_little){
accept = 1;
}
#else
accept = 1;
#endif
}
return accept;
#else
return 1;
#endif
}
(3) CFG_MODEL_SUB_ADD
对应流程图 Gateway的ota流程图 里面的注释是 "subscription set"。
Distributor 给待升级发送此命令,当待升级节点收到此命令时,会为 SIG_MD_BLOB_TRANSFER_S 订阅组号,以便OTA后续发送 firmware data 的时候,可以采用组地址作为目的地址,同时向全部的待升级节点发送 firmware data。
(4) FW_UPDATE_INFO_GET
Distributor获取待升级节点的firmware information信息,主要包含firmware id 等信息。
(5) FW_UPDATE_START
Distributor给待升级节点发送此命令,表示要开始进行firmware update。
(6) BLOB_INFO_GET
Distributor给待升级节点发送此命令,获取待升级节点的 BLOB information,包含 block size,chunk size,transfer mode 等。
(7) BLOB_TRANSFER_START
Distributor给待升级节点发送此命令,通知节点new firmware的size,以及将采用的block size和chunk size参数,并开启 BLOB数据发送流程。
(8) BLOB_BLOCK_START
Distributor给待升级节点发送此命令,通知节点即将要发送的是哪一个 block 数据等。
(9) BLOB_CHUNK_TRANSFER
Distributor给待升级节点发送此命令,传输 firmware data。
(10) BLOB_BLOCK_GET
Distributor传输firmware data 完成后,发送此命令查询所有待升级节点,看是否有丢包,如果有,会重新发送 BLOB_BLOCK_START 和 BLOB_CHUNK_TRANSFER 补发丢包数据,直至所有节点都接收完成。
(11) FW_UPDATE_GET
Distributor 确认所有待升级节点都接收完成 firmware data 后,发送此命令,待升级节点会进行 CRC 校验,并返回校验值。
(12) FW_UPDATE_APPLY和FW_UPDATE_CANCEL
如果返回的校验结果为检验成功,distributor 则发送 FW_UPDATE_APPLY 给该节点,通知他进行重启,并启用新的firmware。如果返回的校验结果为校验失败,distributor 则发送 FW_UPDATE_CANCEL 给该节点,通知他进行重启,并丢弃刚收到的firmware 数据。
Gatt master dongle模式的mesh OTA (kma_dongle)
待升级节点为非LPN节点。
测试条件: 8269 dongle 1个(烧录8269kma_master_dongle)、 8258 dongle 2个
代码配置
节点端默认mesh ota功能不使能,使能方式:把MD_MESH_OTA_EN由0改为1,如下图:
如果ota升级选择直连节点作为 distributor 的模式,需要将DISTRIBUTOR_UPDATE_SERVER_EN由0改为1,如下图:
组网节点
通过master dongle连接pc,打开tools工具依次对2个8258 dongle进行组网,配网成功后,连接其中一个,点击“MESH”按钮打开mesh控制界面,并自动获取当前所有节点,确认所有节点都在UI中显示(因为后续要进行mesh OTA的节点的unicast addr是根据这个UI里面获取的,也就是FW_DISTRIBUT_START的参数区的address list的来源)
选择new firmware
关闭mesh页面返回主页,如下图,点击图中search file按钮,选择目标固件,点击确认,确认后如标记2所示
获取版本
点击 ini命令:fw-update-info-get-all获取当前在网的设备版本,如下图两个设备的版本号显示32 38(即2.8版本),详细请参考 gateway mesh ota的对应的说明。
OTA start
测试的时候,选择以下3个方式中的一个进行测试。
(1) 上位机作为distributor模式
上位机会直接传输firmware数据给目标节点,该模式要求 master dongle和直连节点一直保持GATT连接状态。
双击ini命令:CMD-fw_distribution_start_all,开始进行mesh OTA。
(2) 直连节点作为distributor并且选择verify and apply模式
上位机不直接把firmware数据传输给待升级的节点,而是先通过 GATT 模式快速将固件传输给直连节点,然后再由直连节点将固件传输给待升级的节点,传输给待升级节点的过程中,可以断开 GATT 连接。
由于选择的是verify and apply模式,所以直连节点分发完firmware并确认接收完成后,会主动发送firmware update apply 命令给待升级节点,让待升级节点重启并生效新的firmware
双击ini命令:CMD-fw_initiator_start_verity_apply_all,开始进行mesh OTA。命令格式:(参考 结构体fw_initiator_start_t)
HCI_CMD_MESH_OTA_INITIATOR_START + distribute adress + distribute appkey index + distribute ttl + timeout + distribute transfer mode + group + upload ttl + upload timeout + upload blob id
即: ab ff + 00 00 + ff ff + ff + ff ff + 05 + 00 00 + 00 c0 + ff + ff ff + 61 62 63 64 65 66 67 68
(3) 直连节点作为distributor并且选择verify only模式
该模式和 “选择 verify and apply 模式” 的区别是:直连节点分发完firmware并确认接收完成后,不会主动发送 distribution apply 命令给待升级节点,而是等待 上位机 发送 distribution apply 命令,然后待升级节点才会生效新的firmware。
当前测试为了方便使用,在上位机收到 distributor(直连节点) 上报分发完成后,上位机会自动触发发送 distribution apply 命令给 distributor。如果想取消这个自动发送,可以屏蔽 mesh_ota_initiator_proc() --> case INITIATOR_OTA_ST_DISTR_PRE_APPLY --> 在这个 case 分支里面修改处理,比如改为 什么都不处理,等到手动发送 apply 命令后再 设置为 INITIATOR_OTA_ST_DISTR_GET 状态。
双击ini命令:CMD-fw_initiator_start_verity_only_all,开始进行mesh OTA。命令格式:(和verify apply 模式的差别是 distribute transfer mode 的值不一样)
HCI_CMD_MESH_OTA_INITIATOR_START + distribute adress + distribute appkey index + distribute ttl + timeout + distribute transfer mode + group + upload ttl + upload timeout + upload blob id
即: ab ff + 00 00 + ff ff + ff + ff ff + 01 + 00 00 + 00 c0 + ff + ff ff + 61 62 63 64 65 66 67 68
上图中红色框中为进度参考值:cur:1表示当前为第1个bolck;每256k为1个block,147k的固件即是147/256(如图中的block total:1) ,total chunk:719表示总共要传输719个chunk,cur:4表示当前传输的chunk为第4个,OTA process:7% 表示当前ota的进度为7%。
OTA finish
OTA完毕后,两个设备会进行应用,打印页面会出现distribution completed;flow completed,VC工具页面停止打印,log前面的打钩会被去掉关闭log打印,同时会弹出一个小提示框log disable now,表示OTA完成。log关闭是为了避免升级过程中的log被冲掉,方便查阅log和保存,如下图。
恢复log
所有设备会慢闪6秒,然后会自动重启,重启后。确认log,如果有必要,可以点“Save”按钮保存log。VC工具“log”控件打钩,使打印恢复正常。
检查是否成功
重新连接上网络,可以通过以上的第3步进行查询版本,确认版本是否升级成功,如下图.
LPN Mesh OTA
LPN Mesh OTA gateway模式操作流程
测试条件: 8258 dongle 1 个(烧录 8258_mesh_gw.bin)、8258 dongle 2 个(烧录 8258_mesh_LPN.bin)
(1) 代码配置
a) MD_MESH_OTA_EN打开。
b) 为缩短mesh ota时间,可把宏EXTENDED_ADV_ENABLE设置为1支持扩展广播包模式,需要注意的是该模式不是spec定义的模式。
c) 如过选择gateway节点不作为friend,gateway节点要设置FEATURE_FRIEND_EN为0
(2) 组网节点
参考Gateway mesh ota的组网流程。
注意:
如果组网完成后 UI 中没有显示LPN节点,可以点击该INI命令获取(需要注意目的地址是否正确,默认是 0x0004), CMD-LPN_get_onoff 或者将LPN节点进行断电重新上电操作,LPN会主动发送一次当前状态,UI界面就会显示LPN节点。
(3) 选择new firmware
参考Gateway mesh ota的选择new firmware流程。
(4) 获取版本
参考Gateway mesh ota的选择获取版本流程。
(5) OTA start
点击LPN_fw_distrib_ota_start_04,然后修改下方命令窗口最后两个字节为LPN节点的单播地址
即02 00,然后点击回车键来发送该命令。
(6) OTA finish
OTA成功之后log如下图所示
LPN mesh OTA gatt master dongle模式
测试条件:8269 dongle 1个(烧录8269kma_master_dongle)、 8258 dongle 3个(两个烧录8258_mesh.bin,一个烧录8258_mesh_LPN.bin)
(1) 代码配置
a) MD_MESH_OTA_EN打开。
b) 为缩短mesh ota时间,可把宏EXTENDED_ADV_ENABLE设置为1支持扩展广播包模式,需要注意的是该模式不是spec定义的模式。
c) 如果ota升级选择直连节点作为distributor的模式,需要将DISTRIBUTOR_UPDATE_SERVER_EN由0改为1
(2) 组网节点
参考gatt master dongle mesh ota选择组网节点流程
注意:
如果组网完成后UI中没有显示LPN节点,可以点击该INI命令获取(需要注意目的地址是否正确,默认是 0x0004):
CMD-LPN_get_onoff
或者将LPN节点进行断电重新上电操作,LPN会主动发送一次当前状态,UI界面就会显示LPN节点。
(3) 选择 new firmware
参考gatt master dongle mesh ota 选择new firmware流程
(4) 获取版本
参考gatt master dongle mesh ota 选择获取版本流程
(5) OTA start
a) 上位机作为distributor模式
点击LPN_fw_distrib_ota_start_04,然后修改下方命令窗口最后两个字节为LPN节点的单播地址,即02 00,然后点击回车键发送该 INI 命令。
b) 直连节点作为distributor并且选择 verify apply 模式
点击LPN_initiator_start_v_apply4,然后修改下方命令窗口最后两个字节为LPN节点的单播地址,即02 00,然后点击回车键
c) 直连节点作为distributor并且选择 verify only 模式
点击LPN_initiator_start_v_only4,然后修改下方命令窗口最后两个字节为LPN节点的单播地址,即02 00,然后点击回车键
(6) OTA finish
OTA成功之后log如下图所示
QA
有什么方法区分不同的设备类型进行OTA?
在VC上位机发起mesh OTA ,是点击distribute start命令,该命令的参数格式是:
(1) 方法1:正常情况下,需要把所有需要升级的节点的node address全部放在update_list这个数组里面。不在update list里面的,将不会被OTA。
(1) 方法2:设备端默认会判断新固件的 PID(Product ID) 和 当前的 PID 是否一样,如果不一样,则会拒绝当前 OTA 请求。对应的函数判断是 mesh_ota_slave_need_ota()-->ota_is_valid_pid_vid()。
所以可以通过修改distribute start命令的update list参数来决定对哪个节点发起OTA。比如“fw_distribution_start_02_04”是对0x0002和0x0004发起OTA:
CMD-fw_distribution_start_02_04 =e8 ff 00 00 00 00 00 00 01 00 83 19 00 c0 02 00 04 00
fw_distribution_start_all命令介绍:
在VC上位机,我们为了方便操作,不希望需要通过手动把node address添加到distribute start命令才能发起OTA,而是有一个通用的命令开始执行OTA,所以我们做了一个特殊标号,即当update list的长度为0的时候,我们认为是把所有在这个UI界面显示的节点都添加到update list 里面。如下图:点击“fw_distribution_start_all”后会对0x0004和0x0007节点进行OTA升级。
区分不同设备的方法?
我们目前是通过PID(Product ID) 来判断。
可以在OTA前进行版本确认吗?
在发起端(master端),通过读取Jason文件里面的composition data的PID和CID来识别。
在节点端(被升级端),通过 mesh_ota_slave_need_ota() 来判断从meta data传过来的pid cid来判断是否要升级,如果不需要升级,则返回0. 我们 Demo SDK 默认是由master 来决定对谁升级,节点端只判断pid是否相同,相同则允许升级.
可以恢复成以前版本吗?
默认是可以的,也就是允许OTA降级。如果希望不允许降级,把 OTA_ADOPT_RULE_CHECK_VID_EN设置为1即可。
为了区分设备类型分别进行OTA,需要在fw中怎么处理?
在节点端(被升级端),通过 ota_is_valid_pid_vid() 来判断 从 meta data 传过来的设备类型与新固件的设备类型是否一致来判断是否要升级,如果设备类型不相同,则返回0表示不能升级;如果一致则返回1,表示可以升级。
为了区分FW版本信息进行OTA,需要在fw中怎么处理?
在节点端(被升级端),通过 ota_is_valid_pid_vid() 来判断 从 meta data 传过来的版本信息来判断是否要升级,如果不需要升级,则返回0;如果需要升级,则返回1。
附录log
对unicast address 为0x0002和0x0004的节点进行gateway mesh OTA 升级
(为了节省OTA时间,以下log是基于 使能了私有的扩展广播包模式,即EXTENDED_ADV_ENABLE 设置为 1)
<0106>15:26:57:370 [INFO]:(common)ExecCmd: e8 ff 00 00 00 00 02 00 01 00 83 19 00 c0
<0107>15:26:57:418 [INFO]:(GATEWAY)cmd sendback src:0x0001 dst:0x0001,op 1983(FW_DISTRIBUT_START): 00 c0 02 00 04 00
<0108>15:26:57:433 [INFO]:(GATEWAY)cmd sendback src:0x0001 dst:0x0001,op 1d83(FW_DISTRIBUT_STATUS): 00 00
<0109>15:26:57:449 [INFO]:(cmd_rsp)Status Rsp__: 01 00 01 00 83 1d 00 00
<0110>15:26:57:449 [INFO]:(cmd_name)mesh OTA completed or get info ok!
<0111>15:26:57:449 [INFO]:(GATEWAY)HCI_GATEWAY_RSP_OP_CODE: 91 81 01 00 01 00 83 1d 00 00
<0112>15:26:57:480 [INFO]:(gw_vc_log)OTA, block sum: 0,cur: 0, chunk sum: 0,cur: 0, Progress: 0% NULL
<0113>15:26:57:496 [INFO]:(GATEWAY)cmd sendback src:0x0001 dst:0x0002,op 0a83(FW_UPDATE_METADATA_CHECK): 00 01 00 32 38 00 00 00 00
<0114>15:26:57:512 [INFO]:(cmd_rsp)Status Rsp__: 02 00 01 00 83 0b 08 00
<0115>15:26:57:512 [INFO]:(GATEWAY)HCI_GATEWAY_RSP_OP_CODE: 91 81 02 00 01 00 83 0b 08 00
<0116>15:26:57:620 [INFO]:(GATEWAY)cmd sendback src:0x0001 dst:0x0004,op 0a83(FW_UPDATE_METADATA_CHECK): 00 01 00 32 38 00 00 00 00
<0117>15:26:57:666 [INFO]:(cmd_rsp)Status Rsp__: 04 00 01 00 83 0b 08 00
<0118>15:26:57:666 [INFO]:(GATEWAY)HCI_GATEWAY_RSP_OP_CODE: 91 81 04 00 01 00 83 0b 08 00
<0119>15:26:57:823 [INFO]:(GATEWAY)cmd sendback src:0x0001 dst:0x0002,op 1b80(CFG_MODEL_SUB_ADD): 02 00 00 c0 00 14
<0120>15:26:57:963 [INFO]:(cmd_rsp)Status Rsp__: 02 00 01 00 80 1f 00 02 00 00 c0 00 14
<0121>15:26:57:965 [INFO]:(GATEWAY)HCI_GATEWAY_RSP_OP_CODE: 91 81 02 00 01 00 80 1f 00 02 00 00 c0 00 14
<0122>15:26:58:043 [INFO]:(GATEWAY)cmd sendback src:0x0001 dst:0x0004,op 1b80(CFG_MODEL_SUB_ADD): 04 00 00 c0 00 14
<0123>15:26:58:244 [INFO]:(cmd_rsp)Status Rsp__: 04 00 01 00 80 1f 00 04 00 00 c0 00 14
<0124>15:26:58:249 [INFO]:(GATEWAY)HCI_GATEWAY_RSP_OP_CODE: 91 81 04 00 01 00 80 1f 00 04 00 00 c0 00 14
<0125>15:26:58:260 [INFO]:(GATEWAY)cmd sendback src:0x0001 dst:0x0002,op 0883(FW_UPDATE_INFO_GET): 00 01
<0126>15:26:58:306 [INFO]:(cmd_rsp)Status Rsp__: 02 00 01 00 83 09 01 00 04 01 00 41 00 00
<0127>15:26:58:306 [INFO]:(GATEWAY)HCI_GATEWAY_RSP_OP_CODE: 91 81 02 00 01 00 83 09 01 00 04 01 00 41 00 00
<0128>15:26:58:461 [INFO]:(GATEWAY)cmd sendback src:0x0001 dst:0x0004,op 0883(FW_UPDATE_INFO_GET): 00 01
<0129>15:26:58:538 [INFO]:(cmd_rsp)Status Rsp__: 04 00 01 00 83 09 01 00 04 01 00 41 00 00
<0130>15:26:58:538 [INFO]:(GATEWAY)HCI_GATEWAY_RSP_OP_CODE: 91 81 04 00 01 00 83 09 01 00 04 01 00 41 00 00
<0131>15:26:58:661 [INFO]:(GATEWAY)cmd sendback src:0x0001 dst:0x0002,op 0d83(FW_UPDATE_START): ff 00 00 11 22 33 44 55 66 77 88 00 01 00 32 38 00 00 00 00
<0132>15:26:58:942 [INFO]:(cmd_rsp)Status Rsp__: 02 00 01 00 83 10 40 ff 01 00 00 11 22 33 44 55 66 77 88 00
<0133>15:26:58:942 [INFO]:(GATEWAY)HCI_GATEWAY_RSP_OP_CODE: 91 81 02 00 01 00 83 10 40 ff 01 00 00 11 22 33 44 55 66 77 88 00
<0134>15:26:59:112 [INFO]:(GATEWAY)cmd sendback src:0x0001 dst:0x0004,op 0d83(FW_UPDATE_START): ff 00 00 11 22 33 44 55 66 77 88 00 01 00 32 38 00 00 00 00
<0135>15:26:59:419 [INFO]:(cmd_rsp)Status Rsp__: 04 00 01 00 83 10 40 ff 01 00 00 11 22 33 44 55 66 77 88 00
<0136>15:26:59:419 [INFO]:(GATEWAY)HCI_GATEWAY_RSP_OP_CODE: 91 81 04 00 01 00 83 10 40 ff 01 00 00 11 22 33 44 55 66 77 88 00
<0137>15:26:59:591 [INFO]:(GATEWAY)cmd sendback src:0x0001 dst:0x0002,op 0083(BLOB_TRANSFER_GET)NULL
<0138>15:26:59:885 [INFO]:(cmd_rsp)Status Rsp__: 02 00 01 00 83 03 00 01 11 22 33 44 55 66 77 88
<0139>15:26:59:885 [INFO]:(GATEWAY)HCI_GATEWAY_RSP_OP_CODE: 91 81 02 00 01 00 83 03 00 01 11 22 33 44 55 66 77 88
<0140>15:27:00:054 [INFO]:(GATEWAY)cmd sendback src:0x0001 dst:0x0004,op 0083(BLOB_TRANSFER_GET)NULL
<0141>15:27:00:315 [INFO]:(cmd_rsp)Status Rsp__: 04 00 01 00 83 03 00 01 11 22 33 44 55 66 77 88
<0142>15:27:00:315 [INFO]:(GATEWAY)HCI_GATEWAY_RSP_OP_CODE: 91 81 04 00 01 00 83 03 00 01 11 22 33 44 55 66 77 88
<0143>15:27:00:502 [INFO]:(GATEWAY)cmd sendback src:0x0001 dst:0x0002,op 0683(BLOB_INFO_GET)NULL
<0144>15:27:00:768 [INFO]:(cmd_rsp)Status Rsp__: 02 00 01 00 83 07 12 12 ed 04 d0 00 00 00 03 00 7c 01 01
<0145>15:27:00:768 [INFO]:(GATEWAY)HCI_GATEWAY_RSP_OP_CODE: 91 81 02 00 01 00 83 07 12 12 ed 04 d0 00 00 00 03 00 7c 01 01
<0146>15:27:00:940 [INFO]:(GATEWAY)cmd sendback src:0x0001 dst:0x0004,op 0683(BLOB_INFO_GET)NULL
<0147>15:27:01:203 [INFO]:(cmd_rsp)Status Rsp__: 04 00 01 00 83 07 12 12 ed 04 d0 00 00 00 03 00 7c 01 01
<0148>15:27:01:203 [INFO]:(GATEWAY)HCI_GATEWAY_RSP_OP_CODE: 91 81 04 00 01 00 83 07 12 12 ed 04 d0 00 00 00 03 00 7c 01 01
<0149>15:27:01:403 [INFO]:(GATEWAY)cmd sendback src:0x0001 dst:0x0002,op 0183(BLOB_TRANSFER_START): 40 11 22 33 44 55 66 77 88 c4 11 00 00 12 7c 01
<0150>15:27:01:683 [INFO]:(cmd_rsp)Status Rsp__: 02 00 01 00 83 03 40 02 11 22 33 44 55 66 77 88 c4 11 00 00 12 7c 01 01
<0151>15:27:01:683 [INFO]:(GATEWAY)HCI_GATEWAY_RSP_OP_CODE: 91 81 02 00 01 00 83 03 40 02 11 22 33 44 55 66 77 88 c4 11 00 00 12 7c 01 01
<0152>15:27:01:872 [INFO]:(GATEWAY)cmd sendback src:0x0001 dst:0x0004,op 0183(BLOB_TRANSFER_START): 40 11 22 33 44 55 66 77 88 c4 11 00 00 12 7c 01
<0153>15:27:02:167 [INFO]:(cmd_rsp)Status Rsp__: 04 00 01 00 83 03 40 02 11 22 33 44 55 66 77 88 c4 11 00 00 12 7c 01 01
<0154>15:27:02:167 [INFO]:(GATEWAY)HCI_GATEWAY_RSP_OP_CODE: 91 81 04 00 01 00 83 03 40 02 11 22 33 44 55 66 77 88 c4 11 00 00 12 7c 01 01
<0155>15:27:02:352 [INFO]:(GATEWAY)cmd sendback src:0x0001 dst:0x0002,op 0483(BLOB_BLOCK_START): 00 00 d0 00
<0156>15:27:02:398 [INFO]:(cmd_rsp)Status Rsp__: 02 00 01 00 67 00 00 00 d0 00
<0157>15:27:02:398 [INFO]:(GATEWAY)HCI_GATEWAY_RSP_OP_CODE: 91 81 02 00 01 00 67 00 00 00 d0 00
<0158>15:27:02:552 [INFO]:(GATEWAY)cmd sendback src:0x0001 dst:0x0004,op 0483(BLOB_BLOCK_START): 00 00 d0 00
<0159>15:27:02:598 [INFO]:(cmd_rsp)Status Rsp__: 04 00 01 00 67 00 00 00 d0 00
<0160>15:27:02:598 [INFO]:(GATEWAY)HCI_GATEWAY_RSP_OP_CODE: 91 81 04 00 01 00 67 00 00 00 d0 00
<0161>15:27:02:785 [INFO]:(gw_vc_log)OTA, block sum: 1,cur: 0, chunk sum:22,cur: 0, Progress: 5% NULL
<0162>15:27:02:800 [INFO]:(GATEWAY)cmd sendback src:0x0001 dst:0xc000,op 0066(BLOB_CHUNK_TRANSFER): 00 00 26 80 01 00 32 38 5d 01 4b 4e 4c 54 60 00 88 00 ae 80
<0163>15:27:03:000 [INFO]:(gw_vc_log)OTA, block sum: 1,cur: 0, chunk sum:22,cur: 1, Progress: 9% NULL
<0164>15:27:03:015 [INFO]:(GATEWAY)cmd sendback src:0x0001 dst:0xc000,op 0066(BLOB_CHUNK_TRANSFER): 01 00 08 58 10 50 04 b1 04 b2 f8 87 00 a0 1a 09 1b 0a 91 02
<0165>15:27:03:217 [INFO]:(gw_vc_log)OTA, block sum: 1,cur: 0, chunk sum:22,cur: 2, Progress:14% NULL
<0166>15:27:03:232 [INFO]:(GATEWAY)cmd sendback src:0x0001 dst:0xc000,op 0066(BLOB_CHUNK_TRANSFER): 02 00 70 07 c0 46 00 65 00 f6 00 fe 07 0b 18 40 07 0b 18 40
<0167>15:27:03:448 [INFO]:(gw_vc_log)OTA, block sum: 1,cur: 0, chunk sum:22,cur: 3, Progress:18% NULL
<0168>15:27:03:463 [INFO]:(GATEWAY)cmd sendback src:0x0001 dst:0xc000,op 0066(BLOB_CHUNK_TRANSFER): 03 00 10 6d c0 46 43 06 80 00 b8 00 80 00 ba 00 80 00 00 65
<0169>15:27:03:649 [INFO]:(gw_vc_log)OTA, block sum: 1,cur: 0, chunk sum:22,cur: 4, Progress:23% NULL
<0170>15:27:03:665 [INFO]:(GATEWAY)cmd sendback src:0x0001 dst:0xc000,op 0066(BLOB_CHUNK_TRANSFER): 04 00 f9 c9 01 a2 04 0b 1a 40 34 a0 80 a1 ff 97 78 9f 01 60
<0171>15:27:03:867 [INFO]:(gw_vc_log)OTA, block sum: 1,cur: 0, chunk sum:22,cur: 5, Progress:27% NULL
<0172>15:27:03:882 [INFO]:(GATEWAY)cmd sendback src:0x0001 dst:0xc000,op 0066(BLOB_CHUNK_TRANSFER): 05 00 02 a1 ff 97 15 9f 02 a0 a2 a1 ff 97 11 9f 27 a0 00 a1
<0173>15:27:04:084 [INFO]:(gw_vc_log)OTA, block sum: 1,cur: 0, chunk sum:22,cur: 6, Progress:32% NULL
<0174>15:27:04:099 [INFO]:(GATEWAY)cmd sendback src:0x0001 dst:0xc000,op 0066(BLOB_CHUNK_TRANSFER): 06 00 c1 87 01 a3 63 40 d1 87 60 00 80 00 04 04 04 04 40 0c
<0175>15:27:04:284 [INFO]:(gw_vc_log)OTA, block sum: 1,cur: 0, chunk sum:22,cur: 7, Progress:36% NULL
<0176>15:27:04:314 [INFO]:(gw_vc_log)OTA, block sum: 1,cur: 0, chunk sum:22,cur: 8, Progress:41% NULL
<0177>15:27:04:330 [INFO]:(GATEWAY)cmd sendback src:0x0001 dst:0xc000,op 0066(BLOB_CHUNK_TRANSFER): 08 00 d1 87 bd a0 ff 97 bc 9d 01 ec a1 03 09 f6 09 fe bd a0
<0178>15:27:04:486 [INFO]:(gw_vc_log)OTA, block sum: 1,cur: 0, chunk sum:22,cur: 9, Progress:45% NULL
<0179>15:27:04:502 [INFO]:(GATEWAY)cmd sendback src:0x0001 dst:0xc000,op 0066(BLOB_CHUNK_TRANSFER): 09 00 9c 02 5c c1 0a a9 00 c1 45 82 28 a9 00 c1 29 83 0d a9
<0180>15:27:04:701 [INFO]:(gw_vc_log)OTA, block sum: 1,cur: 0, chunk sum:22,cur:10, Progress:50% NULL
<0181>15:27:04:716 [INFO]:(GATEWAY)cmd sendback src:0x0001 dst:0xc000,op 0066(BLOB_CHUNK_TRANSFER): 0a 00 54 e9 af 0a a4 e8 22 48 13 00 0b 03 23 40 30 6d 02 ac
<0182>15:27:04:903 [INFO]:(gw_vc_log)OTA, block sum: 1,cur: 0, chunk sum:22,cur:11, Progress:54% NULL
<0183>15:27:04:919 [INFO]:(GATEWAY)cmd sendback src:0x0001 dst:0xc000,op 0066(BLOB_CHUNK_TRANSFER): 0b 00 15 82 81 a5 ad f0 cf a3 00 a1 8b 87 05 a9 00 c1 69 81
<0184>15:27:05:120 [INFO]:(gw_vc_log)OTA, block sum: 1,cur: 0, chunk sum:22,cur:12, Progress:59% NULL
<0185>15:27:05:135 [INFO]:(GATEWAY)cmd sendback src:0x0001 dst:0xc000,op 0066(BLOB_CHUNK_TRANSFER): 0c 00 00 c1 01 82 28 a9 00 c1 5e 81 08 a5 3f a3 00 a1 20 87
<0186>15:27:05:320 [INFO]:(gw_vc_log)OTA, block sum: 1,cur: 0, chunk sum:22,cur:13, Progress:63% NULL
<0187>15:27:05:336 [INFO]:(GATEWAY)cmd sendback src:0x0001 dst:0xc000,op 0066(BLOB_CHUNK_TRANSFER): 0d 00 fc a3 00 a1 be 86 09 a9 00 c1 df 80 04 a9 00 c1 a3 81
<0188>15:27:05:521 [INFO]:(gw_vc_log)OTA, block sum: 1,cur: 0, chunk sum:22,cur:14, Progress:68% NULL
<0189>15:27:05:537 [INFO]:(GATEWAY)cmd sendback src:0x0001 dst:0xc000,op 0066(BLOB_CHUNK_TRANSFER): 0e 00 40 a1 57 86 82 a5 6d f0 cf a3 10 a1 52 86 a7 0d fc a3
<0190>15:27:05:742 [INFO]:(gw_vc_log)OTA, block sum: 1,cur: 0, chunk sum:22,cur:15, Progress:72% NULL
<0191>15:27:05:757 [INFO]:(GATEWAY)cmd sendback src:0x0001 dst:0xc000,op 0066(BLOB_CHUNK_TRANSFER): 0f 00 00 a1 ef 85 25 ec f3 a3 00 a1 eb 85 74 0d fc a3 00 a1
<0192>15:27:05:946 [INFO]:(gw_vc_log)OTA, block sum: 1,cur: 0, chunk sum:22,cur:16, Progress:77% NULL
<0193>15:27:05:961 [INFO]:(GATEWAY)cmd sendback src:0x0001 dst:0xc000,op 0066(BLOB_CHUNK_TRANSFER): 10 00 88 85 43 0d fc a3 02 a1 84 85 c4 a5 ad f0 fc a3 02 a1
<0194>15:27:06:149 [INFO]:(gw_vc_log)OTA, block sum: 1,cur: 0, chunk sum:22,cur:17, Progress:81% NULL
<0195>15:27:06:164 [INFO]:(GATEWAY)cmd sendback src:0x0001 dst:0xc000,op 0066(BLOB_CHUNK_TRANSFER): 11 00 c1 a5 ad f0 cf a3 10 a1 1c 85 25 ec f3 a3 04 a1 18 85
<0196>15:27:06:367 [INFO]:(gw_vc_log)OTA, block sum: 1,cur: 0, chunk sum:22,cur:18, Progress:86% NULL
<0197>15:27:06:382 [INFO]:(GATEWAY)cmd sendback src:0x0001 dst:0xc000,op 0066(BLOB_CHUNK_TRANSFER): 12 00 cf 99 f2 a0 08 a1 ff 97 cb 99 04 a0 00 a1 ff 97 7f 9b
<0198>15:27:06:586 [INFO]:(gw_vc_log)OTA, block sum: 1,cur: 0, chunk sum:22,cur:19, Progress:90% NULL
<0199>15:27:06:602 [INFO]:(GATEWAY)cmd sendback src:0x0001 dst:0xc000,op 0066(BLOB_CHUNK_TRANSFER): 13 00 67 99 88 a0 ff 97 44 99 04 ec 87 a0 ff 97 40 99 1f a1
<0200>15:27:06:789 [INFO]:(gw_vc_log)OTA, block sum: 1,cur: 0, chunk sum:22,cur:20, Progress:95% NULL
<0201>15:27:06:804 [INFO]:(GATEWAY)cmd sendback src:0x0001 dst:0xc000,op 0066(BLOB_CHUNK_TRANSFER): 14 00 00 a3 23 50 3b 40 42 06 13 40 04 6c 90 06 f0 6d 66 00
<0202>15:27:06:991 [INFO]:(gw_vc_log)OTA, block sum: 1,cur: 0, chunk sum:22,cur:21, Progress:99% NULL
<0203>15:27:07:007 [INFO]:(GATEWAY)cmd sendback src:0x0001 dst:0xc000,op 0066(BLOB_CHUNK_TRANSFER): 15 00 11 50 a5 a1 19 0a 11 40 01 b2 13 40 10 6d 18 08 ff 97
<0204>15:27:07:195 [INFO]:(GATEWAY)cmd sendback src:0x0001 dst:0x0002,op 0583(BLOB_BLOCK_GET)NULL
<0205>15:27:07:226 [INFO]:(cmd_rsp)Status Rsp__: 02 00 01 00 67 c0 00 00 d0 00 07
<0206>15:27:07:226 [INFO]:(GATEWAY)HCI_GATEWAY_RSP_OP_CODE: 91 81 02 00 01 00 67 c0 00 00 d0 00 07
<0207>15:27:07:396 [INFO]:(GATEWAY)cmd sendback src:0x0001 dst:0x0004,op 0583(BLOB_BLOCK_GET)NULL
<0208>15:27:07:475 [INFO]:(cmd_rsp)Status Rsp__: 04 00 01 00 67 c0 00 00 d0 00 07
<0209>15:27:07:475 [INFO]:(GATEWAY)HCI_GATEWAY_RSP_OP_CODE: 91 81 04 00 01 00 67 c0 00 00 d0 00 07
<0210>15:27:07:614 [INFO]:(GATEWAY)cmd sendback src:0x0001 dst:0xc000,op 0066(BLOB_CHUNK_TRANSFER): 07 00 10 65 0c f6 24 fe a1 f0 23 f1 19 03 21 03 09 f6 09 fe
<0211>15:27:07:833 [INFO]:(GATEWAY)cmd sendback src:0x0001 dst:0x0002,op 0583(BLOB_BLOCK_GET)NULL
<0212>15:27:07:864 [INFO]:(cmd_rsp)Status Rsp__: 02 00 01 00 67 40 00 00 d0 00
<0213>15:27:07:864 [INFO]:(GATEWAY)HCI_GATEWAY_RSP_OP_CODE: 91 81 02 00 01 00 67 40 00 00 d0 00
<0214>15:27:08:052 [INFO]:(GATEWAY)cmd sendback src:0x0001 dst:0x0004,op 0583(BLOB_BLOCK_GET)NULL
<0215>15:27:08:209 [INFO]:(cmd_rsp)Status Rsp__: 04 00 01 00 67 40 00 00 d0 00
<0216>15:27:08:209 [INFO]:(GATEWAY)HCI_GATEWAY_RSP_OP_CODE: 91 81 04 00 01 00 67 40 00 00 d0 00
<0217>15:27:08:271 [INFO]:(GATEWAY)cmd sendback src:0x0001 dst:0x0002,op 0c83(FW_UPDATE_GET)NULL
<0218>15:27:08:582 [INFO]:(cmd_rsp)Status Rsp__: 02 00 01 00 83 10 80 ff 01 00 00 11 22 33 44 55 66 77 88 00
<0219>15:27:08:582 [INFO]:(GATEWAY)HCI_GATEWAY_RSP_OP_CODE: 91 81 02 00 01 00 83 10 80 ff 01 00 00 11 22 33 44 55 66 77 88 00
<0220>15:27:08:754 [INFO]:(GATEWAY)cmd sendback src:0x0001 dst:0x0004,op 0c83(FW_UPDATE_GET)NULL
<0221>15:27:09:035 [INFO]:(cmd_rsp)Status Rsp__: 04 00 01 00 83 10 80 ff 01 00 00 11 22 33 44 55 66 77 88 00
<0222>15:27:09:035 [INFO]:(GATEWAY)HCI_GATEWAY_RSP_OP_CODE: 91 81 04 00 01 00 83 10 80 ff 01 00 00 11 22 33 44 55 66 77 88 00
<0223>15:27:09:219 [INFO]:(GATEWAY)cmd sendback src:0x0001 dst:0x0002,op 0f83(FW_UPDATE_APPLY)NULL
<0224>15:27:09:530 [INFO]:(cmd_rsp)Status Rsp__: 02 00 01 00 83 10 c0 ff 01 00 00 11 22 33 44 55 66 77 88 00
<0225>15:27:09:530 [INFO]:(GATEWAY)HCI_GATEWAY_RSP_OP_CODE: 91 81 02 00 01 00 83 10 c0 ff 01 00 00 11 22 33 44 55 66 77 88 00
<0226>15:27:09:717 [INFO]:(GATEWAY)cmd sendback src:0x0001 dst:0x0004,op 0f83(FW_UPDATE_APPLY)NULL
<0227>15:27:09:999 [INFO]:(cmd_rsp)Status Rsp__: 04 00 01 00 83 10 c0 ff 01 00 00 11 22 33 44 55 66 77 88 00
<0228>15:27:09:999 [INFO]:(GATEWAY)HCI_GATEWAY_RSP_OP_CODE: 91 81 04 00 01 00 83 10 c0 ff 01 00 00 11 22 33 44 55 66 77 88 00
<0229>15:27:10:186 [INFO]:(GATEWAY)mesh OTA success
<0230>15:27:10:201 [INFO]:(GATEWAY)cmd sendback src:0x0001 dst:0x0001,op 1b83(FW_DISTRIBUT_CANCEL)NULL
<0231>15:27:10:217 [INFO]:(GATEWAY)cmd sendback src:0x0001 dst:0x0001,op 1d83(FW_DISTRIBUT_STATUS): 00 00
<0232>15:27:10:232 [INFO]:(cmd_rsp)Status Rsp__: 01 00 01 00 83 1d 00 00
<0233>15:27:10:232 [INFO]:(cmd_name)mesh OTA completed or get info ok!
<0234>15:27:10:232 [INFO]:(GATEWAY)HCI_GATEWAY_RSP_OP_CODE : 91 81 01 00 01 00 83 1d 00 00
<0235>15:27:10:279 [INFO]:(gw_vc_log)OTA, block sum: 0,cur: 0, chunk sum: 0,cur: 0, Progress:100% NULL
Subnet Bridge
功能介绍
一个蓝牙网络包含一个或多个子网,子网间通常使用不同的网络密钥加密来隔离。在mesh 1.0中,只有处于同一子网并使用相同的网络密钥的设备才能相互通信,不同子网间的设备不能相互通信。使用子网进行安全隔离是一个强大的功能:如下图所示,在一个酒店里,每个房间之间通过子网来隔离,一个房间里的设备不会干扰到另外一个房间的设备。
在某些特定的情况下,需要消息能够在子网之间传输。比如,酒店房间里客人按房间清洁服务按钮向管家团队请求服务的需求。由于每个客房的mesh key都不能一样(基于权限管控考虑,客人手机控制端只能有当前房间的key,不能有其他房间的key,包括管家团队的key),在mesh 1.0中,需要为每个房间增加一个非mesh的网关(比如TCP/IP)上报消息来实现,无法直接通过现有的mesh网络通传输该需求到管家团队。
现在,Subnet bridge feature可实现不同子网设备间通信,即使它们没有共同的子网和网络密钥。选择支持两个key的mesh节点作为桥接点,通过配置桥节点的桥接表,可以实现客人按房间清洁按钮后,消息通过子网桥接和定向转发,可到达管家团队,但房间里未配置桥接表的其他设备不能与此房间外的设备通信。
如上图桥接表,
“Directions”指定了是否支持双向。
“NetKeyIndex1,NetKeyIndex2”指定了在哪两个netkey之间建立bridge。
“Address1,Address2”指定了在哪两个address之间建立bridge。
网络层收到消息后,会检查桥接表来决定是否桥接。满足桥接条件的话,使用桥接表里指定的第二个网络的NetKey重新加密转发。
功能概述还可以参考 https://www.bluetooth.com/mesh-feature-enhancements-summary/ 这个SIG 官网的描述。
子网桥接原理
子网桥接功能通过对拥有多个子网的节点配置桥接表,在网络层向特定子网转发消息。
节点在接收消息时,如果消息的源地址和目的地址在桥接表中,并且消息使用源子网的NetKey加密,将使用源子网NetKey解密消息后,用目标子网NetKey重新加密后转发到指定的子网;如果消息的源地址和目的地址不在桥接表,则不会转发到其他子网。
配置方式
(1) 在mesh_config.h文件中把宏 MD_SBR_CFG_SERVER_EN 设置为1.
(2)编译8258_mesh工程。
(3)烧录至少3个dongle 节点:light-Room 1 ,light-Room 2, cleaning device
功能演示
演示情景示意图:
Subnet Bridge功能允许向拥有多个子网的节点配置桥接表来向特定子网转发消息。
具体的操作说明,请参考App操作说明33 Android and iOS APP使用说明对应的"Subnet Bridge Setting"章节。
备注:上位机(sig_mesh_tool on PC)暂时没有UI去配置节点的subnet bridge参数。
Direct Forwarding
Direct Forwarding可将指令在指定路径节点(路由表)参与转发,减少空中转发的包。该功能侧重提高网络利用率,不侧重传输速率的提高。
可控泛洪:是指mesh消息从源头向外传播时,类似于石头扔进水中产生向四周扩散的纹波。通过ttl控制传输的范围,一个节点的relay feature是否使能决定了该节点是否转发该消息,这种传输方式称为可控泛洪。
可控泛洪无法控制消息传递的方向,浪费与消息无关的网络部分的带宽。比如在大会议室中间有2个开关,分别控制讲台和后排的灯,在控制讲台的灯时,消息也会在后排灯节点间重传。
路由表:路由表是指一条路径标识,该路径标识指定了消息传输路径从起点到终点所经过的所有节点,只有路径上的节点才可以转发该条消息。终点可为单播、组播和虚拟地址,每个路由节点保存经过它的路径信息,即路由表。在网络中选择若干个节点组成路径,一条路由可能有1条或多条路径。
路由原理
消息在指定路由方式发送的时候会检查路径是否存在,如果存在,则以路由的方式发送。否则会以泛洪的方式发送消息,并自动触发路由建立。 泛洪方式发送的消息用network key加密,路由发送的消息用directed key(由network key派生)加密。
路由节点收到由directed key加密的mesh消息时,会根据源地址和目的地址查找路由表中是否有对应的路径。如果找到对应路径,以路由方式转发消息,否则不转发此消息。这样就实现了消息按指定的路线转发的目的。
功能概述还可以参考 https://www.bluetooth.com/mesh-feature-enhancements-summary/ 这个SIG官网的描述。
在DF_TEST_MODE_EN模式中,节点在转发用directed key加密的消息时,会进行闪灯。路径建立的消息是directed key加密的,因此所有节点都会闪灯,表示节点转发了路径建立的消息。在路径建立后,只有路径上的节点会闪灯,表示只有路径上的节点转发了这条消息。
路由表种类
路由分为固定路由和非固定路由两种方式:
(1) 固定路由由provisioner配置和管理,在搭建好网络后,根据网络中节点的位置分布选择路由节点。通常这需要专业的人员安装和配置。
(2) 非固定路由由发送端(路径的起点)自动创建和维护。
测试Firmware配置
打开MD_DF_CFG_SERVER_EN和DF_TEST_MODE_EN,然后编译8258_mesh工程。
固定路由
固定路由 由provisioner配置和管理,指定路径上的节点进行转发。所以需要提前在App进行如下配置:
打开Direct Forwarding——Direct Toggles界面,然后将路径上的节点打开Direct Forwarding(Main)、Direct Relay、Direct Proxy、Direct Friend功能 。注:如果不勾选Direct forwarding(main)底部会提示“(relay)check direct forwarding first”。
DF在下文中表示Direct Forwarding
a) Direct Forwarding(main)-这是DF的主开关。如果禁用,所有测向功能将被禁用,包括Direct Relay/Proxy/Friend。
b) Direct Relay-如果禁用,接收测DF消息的当前节点将不会将消息转发到其他节点。DF消息采用DF密钥加密。
c) Direct Proxy-如果禁用,从App发送的消息将无法通过路由方式发送到其他节点,而是通过flooding方式发送。
d) Direct Friend-由于LPN(低功率节点)不支持DF功能,因为它是一个低功耗节点,不会一直处于扫描ADV的状态。并且LPN仅从与当前LPN建立了友谊的Friend节点接收消息。因此,如果其他节点想通过路由方式向LPN发送消息,我们需要启用"Direct Friend"功能。则该消息可以通过路由方式先发送到Friend节点,Friend节点将缓存该消息,然后在LPN唤醒时发送到LPN,实现到LPN的路由。
Direct forwarding界面点击底部Add Table按钮可在mesh网络中添加固定路由的路径。路径包含起点和终点,以及路径经过的节点。消息从起点以路由方式发送时,路径上的节点都会参与消息的转发,不在路径上的节点忽略此消息。
注意,手机发出来的命令,一般不用固定路由模式,而是使用动态路由模式,因为手机App位置不固定,和它进行GATT直连的节点也是不固定的。
所以固定路由的路径配置的起点地址一般是某个灯节点,示例如下:路径起点是 0x0006,路径上的节点是 0x000e,目的地址的节点是 0x0016.
按下0x0006 节点上的 SW2 按键,会发出Generic Onoff 的命令,由于打开了测试模式,即打开了DF_TEST_MODE_EN,该命令的源地址是该节点本身(0x0006),目的地址是第一条固定路由的路径列表里面的目的地址,也就是 0x0016。
命令发出后,我们可以看到路径上的0x06, 0x0e,0x16节点 红色LED闪烁,其它不在路径上的节点LED不闪烁,不闪烁的节点表示不会转发该条信息,也就是实现了路由功能。
非固定路由
非固定路由不需要在APP中的Direct Forwarding进行操作。
当命令发起者是手机App时,由和手机App直连的节点代理进行自动创建和维护路由,以可控泛洪的方式发送消息,并触发路由建立。
当命令发起者不是手机App时,比如时网关等,由命令发送端(路径起点)创建和维护路由信息。
路径建立的消息是network key加密的,因此所有节点都会闪灯,表示节点转发了路径建立的消息。在路径建立后,使用Directed Key加密,所以只有路径上的节点会闪灯,这表示只有路径上的节点转发了指令。
非固定路由建立规则:在满足设定 的能量阈值的情况下,选择最短路径,下图是建立从PO(Path Origon)到PT(Path Target)的2条路径的示意图。
测试示例使用App来发送命令,步骤如下:
(1) 用app把节点加入到网络。
(2) 手机App处于主页状态,自动连接上某一个节点,初始状态是还没有创建任何动态路径。
(3) 手机App发送 Generic Onoff 命令给任意一个非直连节点,比如 0x0016 节点,此时用的是泛洪模式,所以所有节点都会转发该命令,也就是所有节点都会闪烁红色 LED 灯。此时检测到 该源地址和目的地址还没有路径,此时自动触发路径建立。
(4) 5秒后向路径建立完成,建立的路径可以设置为多条,测试模式下,只选最优的一条路径。
(5) 手机APP向同一目的地址0x0016 再次发送开关灯命令,此时只有处于同一路径上的节点(包括路径起点和终点)会以2Hz频率闪烁2秒。
(6) 手机App再次发送Generic Onoff命令给 节点,此时只有上一个步骤的路径的节点在闪烁。一段时间内(测试模式默认是12分钟,非测试模式默认是 24小时),如果该路径都没有消息在发送,则该路径会被删除。再次发开关灯会进入步骤3重新建立路径。
Private-beacon
功能概述可以参考 https://www.bluetooth.com/mesh-feature-enhancements-summary/ 这个SIG 官网的描述。
spec对应的章节介绍请参考"MshPRT_v1.1.pdf"的 "4.2.44 Mesh Private Beacon", "4.4.11 Mesh Private Beacon Server model" 和 "4.4.12 Mesh Private Beacon Client model" 等,也可以通过在 spec 的书签栏 输入 private 检索。
应用背景介绍
在一些场景中,比如可穿戴等需要移动的设备,如果这类设备发出来的 mesh Beacon有明文的静态数据,那这些消息就有可能被跟踪,该设备的位置也就会被跟踪。所以定义了 private-beacon 解决此类问题,因为在private-beacon模式下,这些beacon数据会一直变化和被加密,这样就无法被跟踪。
功能介绍
Private功能可确保信标消息中的静态信息对网络外部的设备不可见,因为已经被使用 network key 加密,从而提高安全性。这可以提高隐私性。
Mesh Private beacon
如下图所示,Beacon 新增了Mesh Private beacon,该beacon解密后的内容和secure network beacon的内容相同,具体的功能也相同。
所以Mesh private beacon也是是在组网成功之后发送的,和secure network beacon不同的是private beacon的ivi index和flag采用的是加密的方式出现,并且adv的地址采用的non-reslovable。
Unprovisioned Device beacon没有对应的private 模式,因为节点还未组网,不涉及到被追踪的问题。
下图是private beacon的每个字段的介绍,以及计算过程,SDK对应的函数是mesh_tx_sec_privacy_beacon():
Private network identity和Private Node Identity
如下图所示,可连接广播包的类型新增了Private network identity和Private Node Identity两种广播包。
其中Private network identity 解密后和 Network ID 等效, Private Node Identity 解密后和Node Identity等效。
下图是介绍节点在发送 Node Identity状态时,以下组合的情况下,设备是应该发送 Node Identity 还是Private Node Identity,其中第一列“Node Identity state”和第二列“Private Node Identity state”是条件,第三列“Advertising”是需要发送的包。
下图是介绍当节点处于发送Network ID转态时,以下组合的情况下,设备应该发送Network ID还是Private network identity,其中第一列“Node Identity state”和第二列“Private Node Identity state”是条件,第三列“Advertising”是需要发送的包。
private-beacon通过两个model来维护状态:private-beacon server model,private-beacon client model。
在SDK里,对应的判断函数是mesh_get_identity_type()。
Opcode介绍
spec对应的章节介绍请参考"MshPRT_v1.1.pdf"的 "4.3.12 Mesh Private Beacon messages"。
PRIVATE_BEACON_SET:使能或关闭private-beacon的发送。
PRIVATE_GATT_PROXY_SET:使能或关闭private gatt proxy的发送,即控制private node identity和private network identity。
PRIVATE_NODE_IDENTITY: 使能或关闭private node identity的发送。
测试步骤
Firmware SDK 打开MD_PRIVACY_BEA 和 PRIVATE_PROXY_FUN_EN。
使用App的测试步骤,请查看33 Android and iOS APP使用说明的章节33.4.5 Private beacon。
Minor Mesh Enhancements
功能概述还可以参考 https://www.bluetooth.com/mesh-feature-enhancements-summary/ 这个SIG官网的描述。
Opcodes Aggregator Server model
应用背景介绍
比如组网的时候,很多个key bind命令,就可以聚合打包为一条命令,节省组网时间。
功能介绍
操作码聚合器允许将同一server model下不同的opcode消息通过LTV结构(length,Opcode,Parameters)打包成一条OPCODES_AGGREGATOR_SEQUENCE消息类型进行发送。
消息请求列表处理过程通过解析LTV结构知道消息的opcode及参数,把回复的状态通过LTV结构打包成一条OPCODES_AGGREGATOR_STATUS进行回复。
操作码聚合器通过把一系列消息压缩到一条,减少了交互、处理和响应的时间。
spec对应的章节介绍请参考"MshPRT_v1.1.pdf"的 "4.4.19 Opcodes Aggregator Server model" 和 "4.4.20 Opcodes Aggregator Client model" 等.
测试步骤
-
Firmware SDK打开MD_OP_AGG_EN。
-
使用sig mesh app对节点组网。
当处于app bind过程时,app会将所有的bind消息压缩为一条消息,减少了交互、处理和响应的时间。如下图所示:
Large Composition Data Models
应用背景介绍
一些设备需要大量可变数据来描述其组成、配置数据和其他属性。当节点有很多element的时候,比如100个,此时composition data的长度会很长,超过380byte(一条 mesh 消息最大只能发送 380byte),此时就无法由单一的消息来发送 composition data status,此时就需要分段获取 composition data status 的内容。
功能介绍
Composition data由一系列page组成,每个page都是组合状态,其中page 0定义了节点组成的元素以及支持的model。LARGE_COMPOSITION_DATA_GET可从指定page的某个字节开始读,以及指定读取长度进行分段读取。
Spec对应的章节介绍请参考"MshPRT_v1.1.pdf"的"4.4.21 Large Composition Data Server model" 和 "4.4.22 Large Composition Data Client model"等。
测试步骤
-
Firmware SDK 打开MD_LARGE_CPS_EN。
-
通过 INI 命令来发送LARGE_COMPOSITION_DATA_GET进行测试。
SAR Configuration Models
应用背景介绍
当不同供应商的设备对长数据包的分包组包行为使用不同的默认值时,比如重试间隔、重试次数、超时时间等,这可能会导致网状网络内的网状消息传输效率低下。通过SAR Configuration Server model对SAR行为进行统一配置,从而提高性能。
功能介绍
SAR是指:Segmentation And Reassembly。
分包和重组参数可通过 SAR Configuration Server model相关命令进行配置,有助于提高segment发送和接收的效率,尤其是网络中有多个制造商的设备的时候。
SAR Transmitter包含发送分包的参数:分包之间时间间隔,以及重传间隔和次数有关的参数。
SAR Receiver包含接收分包的参数:回复seg ack及timeout时间的有关参数。
Spec对应的章节介绍请参考"MshPRT_v1.1.pdf"的"4.4.15 SAR Configuration Server model" 和 "4.4.16 SAR Configuration Client model"等。
测试步骤
-
Firmware SDK 打开MD_SAR_EN。
-
通过 INI 命令来发送SAR 相关命令进行测试。
EPA(Enhanced Provisioning Authentication)
应用背景介绍
增加另一个算法,更进一步增强 组网时数据认证的安全性,特别是组网采用 static OOB 模式时。
- 每次组网的时候,provioner 和 provisionee 都会要求重新生成组网用的 public key 和 private key。
- oob (包含 static oob / output oob/ input oob) 的长度由原来的 16byte 变为 32byte。
- 使用sha256 算法。
功能介绍
旧算法名称是 BTM_ECDH_P256_CMAC_AES128_AES_CCM,增加的新算法的名称是 BTM_ECDH_P256_HMAC_SHA256_AES_CCM。
和spec V1.0 的主要区别是:
- 每次组网的时候,provioner 和 provisionee 都会要求重新生成组网用的 public key 和 private key。确保不能通过重发消息进行数据交互。
- 新算法:EPA(BTM_ECDH_P256_HMAC_SHA256_AES_CCM,)使用长度更长的 sha256 算法(原来的算法是AES128),使得无法使用大型计算机进行遍历运算等。
- 新算法里面的 oob (包含 static oob / output oob/ input oob) 的长度由原来的 16byte 变为 32byte。
- 新算法里面的 random 和 confirm 的内容由原来的16byte改为32 byte,使得无法使用大型计算机进行遍历运算等。
数据格式:
EPA:provisioner判断节点是否支持EPA,是根据节点上报的 capability的参数,如下图的Algorithm 字段。为了兼容所有provisioner,节点可以选择同时置位bit0和bit1,表示同时支持旧算法和新算法, 然后由provisioner来选择用哪种方式入网。
spec 对应的章节介绍请参考"MshPRT_v1.1.pdf"的"5.4.1.2 Provisioning Capabilities"等。
测试步骤
- Firmware SDK 打开PROV_EPA_EN 即可,默认打开。
On-Demand Proxy Model
应用背景介绍
从用户体验的角度来看,在所有节点上启用代理是首选模式,也就是所有节点都在发送可连接广播包,手机 APP 可以随时连接该设备控制网络。但是当节点很多时,可连接广播包会有很多,这会影响可用带宽,导致APP和节点比较难进行GATT连接,以及会增加后续的 mesh ADV 消息的和可连接广播包之间的碰撞,从而对网状网络的性能产生负面影响。这时候就可以使用On-Demand Proxy Model来进行优化。
功能介绍
On-Demand Proxy Model功能使能时,节点处于不发送可连接广播包的状态,然后由代理客户端(手机app)向节点发送Solicitation PDU,请求节点开始广播 private beacon,总共发送多长时间的private beacon,这个时间由 g_mesh_model_misc_save.on_demand_proxy = ON_DEMAND_PRIVATE_GATT_PROXY_S(默认是30秒)来决定。也可以由 APP 或者网关 通过 On-Demand Proxy Model的相关命令来设置这个值。
设备端收到Solicitation PDU后,如果同时满足以下3个条件,则开始发送时长为 g_mesh_model_misc_save.on_demand_proxy 的private beacon(默认是30秒),条件如下:
(1) 节点当前的proxy和private proxy功能均设置为support,但是是 disable的状态。
(2) On-Demand Private GATT Proxy的值在0x01~0xff(通ON_DEMAND_PRIVATE_PROXY_SET设置,最终存储在 g_mesh_model_misc_save.on_demand_proxy)
(3) 用network key能对该Solicitation PDU解密成功。
spec 对应的章节介绍请参考"MshPRT_v1.1.pdf"的"4.4.13 On-Demand Private Proxy Server model" 和 "4.4.14 On-Demand Private Proxy Client model"等
Solicitation PDU 有新增了一套 sequence number的防止重放的机制。
测试步骤
使用APP进行测试:
firmware SDK 需要打开 MD_ON_DEMAND_PROXY_EN,PRIVATE_PROXY_FUN_EN,MD_PRIVACY_BEA。为了方便演示,测试的时候,只有一个设备在当前网络。
-
以上功能配置完成后,SDK 默认节点的 GATT proxy 是打开的,所以不满足本章节 “功能介绍”小节提到的3个条件。所以节点一直处于 发送 可连接广播包的状态。所以mesh App可以连接该设备,并进行配置。
-
进入app主页下,然后长按需要配置的节点,然后进入settings->private beacon,按照如下设置,将config GATT Proxy 和 private GATT Proxy关闭。如下图
备注:如果客户想修改 SDK 默认节点的 GATT proxy 是关闭的,则把 mesh_global_var_init()里面的
model_sig_cfg_s.gatt_proxy = FEATURE_PROXY_EN ? GATT_PROXY_SUPPORT_ENABLE : GATT_PROXY_NOT_SUPPORT;
改为
model_sig_cfg_s.gatt_proxy = FEATURE_PROXY_EN ? GATT_PROXY_SUPPORT_DISABLE : GATT_PROXY_NOT_SUPPORT;
即可。
- 通过以上设置后,退出当前 mesh App,断开app和节点的连接。用通用的蓝牙设备扫描App(比如 Light blue),会看到 30秒(ON_DEMAND_PRIVATE_GATT_PROXY_S)后,该节点不再发送广播包,此时打开 mesh App重新连接节点,可以看到app无法和节点再次连接,如下图:
断开连接后,设定的时间内还在保持可连接广播包的发送,主要目的是考虑,比如因意外断开连接后,方便 App 进行重连等。
- 如果需要让节点重新发送广播包,可以按下图所示,点击 主页-> Newtork -> solicitation PDU 按钮去使节点开始发送广播包。solicitation PDU的目的地址是 ADR_ALL_PROXY。
当app触发发送solicitation后,app会持续发送10s的 solicitation pdu,该时间由App自定义。该solicitation pdu是通过手机发送广播包的方式来发送,不需要执行 GATT 连接,发包间隔约为 100ms。solicitation pdu的详细介绍请参考此章节solicitation-pdu-rpl-cfg-models
当节点收到solicitation pdu后,会开始发送 可连接广播包,时间为前面设置给 g_mesh_model_misc_save.on_demand_proxy 的值。
在这个时间内可以通过 抓包工具或者蓝牙扫描工具看到节点已经在开始发送。然后,我们的 APP 就可以自动对该节点发起连接,然后控制 mesh 网络。
- 和节点重新连接之后的结果如下图
如果需要通过命令修改节点发送可连接广播的时长,可按以下步骤进行操作:
- 长按需要配置的节点进入settings->device config下的界面,点击on demand private proxy下的set,然后输入一个时间,该时间就是前面提到的g_mesh_model_misc_save.on_demand_proxy,该值决定节点在多长时间之后停止发送可连接广播包。如下图。
- 之后可以点击get获取你设置的值(0x0a)是否生效,如下图
Solicitation PDU RPL CFG models
手机发送Solicitation命令给节点,可以请求节点开始发送private beacon。这个Solicitation 命令是以ADV的方式来发送,并且和普通的ONOFF等network message是不一样的格式的,最主要的差别是加解密solicitation PDU的时候,使用的iv index固定是0,原因是:分享网络后,APP不知道iv index,节点又不发送private beacon,所以没办法知道当前网络的真正的iv index是多少,也没办法通过普通的network layer打开 beacon发送,所以就没办法通过proxy连接和控制该节点。
另外,solicitation 命令有独立的 sequence number,和普通的 network PDU 的sequence number不相同,并且有清除 solicitation sequence number cache 的需求。所以spec定义了Solicitation PDU RPL cfg models 用来清除设备端缓存的Solicitation PDU的RPL(Relay Protect List)。
Solicitation PDU RPL cfg models 是 为 On-Demand Proxy Model 服务的。
Networked Lighting Control(NLC)
功能概述可以参考 https://www.bluetooth.com/mesh-feature-enhancements-summary/ 这个SIG官网的描述的这章节的描述:“3. Bluetooth® Mesh Device Profiles”。
应用背景介绍
Bluetooth® Mesh technology provides a rich set of features and options to implement many lighting and sensing applications. This has helped Bluetooth Mesh establish itself as the preferred technology for scalable commercial and industrial applications. However, the optional nature of Bluetooth Mesh features can cause challenges for implementers when they must decide which options to choose for their chosen product segments. If vendors operating in the same product segments choose a different set of options that do not work well with other peer products (e.g., mesh features chosen for light bulbs are not compatible with features selected for light switches), a situation can arise where product ecosystems do not interoperate, which degrades the user experience.
Bluetooth Mesh技术提供了一套丰富的功能和选项,可用于实现许多照明和传感应用。这有助于蓝牙 Mesh成为可扩展商业和工业应用的首选技术。然而,当实施者必须决定为其选择的产品细分市场选择哪些选项时,蓝牙 Mesh功能的可选性质可能会给他们带来挑战。如果在同一产品细分市场中运营的供应商选择了一组不同的选项,但这些选项与其他同类产品不兼容(例如,为灯泡选择的Mesh功能与为灯泡开关选择的功能不兼容),则可能会出现产品生态系统无法互操作的情况,从而降低用户体验。
To address this issue, the Bluetooth SIG has come with the concept of Bluetooth Mesh Device Profiles. These profiles are new class of mesh specifications. Device Profiles define which options and features of the mesh specifications are mandatory for a certain kind of end product. The first suite of mesh device profiles is based on the lighting system architecture described in Building a Sensor-Driven Lighting Control System Based on Bluetooth Mesh whitepaper published in 2020. Collectively these profiles are called as Bluetooth Networked Lighting Control (NLC) Profiles, and they are defined as follows:
为了解决这个问题,蓝牙SIG引入了Bluetooth Mesh Device Profiles的概念。这些Profile是一类新的网格规范。Device Profile定义了Mesh规范的哪些选项和功能对于某种最终产品是强制性的。第一套Device Profile基于2020年发布的《构建基于蓝牙Mesh的传感器驱动照明控制系统》白皮书中描述的照明系统架构。这些Profile统称为蓝牙网络照明控制(NLC)Profile,其定义如下:
NLC包含的各个Profile
包含的Profile
- (ALSNLCP) Ambient Light Sensor NLC Profile
- (BLCNLCP) Basic Lightness Controller NLC Profile
- (BSSNLCP) Basic Scene Selector NLC Profile
- (DICNLCP) Dimming Control NLC Profile
- (OCSNLCP) Occupancy Sensor NLC Profile
- (ENMNLCP) Energy Monitor NLC Profile
灯和sensor联动的主要体验效果
NLC包含有sensor和灯进行联动的处理,这个联动的主要体验,简单概括如下:
- ambient light sensor的体验是:在没有光线 sensor 的时候,灯在 ON 的状态,LED 比如会输出 65535 的最大亮度。 在有光线 sensor 的时候,灯在 ON 的状态,LED 会根据当前环境的光线亮度,经过 PID 算法后,输出一个比较低的亮度,而不是输出 65536 的最大亮度,从而达到节省能耗的目的。(本文先只不演示 PID 算法的效果,先演示白天和黑夜两种状态时的效果)
- occupancy sensor 的体验是:灯根据occupancy sensor发过来的状态,识别是否有人,然后自动触发灯的状态变化,而不需要经过 app 或者 音箱先接收 sensor 数据,再对灯发送控制命令。
- energy monitor sensor 的体验是:检测能耗较大的设备,如果发现能耗异常,则提示 是否 要进行设备更换或者改进等,达到节省能耗的目的。
注意:
Sensor节点发送的是 sensor status message,而不是直接发送onoff SET 、lightness set 等控制命令,因为有可能很多节点都需要监听这个sensor的状态,但是收到后,做的动作可能是不一样的,比如有些要执行开关灯操作,有些要执行亮度调节操作等。如果sensor发送控制命令,那就不能实现这个功能了。
Publish_adress配置方法
NLC 常用到 publish set 命令配置 按键或者 sensor 发送消息的目的地址。
如果想要改变publish adress,可以通过上位机 的 ini命令进行设置,也可以通过 App 进行配置。App的配置方法,请参考33 Android and iOS APP使用说明的“33.2.7 Device Setting(Switch 设备)”。
以下为网关发送publish set命令举例:
CMD-cfg_pub_set_sig =e8 ff 00 00 00 00 00 00 02 00 03 03 00 bb 00 00 00 ff 00 15 05 12
“03 00 bb 00 00 00 ff 00 15 05 12” 的数据对应下面的结构体,spec V1.1 对应章节为 《4.3.2.16 Config Model Publication Set》:
typedef struct{
u16 ele_adr;
u16 pub_adr;
mesh_model_pub_par_t pub_par;
u16 model_id; // u32 for vendor model
}mesh_cfg_model_pub_set_t;
改变以下的三个变量就可以改变设备的publish地址,
ele_adr: element adress, 每个设备上有一个或者一个以上的元素,所以需要去选择元素
pub_adr: publish adress,publish消息的目的地址
model_id: 每个元素上有一个或者一个以上的model, 所以要通过model id去选择model。
下面是通过ini命令设置后的publish adress, 此设置的element adress是0x0003, publish ardess是0x00bb, model id是0x1205(SIG_MD_SCENE_C)
DICNLCP
功能介绍
Dimming Control NLC Profile (DIC) 1.0 – represents a wall slider, a dial, or a long-press switch function to dim lights up/down.
对应的 spec 为 "DICNLCP_v1.0.pdf",详细功能介绍,请参考该文档。
DICNLCP 主要目的是定义一个设备能发送 Generic Onoff,Delta Level,Move Level 消息,并能配置发送消息的目的地址。
Demo SDK DICNLCP 测试使用 firmware demo SDK 的 825x_switch project,设置的功能是同时支持 DICNLCP 和 BSSNLCP。即 NLCP_DIC_EN 和 NLCP_BSS_EN 都等于 1,客户可以根据需要关掉NLCP_BSS_EN。并且处于常供电状态,因为默认需要有 relay 功能,如需关闭,把 SWITCH_ALWAYS_MODE_GATT_EN 设置为 0 即可。关闭后会进入低功耗模式。
nlc_switch按键介绍
硬件使用 遥控器板,详见本文 "Switch操作" 章节。
1_ON 对应 RC_KEY_1_ON;1_OFF 对应 RC_KEY_1_OFF......4_ON 对应 RC_KEY_4_ON;4_OFF 对应 RC_KEY_4_OFF;
A_ON 对应 RC_KEY_A_ON;A_OFF 对应 RC_KEY_A_OFF.
element address 介绍
825x_switch project 默认占用 4 个 element address,首个 element 的地址 称为 ele_adr_primary,由组网的时候分配。
nlc_switch按键功能介绍
Demo SDK 测试 DICNLCP 只使用 RC_KEY_1_ON,RC_KEY_1_OFF,RC_KEY_2_ON, RC_KEY_2_OFF, RC_KEY_3_ON, RC_KEY_3_OFF 和 RC_KEY_A_ON,RC_KEY_A_OFF 来发送命令。
发送命令的时候,RC_KEY_1_ON,RC_KEY_1_OFF 使用 ele_adr_primary 作为 source address,默认使用 0xC000(NLC_DICMP_GROUP_ADDR_START) 作为 destination address;发送 Generic Onoff命令。
RC_KEY_2_ON,RC_KEY_2_OFF 使用 ele_adr_primary + 1 作为 source address,默认使用 0xC001作为 destination address;发送 Generic Onoff命令。
RC_KEY_3_ON,RC_KEY_3_OFF 使用 ele_adr_primary + 2 作为 source address,默认使用 0xC002作为 destination address;发送 Generic Onoff命令。
上下左右按键 (RC_KEY_UP / RC_KEY_DN / RC_KEY_L / RC_KEY_R)用来做模式选择, 不发送命令,RC_KEY_A_ON和RC_KEY_A_OFF也用来做模式选择。模式选择规则详见下方 nlc_switch按键onoff命令模式, nlc_switch按键delta_level命令模式 和 nlc_switch按键move_level命令模式的描述。
Demo 另外 RC_KEY_4_ON 和 RC_KEY_4_OFF 是 作为 BSSNLCP 模式时,发送scene store/recall命令的按键。
以上按键功能定义,客户可以根据实际的PCBA来定义和修改按键功能。
(1) nlc_switch按键onoff命令模式
遥控器板上电后,SDK的select_pub_model_key的默认值等于ONOFF模式。 RC_KEY_1_ON,RC_KEY_1_OFF,RC_KEY_2_ON, RC_KEY_2_OFF, RC_KEY_3_ON, RC_KEY_3_OFF 对应发送 Generic Onoff 命令,详见dicmp_switch_send_publish_command()的 else 分支处理:
void dicmp_switch_send_publish_command(u32 ele_offset, bool4 onoff, u32 select_pub_model_key)
{
......
{
#if NLCP_DIC_EN
if((RC_KEY_UP == select_pub_model_key)||(RC_KEY_DN == select_pub_model_key)){
s32 delta = DICMP_LEVEL_DELTA_VALUE;
if(!onoff){
delta *= -1;
}
u16 pub_addr = dicmp_get_publish_addr(ele_offset, SIG_MD_G_LEVEL_C, 1);
access_cmd_set_delta(pub_addr, 0, delta, CMD_NO_ACK, 0);
}else if((RC_KEY_L == select_pub_model_key)||(RC_KEY_R == select_pub_model_key)){
s16 move = DICMP_LEVEL_DELTA_VALUE;
if(!onoff){
move *= -1;
}
u16 pub_addr = dicmp_get_publish_addr(ele_offset, SIG_MD_G_LEVEL_C, 1);
access_cmd_set_level_move(pub_addr, 0, move, CMD_NO_ACK, 0);
}else{ // SIG_MD_G_ONOFF_C
u16 pub_addr = dicmp_get_publish_addr(ele_offset, SIG_MD_G_ONOFF_C, 0);
access_cmd_onoff(pub_addr, 0, onoff ? G_ON : G_OFF, CMD_NO_ACK, 0);
}
#endif
}
}
(2) nlc_switch按键delta_level命令模式
按下RC_KEY_UP 或者 RC_KEY_DN 设置 select_pub_model_key 的值 等于 Delta Level 模式。 RC_KEY_1_ON,RC_KEY_1_OFF,RC_KEY_2_ON, RC_KEY_2_OFF, RC_KEY_3_ON, RC_KEY_3_OFF对应发送 Delta Level 命令。Delta 命令是控制目标节点的 level 值增加或者减少指定的值。详见 spec “MshMDL_v1.1.pdf” 的 章节“3.2.2.4 Generic Delta Set” 等。
RC_KEY_1_ON,RC_KEY_1_OFF -- 消息的目的地址默认是:0xD000(NLC_DICMP_GROUP_ADDR_START_LEVEL_MODEL)
RC_KEY_2_ON,RC_KEY_2_OFF -- 消息的目的地址默认是:0xD001
RC_KEY_3_ON, RC_KEY_3_OFF -- 消息的目的地址默认是:0xD002
(3) nlc_switch按键move_level命令模式
按下RC_KEY_L 或者 RC_KEY_R 设置 select_pub_model_key 的值 等于 Move Level 模式。 RC_KEY_1_ON,RC_KEY_1_OFF,RC_KEY_2_ON, RC_KEY_2_OFF, RC_KEY_3_ON, RC_KEY_3_OFF 对应发送 Move Level 命令。
move level 简单说是 控制节点的 level 值 从当前值 以指定的速度变化到 最大值(当movel命令的move level参数为正数),或者最小值(当为负数时)。详见 spec “MshMDL_v1.1.pdf” 的 章节“3.2.2.6 Generic Move Set” 等。
消息的目的地址的默认值 和 delta level 模式相同。
(4) nlc_switch按键切换到Onoff命令模式
按下RC_KEY_A_ON 或者 RC_KEY_A_OFF,恢复select_pub_model_key 的值 等于 ONOFF 模式,对应发送 Generic Onoff 命令。
测试步骤
(1) SDK设置
- firmware SDK 使用 825x_switch project, 设置 LIGHT_TYPE_SEL 为 LIGHT_TYPE_NLC_CTRL_CLIENT,然后编译得到 firmware。
注意,此时 model使能相关的配置 使用的是 nlc_ctrl_client_model_config.h 里面的配置信息,而不是 mesh_config.h 里面的配置信息。
(2) 添加到网络
对switch节点进行组网,具体操作可以参考12.5章节Switch工程长按处理逻辑。
(3) App分组命令补充介绍
以App把色温灯添加到Living room为例,为了使用方便,实际上默认是发送了 4 个分组命令,分别给这 4 个 model:SIG_MD_G_ONOFF_S,SIG_MD_LIGHTNESS_S,SIG_MD_LIGHT_CTL_S,SIG_MD_LIGHT_CTL_TEMP_S。
由于 lightness 和 temperature model 是两个独立的变量,但是现在属于同一个Room了,所以,如果需要使用 level delta 等level 控制命令控制某个组的时候,就不能以这个 0xC000 作为目的地址去控制 lightness。因为 temperature model同样会被控制,这个不符合我们的预期。
所以需要使用 level 命令时,需要打开 App 的 "home page" -- "Setting" -- "Enable Subscription Level Service model Id" 功能开关,让 App 再给 element 0(包含 lightness model的element) 添加组号 0xD000; 给 element 1(包含 temperature model的element) 添加组号 0xD001;
这样 level delta 等level 控制命令 就可以通过 组号 0xD000 控制 lightness,通过 组号 0xD001 控制 色温。
- 如果是添加到"Kitchen room",则对应的是 “0xD010,0xD011”;
- 如果是添加到"Master bedroom",则对应的是 “0xD020,0xD021”。
(4) 按键默认功能测试
操作步骤细节可以参考 "nlc_switch按键功能介绍"提到的3个模式进行测试。
a) App模式设置和分组
- 打开 App 的 "home page" -- "Setting" -- "Enable Subscription Level Service model Id" 功能开关。
- 使用 App 把第一组灯节点添加到 "Living room" (涉及组号 0xC000,0xD000,0xD001,详见App分组命令补充介绍)。
- 使用 App 把第二组灯节点添加到 "Kitchen room" (涉及组号 0xC001,0xD010,0xD011)。
- 使用 App 把第三组灯节点添加到 "Master bedroom"(涉及组号 0xC002,0xD020,0xD021)。
b) Generic Onoff 测试
- 按按键 RC_KEY_1_ON 或者 RC_KEY_1_OFF 切换到 onoff 模式。(遥控器刚上电后默认也属于 onoff 模式)。
- 按按键 RC_KEY_1_ON 和 RC_KEY_1_OFF 可以控制属于 Living room(0xC000) 分组的节点的开关状态。
- 按按键 RC_KEY_2_ON 和 RC_KEY_2_OFF 可以控制属于 Kitchen room(0xC001) 分组的节点的开关状态。
- 按按键 RC_KEY_3_ON 和 RC_KEY_3_OFF 可以控制属于 Master bedroom(0xC002) 分组的节点的开关状态。
c) Generic Level Delta 测试
- 按按键 RC_KEY_UP 或者 RC_KEY_DN 切换到 Delta Level 模式。
- 按按键 RC_KEY_1_ON 和 RC_KEY_1_OFF 可以控制属于 Living room(0xC000) 分组的节点进行增减lightness。
- 按按键 RC_KEY_2_ON 和 RC_KEY_2_OFF 可以控制属于 Kitchen room(0xC001) 分组的节点进行增减lightness。
- 按按键 RC_KEY_3_ON 和 RC_KEY_3_OFF 可以控制属于 Master bedroom(0xC002) 分组的节点进行增减lightness。
d) Generic Level Move 测试
- 按按键 RC_KEY_L 或者 RC_KEY_R 切换到 Move Level 模式。
- 按按键 RC_KEY_1_ON 和 RC_KEY_1_OFF 可以控制属于 Living room(0xC000) 分组的节点增加lightness到最大或者减少到最小值。
- 按按键 RC_KEY_2_ON 和 RC_KEY_2_OFF 可以控制属于 Kitchen room(0xC001) 分组的节点增加lightness到最大或者减少到最小值。
- 按按键 RC_KEY_3_ON 和 RC_KEY_3_OFF 可以控制属于 Master bedroom(0xC002) 分组的节点增加lightness到最大或者减少到最小值。
(5) 配置按键的publish地址测试
通过发送publish set命令,来配置按键的目的地址。publish的说明,请参考publish_adress配置方法。
以下以配置 RC_KEY_1_ON 和 RC_KEY_1_OFF 的level delta 命令的 publish 地址为例:
方法 1. 上位机实际测试的 INI 命令参考如下:
CMD-cfg_pub_set_sig =e8 ff 00 00 00 00 00 00 02 00 03 02 00 01 D0 00 00 ff 00 15 03 10
此命令设置遥控器中元素地址为(0x0002)上 level client model(0x1003)的publish address为 0xD001。
方法 2. App的测试方法如下
model: 因为要配置 level delta 或者 level move 命令的publish 地址,所以这里填写 0x1003(Generic Level Client)。
pubAdr: 设置为该按键发送命令的目的地址,此处以修改为控制 属于 “Living room” 的灯的色温为例,所以设置为 0xD001。
BSSNLCP
功能介绍
Basic Scene Selector NLC Profile (BSS) 1.0 – represents a wall switch or a wall station to select lighting scenes or turn the lights on/off.
spec 对应的章节介绍请参考spec"BSSNLCP_v1.0.pdf",详细功能介绍,请参考该文档。
BSSNLCP 定义一个设备能发送 SCENE STORE/RECALL 命令,并能配置发送的消息的目的地址。
硬件介绍
Firmware SDK 使用 825x_switch project,硬件使用 遥控器板,详见 "nlc_switch按键介绍"
按键功能介绍
Demo SDK BSSNLCP 测试使用 firmware demo SDK 的 825x_switch project,设置的功能是同时支持 DICNLCP 和 BSSNLCP。即 NLCP_DIC_EN 和 NLCP_BSS_EN 都等于 1,客户可以根据需要关掉DICNLCP。并且处于常供电状态,因为默认需要有 relay 功能,如需关闭,把 SWITCH_ALWAYS_MODE_GATT_EN 设置为 0 即可。关闭后会进入低功耗模式。
测试 BSSNLCP 发送命令, 目前 demo 仅使用 RC_KEY_4_ON,RC_KEY_4_OFF按键,其它按键的定义请参考 "nlc_switch按键功能介绍",客户可以根据实际需要重新定义。
RC_KEY_4_ON发送SCENE_STORE命令,RC_KEY_4_OFF发送SCENE RECALL命令。
-
RC_KEY_4_ON使用 ele_adr_primary 作为 source address,默认使用 0xC000(NLC_DICMP_GROUP_ADDR_START) 作为 destination address,可通过publish set命令修改;
-
RC_KEY_4_OFF使用 ele_adr_primary 作为 source address,默认使用 0xC001(NLC_DICMP_GROUP_ADDR_START) 作为 destination address,可通过publish set命令修改;
详见下方dicmp_switch_send_publish_command()的 if 分支处理:
void dicmp_switch_send_publish_command(u32 ele_offset, bool4 onoff, u32 select_pub_model_key)
{
#if NLCP_BSS_EN
if(3 == ele_offset){
u32 ele_offset_scene = 0;
if(onoff){ // RC_KEY_4_ON
ele_offset_scene = 0;
u16 pub_addr = dicmp_get_publish_addr(ele_offset_scene, SIG_MD_SCENE_C, 0);
sw_tx_src_addr_offset = ele_offset_scene;
access_cmd_scene_recall(pub_addr, 0, 1, 0, 0);
}else{ // RC_KEY_4_OFF
ele_offset_scene = 1;
u16 pub_addr = dicmp_get_publish_addr(ele_offset_scene, SIG_MD_SCENE_C, 0);
sw_tx_src_addr_offset = ele_offset_scene;
access_cmd_scene_recall(pub_addr, 0, 2, 0, 0);
}
}
...........
}
测试步骤
(1) SDK设置
- Firmware SDK 使用 825x_switch project, 设置 LIGHT_TYPE_SEL 为 LIGHT_TYPE_NLC_CTRL_CLIENT。
注意:
此时 model使能相关的配置 使用的是 nlc_ctrl_client_model_config.h 里面的配置信息,而不是 mesh_config.h 里面的配置信息。
(2) 添加到网络
- 对switch节点进行组网,具体操作可以参考12.5章节Switch工程长按处理逻辑。
(3) 按键测试
- 按下RC_KEY_4_ON会发送SCENE_STORE_NOACK, 按下RC_KEY_4_OFF会发送SCENE_RECALL_NOACK。
RC_KEY_4_ON / RC_KEY_4_OFF 使用的source address 都是 primary address, 默认的 destination address 是 Living room(0xC000),scene ID 是0x0001。
- 通过抓包可以查看命令是否发送成功,如下图:
通过发送 publish set 命令,来配置 按键的 目的地址。publish 的说明,请参考publish_adress配置方法。
以下以配置 RC_KEY_4_ON 和 RC_KEY_4_OFF 的scene recall 命令的 publish 地址为例:
方法 1. 上位机实际测试的 INI 命令参考如下:
CMD-cfg_pub_set_sig =e8 ff 00 00 00 00 00 00 02 00 03 02 00 02 c0 00 00 ff 00 15 05 12
此命令设置遥控器中元素地址为(0x0002)上 scene client model(0x1205)的publish address为 Master bedroom(0xC002)。
方法 2. App的测试方法如下:
model: 因为要配置 scene recall 命令的publish 地址,所以这里填写 0x1205(Scene Recall Client Model)。
pubAdr: 设置为该按键发送命令的目的地址,此处以修改为 Recall 属于 Master bedroom的 灯的 scene 1为例,所以设置为 0xC002。
BLCNLCP
功能介绍
Basic Lightness Controller NLC Profile (BLC) 1.0 – represents a luminaire with an integrated controller.
对应的 spec 为 "BLCNLCP_v1.0.pdf",详细功能介绍,请参考该文档。
BSSNLCP 定义一个灯类型的设备,该设备不仅支持常用的开关、亮度、色温等控制,还支持 light control model。当light control mode 关闭时,就是一个普通的灯,通过 generic onoff,lightness set 等命令控制该灯的状态。当 light control mode 使能时,灯节点对应的 light control server model 可以直接接收 sensor 发过来的状态,比如 OCCUPANCY sensor(PRESENCE_DETECTED),Ambient light sensor 等,然后对灯节点本身的状态直接做相应的响应。常用应用场景,比如步梯楼道的灯,当检测到有人移动时,自动亮一段时间,然后自动渐变到 standby 的亮度状态,standby 的亮度值自定义,可以是 0,也可以是一个比较低的亮度值。
硬件介绍
硬件使用 825x dongle。
测试步骤
注意,light control server model 由于也支持 generic onoff model,当收到 generic onoff message时,为了能区分是控制 亮度的开关,还是控制 light control model的 light control light onoff 状态,所以要求light control model独立占用一个 element,详见 《MshMBT_v1.0.pdf》的 “Table 6.186: Light LC Setup Server elements and states” 表格的介绍。
独立为一个 element后,对light control model的 组号配置就需要 留意参数区的 element address 的配置,light control model在最后一个 element 上。对于 色温灯 是要等于 primary address + 2,对于 HSL 灯是 primary address + 3;设置 LC mode 的时候也是一样。
SDK设置:
Firmware SDK使用825x_mesh project, 设置NLCP_BLC_EN为1即可。
(1) 节点上电后,是 light control mode 默认处于 off 状态,控制灯的亮度等状态的方法和没有 light control 功能的操作一样。
(2) App 发送 Light LC mode set enable命令给节点,或者通过 INI 命令:
light_lc_mode_set =e8 ff 00 00 00 00 00 00 04 00 82 92 01
注意:
destination address 不是 primary address(node address),而是 primary address +(ELE_CNT_EVERY_LIGHT - 1)。
(3) App 发送 LIGHT_LC_ONOFF_SET on 命令给节点,或者通过 INI 命令:
light_lc_onoff_set =e8 ff 00 00 00 00 00 00 04 00 82 9a 01 00
注意:
destination address 不是 primary address(node address),而是 primary address +(ELE_CNT_EVERY_LIGHT - 1)。
节点按以下曲线进行亮度变化。
曲线的每一个步骤的时间参数和亮度值的通过以下宏定义:
#define LC_PROP_VAL_LightnessOn (LIGHTNESS_MAX)
#define LC_PROP_VAL_LightnessProlong ((LIGHTNESS_MAX + 1) / 4)
#define LC_PROP_VAL_LightnessStandby ((LIGHTNESS_MAX + 1) / 20)
......
#define LC_PROP_VAL_TimeFade (2*1000) // unit: ms
#define LC_PROP_VAL_TimeFadeOn (2*1000) // unit: ms
#define LC_PROP_VAL_TimeFadeStandbyAuto (3*1000) // unit: ms
#define LC_PROP_VAL_TimeProlong (4*1000) // unit: ms
#define LC_PROP_VAL_TimeRun (5*1000) // unit: ms
(4) 等待 步骤 3 进入 standby 状态,然后通过 发送 OCCUPANCY sensor(PRESENCE_DETECTED) status 的 INI 命令给节点,节点也会按步骤 3 的曲线进行亮度变化。
sensor_occupancy_64 =e8 ff 00 00 00 00 00 00 05 00 52 01 42 00 64
(5) 等待 步骤 4 进入 standby 状态,然后通过 "ocssnlcp" 介绍的 sensor 节点,发送 sensor status命令给 灯节点,当灯节点收到 sensor status,并且 sensor status 里面的 参数为非 0 的值,(参数为 0 表示感应到有人移动), 此时灯节点也会按步骤 3 的曲线进行亮度变化。
当sensor status message 的目的地址是 一个组号地址的时候,还需要给LC model所在的element的 generic onoff model 或者 light control model 订阅这个组号地址,INI 命令举例如下:
light_lc_model_sub_set =e8 ff 00 00 00 00 02 01 02 00 80 1b 04 00 01 c0 0f 13
或者
light_g_onoff_sub_set =e8 ff 00 00 00 00 02 01 02 00 80 1b 04 00 01 c0 00 10
ocssnlcp
功能介绍
Occupancy Sensor NLC Profile (OCS) 1.0 – represents an occupancy sensor
对应的 spec 为 "OCSNLCP_v1.0.pdf",详细功能介绍,请参考该文档。
当检测到有人靠近时,sensor的状态发生改变,会触发sensor发布sensor status, sensor status中包含当前是否有人的状态。
测试步骤
SDK设置:
Firmware SDK 使用 825x_mesh_project
- 设置 LIGHT_TYPE_SEL 为 LIGHT_TYPE_NLC_SENSOR
- NLC_SENSOR_TYPE_SEL 选择 NLCP_TYPE_OCS
- SENSOR_PROP_ID 选择 PROP_ID_PRESENCE_DETECTED
#elif (NLC_SENSOR_TYPE_SEL == NLCP_TYPE_OCS)
#define SENSOR_PROP_ID PROP_ID_PRESENCE_DETECTED
然后编译得到 firmware。
功能测试:
- 使用ini命令进行publish set
CMD-cfg_pub_set_sig =e8 ff 00 00 00 00 02 00 02 00 03 02 00 ff ff 00 00 FF 00 00 00 11
对于以上命令的说明可以参考"publish_adress配置方法",注意要根据当前测试节点的unicast address来修改 cfg_pub_set_sig 命令里面的 destination address,element address 和 publish address。
sensor_measure_proc() 函数会轮询 sensor 检测到的状态值,看是否达到触发条件,如果达到,则 pub_flag的值为 1,然后再看是否 有配置 publish address,如果有,则发送 sensor status 消息,详见 sensor_measure_proc() 的处理。
u32 sensor_measure_proc()
{
...
u32 measure_val = 0;
memcpy(&measure_val, p_sensor_data->p_raw, min2(sizeof(measure_val), p_sensor_data->len_raw));
if(sensor_measure_quantity < measure_val){
if((measure_val - sensor_measure_quantity) > p_cadence->cadence_unit.delta_down){
pub_flag = 1;
}
}
else{
if((sensor_measure_quantity - measure_val) > p_cadence->cadence_unit.delta_up){
pub_flag = 1;
}
}
if(pub_flag){
model_pub_check_set(ST_G_LEVEL_SET_PUB_NOW, (u8 *)&model_sig_sensor.sensor_srv[0].com, 0);
}
}
measure_val: 上一次的状态值(0/1)
sensor_measure_quantity:当前状态值(0/1)
pub_flag: publish标记,当前状态值跟上一次差值达到阈值时,会置1.
-
可通过发送Sensor Cadence Set配置变化量阈值,未配置时,p_cadence->cadence_unit.delta_down/p_cadence->cadence_unit.delta_up为0。
-
由于目前的 dongle 板子没有 sensor 外设,目前 demo sdk 改变sensor 值的方法有两个:
方法1:通过BDT去设置sensor_measure_quantity的值。例如:第一次设置了1,第二次设置了0,这时节点就会publish sensor status 一次。或者第一次设置了0,第二次设置了1,这时节点就会publish sensor status一次。总之,这次比上一次出现反转,节点就会 publish sensor status。
方法2:通过按键改变 sensor_measure_quantity的值, 除了进行上述"SDK设置"外,再将UI_KEYBOARD_ENABLE打开后,把编译好的固件烧录到8258_dongle, 之后每按下一次SW2,sensor_measure_quantity的值就会变一次,节点就会publish status一次,实现的代码如下:
void mesh_proc_keyboard (void)
{
......
if(KEY_SW2 == kb_event.keycode[0]){
sensor_measure_quantity = !sensor_measure_quantity;
}
......
}
下图为publish sensor status 的抓包结果:
Property ID: PROP_ID_PRESENCE_DETECTED(0x004D),代表此刻的sensor是occupancy sensor.
ALSNLCP
功能介绍
Ambient Light Sensor NLC Profile (ALS) 1.0 – represents an ambient light level sensor.(环境光照度sensor)
对应的 spec 为 "ALSNLCP_v1.0.pdf",详细功能介绍,请参考该文档。
当检测到环境光照度的变化量大于设置的阈值时,会触发sensor发布sensor status,sensor status中包含当前光照度。
测试步骤
SDK设置:
Firmware SDK 使用 825x_mesh_project, 设置 LIGHT_TYPE_SEL 为 LIGHT_TYPE_NLC_SENSOR, 使能然后编译得到 firmware。
功能测试ALSNLCP:
- 使用ini命令进行publish set
CMD-cfg_pub_set_sig =e8 ff 00 00 00 00 02 00 02 00 03 02 00 ff ff 00 00 FF 00 00 00 11
对于以上命令的说明可以参考"publish_adress配置方法"。
- 使用ini命令发送Sensor Cadence Set设置环境光照度变化量阈值
e8 ff 00 00 00 00 02 00 02 00 55 4E 00 02 01 00 00 01 00 00 0C F0 FF 00 10 00 00
命令所对应的结构体如下:
typedef struct{
u16 prop_id;
sensor_cadence_t cadence;
sensor_setting_par_t setting;
}sensor_states_t;
prop_id: sensor property id,这里设置的是PROP_ID_PRESENT_AMBIENT_LIGHT_LEVEL(0x004E)
trig_type: 状态触发类型。 0:变化值。 1:百分比。
delta_down: 触发传感器状态消息发布的光照度的最少变化量,当光照度降低的时候。光照度变小的量达到设置的阈值时,会触发publish。这里设置的是0x000001, 当然也可以设置其他值。
delta_up: 触发传感器状态消息发布的光照度的最少变化量,当光照度增加的时候。光照度变大量达到设置的阈值时,会触发publish。这里设置的是0x000001, 当然也可以设置其他值。
- 通过BDT去设置sensor_measure_quantity的值,即当前的光照度。例如:当前sensor_measure_quantity的值是0,把它设置为10,此时光照度的变化量大于设定的阈值,这时节点就会触发publish status一次。
下面为publish sensor status抓包结果
Property ID: PROP_ID_PRESENT_AMBIENT_LIGHT_LEVEL(0x004E),表示sensor status里包含的是光照度。
ENMNLCP
功能介绍
Energy Monitor NLC Profile (ENM) 1.0 – represents a sensor reporting energy consumption
对应的 spec 为 "ENMNLCP_v1.0.pdf",详细功能介绍,请参考该文档的能量消耗监测器章节。
测试步骤
SDK设置:
Firmware SDK 使用 825x_mesh_project
- 设置 LIGHT_TYPE_SEL 为 LIGHT_TYPE_NLC_SENSOR
- NLC_SENSOR_TYPE_SEL 选择 NLCP_TYPE_ENM
然后编译得到 firmware。
功能测试:
- 使用ini命令进行publish set
CMD-cfg_pub_set_sig =e8 ff 00 00 00 00 02 00 02 00 03 02 00 ff ff 00 00 FF 00 00 00 11
对于以上命令的说明可以参考"publish_adress配置方法"。
-
可通过发送Sensor Cadence Set配置变化量阈值,未配置时默认为0。同样也是设置delta_down。delta_up。详见功能测试ALSNLCP
-
通过BDT去设置sensor_measure_quantity的值,即当前能量值。例如:sensor_measure_quantity的值是0,把它设置为10,这时节点就会publish status。
下面为publish sensor status抓包结果
Property ID: PROP_ID_PRECISE_TOTAL_DEVICE_ENERGY_USE(0x0072),表示sensor status里包含能量检测。
自研蓝牙Mesh解密分析工具操作说明
应用背景
在调试开发蓝牙Mesh产品时,除了打开设备端的Log外,有时还需要用到抓包工具分析空中的Mesh消息格式和交互流程是否正确。目前市面上的抓包仪器价格较贵,在缺少专业的抓包工具时,可通过泰凌自研的蓝牙Mesh包解密分析工具进行初步分析。
此工具只需用到1个TLSR8258 Dongle(以下简称Monitor)和1个串口模块,然后编译8258_mesh_monitor工程(SDK下载链接http://wiki.telink-semi.cn/tools_and_sdk/BLE_Mesh/SIG_Mesh/sig_mesh_sdk.zip),烧录编译得到的8258_mesh_monitor.bin,入网后即可监听并解密当前Mesh网络内广播类型的Mesh消息,暂不支持GATT proxy pdu和解密用Device Key加密的Mesh消息。
操作步骤
配置Monitor串口
抓包工具使用8258_mesh_monitor编译选项,工程设置里已使能MESH_MONITOR_EN。与8258_mesh工程共用应用层代码,在头文件app_config_8258.h中通过宏UART_TX_PIN配置串口的IO。
Clean编译后得到8258_mesh_monitor.bin文件,烧录到Monitor中。
连接串口硬件
把Monitor的uart tx和uart rx分别接到串口模块的rx和tx,打开串口调试助手(通用串口工具即可),选择对应的COM口,设置波特率115200,hex显示。
把Monitor加入Mesh网络
Monitor在加入需要监听的Mesh网络前,相当于一个未配网节点,不开启监听功能。用App/网关把它加入Mesh网络后,会自动开启监听功能。
Log解析
Monitor加入Mesh网络后,会通过串口解密输出监听到的Mesh网络内广播类型Mesh消息,格式为:
0xF5 + rf_head + rf_length + advA + mesh_payload_length(type+PDU) + 0x2A(Mesh type) + Network PDU + RSSI + Frequency Offset + Channel
- rf_len:它后面所有数据长度(不包含rssi,Frequency Offset,channel)
- advA:发包节点的mac
- mesh_payload_length: Mesh type和PDU的数据长度
- RSSI:能量值,单位是dBm。注意:数据类型是s8
- FREQUENCY: 频偏值。单位是kHz。注意:数据类型是s16
- CHANNEL: 表示该数据包是在哪个广播通道发送的
Network PDU对应下图结构体mesh_cmd_bear_t中红框内的成员变量,与Mesh spec中定义的PDU格式是一致的,根据格式解析上报的raw data就知道具体的Mesh消息。
如下图,网关把一个灯节点和Monitor加进来后,往Mesh网络分别发送了onoff set和自定义的vendor set消息,灯节点收到后回复了onoff status和vendor status。
扩展功能
(1)如果要监听默认网络的Mesh包(未执行组网操作之前的网络),可通过串口往Monitor下发命令开启监听功能。
a8 ff + 00 + monitor_en。
- monitor_en为1开启,0关闭。
- 设置成功返回 a8 ff + 00 + 00,
- 设置失败返回 a8 ff + 00 + 01(01 表示错误码为1)
(2)Monitor默认开启sno过滤(即relay protect list),如果需要关闭,可通过串口往Monitor下发命令设置。
a8 ff + 01 + sno_filter。
- sno_filter为1开启sno过滤,0关闭sno过滤。
- 设置成功返回 a8 ff + 01 + 00,
- 设置失败返回 a8 ff + 01 + 01(01 表示错误码为1)
(3)Monitor可以指定抓包信道,可通过以下命令进行设置
a8 ff + 02 + chn1 + chn2 + chn3
- chn为25,26,27广播通道。这里的chn作为参数可以是分别是1,2,3个。比如: (a)a8 ff 02 25 (b) a8 ff 02 25 26 (c)a8 ff 02 25 26 27。注意:25,26,27是作为16进制的,对应十进制的37,38,39。
- 设置成功返回 a8 ff + 02 +00,
- 设置失败返回 a8 ff + 02 + 01(01 表示错误码为1)
(4)Monitor提供了是否抓取未入网unprovision becon广播包,可通过串口下发命令。
a8 ff + 03 + unprovision_beacon_enable
- unprovision_beacon_enable为1开启抓包,0关闭。
- 设置成功返回 a8 ff + 03 +00,
- 设置失败返回 a8 ff + 03 + 01(01 表示错误码为1)
以上就是蓝牙Mesh包解密分析工具的操作步骤,通过抓取空中包,能够快速地定位和分析问题。
Ellisys解密mesh包
点击record进行抓包
填写mesh信息进行解密
- 点击菜单栏view下拉框mesh security选项,填写大端格式的 mesh key信息(network key, appkey key,和IV index。如果要看configuration model等使用device key的消息,比如 publish set命令等,还需填入设备的device key)。
- 如果不知道key的话可以用tedebug从已配网节点里获取,mesh_key全局变量包含:device key,network key和app key;iv index在iv_idx_st里。不同sdk版本结构可能有变化,mesh_key的成员变量的位置详见代码。以V3.3.3为例。
注意:
得到的iv index在BDT里面是小端显示,在ellisys里面需要填写为大端,如上图就是填写 “00000001” 或者“01”即可。
- 进行mesh解密并查看。
对图中的步骤1,2,3进行说明:
1:点击mesh按钮,进行mesh解密;
2: 可以用鼠标拖拽右边“All Fields”的某个项到主界面进行显示;
3: 上图是对步骤2的举例。
其它获取key的方法
通过firmware的Provision流程的 UART log
通过Android App
Home page->setting->Mesh Info
通过iOS App
Home page->setting->Mesh Info
通过JSON文件
Open “mesh_database.json” inside the folder which contains “sig_mesh_tool.exe”. Search “netKeys” and “appKeys” for net key and app key.
Search “ivIndex” for iv index.
Spirit LPN
功能介绍
Spirit LPN是私有定义的低功耗节点,不需要建立friendship,而是通过该节点周期性唤醒一段时间来收包的方式实现,demo SDK 默认是每 360ms唤醒一次,每次唤醒持续 20ms 进行广播扫描,接收命令。由于节点不是一直在收包,所以需要发送端(网关)持续发送命令来保证接收成功率。持续发送命令的时间要大于低功耗节点的唤醒周期加扫描周期,比如 demo SDK 最少是 360 + 20 = 380ms,建议大于 1000ms。
配置方式
网关设置成持续发包模式
(1) 使能按键检测
为方便测试,通过按下网关的SW2按键对spirit LPN进行开关操作, 所以需要在8258_mesh_provision工程中打开UI_KEYBOARD_ENABLE使能按键检测。
在mesh_proc_keyboard()函数中的“#if 0”改成“#if 1”。
(2) 配置网关持续发包次数
在set_material_tx_cmd()函数中设置额外发包的次数,额外发包的间隔是10~12ms,其中2ms是mainloo循环的误差。将#if 0改为#if 1,demo中以10~12ms间隔持续发包100次。当然也可以根据需求设置不同的值,即改变p_tx_head->val[0]的值。
设置lpn的唤醒周期和扫描窗口
代码里默认spirit LPN节点的唤醒周期是360ms, 扫描窗口是20ms。如果想改变它的唤醒周期,扫描窗口时间,可以按照以下进行修改。
(1) 设置唤醒的周期
即改变ADV_INTERVAL_MIN,ADV_INTERVAL_MAX的值。
(2) 设置唤醒后的扫描窗口
即改变RUN_TIME_US的值。
功能演示
-
编译8258_mesh_spirit_lpn和8258_mesh_gw工程,将编译好的fw通过BDT分别烧录到8258的dongle里面。网关插入到USB口,通过sig_mesh_tool.exe对spirit lpn进行组网。
-
组网完成后按网关SW2可以控制spirit_lpn节点的开关。每按下一次,spirit_lpn节点的灯状态会发生反转。
平台接入设置
Spirit LPN默认是通用模式,可以被demo SDK的网关, App进行组网。
如果是接入天猫精灵模式,把 MESH_USER_DEFINE_MODE 设置为 MESH_SPIRIT_ENABLE 即可,具体请参考 平台接入 章节。
Android and iOS APP使用说明
App 下载路径
Android App在固件SDK开发包中获取,例如:\telink_sig_mesh_sdk\app\android\TelinkBleMesh\TelinkBleMeshDemo-V4.1.0.0-20231113.apk。
iOS App可以在App Store通过搜索 telinksigmesh 获取。
开发者也可以自己重新编译,对应的 code 在SDK开发包中获取:\telink_sig_mesh_sdk\app
设备组网
组网分为手动组网模式和自动组网模式。
手动Provision组网模式
(1) 手动模式添加设备
Android/iOS版本APP默认手动provision模式,打开app后,点击主界面右上角的“+”按钮进入添加界面,APP会自动搜索周边设备(如下图),可点击对应设备右边的按钮单独组网,也可以点击对应设备右上角删除对应的设备后通过ADD ALL按钮组网列表里面的所有设备
(2) 手动模式添加设备过程中显示的状态
Android版APP
Scan-device found为扫描到设备的状态,左边箭头可展开组网过程中的各项状态(如下图)
iOS版APP
iOS版APP只能显示当前状态,更加详细的信息可查看APP的log记录
Auto provision组网模式
APP首页——setting——settings 在Provision Mode里面选择Normal(Auto)切换到Auto provision模式,首页通过右上角的+号进入Device Scan界面,此时标题会显示Device Scan(Auto)并自动添加周边所有未组网的设备(如下图),扫描超时Android版APP左上角弹出返回按钮,iOS版APP底部弹出Go Back返回按钮
重新扫描周边设备
Device Scan界面右上角为重新加载按钮,手动provision组网模式可重新加载设备列表,Auto provision模式可重新扫描周边设备并自动组网。
Device界面指引
Device界面(APP首页)列出了4种状态,直连节点名称显示蓝色、关闭状态显示深灰、离线状态显示浅灰并带斜划线、不同的Pid设备显示不一样的图标
刷新Device
Android/iOS版APP首页左上角按钮可刷新当前已组网的设备状态。
All on/off
Android/iOS版APP通过发送all on/off 指令控制所有已组网的设备开/关灯(蓝色为开启状态,深灰色为关闭状态)。
单设备on/off
点击对应设备图标可开/关灯(蓝色为开启状态,深灰色为关闭状态)。
CMD指令
CMD指令里面内置Vendor on/off、Vendor on/off no-ACK、Vendor on/off get、Generic on/off(iOS暂时没有内置)、Android版Opcode Aggregator(Lightness Default/Range Get、TTL/Friend/Relay Get)、iOS版Device/App Key Get指令,用户也可以通过Android版APP Custom(iOS版APP Vendor Data)输入栏自定义指令,可自定义:
- Access Type: (决定用什么key来发送)
- dst adr:(目的地址)
- opcode:
- params:opcode后面跟着的参数。
- rsp opcode:(opcode对应的response code)
- rsp max:(如果rsp opcode不为0,该值为预期要收到多少个节点的回复,如果收到的回复个数不够,则会进行 retry)、retry count(如果没有收到rsp max所指定的回复个数,则进行retry的最大次数)
- ttl:中继转发次数。
- tid position:对于非vendor命令,该值必须为0,对于vendor命令,表示 tid 的位置(值为 0 表示没有 tid 字段,1 表示 tid 在 para[0] 位置,2 表示在 para[1] 的位置…)。
Log
默认打开log记录当前操作log信息,Android版可在APP首页——setting——settings里面打开/关闭Enable LOG,Save In File按钮可保存log,保存路径:默认存储/TelinkBleMesh(注:iOS自动保存,如需将log导出到PC端可通过iTunes——文件共享——TelinkBleMesh导出),Refresh按钮可刷新log(iOS版退出重进log界面),Clear按钮清空log信息。
Device Setting(Light设备)
Android/iOS APP首页长按已组网的Light设备图标可进入Device Setting界面
(1) Light设备Control
Ele Adr X可开关设备,Lum Level可调节设备亮度,Temp可调节设备色温
Light设备Network Lighting Control
当Light设备支持Light LC Server时,设备Control页面下会出现Network Lighting Control子页面入口。Network Lighting Control页面如下图所示。
(a) Light设备需要打开Enable LC mode和Enable LC Occupancy mode这两个开关,才会处理sensor设备上报的传感器数据并判断是否执行Light Control的动作。 (b) Set LC light on/off按钮用于给Light设备发送
LightLCLightOnOffSet
指令。 (c) 下面的Properties列表包含3个Lightness参数和7个Time参数,所有参数设备端默认已经配置好了。用户可以获取和设置这些参数,每个参数详细的说明请查看固件端的Handbook文档或者sig mesh协议文档。
Sensor设备Sensor Control
当Sensor设备支持Sensor Server时,设备Control页面下会出现Sensor Control子页面入口。Sensor Control页面如下图所示。
(a) publish adr是能够接收到传感器状态数据的地址。publish adr为0表示传感器不上报状态数据。publish adr为0xFFFF表示所有设备都可以接收到传感器状态数据。如果只想单个设备接收到这个传感器的状态数据,则需要将publish adr设置为该设备的Light LC Server所在的element地址。 (b) period默认设置为0,表示Sensor数据不会周期性上报,而是Sensor数据的值有变化时才会进行数据上报。 (c) Sensor Data是传感器通过
SensorStatus
指令上报的传感器数据,Sensor Descriptor是传感器通过SensorDescriptorStatus
指令上报的传感器固化的配置参数,Sensor Cadence是传感器通过SensorCadenceStatus
指令上报的传感器可修改的配置参数。
(2) 单个设备Group
Group可以给该设备分组(单设备最大支持8个组)
(3) Light设备Settings
Settings菜单可以查看UUID、进行Device Config、Composition Data、Network Keys、Subnet Bridge Setting、Schedule Setting、Subscrlption Models、Device Ota、Publication、Kick Out操作。
Device Config
Device Config可以配置该设备的TTL、Relay & Relay Retransmit、Secure Network Beacon、Gatt Proxy、Nedeidentity、Friend、Key Refresh Phase、Network Transmit
Composition Data
Composition data可以查看该设备各项数据(包括:cid/pid/vid/crpl/features/relay support/proxy support/freind support/low power support/各sig model和vendor model对应的位置类型),右上角可以刷新数据。
Networ Keys(iOS:NetKey List / AppKey List)
一个Network里面可创建不同的Network Keys(iOS:NetKey List / AppKey List),Network Keys(iOS:NetKey List / AppKey List)可以查看当前设备所绑定的key,也可以给指定的节点配置新的Networ Key以便不同的key连接不同的设备,也可以通过key的方式将指定的节点分享出去变成共享设备,具体操作如下:
预置条件:准备A、B两台手机,A手机Default Network key添加2个以上设备
安卓版操作步骤:(安卓作为A手机)
a) A手机给指定设备新建Net Key,具体操作操作:
APP首页——长按一个需要新建Net Key的设备——Settings——Network Keys——点击右上角+号选择一个Net key(当前内置两个Net / APP Key,可在APP首页——点击右下方的Network——Mesh info里面查看当前Network的详细信息)。
b) A手机通过分享Net Key的方式分享给B手机操作步骤:
A手机APP首页——点击右下角setting——Manage Network——点击刚才配置Network Key的Network——Share Export——选择刚才新建的Network Key——通过文件/二维码方式导出——Export 。
B手机APP首页——setting——Manage Network——右下角的导入按钮——通过文件/二维码方式导入
此时刚才新配置Net key的设备变成共享设备,B手机能获取并控制该Net key对应的设备状态,另一个设备因Net key不同显示离线状态。
iOS版操作步骤:(iOS作为A手机)
a) A手机给指定设备新建Net Key/App Key,具体操作操作:
APP首页点击右下方的Network——Mesh info——Netkey List里面新建一个Net Key;
返回Mesh info界面——App Key List里面新建一个App Key(注:key需要与当前存在的App Key相同,index、BoundNetkey绑定刚才新建Net Key);
APP首页长按一个需要新建Net Key/App Key的设备——Settings——NetKeys List——点击右上角+号选择一个Net key——Done——返回Device Setting选择AppKey List——点击右上角+号选择一个App key——Done。
b) A手机通过分享Net Key的方式分享给B手机:
A手机APP首页——Setting——Manage Network——点击刚才配置Network Key的Network——Share Export——选择刚才新建的Network Key——通过文件/二维码方式导出——Export
B手机APP首页——setting——Manage Network——右下角的导入按钮——通过文件/二维码方式导入
此时刚才新配置Net key的设备变成共享设备,B手机能获取并控制该Net key对应的设备状态,另一个设备因Net key不同显示离线状态。
Subnet Bridge Setting
Subnet Bridge功能允许在一个Network内向多个子网(Network Keys)节点配置桥接表,允许向特定子网转发消息。 比如节点1配置了Netkey1和Netkey2的共享设备,节点2仅配置Netkey1的私有设备,Netkey2想要控制Netkey1的设个私有设备,则需要通过对共享节点1配置从网络Netkey2到Netkey1的桥接表。
A手机操作(最初配网共享设备):APP首页长按一个共享设备(配置了Netkey1和Netkey2,Netkey2已分享到手机B)——setting——Subnet Bridge Setting——打开Enable Subnet Bridge开关——点击ADD Subnet Bridge按钮——Add Bridging Table界面,Net key 1填入已分享的Net key;Net Key 2填写需要转换成的Net Key(注:也就是最初配网共享设备的Net Key)——Address 1填入已经分享Net Key端的Local Address(查看步骤:通过分享方式导入Net key的B手机:APP首页——Network——Mesh info)—— Address 2填入需要控制设备的短地址——Add Bridge Table保存
B手机操作(通过分享获取到netkey):长按A手机指定的节点进入Device Setting界面开关控制节点
Scheduler
Scheduler list界面点击右上角+号添加scheduler,scheduler setting界面设置条件后点击获取和设置时间(iOS版点击setTime),点击保存scheduler (注:Scheduler默认关闭,需要设备打开MD_TIME_EN宏)。
Scheduler list界面添加有scheduler也可以点击编辑,scheduler setting界面设置条件后点击 获取和设置时间(iOS版点击setTime),点击保存scheduler。
Subscription models
Subscription models可查看当前设备支持的Models:
ID:0x1000(model name:Generic onoff server)
ID:0x1300(model name:Light Lightness server)
ID:0x1303(model name:Light CTL server)
ID:0x1306(model name:Light CTL Temperature server)
ID:0x1307(model name:Light HSL server)
Device OTA
Android版APP
Device OTA可对该设备进行GATT OTA升级,OTA界面可显示当前设备信息、不同pid设备升级选项(默认取消勾选,用户可根据需要勾选该项)、点击select file选择固件,选择固件后显示目标版本信息,START开始升级会提示start OTA并显示进度,当升级完成后提示OTA_SUCCESS进度显示100%,设备慢闪。查看设备是否升级到目标版本可在APP首页长按该设备——settings——Composition Data 刷新查看vid数据(请参考33.2.6.3 Light设备Settings中的Composition Data)。
iOS版APP
iOS版Device OTA界面可显示当前设备和目标版本的Pid、Vid(如下图),勾选对应的版本后点击Start OTA开始升级(如下图),升级完成后设备慢闪,查看设备是否升级到目标版本可在APP首页长按该设备——settings——Composition Data 刷新查看vid数据(请参考33.2.6.3 Light设备Settings中的Composition Data)。
Publication(ele:xxxx model:CTL)
Android/iOS APP打开对应设备publication之后会每隔20秒发送一次status状态(可在log界面查看,CT灯上报Ctlstatusmessage、HSL灯上报Hslstatusmessage)
KICK OUT
Kick out可剔除当前设备,剔除后设备慢闪,此时该设备将会处于待组网状态
Device Setting(Switch设备)
长按未组网Switch遥控器的SW10+SW13键触发广播(注:未组网状态灯闪烁频率200ms/s,组网状态500ms/s)进行组网,组网成功后同样需要触发广播,APP首页长按Switch图标进入Device Setting界面进行连接遥控器,此时界面底部会显示Device Connected代表已连接,如果连接失败该位置会弹出按钮,可以点击重连。
(1) Switch设备Control
Eleadr中的0x0008对应Switch遥控器SW7/SW10键、0x0009对应SW8/SW11、0x000A对应SW9/SW12、0x000B对应SW3/SW6,Model能执行Switch支持的model(详情请看Switch设备Composition Data),Pubadr中的0xC000对应Group中的Living room、0xC001对应Kichen、0xC002对应Masterbedroom、0xC003对应Secondary bedroom,可以在指定按键设置需要控制的Group
(2) Switch设备setting
请参考章节33.2.6.3 Light设备Settings。
Group界面
Group界面有8个分组,在操作前先给组分配设备(请参考33.2.6.2 单个设备Group,Device界面——长按一个设备——group进行分组)。
On/off Group
Group界面可以On/off属于对应该组的设备
Group Setting
长按对应的Group可以进入该Group Setting界面(如图)
(1) 单独On/off组内设备
Group setting界面点击设备图标可on/off该设备(蓝色图标为On状态,灰色为Off状态),蓝色设备名是直连设备。
(2) Lum & Temp
Lum可调节属于该组设备的亮度,Temp调节色温。
(3) Extend Address Control
Extend Address Control可在组下面支持使能分组的Level控制功能,可以分别单独控制分组的Lum Level、Temp Level、Hue Level、Sat Level 。节点分组前需要在APP 首页——setting——settings里面打开 Extend SubscriptionLevel Service Model ID 。
(4) HSL
色盘可以调节GRB的颜色,也可以单独调节R、G、B或H、S、L来调节颜色,RGB与HSL颜色相对应,色盘下方的V可调节亮度。注:需要设备打开LIGHT_TYPE_HSL宏
Network界面
V4.1.0.0版本新增Network界面,用于单独查看、设置当前Network的Mesh Info、Scenes、Direct Forwarding、Mesh OTA、Private Beacon 。
Mesh info
Mesh Info界面能看当前Network的Mesh Name(点击右上角编辑按钮可编辑名称)、Mesh UUID、Iv Index、Sequence Number、Local Address、Net Keys/App Keys的name及其所属的index和key,长按key可复制对应的key(当前Android内置三个key,iOS需要在Netkey List、Appkey List手动添加)
Scenes
Scene可把指定设备当前状态保存成Scene,以便快速启动设定好的场景。
(1) 创建Scene
iOS APP Scene界面右上角+号可将指定设备的当前状态保存成Scenes(Android版当前仅创建Scene,需要再次点编辑按钮配置Scene)。
(2) 编辑Scene
创建Scene后,点击Scene List界面可编辑Scene。
Android编辑scene: scene setting界面“address”左边图标显示当前节点开关灯状态,“address”右边显示对应的地址,仅支持一个element的节点在右边显示方框(另一个element显示not support),可勾选方框将节点加入该场景。之前已在该场景的节点需要更新状态的,可点击将节点更新到当前状态。
iOS编辑scene:scene setting界面“adr”左边图标及下方显示当前节点开关灯状态,“adr”右边显示对应的地址,勾选对应的方框点击右上角保存按钮可将选中的节点scene更新到当前状态。
Direct Forwarding
Direct Forwarding可将指令在指定路径节点(路由表)参与转发,减少空中转发的包。
可控泛洪:是指mesh消息从源头向外传播时,类似于石头扔进水中产生向四周扩散的纹波。通过ttl控制传输的范围,relay feature控制参与转发的节点,这种传输方式称为可控泛洪。
可控泛洪无法控制消息传递的方向,浪费与消息无关的网络部分的带宽。比如在大会议室中间有2个开关,分别控制讲台和后排的灯,在控制讲台的灯时,消息也会在后排灯节点间重传。
路由表:是指一条指令从起点到终点途径中间节点参与转发的一条路径标识,终点可为单播、组播和虚拟地址,每个路由节点保存经过它的路径信息,即路由表。在网络中选择一个子集路由节点组成路径,一条路由可能有1条或多条路径。
消息在指定路由方式发送的时候会检查路径是否存在,如果存在,则以路由的方式发送,否则会以泛洪的方式发送消息,并触发路由建立。 泛洪方式发送的消息用network key加密,路由发送的消息用directed key(由network key派生)加密。在DF_TEST_MODE_EN模式中,节点在转发用directed key加密的消息时,会进行闪灯。
路径建立的消息是directed key加密的,因此所有节点都会闪灯,表示节点转发了路径建立的消息。在路径建立后,只有路径上的节点会闪灯,表示只有路径上的节点转发了指令。
(1) 固定路由
固定路由 由provisioner配置和管理,以指定路径上的节点进行转发。需要提前在Direct Forwarding——Direct Toggles界面将路径上的节点打开Direct Forwarding(Main)、Direct Relay、Direct Proxy、Direct Friend功能 。注:如果不勾选Direct forwarding(main)底部会提示“(relay)check direct forwarding first”。
Direct forwarding界面点击底部Add Table按钮可在mesh网络中添加固定路由的路径。路径包含起点和终点,以及路径经过的节点。消息从起点以路由方式发送时,路径上的节点都会参与消息的转发,不在路径上的节点忽略此消息。可以看到路径上的0x06, 0x0e,0x16节点LED闪烁,其它不在路径上的节点LED不闪。
(2) 非固定路由
非固定路由不需要在APP中的Direct Forwarding进行操作,由发送端(路径起点)创建和维护,以可控泛洪的方式发送消息,并触发路由建立。路径建立的消息是Directed Key加密的,因此所有节点都会闪灯,表示节点转发了路径建立的消息。在路径建立后,只有路径上的节点会闪灯,表示只有路径上的节点转发了指令。
非固定路由建立规则:在满足设定的能量阈值的情况下,选择最短路径,下图是建立从PO(Path Origon)到PT(Path Target)的2条路径的示意图。
Mesh OTA
Mesh OTA可对mesh网络指定的多个设备同时进行OTA升级,Mesh OTA加载的方式有3种:(1) No Extend(所有节点短包加载);(2) Extend GATT Only(直连节点长包加载,非直连节点短包加载);(3) Extend GATT & ADV(所有节点长包加载),设置路径:APP首页点击右下角Setting——Settings——Extend Bearer Mode里面设置。
注:
(a)Mesh OTA默认关闭,需要打开节点 MD_MESH_OTA_EN 的宏,否则将不支持Mesh OTA,无法勾选该设备作为mesh OTA的对象。打开方法:mesh_config.h文件里面,
- 打开MD_MESH_OTA_EN
- 如果测试后面的直连节点作为distributor模式的时候,还需要打开DISTRIBUTOR_UPDATE_SERVER_EN。
- 如果需要使用Telink的扩展广播包模式加快OTA时间,则还需要设置 EXTENDED_ADV_ENABLE为1.
(b)Mesh OTA升级完成后退出mesh OTA界面再进入,来读去升级后的版本
(c)iOS版需要通过itunes——文件共享——TelinkSigMesh把升级的bin文件放到里面才能在mesh OTA升级界面显示出来。
(1) Distributor:phone方式升级(App作为distributor模式)
Distributor选择Phone方式升级将会通过手机直接传输OTA数据给目标设备。
具体操作步骤:
Android版:
APP首页点击右下方的Network——Mesh OTA按下图步骤操作
iOS版:
APP首页点击右下方的Network——Mesh OTA按下图步骤操作
(2) Distributor:Verify and Apply方式升级(直连节点作为distributor模式)
Distributor选择connected device,Apply Policy选择Verify and Apply方式升级,通过手机上传固件到直连节点,再通过直连节点分发给目标节点,加载完成后自动应用新的版本。
注意:
请确保直连节点的 DISTRIBUTOR_UPDATE_SERVER_EN 宏已经使能。
具体操作步骤:
Android版:
APP首页点击右下方的Network——Mesh OTA按下图步骤操作
iOS版:
APP首页点击右下方的Setting——Mesh OTA按下图步骤操作
(3) Distributor:Verify only方式升级(直连节点作为distributor模式)
Distributor选择Verify only方式升级,通过手机上传固件到直连节点,再通过直连节点分发给目标节点,加载完成后需要APP重新连接节点才应用新的版本。
注意:
请确保直连节点的 DISTRIBUTOR_UPDATE_SERVER_EN 宏已经使能。
具体操作步骤:
Android版:
APP首页点击右下方的Network——Mesh OTA按下图步骤操作
iOS版:
APP首页点击右下方的Setting——Mesh OTA按下图步骤操作
Private beacon
Private beacon可以设置节点组网后发送指定的广播,需要在mesh_config.h文件里面打开MD_PRIVACY_BEA和PRIVATE_PROXY_FUN_EN
(1) Config GATT Proxy
单独打开Config GATT Proxy,一直只发送Network ID广播,light blue APP广播类型为0x00 。
(2) Private GATT Proxy
单独打开private GATT Proxy,Network ID为私有状态,以新的Mac地址发送加密处理后Network ID,light blue APP广播类型为0x02。
(3) Config Node Identity
单独打开Config Node Identity,60秒内发送Node Identity信息(light blue APP广播类型为0x01),60秒后自动切换发送默认的Network ID广播(light blue APP广播类型为0x00)。
(4) Private Node Identity
单独打开Private Node Identity,Node Identity为私有状态,60秒内以新的Mac地址发送加密处理后的Node Identity广播(light blue APP广播类型为0x03),60秒后自动切换发送默认的Network ID广播(light blue APP广播类型为0x00)。
(5) Config GATT Proxy + Config Node Identity
同时打开Config GATT Proxy 和 Config Node Identity,60秒内发送Node Identity信息(light blue APP广播类型为0x01),60秒后自动切换发送默认的Network ID广播(light blue APP广播类型为0x00)。
(6) Config GATT Proxy + Private Node Identity
同时打开Config GATT Proxy 和 Private Node Identi,Node Identity为私有状态,60秒内以新的Mac地址发送加密处理后的Node Identity广播( light blue APP广播类型为0x03 ),60秒后自动切换发送默认的Network ID广播( light blue APP广播类型为0x00 )。
(7) Private GATT Proxy + Config Node Identity
同时打开Private GATT Proxy + Config Node Identity,60秒内发送Node Identity信息( light blue APP广播类型为0x01 ),60秒后Network ID为私有状态,以新的Mac地址经过加密处理后再对外发送广播, light blue APP广播类型为0x02 。
(8) Private GATT Proxy + Private Node Identity
同时打开Private GATT Proxy +Private Node Identity,Node Identity和Network ID都为私有状态,60秒内以新的Mac地址发送加密处理后的Node Identity广播( light blue APP广播类型为0x03 ),60秒后以新的Mac地址发送加密处理后Network ID广播( light blue APP广播类型为0x02 )。
(9) Config Beacon
打开Config Beacon后,节点会每隔10秒发送Secure Network Beacon类型的Non-Comnecable undrected Adv Packet ,可以通过light blue APP查看(节点每隔10秒发beacon包时手机是否能收到取决于手机蓝牙刷新率),也可以通过Netkey和APPkey通过ellisys抓包查看。
(10) Private Beacon
打开Private Beacon需要同时打开Private GATT Proxy,节点会更换Mac地址,每隔10秒发送Reserved(0x02) Beacon类型的Non-Comnecable undrected,可以通过light blue APP查看(节点每隔10秒发beacon包时手机是否能收到取决于手机蓝牙刷新率),也可以通过Netkey和APPkey通过ellisys抓包查看。
(11) Beacon + Private Beacon
同时打开Beacon 和Private Beacon(需要同时打开Private GATT Proxy),节点会以原来Mac地址每隔10秒发送Secure Network Beacon类型的Non-Comnecable undrected Adv Packet,同时会更换Mac地址,每隔10秒发送Reserved(0x02) Beacon类型的Non-Comnecable undrected,可以通过light blue APP查看(节点每隔10秒发beacon包时手机是否能收到取决于手机蓝牙刷新率),也可以通过Netkey和APPkey通过ellisys抓包查看。
Setting界面
Setting界面可进行Manage Network、OOB Database、Root Cert、settings(Enable Log、Enable Private Mode、Provision Mode、Enable Subscription Level Service、Extend Bearer Mode、Use No-OOB Automatically、Share Inport Complete Action、Online Status、Reset Settings)、How To Inport Bin File、Get More Telink Apps操作。
Manage Network
Manage Network与APP底部的Network界面区别:Manage Network可查看、管理、应用、分享不同的Network,而APP底部的Network是针对当前应用生效的Network进行Mesh OTA、Scene等操作。蓝框选中的为当前应用生效的Network,点击右上角+号可以创建新的Network 。
(1) Show Detail
点击想要查看的Network,选择Show detail可查看该Network的详细信息(Mesh Info界面可33.4.1章节)
(2) Share Export
(a) 通过文件方式导出
Network List界面点击一个分享的Network选择Share Export,选择需要分享的Net Key,分享方式选择Json File点击export按钮导出json文件,Android版导出的json文件保存在storage/emulated/0/TelinkBleMesh目录里面,iOS版导出的json文件通过iTunes文件共享里面 。
(b) 通过二维码导出
Network List界面点击一个分享的Network选择Share Export,选择需要分享的Net Key,分享方式选择QRCode点击export按钮将会二维码的方式显示json信息(二维码有时效性,300秒后失效)。
(3) Switch To This Network
点击指定的Network选择Switch to this network可切换到选中的Network(蓝框同步切换到指定的Network)。
(4) 导入mesh
Network List界面点击右下方的导入按钮可导入Network
(a) 通过json文件导入
Android版APP
Network List界面点击右下角的导入按钮,Select source选择Json File,Select File里面选择要导入的json文件,选择json文件后Preview按钮可预览json文件信息,import按钮可导入Network ,导入成功后返回到Network List界面默认弹出询问是否切换到刚导入的Network,可根据需要不切换或切换(之前的Network会保留),也可以设置导入后自动切换到导入的Network,设置路径:APP首页——右下角Setting——Settings——Share Import Complete Action里面选择Auto Switch)。
iOS版APP
通过iTunes把json文件放到TelinkSigMesh APP,Network List界面右下角导入按钮,select sorce里面选择json file,点击Import按钮选择要导入的json文件,点击import,弹出是否导入json数据提示框,cancel不导入network,confirm导入network。导入network后默认弹出询问是否切换到刚导入的Network,可根据需要不切换或切换(之前的Network会保留),也可以设置导入后自动切换到导入的Network,设置路径:APP首页——右下角Setting——Settings——Share Import Complete Action里面选择Auto Switch)。
(b) 通过扫描二维码导入
Android版APP
Network List界面点击右下角的导入按钮,Select source选择QRCode,点击Import按钮,扫描对方分享Network的二维码,会弹出是否导入Network ,导入成功后默认自动弹出是否切换到刚导入的Network,可根据需要不切换或切换(之前的Network会保留),也可以设置导入后自动切换到导入的Network,设置路径:APP首页——右下角Setting——Settings——Share Import Complete Action里面选择Auto Switch)。
(5) 删除Network
Network List界面点击选中要删除的Network,选择DELETE,会弹出是否删除Network提示框,可根据需要来删除Network,需要注意的是当前生效的network无法被删除,可切换到其它Network 后再删除 。
(6) 清除所有Network
Network List点击底部的Remove All可清除所有Network 。
OOB Database
OOB Database用于在设备支持static-oob方式进行provision时, App查找该设备对应的Auth Value。
App在查找Auth Value时, 会从database中以deviceUUID作为key查表,如果设备写入有OOB数据需在APP录入对应的UUID和OOB数据才能正常组网,反之将会组网失败。
当前支持16位和32位OOB数据,可在flash77800位置根据需要写入。
(1) 手动添加OOB Database
OOB List界面点击右上角+号选择Manual input输入UUID和OOB数据,
UUID和OOB查询如下:
OOB:烧录8258 mesh、8269 mesh、8278 mesh等工程,在77800位置写入16或32位OOB数据。
UUID:设备待组网状态下用通用light blue APP查看设备的UUID,具体操作:APP扫描到需要获取UUID的设备(如下图)——查看Complete list of 16-bit Service UUID,图中黄色区域为UUID
(2) 通过txt文件导入OOB Database
新建一个txt文档——输入16字节UUID,空一格输入16或32字节OOB数据后保存——OOB List界面点击右上角+号选择import from file——选择刚才保存的txt文件。
(3) 删除OOB Database
长按其中一个OOB数据可单独删除该OOB数据,点击右上角垃圾桶按钮可清空所有OOB数据。
(4) Use No-OOB Automatically
Use No-OOB Automatically可添加在77800位置写入OOB数据但没在APP上录入该OOB数据的设备(前提需要打开ENABLE_NO_OOB_IN_STATIC_OOB宏,同时在APP首页——setting——settings里面打开Use No-OOB Automatically开关)
Root Cert
组网时通过校验证书来确定是否允许设备加入mesh网络,当证书校验通过后能正常组网成功,不通过将会组网失败并提示certificate recordcheck error.
(1) 通过默认证书组网
V4.1.0.0版本APP自带有一个默认证书,当设备使用默认证书组网时,将使用当前生效的默认证书进行校验,可在APP首页——Setting——Root Cert里面查看。
预置条件:
a) mesh_config.h文件里面打开CERTIFY_BASE_ENABLE宏;
b) certify_base_crypto.c文件里面的CERT_TYPE设置为CERTIFY_OOB_BY_DEFAULT_CERT(如下图),编译成固件烧录到设备中。
操作步骤:
手动组网模式:
需要校验证书的设备组网时左上角会显示证书图标,证书校验通过后组网成功,如果当前设备是其他证书、APP生效的是其他证书或与设备对应的证书被删除,将会校验不过提示 Provision - intermediate cert verify fail。
自动组网模式:
需要校验证书的设备相比不校验证书的设备在Device Scan(Auto)界面组网时,设备左上角会显示证书图标,证书校验通过后组网成功,校验不过将会提示Provision - intermediate cert verify fail。
(2) 生成并导入新证书组网
生成新证书预置条件:
a) 电脑需要安装更新到最新版本的git、TortoseGit
b) mesh_config.h文件里面打开CERTIFY_BASE_ENABLE宏,certify_base_crypto.c文件里面的CERT_TYPE设置为CERTIFY_OOB_BY_READING_FLASH
生成新证书操作步骤:
a) sdk_git_lab/tools/bash-certifybase目录空白位置点击鼠标右键选择Open Git Bash here
b) 输入./gen-root.bash——点击回车键,生成“root.der”证书
c) APP导入root.der证书
Android版导入root.der证书:APP首页点击Setting——root cert——点击右上角 + 号——选择root.der文件
iOS版导入root.der证书:iOS系统需通过iTunes将root.der文件放入TelinkSigMesh APP,APP首页点击Setting——Root Cert——勾选证书文件——Save(注:勾选后需要点保存才能生效)
d) 输入./gen-intermediate.bash——点击回车键,生成中间证书
e) 编辑gen-device.config中的UUID、CID、PID
f) 输入./gen-device.bash——点击回车键,生成4K大小的“device.bin”设备证书
g) 将“device.bin”设备证书烧录到flash 78000位置,用APP组网设备
(3) 切换certify Base证书
长按指定证书,选择Set As ROOT Cert可切换到该证书,紫色证书图标为当前生效证书,灰色为已保存但没生效证书。
(4) 删除certify Base证书
长按指定证书,选择Delete Cert可删除该证书,点击右上角垃圾桶按钮可清除所有证书。
Settings
Settings里面可进行Enable Log、Enable Private Mode、Enable Provision Mode、Enable Subscription Level Servicemodel Id、Extend Bearer Mode、Use No -Oob Automatically、Share Import Complete Action、Online Status、Reset Settings操作。
注:Android和iOS相对有所差异,详情请看下图:
(1) Enable Log
Android版APP
打开Enable Log可以记录控制mesh时的log信息,该项默认关闭,可根据需要打开(请参考章节33.2.5 Log的介绍)。
iOS版APP
没有Log开关,App默认打开Log功能(请参考章节33.2.5 Log的介绍)。
(2) Enable Private Mode(Default Bound)
Enable Default Bound 为默认绑定模式, 需设备支持. 该模式下, app key绑定过程只需要app key add执行成功即可完成, 设备会自动将app key绑定至所有需要绑定的model.
(3) Provision Mode
Provision Mode里面默认设置Normal(Selectable)手动组网模式(请参考章节33.1.1 手动Provision组网模式)、也可以设置Normal(Auto)自动组网模式(请参考章节33.1.2 Auto provision组网模式)、remote provision和fast provision,可根据需要打开,以下主要介绍remote provision和fast provision两种模式。
(a) Remote Provision
Remote provision组网时是逐个添加多跳范围的设备,能够添加较远距离的设备,同时带有relay的功能 。
具体操作:Normal(selectable或auto)组网一个支持Remote Provision的设备(即打开MD_REMOTE_PROV宏)——App首页点击setting——settings——Provison Mode里面选择Remote Provision——APP首页点击+号进行Remote Provision.
注:Remote provision默认关闭,需要设备打开MD_REMOTE_PROV宏。
(b) Fast provison
Fast provision批量组网模式,可以把多跳范围内未组网的多个设备同时组网进来。使用的device key按一定规则根据mac地址生成,不需要单独分配,操作步骤:App首页点击setting——settings——Provison Mode里面选择Fast Provision——APP首页点击+号进行Fast Provision
注:Fast provision默认关闭,需要设备打开FAST_PROVISION_ENABLE宏)。
(4) Enable Subscription Level Service model ID
打开了Enable Subscription Level Servicemodel Id可支持使能分组的Level控制功能,可以分别单独控制分组的Lum Level、Temp Level、Hue Level、Sat Level 。节点分组前需要在APP 首页--setting--settings 里面打开 Extend SubscriptionLevel Service Model ID(在章节3.2.3也有备注)。
(5) Enable DLE Mode Extend Bearer
Enable DLE Mode Extend Bearer 为发送长包可选项, 需设备支持. 使能后, access层短包最大长度会由11改为225.
(6) Online Status
Online Status可查看当前只连设备是否支持Online Status功能,在设备状态改变时上报状态。
(7) Reset Settings
Reset settings可将settings界面的所有选项恢复成默认状态。
常用API
This chapter introduces the commonly used APIs for mesh SDK development. For an introduction to the parameters of the APIs listed in this chapter, please refer to the comments on the API functions in mesh SDK.
Provisioning callbacks
Provision event callback
(1) void mesh_node_prov_event_callback(u8 evt_code)
This function is callback function for provision event of mesh node in each provision state.
(2) u8 is_provision_success()
This function get whether the node is at provision success state.
(3) rf_link_light_event_callback (u8 status)
This function is callback function to define LED indication event, such as how to do LED indication when provision success.
Provisioning message handle
(1) PB_ADV
void mesh_node_rc_data_dispatch(pro_PB_ADV *p_adv)
This function is for a unprovisioned device to be provisioned through PB_ADV bearer, and handle all messages during provision flow.if a provison message need to be assembled, it was assembled in mesh_provison_process() before.
(2) PB_GATT
void dispatch_pb_gatt(u8 *p ,u8 len )
This function is for a unprovisioned device to be provisioned through PB_GATT bearer, and handle all messages during provision flow.if a provison message need to be assembled, it was assembled in pkt_pb_gatt_data() before.
Proxy server API
Provision service
(1) int pb_gatt_Write (void *p)
This function server to process Proxy PDU of provision service from GATT master, such as cell phone.
Proxy service
(1) int proxy_gatt_Write(void *p)
This function server to process Proxy PDU of proxy service from GATT master, such as cell phone.
Configuration Callbacks API
This section introduce APIs of all opcodes of configuration server and client model.
Most of them are located within config_model.c. and processing callback function when receiving a opcode are defined by mesh_cmd_sig_func[].cb.
Take opcode of “APPKEY_ADD” for example:
int mesh_cmd_sig_cfg_appkey_set()
* @brief This function will be called when receive the opcode of "Config AppKey Add"
* @param[in] par - parameter of this message
* @param[in] par_len - parameter length
* @param[in] cb_par - parameters output by callback function which handle the opcode received.
* @return 0: success; others: error code of tx_errno_e
* @note
int mesh_cmd_sig_cfg_appkey_set(u8 *par, int par_len, mesh_cb_fun_par_t *cb_par)
model_enable
This section introduce APIs of all opcodes of other SIG server and client model.
all processing callback function when receiving a opcode are defined by mesh_cmd_sig_func[].cb.
MD_SAR_EN
SAR Configuration Server model and client model. SAR means segmentation and reassembly.
MD_ON_DEMAND_PROXY_EN
On Demand Private Proxy Server model and client model.
MD_OP_AGG_EN
Opcodes Aggregator Server model and client model.
MD_LARGE_CPS_EN
Large Composition Data Server model and client model.
MD_SOLI_PDU_RPL_EN
Solicitation PDU RPL Configuration Server model and client model. RPL means Replay Protection List.
MD_DF_CFG_SERVER_EN and MD_DF_CFG_CLIENT_EN
directed forwarding server model and client model.
MD_SBR_CFG_SERVER_EN and MD_SBR_CFG_CLIENT_EN
subnet bridge server model and client model.
MD_REMOTE_PROV
Remote Provisioning Server model and client model.
MD_PRIVACY_BEA
Mesh Private Beacon Server model and client model
MD_BATTERY_EN
Generic Battery Server and client model
MD_LOCATION_EN
Generic Location Server and client model
MD_LEVEL_EN
Generic Level Server and client model
MD_DEF_TRANSIT_TIME_EN
Generic Default Transition Time Server and client model
MD_POWER_ONOFF_EN
Generic Power OnOff Server model and client model
MD_SCENE_EN
Scene Server and client model
MD_TIME_EN
Time Server model and client model
MD_SCHEDULE_EN
Scheduler Server model and client model
MD_SENSOR_EN
Sensor Server model and client model
MD_MESH_OTA_EN
Device firmware update Server model and client model
Mesh Binary Large Object Transfer Server model and client model
MD_LIGHTNESS_EN
Light Lightness Server model and client model
MD_LIGHT_CONTROL_EN
Lighting control models model and client model
LIGHT_TYPE_CT_EN
Light CTL Server model and client model
LIGHT_TYPE_HSL_EN
Light HSL Server model and client model
LIGHT_TYPE_XYL
Light xyL Server model and client model
LIGHT_TYPE_POWER
Generic Power Level Server model and client model
MD_PROPERTY_EN
Generic User Property Server model and client model
Generic Admin Property Server model and client model
Generic Manufacturer Property Server model and client model
Generic Client Property Server model and client model
Light CT and RGB PWM output API
void light_dim_refresh(int idx)
Refresh the light status once the current lightness, CT and HSL, etc is changed, include during transition process.
In this function, user can get the lightness and CT, HSL, etc, and then user can redefine how to driver PWM output which is depend on hardware, if it is needed.
Vendor model client and server API
This section introduce APIs of all opcodes of other Vendor server and client model.
All processing callback function when receiving a opcode are defined by mesh_cmd_vd_func[].cb.
The VENDOR_OP_MODE_SEL is set to VENDOR_OP_MODE_DEFAULT as default. Include model of VENDOR_MD_LIGHT_S which is server model and VENDOR_MD_LIGHT_C which is client model
Firmware update and blob transfer API
This section introduce APIs of all opcodes of firmware update and blob transfer server and client model.
All processing callback function when receiving a opcode are defined by mesh_cmd_sig_func[].cb.
And they are enable by set MD_MESH_OTA_EN to 1.
QA(常见问答)
本章节主要收录一些常问的问题。
Q1. 组网成功的回调在哪里?
在闪灯回调函数rf_link_light_event_callback()里面的LGT_CMD_SET_MESH_INFO分支。 LGT_CMD_SET_MESH_INFO(和LGT_CMD_PROV_SUC_EVE相同):表示light节点端,provision 成功后的闪灯事件.
Q2. 如何判断节点是否处于组网状态?
is_provision_success(); 返回 1 表示已经组网过。要注意的是,这里是指执行完 provision 流程,不包含后续的 app key add 和 key bind流程。
如果是判断 key bind 完成,是没有一个适用于所有情景的判断方法的。因为逻辑上,app 组网流程执行完后,不是必须马上就进行app key add 和 key bind。 如果是以我们的 app 流程为例的话,app 组网完后,紧接着进行app key add 和 key bind, 此时,可以在 main_loop() 里面 判断 node_binding_tick,如下所示:
void main_loop ()
{
......
if(is_provision_success() && node_binding_tick && clock_time_exceed(node_binding_tick, 3*1000*1000)){
// key bind success here
}
......
}
LPN 就是按上述逻辑进行判断的。
Q3. 剔除、删除或者踢出节点的时候,可以用组号作为目的地址吗?
设备剔除出网络,是必须一个一个去kick out的。 原因是 kick out 发送的是 "NODE_RESET" 命令,这个命令属于 configure model,需要使用 device key加解密,而每个节点的device key是不一样的,所以没办法使用 组播 的方式发送命令。除非客户自定义 vendor 命令去做kick out动作。
Q4. 16k和32k retention需要切换 cstartup.S和boot.link文件吗?
不需要。详见 "启动文件cstartup.s和链接文件boot.link" 章节的说明。
Q5. 给节点的onoff model分组后,给目的地址 0xc000 发送 onoff set 或者lightness set命令,节点正常受控,但是vendor命令或者sensor配置命令却不受控,是什么原因?
原理分析说明详见"分组特性及share-model的介绍"章节的说明。
上位机工具的UI操作,默认只给onoff model发送Config Model Subscription Add。所以标准做法是除了给 onoff model发送 Subscription Add命令外,还需要通过 INI 等方式对vendor model、 sensor model发送Subscription Add。 如果希望增加私有做法,比如想把onoff model 和 vendor model等的组号共享,则打开SHARE_ALL_LIGHT_STATE_MODEL_EN 即可。详见该宏对应的code。
Q6. 连续发送长包(segment),会报错 “get ut tx buffer failed: tx segment busy”, 是什么原因?
当发送长包时,需要进行分包,如果目的地址是单播地址,发送完所有分包后,需要等待接收端回复 block Ack,看是否已经收完所有的分包,如果有,则会进行补包,然后再等待接收端回复是否已经收完所有的分包。直至对方接收完成或者超过最大重发次数。
目前 SDK 只支持一个状态机来管理 segment 流程,所以当前面的 segment 发包未完成时,再次发包函数发送长包,会报错“get ut tx buffer failed: tx segment busy”。
即使目的地址是组播地址,也需要等所有分包发完了,才允许发送下一个长包。因为发送分包的过程是先发一个分包,当这个分包发完后(大概需要 200多ms),再发下一个分包,直至所有的分包都发送完成,才会把 segment busy 清零。不连续把分包压入发送 fifo 的原因是我们的 发包 buffer 没有设置那么大,连续压,很可能会buffer不足导致出错。
建议的解决方式是,判断发包函数的返回值,如果不为0,则等待一段时间后再发送,或者发送前调用 is_busy_mesh_tx_cmd() 判断当前是否处于 busy 状态。或者启用私有的扩展广播包模式,详见 "telink自定义的通过扩展广播包_extend_adv_发送mesh消息的模式" 章节的说明。
Q7. 如何获取当前message的rssi?
在消息回调函数里,比如 mesh_cmd_sig_g_onoff_set(),读取全局变量 rssi_pkt 即可,该全局变量在调用 app_event_handler() 前会被赋值。需要注意的是,该rssi不能作为当前节点和发送该命令的节点之间距离的判断依据,因为 该消息有可能是中继转发过来的。
Q8. 如何获取当前message的ttl?
在消息回调函数里,比如 mesh_cmd_sig_g_onoff_set(),读取 cb_par->p_nw.ttl 即可。
Q9. onoff等命令Transition Time里的Step Resolution和Number of Steps是定义灯的渐变过程的每次的变化量吗?
不是的,Transition Time只是定义灯多久变化完成。每次的变化量是根据硬件自定义的,如需修改,修改LIGHT_ADJUST_INTERVAL的值即可。
Q10. 为什么要在driver里面增加AS_PWM_SECOND类型?
在mesh SDK中,PWM 属性只保留了 AS_PWM 并增加了 AS_PWM_SECOND, 是因为 我们希望 客户在 修改灯的 PWM口为另一个口 的时候,客户只需要修改 GPIO 就可以,其他都是代码自动完成, 不需要再配置 PWM ID、invert 属性等。
由于有几个管脚是支持两个PWM ID的,比如B85m 的 GPIO_PC1,GPIO_PC4,GPIO_PC5 。所以,客户在 mesh SDK 配置 PWM 属性的时候,需要查看下datasheet的 GPIO 表,如果 GPIO 不属于 这 3个中的一个,那都统一设置为 AS_PWM, 因为该GPIO都只有一个 PWM index供使用。如果属于,那如果是选第一个PWM index,那就设置为 AS_PWM, 如果选第二个PWM index, 那就设置为 AS_PWM_SECOND。
Q11. 低功耗设备应该选择哪个编译选项?
如果为了更低的功耗,只有在配网阶段,或者主动进入接收模式,才会去接收命令,其他时间都只需要发送功能,建议使用 8258_mesh_Switch 编译选项。
如果是要不定时的接收功能,即别的节点可能时不时的发送数据过来,要求低功耗节点接收数据, 那就需要使用SIG mesh spec定义的LPN模式,对应我们的编译选项是8258_mesh_LPN。该模式需要Friend节点。 也还可以考虑私有的spirit LPN 的编译选项,该模式不需要Friend节点,但是要求发送端发送命令的时候,连续发送1秒,间隔也要求比较短。因为spirit LPN是周期唤醒一小段时间来接收数据。功耗比标准的mesh LPN要大。详情请参考 Spirit LPN.
Q12. Flash超过192KB的建议改善措施?
当firmware 优化完后,还是超过了192KB,首要建议的还是更换flash类型为1MB的芯片。
如果不想换,并且超的不多,比如十几 k 大小,此时可以考虑以下几个优化方向:
-
重排 flash map,把一些不用的功能对应的参数区去掉(但是要评估后续是否会使用),比如默认没有使用的 FLASH_ADR_MD_TIME_SCHEDULE, FLASH_ADR_MD_LIGHT_LC, FLASH_ADR_MD_SENSOR, FLASH_ADR_MD_LIGHT_HSL, FLASH_ADR_MD_PROPERTY(FLASH_ADR_MD_DF_SBR), FLASH_ADR_MD_G_POWER_ONOFF, FLASH_ADR_MD_SCENE, FLASH_ADR_MESH_TYPE_FLAG, FLASH_ADR_MD_MESH_OTA, FLASH_ADR_MD_MISC_PAR。
-
去掉 device uuid 的 md5 算法,具体函数是 uuid_create_by_mac() 的 uuid_create_md5_from_name,大约有 2k。
注意:
重排flash后,需要修改FW_SIZE_MAX_K,以及确认factory_reset()是否需要调整。
Q13. 节点如何查看自身的组网信息?
组网信息主要包含netkey,device key,appkey,iv index,unicast address,这些信息包含:
typedef struct{
u8 net_work_key[16];
u16 key_index;
union{
mesh_ctl_fri_update_flag_t prov_flags;
u8 flags;
};
u8 iv_index[4];
u16 unicast_address;
}
u8 device_key[16];
u8 app_key[16];
节点要查看自身的组网信息,其中netkey,device key,appkey在mesh_key中查看;iv index在iv_idx_st中查看iv_cur;unicast address在ele_adr_primary中查看。
Q14. 如何发自定义的广播包?
用户要发送自定义的广播包可通过修改gatt_adv_prepare_handler函数中指针 p 指向的内容设置广播包;可参考pre_set_beacon_to_adv(),返回1表示发送,0表示没有数据要发送。
Q15. 为什么Scheduler的数量最大为16?
因为mesh model spec中Scheduler的Index字段大小只有四个bit,所以Scheduler的数量最大为16。具体内容可以查看mesh model spec,比如《MshMDL_v1.1.pdf》的第5.2.3.4节。注意是每个节点支持16个scheduler,而不是整个mesh网络只有16个scheduler,所以也是足够的。
Q16. 如何删除Scheduler?
要删除Scheduler,可以通过发送Scheduler Action Set命令,并且将Scheduler的action字段设置为SCHD_ACTION_NONE,即0F,这样就可以达到删除Scheduler的目的。
typedef struct{
u64 valid_flag_or_idx : 4; // flag: when save; index: in message
u64 year : 7;
u64 month : 12;
u64 day : 5;
u64 hour : 5;
u64 minute : 6;
u64 second : 6;
u64 week : 7; // bit0 means monday,
u64 action : 4;
u64 trans_t : 8; // transition time
u16 scene_id;
u8 rand_hour;
u8 rand_min;
u8 rand_sec;
u8 rsv;
}scheduler_t;