OneNET云平台-EDP协议数据传输

OneNet真是中移动的良心之作,对比阿里云和庆科云,OneNet不但免费而且功能也足够嵌入式应用,对学生党而言真是大大的福利,感谢中移动!!!。

一、云端创建设备与应用

(1)创建产品:进入开发者中心有个蓝色框点击就可以创建产品,自己使用的时候,产品信息什么的,自己填完整的就好,这里需要说明的是设备接入方式和设备接入协议,设备接入方式一般选择公开协议,调试之用,没必要自己定义协议,设备接入协议选择有个下拉框可供选择:


在右边框框里有简单介绍协议的基本功能,在这里我选择是EDP协议,可以发送数据和接收命令,能完成基本功能就好。关于这些协议具体介绍在云平台公开协议产品指南里有详细介绍:


(2)注册设备:产品注册成功后会提示是否生成设备,这时候就可以填写自己想要接入设备信息,生成之后是酱紫的:


在产品概况界面是如下显示:


这里有个产品ID和APIKey,这个产品ID和设备ID是不同的,但用这个APIKey可以默认访问产品下的所有设备,其实还可以为每个设备申请APIKey的,在设备管理里,,点击“添加APIKey”就可以为当前设备申请APIKey,下面是我申请设备的完整信息,有了设备ID和APIKey,这是访问该设备的最直接办法。


(3)生成应用:为该设备创建数据流和应用:在数据流模板界面有添加数据流按钮,添加一个名为temp的数据流,再在应用管理界面添加一个名为test的应用,使用一个折线图控件来显示当前temp的值。


注意在右边要为当前折线图选择数据流,才能将应用与设备的数据流绑定。

二、利用EdpProtoDebugger调试工具进行数据传输

接下来,我们就为temp传送数据,官方给出了EdpProtoDebugger调试工具,给出下载链接:https://open.iot.10086.cn/doc/art254.html#68

将之前的设备ID和APIKey填进去,默认消息类型是连接云操作,消息子类型是设备ID和APIKey,表示采用设备ID和APIKey方式来访问云端。

先点击“生成编码”再点击发送到设备云,会有如下16进制数据显示,这些数据是有意义的,在刚才给的下载链接里有相关EDP协议具体介绍


在这里,用调试工具自带的翻译器查看一下接收消息的意思


连接成功,在开发者中心的设备管理里,设备前的类似灯的按钮会亮,表示设备在线。如果五分钟之内没有数据发送与接收,云端服务器会自动断开连接。

接下来是传输数据:消息类型选择SaveData类型,消息子类型就选择数据类型一:JSON格式串,同样的这些协议具体内容都在文档里写清楚,慢慢看就能懂。给个JSON格式串参考:

{
        "datastreams":  [{
                        "id":   "temp",
                        "datapoints":   [{
                                        "value":        "25"
                                }]
                }]
}

将上面的JSON串复制到调试工具的数据文本框中,然后点击生成编码,再点击发送到设备,会在云端应用的折线图看到数据,数据发送接收成功。


再看下云端向设备发送命令,先将调试工具连上云端就什么都不干,静静地等待云端命令就好,然后在云端应用中添加一个旋钮控件来发送命令。


将旋钮与设备test绑定,但没有选择数据流,因为我们是希望云端向设备发送命令,这个就不是设备的数据流了。需要注意的这个EDP命令内容,我在这卡了很久表示不理解这到底是个啥(当然,如果你自己已经看懂了这是什么,这里就跳过),这里先留个疑问,等会在代码调试时再回过头来看会更清楚。

旋转按钮时,会向已连接的调试工具发送消息,在调试工具里有接收到消息,会有相应的16进制数据,用翻译器大致看下就好:



第0字节表示命令请求信息,说明确确实实接收到云端的命令请求,达到我们的期望。

到这里,基本上可以完成设备与OneNet云的连接、发送与接收数据。最后是重点,代码如何实现?

三、代码实现上述功能

也许,有心的同学能发现,其实,OneNet文档中心已经给出了代码实例,在git上已经开源SDK,这里给出链接:https://github.com/cm-heclouds/edp_c

我自己调试的代码也是根据这写些,我自己调试也花了些时间,共享出来,贴出链接,收点劳动积分,共同学习(注意:我是在Linux环境下调试的C代码):http://download.csdn.net/download/kuangzuxiaon/10201344

主要说一下代码框架,怎么实现发送与接收数据的,涉及到简单socket套接字网络编程。

从git上下载到SDK,主要是几个c文件和对应的h文件,包括,cJSON.c、EdpKit.c、Openssl.c和一个简单的应用例程,这个应用例程是有发送和接收数据框架的。

