[STM32F4] 【Nucleo设计分享】411RET6的W5500采集温度上传至Yeelink

[复制链接]
湛只为无双 发表于 2015-2-27 16:25 | 显示全部楼层 |阅读模式
本帖最后由 湛只为无双 于 2015-2-27 16:37 编辑

W5500_上传温度至Yeelink
本次设计的目的是在之前的基础上将获取到的DS18B20温度值上传到Yeelink用于在线监测,通过Yeelink这个平台,可以很方便的在在线查看温度值,以及历史记录。同样,Yeelink平台的移动客户端也有很好的支持,这样当人们在出行的时候可以随时查看数据,实现了物联网的这样一种设计方案。
具体的操作如下:
一、硬件连接简述:
本次设计中需要用到的硬件连接如下所示:
PA5作为SPI1的时钟SCK,PA6作为SPI1的主机输入从机输出MISO,PA7作为SPI1的主机输出从机输入MOSI,然后PB5用作SD卡接口的片选信号(本次设计保留,不做使用),PB6用作W5500网络接口的片选。以上是本次设计中用于网络通信的硬件连接。
DS18B20使用了PB9作为信号线,用于获取温度值。另外使用了串口2作为本次设计的调试信息的输出,使用的端口为PA2(UART2_TX)和PA3(UART2_RX)。
(在下面可以添加上述硬件的连接图,可以使用Visio进行硬件连接图的设计,并添加至此。)
二、软件工作过程简述:
首先对所需要用的端口和外设进行初始化,包括了GPIO端口,SPI1外设,UART串口。然后是对W5500进行设置和DS18B20的检测,对于W5500和DS18B20的具体操作在上一篇**中已经详细介绍了,在此就不做赘述,有需要的可以参考上一篇的帖子。
同样在初始阶段中需要对W5500中8个SOCKET的缓冲区进行初始化操作,等待PHY的连接状态打开,对W5500的MAC地址、本机IP、网关、子网掩码、DMS服务器和DHCP状态进行初始化,并设置好溢出时间值和最大重发次数(可不设置)。
根据Yeelink的API相关文档,将从DS18B20所获取到的温度值,转换为所需要发送的字符串数据,然后通过TCP的Client工作模式向Yeelink上传数据,上传数据成功后等待十秒,然后进行下一次数据的发送(Yeelink的相关平台要求了每次数据的发送间隔应该大于十秒,否则会收到错误信息)。
三、Yeelink字符串的生成过程:
本次设计中的此部分引用了EmbedNet论坛飞鸿踏雪网友的字符串生成方法,个人感觉方法不错,并且操作起来灵活多变,尤其适合于多个传感器数据需要提交的情况。
函数的声明如下:
  1. char* Yeelink_PostString(const char *device_id,const char*sensors_id,float value);
其中第一个输入形参为设备id的字符串指针,第二个输入形参为传感器id的字符串指针,最后一个输入形参为浮点型的传感器数据值,返回参数为生成后的字符串的指针,用于被调用。
具体的函数内容如下:
  1. char* Yeelink_PostString(const char *device_id,const char *sensors_id,float value)
  2. {
  3.         char remote_server[] = "api.yeelink.net";
  4.         char str_tmp[128] = {0};
  5.         // Http内容,表单内容
  6.         char http_content[32] = {0};
  7.        
  8.         //表头的内容
  9.         sprintf(str_tmp,"/v1.0/device/%s/sensor/%s/datapoints",device_id,sensors_id);
  10.         // 确定提交内容的值 例如 {"value":20。1}
  11.         sprintf( http_content , "{"value":%.1f}" , value);
  12.         // 确定 HTTP请求首部
  13.         // 例如POST /v1.0/device/dbeacfc4943e593ebe2bcf46a684f794/1/1/datapoints/add HTTP/1.1\r\n
  14.         sprintf( http_request , "POST %s HTTP/1.1\r\n",str_tmp);
  15.         // 增加属性 例如 Host: api.machtalk.net\r\n
  16.         sprintf( str_tmp , "Host:%s\r\n" , remote_server);
  17.         strcat( http_request , str_tmp);

  18.         // 增加密码 例如 APIKey: dbeacfc4943e593ebe2bcf46a684f794
  19.         sprintf( str_tmp , "U-ApiKey:%s\r\n" , "dbeacfc4943e593ebe2bcf46a684f794");//需要替换为自己的APIKey
  20.         strcat( http_request , str_tmp);
  21.         //
  22.         strcat( http_request , "Accept: */*\r\n");
  23.         // 增加提交表单内容的长度 例如 Content-Length:12\r\n
  24.         sprintf( str_tmp , "Content-Length:%d\r\n" ,strlen(http_content) );
  25.         strcat( http_request , str_tmp);
  26.         // 增加表单编码格式 Content-Type:application/x-www-form-urlencoded\r\n
  27.         strcat( http_request , "Content-Type: application/x-www-form-urlencoded\r\n");
  28.         strcat( http_request , "Connection: close\r\n");
  29.         // HTTP首部和HTTP内容 分隔部分
  30.         strcat( http_request , "\r\n");
  31.         // HTTP负载内容
  32.         strcat( http_request , http_content);
  33.        
  34.         return http_request;//返回生成的所需要发送的字符串指针
  35. }

