本帖最后由 湛只为无双 于 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论坛飞鸿踏雪网友的字符串生成方法,个人感觉方法不错,并且操作起来灵活多变,尤其适合于多个传感器数据需要提交的情况。 函数的声明如下: char* Yeelink_PostString(const char *device_id,const char*sensors_id,float value);
其中第一个输入形参为设备id的字符串指针,第二个输入形参为传感器id的字符串指针,最后一个输入形参为浮点型的传感器数据值,返回参数为生成后的字符串的指针,用于被调用。 具体的函数内容如下: char* Yeelink_PostString(const char *device_id,const char *sensors_id,float value)
{
char remote_server[] = "api.yeelink.net";
char str_tmp[128] = {0};
// Http内容,表单内容
char http_content[32] = {0};
//表头的内容
sprintf(str_tmp,"/v1.0/device/%s/sensor/%s/datapoints",device_id,sensors_id);
// 确定提交内容的值 例如 {"value":20。1}
sprintf( http_content , "{\"value\":%.1f}" , value);
// 确定 HTTP请求首部
// 例如POST /v1.0/device/dbeacfc4943e593ebe2bcf46a684f794/1/1/datapoints/add HTTP/1.1\r\n
sprintf( http_request , "POST %s HTTP/1.1\r\n",str_tmp);
// 增加属性 例如 Host: api.machtalk.net\r\n
sprintf( str_tmp , "Host:%s\r\n" , remote_server);
strcat( http_request , str_tmp);
// 增加密码 例如 APIKey: dbeacfc4943e593ebe2bcf46a684f794
sprintf( str_tmp , "U-ApiKey:%s\r\n" , "dbeacfc4943e593ebe2bcf46a684f794");//需要替换为自己的APIKey
strcat( http_request , str_tmp);
//
strcat( http_request , "Accept: */*\r\n");
// 增加提交表单内容的长度 例如 Content-Length:12\r\n
sprintf( str_tmp , "Content-Length:%d\r\n" ,strlen(http_content) );
strcat( http_request , str_tmp);
// 增加表单编码格式 Content-Type:application/x-www-form-urlencoded\r\n
strcat( http_request , "Content-Type: application/x-www-form-urlencoded\r\n");
strcat( http_request , "Connection: close\r\n");
// HTTP首部和HTTP内容 分隔部分
strcat( http_request , "\r\n");
// HTTP负载内容
strcat( http_request , http_content);
return http_request;//返回生成的所需要发送的字符串指针
}
需要注意的是此函数中需要定义好自己的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发送字符串采用的是TCP的Client方式,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用于指定当前设备的工作状态,可以取值如下面的宏定义: #define Yeelink_STATUS_SEND 0 //初始状态用于初始化网络
#define Yeelink_STATUS_RECV 1 //当发送成功后进入此状态
#define Yeelink_STATUS_OVER 2 //发送成功后如果接收到数据进入此状态
#define Yeelink_POSTOK 3 //当接收的内容含有OK则为此状态
#define Yeelink_POSTERR 4 //当接收的内容含有ERROR进入此状态
然后是通过Yeelink_SendTCPC这个函数来向服务器发送数据,接收服务器返回的数据,并解析数据。 函数声明为: int32_t Yeelink_SendTCPC(uint8_t sn,char *pYeelinkStr,int16_t port);
第一个输入形参sn为所使用的W5500的SOCKET编号,取值范围为0~7,第二个输入形参为所需要发送的字符串的指针,第三个输入形参为第一个参数中SOCKET所需要对应的端口号,返回值为发送过程中的相关错误信息。 当程序上电后用于指定设备当前工作状态的变量初始值为Yeelink_STATUS_SEND,然后此时需要进行的处理如下: if(Yeelink_Status == Yeelink_STATUS_SEND)
{
switch(getSn_SR(sn))//获取SOCKET的状态
{
case SOCK_CLOSED://SOCKET最初的状态为关闭 需要配置完成后进入SOCK_INIT
printf(“%d:Start!\r\n”,sn);
if((ret=socket(sn,Sn_MR_TCP,port,Sn_MR_ND)) != sn)
return ret;
printf(“%d:Opened!\r\n”,sn);
break;
case SOCK_INIT://SOCKET进入初始化
printf(“%d:Connect,port[%d]\r\n”,sn,port);
if( (ret=connect(sn,yeelink_ip,80)) != SOCK_OK)
return ret;
break;
case SOCK_ESTABLISHED://SOCKET连接成功
if(getSn_IR(sn))
{
setSn_IR(sn,Sn_IR_CON);
}
ret = send(sn,(unsigned char *)pYeelinkStr,strlen(pYeelinkStr));//发送数据包
if(ret != strlen(pYeelinkStr))//如果发送不成功
{
printf(“%d:Socket Send Error\r\n”,sn);
close(sn);//SOCKET关闭 然后重新初始化
return ret;
}
else//如果发送成功了
{
Yeelink_Status=Yeelink_STATUS_RECV;//进入下一个状态
}//end of ret==strlen(pYeelinkStr)
break;
case SOCK_CLOSE_WAIT://SOCKET等待关闭
printf(“%d,CloseWait\r\n”,sn);
if( (ret=disconnect(sn)) != SOCK_OK ) return ret;
printf(“%d,Closed\r\n”,sn);
break;
}
}
上述程序中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,进行重新初始化。经过事实也证明了此种举措是很有必要的,在更改完程序后,进行连续工作,每天平均测试八个小时,连续两天的时间,并偶尔拔掉设备的网线进行人为破坏,发现设备均可正常工作,不出现死机的现象。 具体的程序如下: while(1)
{
retry=0;//retry的功能是为了防止持续停留在某一状态 假如停留的时间过长 则应当退出当前状态 重新进行初始化
/*注:在起初是没有retry的相关代码的 但是发现当运行半个小时后就死机了 加上后测试了8个小时工作良好*/
pString=Yeelink_PostString(“14597”,”24869”,DS18B20_Get_Temp());
do
{
Yeelink_SendTCPC(0,pString,5000);
delay_ms(10);
retry++;
}while( (Yeelink_GetStatus()<Yeelink_POSTOK) && (retry<100) );
Yeelink_SetStatus(Yeelink_STATUS_SEND);
delay_ms(10000);
}
|