首先说一下这几个文件的大致作用,cJSON.c其实也是一个开源的软件,是ANSI-C标准的JSON解析器,只包含一个.c和.h文件,移植性和效率都是比较好的;Openssl.c同样是一个开源的库,是一个安全套接字层密码库,说简单点就是用于数据加密;所以真正属于OneNET的是EdpKit.c这个文件,这个也是写应用程序参考的最多的一个文件,主要是采用云平台公开协议对数据进行打包和解析的一些函数;SDK会提供一个应用例程Main.c,这个例程主要是采用命令行交互式操作,主要是为了测试EdpKit而写的,也向用户展示了如何使用EdpKit。总体看来,OneNET应用还是比较简单的。

我首先把这个应用例程做了一些修改,主文件重新命名为main.c,源码在上面CSDN的资源页可以下载,首先看下Makefile,默认是不需要数据加密功能的,在Makefile中将编译Openssl的命令屏蔽掉,在main.c中涉及到Openssl的语句都是采用预编译命令包括的。

连接OneNET云平台:主要是利用socket套接字,与基本网络编程思路是一样的:产生套接字->连接服务器->数据传输三个步骤:

int main(int argc, char *argv[]) 
{
    int sockfd, ret=0;
    EdpPacket* send_pkg;  
    pthread_t id_1;
    uint8 sys_time=0, temp=0; 
    char strBuf[25];
  
    /*1、 创建一个与服务器的socket连接*/
    sockfd = Open(SERVER_ADDR, SERVER_PORT);
    if(sockfd < 0)
    {
		printf("Connect Server Faild ! \n");
        return -1;
    }
	
    /* create a recv thread */
    ret=pthread_create(&id_1,NULL,(void *(*) (void *))recv_thread_func, &sockfd);  //创建接收线程
  
    /*2、产生并发送EDP设备连接请求的数据包 */
    send_pkg = PacketConnect1(DEV_ID, API_KEY);  
	ret = DoSend(sockfd, (char*)send_pkg->_data, send_pkg->_write_pos);		
	if((!send_pkg) || (ret <= 0))			//连接失败,直接退出
	{
		goto faild;
	} 
	DeleteBuffer(&send_pkg);
	
    while(1)
    { 
		sleep(5); 
		
        /*3、产生并发送EDP用户数据包 */
		bzero(strBuf, sizeof(strBuf));
		sprintf(strBuf, "%d", sys_time++ % 10);
        ret = write_func(sockfd, "sys_time", strBuf); 
        if (ret < 0)
        {
            break;
        }
     }
	/*关闭socket连接*/
	Close(sockfd);
	/* 关闭接收线程 */
    pthread_join(id_1,NULL);
	
	return 0;
	
faild:
	printf("DoSend faild ! (%d)\n", __LINE__); 
	
	DeleteBuffer(&send_pkg); 
	Close(sockfd);
	pthread_join(id_1,NULL);
	return -1; 
	
}

数据发送过程:这部分是OneNET文档中心给出的例程做的简单修改,

int write_func(int32 socket_fd, char* value_ID, char* val)
{ 
    int32 sockfd = socket_fd;
    EdpPacket* send_pkg;
    cJSON *save_json;
    int32 ret = 0; 
	char send_buf[100];
    
    /*产生JSON串,其中携带了要上传的用户数据*/
	bzero(send_buf, sizeof(send_buf));
    strcat(send_buf,"{\"datastreams\": [{");
    // strcat(send_buf,"\"id\": \"sys_time\",");
    strcat(send_buf,"\"id\": \"");
	strcat(send_buf, value_ID);
	strcat(send_buf, "\",");
    strcat(send_buf,"\"datapoints\": [");
    strcat(send_buf,"{\"value\": \""); 
	strcat(send_buf, val); 
    strcat(send_buf,"\"}]}]}"); 
  
    /*将JSON串封装成EDP数据包*/
    save_json=cJSON_Parse(send_buf);
    if(NULL == save_json)
    {
		printf("[%d]Error before: [%s]\n", __LINE__, cJSON_GetErrorPtr()); 	
        return -1;
    }else{ 
		/* 解析JSON格式,用作调试 */  
		// printf("[%d]cJSON_Print : \n%s\n", __LINE__, cJSON_Print(save_json));	//带格式输出
		printf("cJSON_Print : %s (%d)\n", cJSON_PrintUnformatted(save_json), __LINE__);	//不带格式输出
	} 
   
    send_pkg = PacketSavedataJson(DEV_ID, save_json, kTypeFullJson, 0); 
	
    if(NULL == send_pkg)
    {
        return -1;
    } 
	
	cJSON_Delete(save_json);		//删除构造的json对象
	
    /*发送EDP数据包上传数据*/
    ret = DoSend(sockfd, (char*)send_pkg->_data, send_pkg->_write_pos);
	if(ret < 0)
	{
		printf("DoSend faild ! (%d)\n", __LINE__); 
		// send_pkg = PacketPing();
	}
	
    DeleteBuffer(&send_pkg);
	
    return ret;
}

