@marsleung
2019-12-20T06:46:29.000000Z
字数 15404
阅读 8864
triton desktop neptune windows
本文将介绍,如何通过 Triton Windows 把您的设备连接到 Neptune工业设备物联云平台,体验快速连接并传输数据的乐趣。
Triton Windows 适用于您的控制程序或者您的逻辑程序运行于Windows(x86/x64)之上,通过您的程序和Triton交互,实现设备连接和数据传输的功能。
交互方式有以下三种,可根据您的实际情况选择使用:
- 您的程序实现TCP Client与Triton进行通讯;
- 您的程序实现Modbus协议(从站),Triton作为Modbus主站向您的程序读取数据;
- 您的程序集成Triton外部库(C/C++),您的程序直接与Triton进行通讯;
如果您使用的运行平台为Linux或者WinCE,请参考以下文档:

STEP#01
访问 https://www.neptune-iiot.net, 点击右上角注册;

STEP#02
填写注册信息,注册分为三个步骤:
a) 填写账户信息(使用邮件或者手机号码注册);

b) 验证账户信息(使用邮件或者手机号码接收验收激活邮件或者验证码);
以邮箱注册为例子,将收到上述邮件。点击激活邮箱,跳转到团队创建界面。
c) 创建或者加入一个团队[说明1], 完成注册;
新建一个团队,输入一个您的团队名称,点击创建;

加入一个团队,通过关键字搜索一个团队的名称,点击申请加入[说明2]。

说明1:团队
团队的含义是一个域,用来为用户划定一个空间。用户创建团队时,会自动成为该团队的管理员,别的用户可以申请加入您的团队,您也可以在团队中邀请用户加入。
说明2:申请加入
当申请加入一个团队时,需要对方团队的管理员通过您的请求。请在申请加入后,耐心等待通过。
STEP#03 注册完成
注册完成后,页面跳转到登录界面。您可以使用您的账号密码进行登录。

本文档关注使用Triton将设备连接至Neptune,因此平台功能在本文档中不作过多介绍,平台功能的介绍将在其它文档中介绍。
在Neptune账号注册完成后,我们需要在Neptune中“创建一个设备”,这个“设备”将获得一个全平台唯一的序列号,并且和实体设备一一对应起来。
STEP#01
点击左侧菜单栏分组设备列表,选中菜单设备,右侧出现设备列表,此时设备列表显示为空。

点击列表上方创建按钮,出现以下对话框:

STEP#02
为您的设备输入一个名称和描述,其它的栏位将会在平台使用教程中说明,例如:

STEP#03
点击确认后,Neptune为该设备生成设备序列号以及团队令牌。上述信息是设备通过Triton能正确连接到Neptune的重要信息,后续仍可以在平台中查询得到。

STEP#04
再次返回到设备列表,就可以看到您的设备已经添加到Neptune中了。

Triton Windows
x86 https://www.neptune-iiot.net/triton-release/triton-windows-386.exe
x64 https://www.neptune-iiot.net/triton-release/triton-windows-amd64.exe
Triton目前支持以下版本:
Windows(x86/x64)、WinCE(x86)、Linux(x86/x64/ARMv5以上/AArch64),请按实际情况选择适配版本。其它版本请从以下地址下载:
Triton WinCE
x86 https://www.neptune-iiot.net/triton-release/triton-wince-386.zip
Triton Linux
x86 https://www.neptune-iiot.net/triton-release/triton-linux-386
x64 https://www.neptune-iiot.net/triton-release/triton-linux-amd64
AArch64 https://www.neptune-iiot.net/triton-release/triton-linux-arm64
ARMv5 https://www.neptune-iiot.net/triton-release/triton-linux-armv5
ARMv6 https://www.neptune-iiot.net/triton-release/triton-linux-armv6
ARMv7 https://www.neptune-iiot.net/triton-release/triton-linux-armv7
STEP#01
在运行Triton前,需要生成Triton的配置文件。请在Windows命令提示符(可通过 Win键+R 弹出运行并输入cmd调用)中,定位到下载的Triton的所在目录。
STEP#02
输入以下命令:
triton-windows-amd64.exe -g triton.toml

命令执行后,在triton所在目录下,生成配置文件 triton.toml