需要注意的是此函数中需要定义好自己的APIkey,这个的获取方法见Yeelink平台的入门指导教程,里面包含了怎样申请账号,怎样创建设备,创建传感器,以及查看APIkey的具体操作步骤。
对于上面的函数,调用的过程举例:
pString=Yeelink_PostString("14597","24869",DS18B20_Get_Temp());
最后生成的pString字符型指针所指向的内容如下(示例):
POST /v1.0/device/14597/sensor/24869/datapoints HTTP/1.1
Host:api.yeelink.net
Accept:*/*
U-ApiKey:dbeacfc4943e593ebe2bcf46a684f794
Content-Length: 14
Content-Type: application/x-www-form-urlencoded
Connection: close

{“value”:12.1}
四、与Yeelink进行数据交互:
Yeelink发送字符串采用的是TCPClient方式,Yeelink作为一个服务器Service,当需要发送数据时就和服务器进行连接,发送完数据后服务器返回发送的结果,然后服务器主动断开连接。因此,通过对Yeelink.net进行Ping操作,获取对应的ip地址,如图一所示。
由图一可以得知,Yeelink的ip地址为42.96.64.52。
根据Yeelink的API相关文档可以得到TCP的Service服务器端口为80端口,所以设计中需要将之前生成的字符串通过TCP的Client方式向42.96.64.52:80端口发送过去,并对返回的数据进行分析,查看是否发送成功。
通过TCP的Client方式连接服务器的具体操作过程如下:
首先定义的一个变量Yeelink_Status用于指定当前设备的工作状态,可以取值如下面的宏定义:
  1. #define Yeelink_STATUS_SEND 0     //初始状态用于初始化网络
  2. #define Yeelink_STATUS_RECV 1     //当发送成功后进入此状态
  3. #define Yeelink_STATUS_OVER 2     //发送成功后如果接收到数据进入此状态
  4. #define Yeelink_POSTOK           3       //当接收的内容含有OK则为此状态
  5. #define Yeelink_POSTERR        4       //当接收的内容含有ERROR进入此状态
然后是通过Yeelink_SendTCPC这个函数来向服务器发送数据,接收服务器返回的数据,并解析数据。
函数声明为:
  1. int32_t Yeelink_SendTCPC(uint8_t sn,char *pYeelinkStr,int16_t port);
第一个输入形参sn为所使用的W5500的SOCKET编号,取值范围为0~7,第二个输入形参为所需要发送的字符串的指针,第三个输入形参为第一个参数中SOCKET所需要对应的端口号,返回值为发送过程中的相关错误信息。
当程序上电后用于指定设备当前工作状态的变量初始值为Yeelink_STATUS_SEND,然后此时需要进行的处理如下:
  1. if(Yeelink_Status == Yeelink_STATUS_SEND)
  2. {
  3.         switch(getSn_SR(sn))//获取SOCKET的状态
  4.         {
  5.         case SOCK_CLOSED://SOCKET最初的状态为关闭 需要配置完成后进入SOCK_INIT
  6.                 printf(“%d:Start!\r\n”,sn);
  7.                 if((ret=socket(sn,Sn_MR_TCP,port,Sn_MR_ND)) != sn)
  8.                         return ret;
  9.                 printf(“%d:Opened!\r\n”,sn);
  10.                 break;
  11.         case SOCK_INIT://SOCKET进入初始化
  12.                 printf(“%d:Connect,port[%d]\r\n”,sn,port);
  13.                 if( (ret=connect(sn,yeelink_ip,80)) != SOCK_OK)
  14.                         return ret;
  15.                 break;
  16.         case SOCK_ESTABLISHED://SOCKET连接成功
  17.                 if(getSn_IR(sn))
  18.                 {
  19.                         setSn_IR(sn,Sn_IR_CON);
  20.                 }
  21.                
  22.                 ret = send(sn,(unsigned char *)pYeelinkStr,strlen(pYeelinkStr));//发送数据包
  23.                 if(ret != strlen(pYeelinkStr))//如果发送不成功
  24.                 {
  25.                         printf(“%d:Socket Send Error\r\n”,sn);
  26.                         close(sn);//SOCKET关闭 然后重新初始化
  27.                         return ret;
  28.                 }
  29.                 else//如果发送成功了
  30.                 {
  31.                         Yeelink_Status=Yeelink_STATUS_RECV;//进入下一个状态
  32.                 }//end of ret==strlen(pYeelinkStr)
  33.                 break;
  34.         case SOCK_CLOSE_WAIT://SOCKET等待关闭
  35.                 printf(“%d,CloseWait\r\n”,sn);
  36.                 if( (ret=disconnect(sn)) != SOCK_OK ) return ret;
  37.                 printf(“%d,Closed\r\n”,sn);
  38.                 break;
  39.         }
  40. }

上述程序中SOCKET的初始状态为SOCK_CLOSED,此时需要对SOCKET的工作模式进行配置,配置为TCP模式,端口号为函数给出的端口。如果配置成功,再次获取SOCKET状态将会变为SOCK_INIT初始化模式,在这个模式下就需要设定目标服务器的IP和端口号,并尝试连接,调用的函数为connect。如果连接成功,获取到的SOCKET状态将会是SOCK_ESTABLISHED,表明已经与目标服务器连接成功了,接着就是需要发送之前生成带有数据的字符串,调用的函数为send,并检查是否发送成功。如果发送失败,关闭SOCKET,进行重新连接并发送;如果发送成功,这时候用于指定当前工作状态的变量将会变为Yeelink_STATUS_RECV,进入到了接收服务器返回数据的状态。在接收状态下,循环检测SOCKET的接收状态寄存器,当有数据到来后,就调用recv函数读取SOCKET缓冲区中接收到的数据,并将用于指定当前状态的变量值设定为Yeelink_STATUS_OVER。在接收完成后,需要解析数据中是否带有OK字符,根据是否带有OK来指定当前状态变量为Yeelink_POSTOK还是Yeelink_POSTERR,如果为Yeelink_POSTERR会把接收到的数据打印出来,查看错误发送的原因,最后关闭SOCKET。
需要注意的是,如果按照以上的工作过程,在一切连接良好的过程中,工作起来是没有问题的,但是实际的工作网络中,或多或少的存在一些干扰,导致正常的工作流程被打断,这样就有可能导致系统一直处于某一种状态无法进行后续的工作,也就是导致了设备的死机。例如,由于网络干扰的原因,在接收服务器返回的数据的过程中,没能到达设备,那么设备将会一直处于接收数据的状态,一直循环等待数据的带来,这样很显然是不科学的。在实际测试的过程中,设备工作大约半个小时后就由于某种原因停止了数据的采集,因此,思考良久,添加了防止处于某一状态循环的程序。
添加的程序的本质在于如果连续处于某一状态超过100次后,每次大约10ms,总共一秒的时间,那么自动退出循环,并强制设置当前工作状态为Yeelink_STATUS_SEND,进行重新初始化。经过事实也证明了此种举措是很有必要的,在更改完程序后,进行连续工作,每天平均测试八个小时,连续两天的时间,并偶尔拔掉设备的网线进行人为破坏,发现设备均可正常工作,不出现死机的现象。
具体的程序如下:
  1. while(1)
  2. {
  3.         retry=0;//retry的功能是为了防止持续停留在某一状态 假如停留的时间过长 则应当退出当前状态 重新进行初始化
  4.         /*注:在起初是没有retry的相关代码的 但是发现当运行半个小时后就死机了 加上后测试了8个小时工作良好*/
  5.         pString=Yeelink_PostString(“14597”,”24869”,DS18B20_Get_Temp());
  6.         do
  7.         {
  8.                 Yeelink_SendTCPC(0,pString,5000);
  9.                 delay_ms(10);
  10.                 retry++;
  11.         }while( (Yeelink_GetStatus()<Yeelink_POSTOK) && (retry<100) );
  12.         Yeelink_SetStatus(Yeelink_STATUS_SEND);
  13.         delay_ms(10000);
  14. }

 楼主| 湛只为无双 发表于 2015-2-27 16:26 | 显示全部楼层