数据接收过程:这一部分是原来SDK的源码,基本上没有修改,只是添加了几个调试语句

/* 
 * 函数名:  recv_thread_func
 * 功能:    接收线程函数
 * 参数:    arg     socket描述符
 * 说明:    这里只是给出了一个从socket接收数据的例子, 其他方式请查询相关socket api
 *          一般来说, 接收都需要循环接收, 是因为需要接收的字节数 > socket的读缓存区时, 一次recv是接收不完的.
 * 相关socket api:  
 *          recv
 * 返回值:  无
 */
void recv_thread_func(void* arg)
{
    int sockfd = *(int*)arg;
    int error = 0;
    int n, rtn;
    uint8 mtype, jsonorbin;
    char buffer[4096];
    RecvBuffer* recv_buf = NewBuffer();
    EdpPacket* pkg;
    
    char* src_devid;
    char* push_data;
    uint32 push_datalen;

    cJSON* save_json;
    char* save_json_str;

    cJSON* desc_json;
    char* desc_json_str;
    char* save_bin; 
    uint32 save_binlen;
    unsigned short msg_id;
    unsigned char save_date_ret;

    char* cmdid;
    uint16 cmdid_len;
    char*  cmd_req;
    uint32 cmd_req_len;
    EdpPacket* send_pkg;
    char* ds_id;
    double dValue = 0;
    int iValue = 0;
    char* cValue = NULL;

    char* simple_str = NULL;
    char cmd_resp[] = "ok";
    unsigned cmd_resp_len = 0;

	DataTime stTime = {0};

    FloatDPS* float_data = NULL;
    int count = 0;
    int i = 0;

    struct UpdateInfoList* up_info = NULL;

#ifdef _DEBUG
    printf("[%s(%d)] recv thread start ...\n", __func__, __LINE__);
#endif

    while (error == 0)
    {
        /* 试着接收1024个字节的数据 */
        n = Recv(sockfd, buffer, sizeof(buffer), MSG_NOSIGNAL);
        if (n <= 0)
            break;
        printf("[%d]recv from server, bytes: %d\n", __LINE__, n);
        /* wululu test print send bytes */
        hexdump((const unsigned char *)buffer, n);
        /* 成功接收了n个字节的数据 */
        WriteBytes(recv_buf, buffer, n);
        while (1)
        {
            /* 获取一个完成的EDP包 */
            if ((pkg = GetEdpPacket(recv_buf)) == 0)
            {
                printf("[%d]need more bytes...\n", __LINE__);
                break;
            }
            /* 获取这个EDP包的消息类型 */
            mtype = EdpPacketType(pkg);
			printf("EdpPacketType is %d \n", mtype);
#ifdef _ENCRYPT
            if (mtype != ENCRYPTRESP){
                if (g_is_encrypt){
                    SymmDecrypt(pkg);
                }
            }
#endif
            /* 根据这个EDP包的消息类型, 分别做EDP包解析 */
            switch(mtype)
            {
#ifdef _ENCRYPT
            case ENCRYPTRESP:															//加密请求响应 (client to server)
                UnpackEncryptResp(pkg);
                break;
#endif
            case CONNRESP:																//连接请求响应 (client to server)
                /* 解析EDP包 - 连接响应 */
                rtn = UnpackConnectResp(pkg);
                printf("[%d]recv connect resp, rtn: %d\n", __LINE__, rtn);
                break;
            case PUSHDATA:																//转发(透传)数据 (双向)
                /* 解析EDP包 - 数据转发 */
                UnpackPushdata(pkg, &src_devid, &push_data, &push_datalen);
                printf("recv push data, src_devid: %s, push_data: %s, len: %d\n",
                       src_devid, push_data, push_datalen);
                free(src_devid);
                free(push_data);
                break;
            case UPDATERESP:															//平台下发当前最新的软件信息 (client to server)
                UnpackUpdateResp(pkg, &up_info);
                while (up_info){
                    printf("name = %s\n", up_info->name);
                    printf("version = %s\n", up_info->version);
                    printf("url = %s\nmd5 = ", up_info->url);
                    for (i=0; i<32; ++i){
                        printf("%c", (char)up_info->md5[i]);
                    }
                    printf("\n");
                    up_info = up_info->next;
                }
                FreeUpdateInfolist(up_info);
                break;

            case SAVEDATA:																//存储(转发)数据 (双向)
                /* 解析EDP包 - 数据存储 */
                if (UnpackSavedata(pkg, &src_devid, &jsonorbin) == 0)
                {
                    if (jsonorbin == kTypeFullJson
                        || jsonorbin == kTypeSimpleJsonWithoutTime
                        || jsonorbin == kTypeSimpleJsonWithTime)
                    {
                        printf("[%d]json type is %d\n", __LINE__, jsonorbin);
                        /* 解析EDP包 - json数据存储 */
                        /* UnpackSavedataJson(pkg, &save_json); */
                        /* save_json_str=cJSON_Print(save_json); */
                        /* printf("recv save data json, src_devid: %s, json: %s\n", */
                        /*     src_devid, save_json_str); */
                        /* free(save_json_str); */
                        /* cJSON_Delete(save_json); */

                        /* UnpackSavedataInt(jsonorbin, pkg, &ds_id, &iValue); */
                        /* printf("ds_id = %s\nvalue= %d\n", ds_id, iValue); */

                        UnpackSavedataDouble(jsonorbin, pkg, &ds_id, &dValue);
                        printf("[%d]ds_id = %s\nvalue = %f\n", __LINE__, ds_id, dValue);

                        /* UnpackSavedataString(jsonorbin, pkg, &ds_id, &cValue); */
                        /* printf("ds_id = %s\nvalue = %s\n", ds_id, cValue); */
                        /* free(cValue); */

                        free(ds_id);
				
                    }
                    else if (jsonorbin == kTypeBin)
                    {/* 解析EDP包 - bin数据存储 */
                        UnpackSavedataBin(pkg, &desc_json, (uint8**)&save_bin, &save_binlen);
                        desc_json_str=cJSON_Print(desc_json);
                        printf("recv save data bin, src_devid: %s, desc json: %s, bin: %s, binlen: %d\n",
                               src_devid, desc_json_str, save_bin, save_binlen);
                        free(desc_json_str);
                        cJSON_Delete(desc_json);
                        free(save_bin);
                    }
                    else if (jsonorbin == kTypeString ){
                        UnpackSavedataSimpleString(pkg, &simple_str);
			    
                        printf("%s\n", simple_str);
                        free(simple_str);
                    }else if (jsonorbin == kTypeStringWithTime){
						UnpackSavedataSimpleStringWithTime(pkg, &simple_str, &stTime);
			    
                        printf("time:%u-%02d-%02d %02d-%02d-%02d\nstr val:%s\n", 
							stTime.year, stTime.month, stTime.day, stTime.hour, stTime.minute, stTime.second, simple_str);
                        free(simple_str);
					}else if (jsonorbin == kTypeFloatWithTime){
                        if(UnpackSavedataFloatWithTime(pkg, &float_data, &count, &stTime)){
                            printf("UnpackSavedataFloatWithTime failed!\n");
                        }

                        printf("read time:%u-%02d-%02d %02d-%02d-%02d\n", 
                            stTime.year, stTime.month, stTime.day, stTime.hour, stTime.minute, stTime.second);
                        printf("read float data count:%d, ptr:[%p]\n", count, (FloatDPS*)float_data);
                        
                        for(i = 0; i < count; ++i){
                            printf("ds_id=%u,value=%f\n", float_data[i].ds_id, float_data[i].f_data);
                        }

                        free(float_data);
                        float_data = NULL;
                    }
                    free(src_devid);
                }else{
                    printf("error\n");
                }
                break;
            case SAVEACK:																			//存储确认(server to client)
                UnpackSavedataAck(pkg, &msg_id, &save_date_ret);
                printf("[%d]save ack, msg_id = %d, ret = %d\n", __LINE__, msg_id, save_date_ret);
                break;
            case CMDREQ:																			//云端发送命令响应(server to client)
                if (UnpackCmdReq(pkg, &cmdid, &cmdid_len, &cmd_req, &cmd_req_len) == 0)
				{
					printf("[%d]cmdid: %s, req:%s, req_len: %d\n", __LINE__, cmdid, cmd_req, cmd_req_len);
                    /*
                     * 用户按照自己的需求处理并返回,响应消息体可以为空,此处假设返回2个字符"ok"。
                     * 处理完后需要释放
                     */ 
#ifdef _NEEDRESP					 
						cmd_resp_len = strlen(cmd_resp);
						send_pkg = PacketCmdResp(cmdid, cmdid_len, cmd_resp, cmd_resp_len);
#ifdef _ENCRYPT
						if (g_is_encrypt){
							SymmEncrypt(send_pkg);
						}
#endif
						DoSend(sockfd, (const char*)send_pkg->_data, send_pkg->_write_pos);		//将接收的数据上传
						DeleteBuffer(&send_pkg);
#endif						 
		    
                    free(cmdid);
                    free(cmd_req);
                }
                break;
            case PINGRESP:																			//心跳响应 (client to server)
                /* 解析EDP包 - 心跳响应 */
                UnpackPingResp(pkg);
                printf("recv ping resp\n");
                break;

            default:
                /* 未知消息类型 */
                error = 1;
                printf("recv failed...\n");
                break;
            }
            DeleteBuffer(&pkg);
        }
    }
    DeleteBuffer(&recv_buf);

#ifdef _DEBUG
    printf("[%s(%d)] recv thread end ...\n", __func__, __LINE__);
#endif
} 