STEP#03
使用文本编辑器打开triton.toml,推荐使用Notepad++, EditPlus等文本编辑工具进行操作,使用Windows自带的记事本打开,可能会丢失格式,但不影响使用。以下截图使用Visual Studio Code打开:

STEP#04
修改配置文件中的第四行、第二十二行内容:
第四行需要填写设备的序列号,序列号可以在设备列表中查找得到:

Serial = "6ed92f55a083f1038b005eb28ef0c760"
第二十二行需要填写团队令牌,团队令牌可以在团队设置中查找得到:

Token = "05712fb7cf95547d3e0f2ad076c3db75"
如果您需要使用证书加密连接,请将UseSSL改为true,连接端口改为8883,并修改相关配置项:
[MessageQueue]#服务器地址保持不变Address = "www.neptune-iiot.net"Token = "05712fb7cf95547d3e0f2ad076c3db75"#修改端口为8883Port = "8883"#启用加密UseSSL = true#加密连接属于公测阶段,InsecureSSL需要设置为`true`才能正常连接InsecureSSL = true#CA证书路径CAPath = "/path/to/ca.crt"#客户证书路径CertPath = "/path/to/client.crt"#客户私钥路径KeyPath = "/path/to/client.key"#客户私钥密码KeyPassphrase = "hunter2"
修改配置文件中最后的LogLevel配置,让程序在日志中显示更多的内容:
LogLevel = 5
修改完成后,保存triton.toml, 双击运行triton-windows-amd64.exe(Windows可能会提示拦截该程序,点击仍要运行即可),输出如下内容:

打开Neptune,设备列表,查看列表中的设备是否已经显示在线,在线表示Triton与Neptune已经成功通讯。如果依然显示为离线,请检查:
- Triton所在的计算机网络是否正常;
- triton.toml中需要修改的两项内容是否正确填写;

Triton目前提供了三种数据交互的方式,分别为:
请根据您的实际情况,选择适配的方法。
所有需要传输的数据都需要先在Neptune中先定义,Neptune中称为数据定义,每一个设备需要维护一份数据定义。如果数据不事先定义好,在数据传输时,平台会把无定义的数据丢弃。
STEP#01
从设备列表中找到您需要添加数据定义的设置,点击设备名称进入设备表单;

STEP#02
采用key/value模式(即编号/值)规划好您需要传输的数据,您需要为每个需要传输的变量定义一个在本设备中唯一的编号,建议编写具有阅读性、便于理解的编号;比如我们要传输以下内容,编号规划如下:
- 温度:temp,数字
- 湿度:humidity, 数字
- 照度:illuminance, 数字
- 是否工作:is_running, 布尔
- 工作状态:status, 文本
STEP#03
将上述内容输入到数据定义表格中;
a) 点击添加,在对话框中逐项输入第2点中的范例数据[说明1]:

说明1
1. 数据ID由数字和字母组成,不能包含特殊字符: + - * / > < = % . \ & $ # @, 但可以包含 _ (下划线)。
2.数据ID、名称以及数据类型为必填项,其中数据类型可选值为:数字(不区分整形和浮点型)、字符、布尔类型;
b) 如果您的数据存在枚举型(即用数值代表一个选项,例如:1->男,2->女),可以使用数据转义,数据转义会将得到的数据值,直接翻译为转义值

c) 全部添加完成后,效果如下:

STEP#04
每个设备都需要有这样一份数据定义,首个设备需要按照上述步骤手动创建,创建后可以将其保存为模板或者导出Excel方便下次直接引用或者使用Excel文件导入,以减少重复的操作次数;
说明
与3.4 使用Modbus协议读取您的数据二选其一即可。
按照2.4 运行Triton中的描述正确运行Triton程序。
Triton正常运行后,它会建立一个Socket Server,Server信息如下:
Address = 127.0.0.1 (或本机局域网IP)Port = 8864
请您在您的工程中,通过代码使用Socket通讯连接到上述地址;
当您有数据发送需求时,您需要将需要传输的数据按照特定的格式构造字符串,并将字符串发送到Triton建立的Socket Server中。字符串的格式为:
s|key1,value1|key2,value2|……|keyN,valueN
其中s|为固定的字符前缀,所有数据以编号和值作为一组,编号与值的分隔符为,(英文),每组编号值之间分隔符为|。
以3.2 数据定义中的数据为例,构造的字符串为:
s|temp,25.8|illuminance,600|humidity,70|is_running,1|status,2
将上述字符串以Socket的方式发送到127.0.0.1:8864即可。
特别说明
1. 数据发送的时机及频率由用户的程序决定;
2. 每次发送时,仅需要发生当前需要发送的key/value即可,不必将所有的key/value都构造在发送字符串中。
除了3.3.2.1 发送数据到Neptune中介绍的将数据从您的设备发送到Neptune以外,还支持接收来自Neptune的数据;从Neptune下发到设备的数据分为两类:
- 系统用数据
- 用户自定义数据
只有用户自定义数据用户才能接收得到,系统用数据会由Triton接收后进行特定处理,不会转交给用户。用户可以通过自行编写下发的数据,并在用户程序接收后做特定的处理。
当用户设备有数据接收需求时,可以向Triton建立的Socket Server中发送以下消息:
r
Socket Server将Neptune的数据返回给到TCP Client,Neptune的数据以队列的方式存在,每次向Socket Server发送r时,取出一条数据,直到所有数据取完后,Socket Server返回NODATA,表示队列中已无数据。
说明
与3.3 使用TCP与Triton进行Socket通讯二选其一即可。
在开始使用Modbus协议作为数据交互方式时,需确认以下内容:
- 您的设备程序需要实现Modbus协议,并作为从站(Slave)将数据写入Modbus指定的存储区中;
- 您的设备程序如果与设备是分离的,那么设备与运行程序的计算机之间需要用网线或者串口线相连,网络连接实现Modbus-TCP,串口线连接实现Modbus-RTU;建议使用网线连接并实现Modbus-TCP协议;
- 您的程序已正确运行,并正确调用Triton动态库;
- 建议在开始本章节的测试前,您已经使用Modbus工具成功地从设备中读取到数据,以减少影响后续操作的因素;
Modbus Connector应用套件该工具目前处于内测阶段,平台注册用户默认已开始此功能,因此不需要额外的安装工作,请直接查看3.5.2 结果测试-Modbus。
Triton动态库包含一个库文件和一个头文件,请根据您使用的开发语言及平台下载对应的动态库:
Triton for Windows Library
x86 https://www.neptune-iiot.net/triton-release/libs/triton-windows-386.zip
libtriton.h内容如下:
#pragma once#ifdef libtriton_EXPORTS#define LIBTRITON_API __declspec(dllexport)#else#define LIBTRITON_API __declspec(dllimport)#endif#ifndef NELEMS#define NELEMS(x) (sizeof(x) / sizeof((x)[0]))#endif#define RECV_BUFSIZE 16384typedef struct KeyValue {char* Key;char* Value;}KeyValue;typedef struct Payload {int size;KeyValue *keyValue;}Payload;#ifdef __cplusplusextern "C" {#endif// Send data// Parameters:// kvs - array of key value pairs (KeyValue structs) to be sent// size - length of array (kvs)// Return values:// 0 - success// -1 - error connecting to server// -2 - error writing to server// -3 - error reading response from server// -4 - data errorLIBTRITON_API int Send(KeyValue kvs[], int size);LIBTRITON_API int Send_T(KeyValue kvs[], int size);LIBTRITON_API int Send_Compat(KeyValue kvs[], int size);LIBTRITON_API int Send_Compat_T(KeyValue kvs[], int size);// Receive data// Parameters:// buffer - pointer to buffer for writing received message// Returns received data length by default, otherwise:// -1 - error connecting to server// -2 - error writing to server// -3 - error reading response from server// -4 - data errorLIBTRITON_API Payload* Receive();LIBTRITON_API Payload* Receive_T();LIBTRITON_API Payload* Receive_Compat();LIBTRITON_API Payload* Receive_Compat_T();// MQTT status// Returns MQTT connection status, true if connected, false otherwiseLIBTRITON_API bool Status();LIBTRITON_API bool Status_T();LIBTRITON_API bool Status_Compat();LIBTRITON_API bool Status_Compat_T();LIBTRITON_API void freePayload(Payload *payload);#ifdef __cplusplus}#endif
- 需要发送的数据按结构体
KeyValue构造,将KeyValue数组传入发送函数Send()中;- 带有
_T后缀的函数为包含执行时间输出的函数,以便于测试- 带有
_Compat后缀的函数为兼容模式函数,一般情况下不需使用Receive函数必须由调用者定期调用以获取消息
编写程序,在适当的时机以适当的频率调用发送和接收方法即可。可参考教程中的示例程序。
#include "stdafx.h"#include <windows.h>#include <iostream>#include <string>using namespace std;// Import the header#include "libtriton.h"enum{eLedBlink = 1,eLedOn,eLedOff};const unsigned int iMaxSize = 256;static bool isExit = false;static int iLedStatus = eLedBlink;DWORD WINAPI MyReceiveThread(LPVOID lpParam);int SendDataToServer(string led1_state,string led2_state);void LedOn();void LedOff();void LedBlink();int main(){DWORD dwThreadId;HANDLE ThreadHandle;ThreadHandle = CreateThread(NULL,0,MyReceiveThread,NULL,0,&dwThreadId);if (ThreadHandle == NULL){printf("Create thread error\n");return -1;}//Get UUIDchar uuid[32] = { 0 };GetUUID(uuid);printf("uuid %s\n", uuid);// Get MQTT connection statusbool online = Status();if (online){printf("Network is connected\n");}else{printf("Network is disconnect\n");}while (!isExit){if (iLedStatus == eLedOn){LedOn();}else if (iLedStatus == eLedOff){LedOff();}else if (iLedStatus == eLedBlink){LedBlink();}Sleep(500);// Send the LED status to server}system("pause");WaitForMultipleObjects(1, &ThreadHandle, TRUE, INFINITE);return 0;}DWORD WINAPI MyReceiveThread(LPVOID lpParam){while (!isExit){// Receive data from NeptunePayload *payload = Receive();// Check if data size is not zeroif (payload->size > 0){for (int i = 0; i < payload->size; ++i){string strKey = payload->keyValue[i].key;string strValue = payload->keyValue[i].value;if (strcmp(strKey.c_str(),"switch") == 0){if (strcmp(strValue.c_str(),"ALL_ON") == 0){iLedStatus = eLedOn;}else if (strcmp(strValue.c_str(), "ALL_OFF") == 0){iLedStatus = eLedOff;}else if (strcmp(strValue.c_str(), "NORMAL") == 0){iLedStatus = eLedBlink;}}//printf("%d. key: %s, value: %s", i, payload->keyValue[i].key, payload->keyValue[i].value);}}// Use `freePayload` helper function to free the payloadfreePayload(payload);Sleep(1000);}return 0;}void LedOn(){printf("Led1 ●,Led2 ●\n");SendDataToServer("On", "On");}void LedOff(){printf("Led1 ○,Led2 ○\n");SendDataToServer("Off", "Off");}void LedBlink(){printf("Led1 ○,Led2 ●\n");SendDataToServer("Off", "On");Sleep(500);printf("Led1 ●,Led2 ○\n");SendDataToServer("On", "Off");}int SendDataToServer(string led1_state, string led2_state){// Let's say we have 5 pairs of key-values that we need to sendKeyValue kv[2];//new space what you needfor (int i = 0; i < 2; i++){kv[i].key = new char[iMaxSize];kv[i].value = new char[iMaxSize];}// Write keys/values to the structssprintf_s(kv[0].key, iMaxSize, "led1");sprintf_s(kv[0].value, iMaxSize, "%s",led1_state.c_str());sprintf_s(kv[1].key, iMaxSize, "led2");sprintf_s(kv[1].value, iMaxSize, "%s", led2_state.c_str());// Send the key values to server and get its return valueint ret = Send(kv, 2);for (int k = 0; k < 2; k++){delete kv[k].key;delete kv[k].value;}return ret;}
结果测试将使用Neptune上的开发者面板进行,开发者面板的作用是查看平台数据的交互结果是否正确,以便提高调试效率,并非最终的数据应用方式。
STEP#01
在Neptune中打开需要调试的对象设备,同时请注意设备的状态需要是“在线”,如果设备不在线,请检查:
- Triton是否正确运行;
- 网络连接是否正确;