本帖最后由 湛只为无双 于 2015-2-27 16:43 编辑

五、实验现象和数据说明:
如图二所示,为上电后数据连接正常,串口向电脑打印出来的信息。由返回的信息可以知道在上电的过程中,首先是打印出配置好的MAC地址、本机IP、网关、子网掩码和DNS服务器,并标明网络状况良好。紧接着就是SOCKET0的开始、打开、连接,以及最后的响应OK。这就是一个完整的温度数据上传过程,剩下的就是循环进行SOCKET的开始、打开、连接和响应。
如果在上传的过程中拔掉网线,串口返回的信息如图三所示。可以看到,当断开网络后,对于SOCKET0而言,不断的开始、打开和连接,连接无效后再次进行开始、打开和连接,周而复始。
如果在断开网线后再次把网线插上,就可以看到服务器的响应是OK的,如图四所示。这样表明了数据的上传是完整的,重新恢复了原来的连接。
然后登陆Yeelink网页,查看上传的温度数据,在上传温度的过程中,同样用手给传感器进行加热,查看温度的变化,如图五和图六所示。
最后是连续时间测试,本次给大家分享一个下午的时间内,从两点到五点半的室内温度测试结果图,可以看出,室内的温度变化可真快呀。

图一通过ping来获取Yeelink的IP地址