主要的代码就是这些,直接看调试结果。

上面的截图显示了连接服务器和数据发送过程的调试打印信息,首先是连接服务器成功,在main函数中创建了一个接收线程,会收到服务器发送的响应信息,与前面用调试工具打印的信息是一致的:四个字节的16进制数:20 02 00 00;然后是向服务器发送数据,在write_func函数中采用拼接字符串的方式来产生需要发送的数据,再利用cJSON_Parse函数将数据打包成JSON串,调试打印对应输出无格式JSON串,然后采用PacketSavedataJson函数将JSON串打包成公开协议EDP数据包消息类型为Savedata。

在while代码块中屏蔽发送相关的语句,代码实现的功能就是连接服务器,然后就是等待服务器向设备发送命令:


注意上面打印信息的[490]这一行,这就是接收到服务器的命令信息,与上面的调试工具是类似的。记得前面还有一个问题,在应用中发送命令时这个EDP的命令内容到底表示什么含义?

根据这里的打印信息可以看到,命令请求是“{setting}22”,我是应用中的旋钮旋转到22的值,还记得在应用中旋钮设置的命令内容吗?我的设置是“{setting}{V}”,在介绍中说{V}表示通配符。通过这个打印信息,那里的命令内容就很显然了,因此,我们在设备端只要解析这个命令字符串就可以实现自己想要操作的设备功能。