STEP#02
打开开发者面板;

STEP#03
选择密令项[说明1] start_data,并点击按钮推送密令到设备,点击后Triton会接收到该密令,并将数据打印在开发者面板之上。

说明1
密令项指Neptune和Triton之间的内部沟通指令,用户也可以向设备推送自定义的沟通指令,以实现用户自定义的功能;
STEP#04
观察开发面板的数据输出窗口是否已经打印出您上传的数据,数据格式为json,您上发的数据位于data标签中,其它的键值为Neptune内部使用,用户不必关注。

STEP#05
数据上传测试完成。
STEP#01
打开开发者面板;

STEP#02
编写向下推送的密令,向下发送的密令只需要符合json格式(即key/value格式)即可,内容没有特定的要求,只需要您的程序能够使用即可。比如,用户期望推送"300秒后关机"到设备上,让其执行关机,那么我们可以编写以下内容:
{"action":"shutdown","delay":"300"}
STEP#03
将上述密令粘贴到密令输入框入,然后点击推送。消息正确推送时,界面不会有任何的错误提示,如果有出现错误提示,按提示的内容检查即可。

STEP#04
数据推送下去后,需要使用r命令进行数据读取,如果不读取数据,将一直留存在列队中。每次通过r命令读取后,服务器中的消息就会减少一条,直到全部取完。以下我们使用SocketTool作为测试的工具:

这里看到,推送的数据都收到了,但数据里多了一项uuid,这一项是系统默认会带上的,用于标记数据是发给哪一个设备。
{"action":"shutdown","delay":"300","uuid":"6ed92f55a083f1038b005eb28ef0c760"}
此时我们再向Triton发送r命令,可以看到,所有数据取完后,Triton回复NODATA:

STEP#05
数据下发测试完成。
- 由于Modbus协议本身的设计规范,无法实现下发数据的功能,即不能够实现从Neptune向您的设备推送数据;
- 考虑到实际安全性的问题,Modbus协议暂不考虑实现修改存储区数值的功能,仅保留数据读取的功能。
工作流程:推送配置 -> 读取数据
STEP#01
在Neptune中打开需要调试的对象设备,同时请注意设备的状态需要是“在线”,如果设备不在线,请检查:
- Triton是否正确运行;
- 网络连接是否正确;

STEP#02
打开开发者面板;

STEP#03
选择密令项[说明1]modbus_setting_tcp

说明1
密令项指Neptune和Triton之间的内部沟通指令,用户也可以向设备推送自定义的沟通指令,以实现用户自定义的功能;
右侧出现密令的详细内容,我们需要修改密令的内容,建议将密令内容复制到文本处理工具中编辑后再粘贴到此窗口:
{"action": "modbus","type": "settings","uuid": "IdeaBoxPro-Left","details": {"protocol": "tcp","address": "192.168.1.1","slave_id": 1,"dataset": [{"key": "switches 1","region": "coilro","address": 0,"length": 8},{"key": "switches 2","region": "coilrw","address": 0,"length": 16}]}}
您需要修改的部分为 address, slave_id以及dataset
a. address:即Modbus设备的IP地址;
"address": "192.168.1.1"
b. slave_id:即Modbus设备的从站ID;
"slave_id": 1
c. dataset:即通过Modbus协议读取的数据组,dataset为列表,dataset中的每一个元素为需要读取的数据,每个数据由数据编号、读取区域、起始地址和读取长度构成,其中数据编号与3.2.1 添加数据定义中所述的数据定义一一对应;
"dataset": [{"key": "temp","region": "coilro","address": 0,"length": 8},{"key": "humidity","region": "coilrw","address": 0,"length": 16}]
以上配置表示通过Modbus协议读取两组数据:
- 第一组:从
coilro(Coil)中起始地址为0的位置读取长度为8的数据,数据对应数据定义中的temp;- 第二组:从
coilrw(Discret Input)中起始地址为0的位置读取长度为16的数据,数据对应数据定义中的humidity;
STEP#04
当配置内容确定后,将全部的密令内容粘贴到密令窗口中,点击推送密令到设备

STEP#05
选择密令modbus_read,将按第4点中的配置内容向设备请求数据,并将结果返回到输出面板中。

STEP#01
在Neptune中打开需要调试的对象设备,同时请注意设备的状态需要是“在线”,如果设备不在线,请检查:
- Triton是否正确运行;
- 网络连接是否正确;

STEP#02
打开开发者面板;

STEP#03
选择密令项[说明1]modbus_ports,该密令能够将计算机上的串口列表返回供用户选择,如果您已经清楚您设备的连接串口,可以跳过此步骤。