图一通过ping来获取Yeelink的IP地址

图二 上电后串口返回的信息

图二 上电后串口返回的信息

图三 连接过程中拔去网线

图三 连接过程中拔去网线

图四 重新插上网线后工作正常

图四 重新插上网线后工作正常

图五 Yeelink的温度显示一

图五 Yeelink的温度显示一

图六 Yeelink的温度显示二

图六 Yeelink的温度显示二
 楼主| 湛只为无双 发表于 2015-2-27 16:26 | 显示全部楼层
本帖最后由 湛只为无双 于 2015-2-27 16:59 编辑

老规矩这一层上传相关的源代码和资料
以及实物图
1.jpg
2.jpg
3.jpg
4.jpg
5.jpg
6.jpg

Nucleo411_W5500_TCPC_Yeelink.zip

506.18 KB, 下载次数: 60

源代码

 楼主| 湛只为无双 发表于 2015-2-27 16:26 | 显示全部楼层
本帖最后由 湛只为无双 于 2015-2-27 16:53 编辑

这一层上传室内温度连续变化图,供希望看的网友,以压缩包的形式,以免手机党费流量。

pic.zip

230.02 KB, 下载次数: 15

某天下午的室内温度变化

21ic-09 发表于 2015-2-27 18:26 | 显示全部楼层
:handshake
 楼主| 湛只为无双 发表于 2015-2-27 22:30 | 显示全部楼层
zh113214 发表于 2015-2-28 15:17 | 显示全部楼层
这个看上去也得用到传感器吧
小浣熊 发表于 2015-2-28 15:46 | 显示全部楼层
不错,我也想知道如何设计。。。
 楼主| 湛只为无双 发表于 2015-2-28 15:49 来自手机 | 显示全部楼层
zh113214 发表于 2015-2-28 15:17
这个看上去也得用到传感器吧

是的,加了个温度传感器,你可以通过实物图看到,上面还有个小东西呢!
 楼主| 湛只为无双 发表于 2015-2-28 15:50 来自手机 | 显示全部楼层
小浣熊 发表于 2015-2-28 15:46
不错,我也想知道如何设计。。。

只要走了硬件就可以做出来的,硬件怎么连上面已经给出来了,然后软件代码也公布出来了,你可以照着做就是了。
zh113214 发表于 2015-2-28 21:44 | 显示全部楼层
湛只为无双 发表于 2015-2-28 15:49
是的,加了个温度传感器,你可以通过实物图看到,上面还有个小东西呢! ...

恩 和我想的差不多
zh113214 发表于 2015-3-5 21:11 | 显示全部楼层
湛只为无双 发表于 2015-2-28 15:49
是的,加了个温度传感器,你可以通过实物图看到,上面还有个小东西呢! ...

恩 是的啊
meselfly 发表于 2015-3-22 17:03 | 显示全部楼层
请问,设备id 和传感器id的实参在哪里
 楼主| 湛只为无双 发表于 2015-3-22 19:54 来自手机 | 显示全部楼层
你可以看下工程文件,里面有主函数,这个是在主函数里面被调用的。
沉默胜过白金 发表于 2015-4-5 15:49 | 显示全部楼层
好帖子,借鉴下。
ljl342301 发表于 2015-5-25 21:45 | 显示全部楼层
最近正需要,非常感谢
您需要登录后才可以回帖 登录 | 注册

本版积分规则

15

主题

171

帖子

9

粉丝
快速回复 在线客服 返回列表 返回顶部

15

主题

171

帖子

9

粉丝
快速回复 在线客服 返回列表 返回顶部