在应用中还有一个控件专门用来输入命令的:

同样的,先绑定设备test,下发命令“Hello”,在设备端会接收到消息如下:

打印的命令请求正是“Hello”,至此,设备与OneNET连接和数据传输基本功能基本上是实现了,接下来就是真正的可以在项目中运用了。







  • 0
    点赞
  • 103
    收藏
    觉得还不错? 一键收藏
  • 7
    评论
以下是使用EDP协议连接Onenet云平台的封装代码示例: ```python import socket import struct import json class OnenetClient: def __init__(self, device_id, api_key): self.device_id = device_id self.api_key = api_key self.host = '183.230.40.39' self.port = 876 self.sock = None def connect(self): self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.sock.connect((self.host, self.port)) def disconnect(self): if self.sock: self.sock.close() self.sock = None def send(self, data): if self.sock: self.sock.send(data) def receive(self): if self.sock: return self.sock.recv(1024) def login(self): timestamp = int(time.time()) nonce = random.randint(0, 65535) version = 0 sign_method = 'sha1' data = { 'auth_info': { 'device_id': self.device_id, 'timestamp': timestamp, 'nonce': nonce, 'sign_method': sign_method } } data_json = json.dumps(data) sign = hmac.new(self.api_key.encode(), data_json.encode(), hashlib.sha1).hexdigest() packet = struct.pack('!BBH', version, len(data_json), 0) + data_json.encode() + sign.encode() self.send(packet) response = self.receive() print(response) def upload(self, data): timestamp = int(time.time()) nonce = random.randint(0, 65535) data_type = 1 data = { 'datastreams': [ { 'id': 'data', 'datapoints': [ { 'at': timestamp, 'value': data } ] } ] } data_json = json.dumps(data) packet = struct.pack('!BBH', 0, len(data_json), data_type) + data_json.encode() self.send(packet) response = self.receive() print(response) ``` 使用示例: ```python client = OnenetClient(device_id='your_device_id', api_key='your_api_key') client.connect() client.login() client.upload('your_data') client.disconnect() ``` 其中,`device_id` 和 `api_key` 分别是你在Onenet云平台上创建的设备ID和API Key。`connect` 方法用于连接Onenet云平台服务器,`disconnect` 方法用于断开连接。`login` 方法用于登录,`upload` 方法用于上传数据。在 `upload` 方法中,你需要将要上传的数据作为参数传入。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值