该计算机共有三个串口,本示例中的设备连接在串口/dev/ttyS0上。
说明1
密令项指Neptune和Triton之间的内部沟通指令,用户也可以向设备推送自定义的沟通指令,以实现用户自定义的功能;
STEP#04
选择密令项modbus_setting_rtu;

右侧出现密令的详细内容,我们需要修改密令的内容,建议将密令内容复制到文本处理工具中编辑后再粘贴到此窗口:
{"action": "modbus","type": "settings","details": {"protocol": "rtu","serial_port": "/dev/ttyS0","baudrate": 9600,"databits": 8,"parity": "none","stopbits": 1,"slave_id": 1,"uuid": "IdeaBoxPro-Right","dataset": [{"key": "temp","region": "coilro","address": 0,"length": 8},{"key": "humidity","region": "coilrw","address": 0,"length": 16}]}}
您需要修改的部分为 serial_port、baudrate、databits、parity、stopbits、slave_id、dataset
a. serial_port:计算机与设备相连的串口;
"serial_port": "/dev/ttyS0";
b. baudrate:波特率;
"baudrate": 9600
c. databits:数据位;
"databits": 8
d. parity:奇偶校验;
"parity": "none"
d. stopbits:停止位;
"stopbits": 1
e. slave_id:从站ID;
"slave_id": 1
f. dataset:即通过Modbus协议读取的数据组,dataset为列表,dataset中的每一个元素为需要读取的数据,每个数据由数据编号、读取区域、起始地址和读取长度构成,其中数据编号与3.2.1 添加数据定义中所述的数据定义一一对应。
"dataset": [{"key": "temp","region": "coilro","address": 0,"length": 8},{"key": "humidity","region": "coilrw","address": 0,"length": 16}]
以上配置表示通过Modbus协议读取两组数据:
- 第一组:从
coilro(Coil)中起始地址为0的位置读取长度为8的数据,数据对应数据定义中的temp;- 第二组:从
coilrw(Discret Input)中起始地址为0的位置读取长度为16的数据,数据对应数据定义中的humidity;
STEP#05
当配置内容确定后,将全部的密令内容粘贴到密令窗口中,点击推送密令到设备

STEP#06
选择密令modbus_read,将按第4点中的配置内容向设备请求数据,并将结果返回到输出面板中。

结果测试将使用Neptune上的开发者面板进行,开发者面板的作用是查看平台数据的交互结果是否正确,以便提高调试效率,并非最终的数据应用方式。
STEP#01
在Neptune中打开需要调试的对象设备,同时请注意设备的状态需要是“在线”,如果设备不在线,请检查:
- Triton是否正确运行;
- 网络连接是否正确;

STEP#02
打开开发者面板;

STEP#03
选择密令项[说明1] start_data,并点击按钮推送密令到设备,点击后Triton会接收到该密令,并将数据打印在开发者面板之上。

说明1
密令项指Neptune和Triton之间的内部沟通指令,用户也可以向设备推送自定义的沟通指令,以实现用户自定义的功能;
STEP#04
观察开发面板的数据输出窗口是否已经打印出您上传的数据,数据格式为json,您上发的数据位于data标签中,其它的键值为Neptune内部使用,用户不必关注。

STEP#05
数据上传测试完成。
STEP#01
打开开发者面板;

STEP#02
编写向下推送的密令,向下发送的密令只需要符合json格式(即key/value格式)即可,内容没有特定的要求,只需要您的程序能够使用即可。比如,用户期望推送"300秒后关机"到设备上,让其执行关机,那么我们可以编写以下内容:
{"action":"shutdown","delay":"300"}
STEP#03
将上述密令粘贴到密令输入框入,然后点击推送。消息正确推送时,界面不会有任何的错误提示,如果有出现错误提示,按提示的内容检查即可。

STEP#04
数据推送下去后,按上述说明使用triton_recv()进行读取,以下为程序输出结果:

这里看到,推送的数据都收到了,但数据里多了一项uuid,这一项是系统默认会带上的,用于标记数据是发给哪一个设备。
{"action":"shutdown","delay":"300","uuid":"6ed92f55a083f1038b005eb28ef0c760"}
此时我们再调用triton_recv()方法,可以看到,所有数据取完后,Triton回复NODATA:

STEP#05
数据下发测试完成。