STM32玩转物联网实战篇:3.1.ESP8266 WIFI模块WEBClient通信示例详解GET、POST(心知天气、Onenet)
1、准备开发板开发板功能区分布图开发板俯视图2、HTTP协议介绍HTTP协议简介 HTTP(HyperText Transfer Protocol)协议,即超文本传输协议,是一种用于分布式、协作式和超媒体信息系统的应用层协议。HTTP 协议是基于 TCP/IP 协议的网络应用层协议。 HTTP是一个客户端终端(用户)和服务器端(网站)请求和应答的标准(TCP)。通过使用网页浏览器、网络
·
1、准备开发板
开发板功能区分布图
开发板俯视图
2、HTTP协议介绍
HTTP协议简介
HTTP(HyperText Transfer Protocol)协议,即超文本传输协议,是一种用于分布式、协作式和超媒体信息系统的应用层协议。HTTP 协议是基于 TCP/IP 协议的网络应用层协议。
HTTP是一个客户端终端(用户)和服务器端(网站)请求和应答的标准(TCP)。通过使用网页浏览器、网络爬虫或者其它的工具,客户端发起一个HTTP请求到服务器上指定端口(默认端口为80)。服务器接收到请求之后,通过接收到的信息判断响应方式,并且给予客户端相应的响应,完成整个 HTTP 数据交互流程。
HTTP 工作原理
HTTP 请求/响应的步骤 | |
---|---|
1. 客户端连接到Web服务器 | 一个HTTP客户端,通常是浏览器,与Web服务器的HTTP端口(默认为80)建立一个TCP套接字连接。 |
2. 发送HTTP请求 | 通过TCP套接字,客户端向Web服务器发送一个文本的请求报文,一个请求报文由请求行、请求头部、空行和数据包4部分组成。 |
3. 服务器接受请求并返回HTTP响应 | Web服务器解析请求,定位请求资源。服务器将资源复本写到TCP套接字,由客户端读取。一个响应由状态行、响应头部、空行和响应数据4部分组成。 |
4. 释放连接TCP连接 | 若connection 模式为close,则服务器主动关闭TCP连接,客户端被动关闭连接,释放TCP连接;若connection 模式为keepalive,则该连接会保持一段时间,在该时间内可以继续接收请求; |
5. 客户端浏览器解析HTML内容 | 客户端浏览器首先解析状态行,查看表明请求是否成功的状态代码。然后解析每一个响应头,响应头告知以下为若干字节的HTML文档和文档的字符集。客户端浏览器读取响应数据HTML,根据HTML的语法对其进行格式化,并在浏览器窗口中显示。 |
HTTP协议请求头Request Headers
客户端发送一个HTTP请求到服务器的请求头主要包括:请求行、请求头部、空行、数据包
如下图是一个 POST 请求的信息:
HTTP 协议响应信息 Response
HTTP 协议状态码
状态代码 | 类别 | 原因短语 |
---|---|---|
1xx | Infomational(信息性状态码) | 接收的请求正在处理 |
2xx | Success(成功状态码) | 请求正常处理完毕 |
3xx | Redirection(重定向状态码) | 需要进行附加操作以完成请求 |
4xx | Client Error(客户端错误状态码) | 服务器无法处理请求 |
5xx | Server Error(服务器错误状态码) | 服务器处理请求出错 |
3、在MDK中编写代码
WebClient客户端代码 | |
---|---|
web_strdup | 将字符串复制到新开辟的内存空间 |
webclient_session_create | 创建webclient客户端 |
webclient_header_fields_add | 将请求句柄封装到客户端缓存区 |
webclient_header_fields_get | 解析响应数据的单一消息报头 |
webclient_header_length_response | 获取请求头的长度 |
webclient_handle_response | 获取服务器响应的状态码 |
webclient_data_analysis | 获取服务器返回的数据包 |
HTTP请求方法 | |
---|---|
webclient_get_method | 客户端GET请求方法 |
webclient_post_method | 客户端POST请求方法 |
修改ESP8266.c代码中的NET_DEVICE_LinkServer_Init函数(代码在上一章)
ESP8266_RETTYPE NET_DEVICE_LinkServer_Init(void)
{
unsigned char errCount = 0, errType = 0;
char cfgBuffer[70];
switch(netDeviceInfo.initStep)
{
case 0:
if(ESP8266_Net_Mode_Choose(STA) == ESP8266_OK)
netDeviceInfo.initStep++;
break;
case 1:
if(ESP8266_Enable_MultipleId(DISABLE) == ESP8266_OK)
netDeviceInfo.initStep++;
break;
case 2:
if(ESP8266_JoinAP(netDeviceInfo.staName,netDeviceInfo.staPass) == ESP8266_OK)
netDeviceInfo.initStep++;
break;
case 3:
//注释掉这一行,因为请求的网址不同
// if(ESP8266_Link_Server(enumTCP,netDeviceInfo.staIPAddress,netDeviceInfo.staPort,Single_ID_0) == ESP8266_OK)
netDeviceInfo.initStep++;
break;
default:
netDeviceInfo.netWork = 1;
errType = 3;
break;
}
return errType;
}
在webclient.h中编写以下代码
#ifndef __WEBCLIENT_H_
#define __WEBCLIENT_H_
#include "sys.h"
#ifndef WEBCLIENT_OK
#define WEBCLIENT_OK 0
#endif
#ifndef WEBCLIENT_NOK
#define WEBCLIENT_NOK 1
#endif
typedef struct __webclient_header
{
char* buffer;
unsigned int length; /* content header buffer size */
unsigned int size; /* maximum support header size */
}webclient_header;
typedef struct __webclient_session
{
webclient_header* header; /* webclient response header information */
int resp_status;
int content_length;
}webclient_session;
void webclient_get_method(void);
void webclient_post_method(void);
#endif
在webclient.c中编写以下代码
#include "webclient.h"
#include "ESP8266.h"
#include "usart.h"
#include "StringUtil.h"
//将字符串复制到新开辟的内存空间
char* web_strdup(const char* s)
{
uint16_t len = strlen(s) + 1;
char* tmp = (char*)malloc(len);
if (!tmp)
return NULL;
memcpy(tmp, s, len);
return tmp;
}
//创建webclient客户端
webclient_session* webclient_session_create(uint16_t header_sz)
{
webclient_session* session;
/* create session */
session = (webclient_session*)calloc(1, sizeof(webclient_session));
if (session == NULL)
{
printf("webclient create failed, no memory for webclient session!");
return NULL;
}
/* initialize the socket of session */
session->content_length = -1;
session->header = (webclient_header*)calloc(1, sizeof(webclient_header));
if (session->header == NULL)
{
printf("webclient create failed, no memory for session header buffer!");
free(session->header);
free(session);
session = NULL;
return NULL;
}
session->header->size = header_sz;
session->header->buffer = (char*)calloc(1, header_sz);
if (session->header->buffer == NULL)
{
printf("webclient create failed, no memory for session header buffer!");
free(session->header);
free(session);
session = NULL;
return NULL;
}
return session;
}
//将请求句柄封装到客户端缓存区
unsigned char webclient_header_fields_add(webclient_session* session, const char* fmt, ...)
{
int length;
va_list args;
va_start(args, fmt);
length = vsnprintf(session->header->buffer + session->header->length, session->header->size - session->header->length, fmt, args);
if (length < 0)
{
printf("add fields header data failed, return length(%d) error.", length);
return WEBCLIENT_NOK;
}
va_end(args);
session->header->length += length;
if (session->header->length >= session->header->size)
{
printf("not enough header buffer size(%d)!", session->header->size);
return WEBCLIENT_NOK;
}
return WEBCLIENT_OK;
}
//解析响应数据的单一消息报头
const char* webclient_header_fields_get(webclient_session* session, const char* fields)
{
char* resp_buf = NULL;
size_t resp_buf_len = 0;
char* dataPtr;
char* mime_ptr = NULL;
resp_buf = session->header->buffer;
dataPtr = strstr(resp_buf,fields);
if(dataPtr != NULL)
{
mime_ptr = strstr(dataPtr,":");
if(mime_ptr != NULL)
{
mime_ptr += 1;
while (*mime_ptr && (*mime_ptr == ' ' || *mime_ptr == '\t'))
mime_ptr++;
return mime_ptr;
}
}
return NULL;
}
//获取请求头的长度
unsigned int webclient_header_length_response(webclient_session* session)
{
return (strlen(session->header->buffer) - session->content_length - 4);
}
//获取服务器响应的状态码
unsigned char webclient_handle_response(webclient_session* session)
{
int rc = 0;
char* mime_buffer = NULL;
char* mime_ptr = NULL;
const char* transfer_encoding;
int i;
if(webclient_header_fields_get(session, "Content-Length") != NULL)
{
session->content_length = atoi(webclient_header_fields_get(session, "Content-Length"));
}
session->header->length = webclient_header_length_response(session);
/* get HTTP status code */
mime_ptr = web_strdup(session->header->buffer);
if (strstr(mime_ptr, "HTTP/1."))
{
char* ptr = mime_ptr;
ptr += strlen("HTTP/1.x");
printf("ptr: %s\r\n", ptr);
while (*ptr && (*ptr == ' ' || *ptr == '\t'))
ptr++;
/* Terminate string after status code */
for (i = 0; ((ptr[i] != ' ') && (ptr[i] != '\t')); i++);
ptr[i] = '\0';
session->resp_status = (int)strtol(ptr, NULL, 10);
}
if (mime_ptr)
{
free(mime_ptr);
}
return session->resp_status;
}
//获取服务器返回的数据包
uint8_t* webclient_data_analysis(webclient_session* session)
{
char* dataptr;
char* mime_ptr = session->header->buffer;
//printf("session->header->buffer:%s\r\n",session->header->buffer);
dataptr = strstr(mime_ptr,"\r\n\r\n");
if(dataptr != NULL)
{
dataptr += 4;
return (uint8_t*)dataptr;
}
return NULL;
}
//客户端GET请求方法
void webclient_get_method(void)
{
webclient_session* session;
uint8_t uwRet = WEBCLIENT_NOK;
uint8_t *dataPtr;
session = webclient_session_create(RX_BUF_MAX_LEN);
if(session == NULL)
{
printf("创建客户端失败\r\n");
goto __exit;
}
// webclient_header_fields_add(session,"GET http://%s:%s/getBinFile?IMEI=868626044941824 HTTP/1.1\r\n",netDeviceInfo.staIPAddress,netDeviceInfo.staPort);
// webclient_header_fields_add(session,"\r\n");
// GET https://api.seniverse.com/v3/weather/now.json?key=smtq3n0ixdggurox&location=nanning&language=zh-Hans&unit=c HTTP/1.1\r\n
webclient_header_fields_add(session,"GET https://api.seniverse.com/v3/weather/now.json?key=smtq3n0ixdggurox&location=nanning&language=en&unit=c HTTP/1.1\r\n");
webclient_header_fields_add(session,"Host:www.baidu.com\r\n");
webclient_header_fields_add(session,"\r\n");
if(ESP8266_DisconnectServer(0) != ESP8266_OK) //断开服务器连接,
goto __exit;
if(ESP8266_Link_Server(enumTCP,"api.seniverse.com","80",Single_ID_0) == ESP8266_OK) //连接服务器
{
uwRet = ESP8266_SendData(DISABLE,(uint8_t*)session->header->buffer,session->header->length,Single_ID_0); //发送数据
if(uwRet == WEBCLIENT_OK) //发送成功
{
dataPtr = ESP8266_GetIPD(DISABLE,2000); //解析服务器返回的数据
if(dataPtr != NULL)
{
memset(session->header->buffer,0,session->header->size);
session->header->length = 0;
memcpy(session->header->buffer,dataPtr,session->header->size);
if(webclient_handle_response(session) == 200) //解析服务器返回的状态码
{
printf("strlen(session->header->buffer):%d\r\n",strlen(session->header->buffer));
printf("session->header->length:%d\r\n",session->header->length);
dataPtr = webclient_data_analysis(session); //获取服务器返回的数据
if(dataPtr != NULL)
{
if(strstr((char*)dataPtr,"+IPD,")) //查找是否有IPD头
{
dataPtr = Filter_string((char*)dataPtr,(char*)"+IPD,",(char*)":"); //过滤IPD头
if(dataPtr!=NULL)
{
printf("dataPtr:%s\r\n",dataPtr);
//printf("session->content_length:%d\r\n",session->content_length);
}
}
else
{
printf("dataPtr:%s\r\n",dataPtr);
}
}
}
}
}
}
__exit:
if(session->header->buffer != NULL)
{
free(session->header->buffer);
session->header->buffer=NULL;
}
if(session != NULL)
{
free(session);
session = NULL;
}
}
//客户端POST请求方法
void webclient_post_method(void)
{
webclient_session* session;
uint8_t uwRet = WEBCLIENT_NOK;
uint8_t *dataPtr;
session = webclient_session_create(RX_BUF_MAX_LEN);
if(session == NULL)
{
printf("创建客户端失败\r\n");
goto __exit;
}
// webclient_header_fields_add(session,"GET http://%s:%s/getBinFile?IMEI=868626044941824 HTTP/1.1\r\n",netDeviceInfo.staIPAddress,netDeviceInfo.staPort);
// webclient_header_fields_add(session,"\r\n");
// GET https://api.seniverse.com/v3/weather/now.json?key=smtq3n0ixdggurox&location=nanning&language=zh-Hans&unit=c HTTP/1.1\r\n
webclient_header_fields_add(session,"POST /devices/583402349/datapoints HTTP/1.1\r\n");
webclient_header_fields_add(session,"api-key:rBIh6FFxeyW=kVJyybB2FzD5QAQ=\r\n");
webclient_header_fields_add(session,"Host: api.heclouds.com\r\n");
webclient_header_fields_add(session,"Content-Length:66\r\n");
webclient_header_fields_add(session,"\r\n");
webclient_header_fields_add(session,"{\"datastreams\":[{\"id\":\"test_stream\",\"datapoints\":[{\"value\":30}]}]}");
if(ESP8266_DisconnectServer(0) != ESP8266_OK) //断开服务器连接
goto __exit;
if(ESP8266_Link_Server(enumTCP,"api.heclouds.com","80",Single_ID_0) == ESP8266_OK) //连接服务器
{
uwRet = ESP8266_SendData(DISABLE,(uint8_t*)session->header->buffer,session->header->length,Single_ID_0); //发送数据
if(uwRet == WEBCLIENT_OK) //发送成功
{
dataPtr = ESP8266_GetIPD(DISABLE,2000); //解析服务器返回的数据
if(dataPtr != NULL)
{
memset(session->header->buffer,0,session->header->size);
session->header->length = 0;
memcpy(session->header->buffer,dataPtr,session->header->size);
if(webclient_handle_response(session) == 200) //解析服务器返回的状态码
{
printf("strlen(session->header->buffer):%d\r\n",strlen(session->header->buffer));
printf("session->header->length:%d\r\n",session->header->length);
dataPtr = webclient_data_analysis(session); //获取服务器返回的数据
if(dataPtr != NULL)
{
if(strstr((char*)dataPtr,"+IPD,")) //查找是否有IPD头
{
dataPtr = Filter_string((char*)dataPtr,(char*)"+IPD,",(char*)":"); //过滤IPD头
if(dataPtr!=NULL)
{
printf("dataPtr:%s\r\n",dataPtr);
//printf("session->content_length:%d\r\n",session->content_length);
}
}
else
{
printf("dataPtr:%s\r\n",dataPtr);
}
}
}
}
}
}
__exit:
if(session->header->buffer != NULL)
{
free(session->header->buffer);
session->header->buffer=NULL;
}
if(session != NULL)
{
free(session);
session = NULL;
}
}
在StringUtil.h中编写以下代码
#ifndef __STRING_UTIL_h
#define __STRING_UTIL_h
#include "string.h"
#include <ctype.h>
#include <stdlib.h>
#include "usart.h"
int find_end(char * usart_buffer, int number);
int Find_string(char *pcBuf,char *left,char *right, char *pcRes);
void smart_array(unsigned char* addr,unsigned char *ip);
void Hex2Str(char *pbDest, char *pbSrc, int nLen);
unsigned char* Filter_string(char* pcBuf, char* left, char* right);
#endif
在StringUtil.c中编写以下代码
#include "StringUtil.h"
/**
*@brief ip网络地址转换
*@param adr:地址 ip:ip
*@return 无
*/
void smart_array(unsigned char* addr,unsigned char *ip)
{
int i;
char taddr[30];
char * nexttok;
char num;
strcpy(taddr,(char *)addr);
nexttok = taddr;
for(i = 0; i < 4 ; i++)
{
nexttok = strtok(nexttok,".");
// if(nexttok[0] == '0' && nexttok[1] == 'x') num = atoi16(nexttok+2,0x10);
// else num = atoi16(nexttok,10);
ip[i] = num;
nexttok = NULL;
}
}
/***********************************************************
函数名称:Find_string(char *pcBuf,char*left,char*right, char *pcRes)
函数功能:寻找特定字符串
入口参数:
char *pcBuf 为传入的字符串
char*left 为搜索字符的左边标识符 例如:"["
char*right 为搜索字符的右边标识符 例如:"]"
char *pcRes 为输出转存的字符串数组
返回值:用来校验是否成功,无所谓的。
备注: left字符需要唯一,right字符从left后面开始唯一即可
服务器下发命令举例:+MQTTPUBLISH: 0,0,0,0,/device/NB/zx99999999999999_back,6,[reastrobot]
此函数会操作内存空间 强烈建议 提前清空buf memset(Find_token,0x00,sizeof(Find_token));
***********************************************************/
int Find_string(char *pcBuf,char *left,char *right, char *pcRes)
{
char *pcBegin = NULL;
char *pcEnd = NULL;
pcBegin = strstr(pcBuf, left); //找到第一次出现的位置
pcEnd = strstr(pcBegin+strlen(left), right); //找到右边标识第一次出现位置
if(pcBegin == NULL || pcEnd == NULL || pcBegin > pcEnd)
{
printf("string name not found!\n");
return 0;
}
else
{
pcBegin += strlen(left);
memcpy(pcRes, pcBegin, pcEnd-pcBegin);
return 1;
}
}
//+IPD, 268:hello world\r\n
//过滤left和right之间的字符串,返回过滤之后的字符串
unsigned char* Filter_string(char* pcBuf, char* left, char* right)
{
char* ptrIPD;
ptrIPD = strstr((char*)pcBuf, left); //搜索“left”头
if (ptrIPD == NULL)
{
// UsartPrintf(USART_DEBUG, "\"left\" not found\r\n");
}
else
{
ptrIPD = strstr(ptrIPD, right); //找到'right'
if (ptrIPD != NULL)
{
ptrIPD+=strlen(right);
return (unsigned char*)(ptrIPD);
}
else
return NULL;
}
}
//int Smart_array(char *pcBuf,char *fu)
//{
//memcpy(&citc_server_ip[0],back_ip,strstr(back_ip,".")-back_ip);
// char *pcBegin = NULL;
// int8_t len =0;
// pcBegin = strstr(pcBuf, fu);//找到第一次出现的位置
// memcpy(&pcRes[i], pcBuf,pcBegin-pcBuf);
// }
// len= strlen(pcBuf);
// pcBegin = strstr(pcBuf, fu);//找到第一次出现的位置
// memcpy(&pcRes[0], pcBuf,pcBegin-pcBuf);
//}
//寻找字符后面最近的结束符
//int find_end(char * usart_buffer, int number) {
// int i;
// for (i = 0; i < 100; i++) {
// if ((usart_buffer[number + i] == '/') && (usart_buffer[number + i + 1] == '>')) {
// return number + i+2 ; //如果有换行符 需要加4
// }
// }
//}
//智能查找和匹配字符串
int8_t Find_AttributeStringAll(char *pcBuf, char *pcRest,uint16_t row,uint8_t col, char *left, char *right,uint8_t *Num)
{
char pcFind[20] = {0};
char *pcBegin = NULL;
char *pcEnd = NULL;
pcEnd = pcBuf;
while(1)
{
memset(pcFind,0,20);
pcBegin = strstr(pcEnd+1, left);
if((pcBegin == NULL) || (strcmp(pcBegin,"}") == 0))
{
printf("找到指定字符 !\n");
return 1;
}
else
{
pcEnd = strstr(pcBegin+strlen(left), right);
if ((pcEnd == NULL) || (pcBegin > pcEnd))
{
printf("Mail name not found!\n");
return -1;
}
else
{
if(*Num>row)
{
return 1;
}
else
{
pcBegin += strlen(left);
memcpy(pcFind, pcBegin, pcEnd - pcBegin);
memset(pcRest+*Num*col,0,col);
memcpy(pcRest+*Num*col,pcFind,sizeof(pcFind));
(*Num) ++;
}
}
}
}
}
void Hex2Str(char *pbDest, char *pbSrc, int nLen)
{
char ddl,ddh;
int i;
for (i=0; i<nLen; i++)
{
ddh = 48 + pbSrc[i] / 16;
ddl = 48 + pbSrc[i] % 16;
if (ddh > 57) ddh = ddh + 7;
if (ddl > 57) ddl = ddl + 7;
pbDest[i*2] = ddh;
pbDest[i*2+1] = ddl;
}
pbDest[nLen*2] = '\0';
}
void Str2Hex(char* str, char* hex)
{
const char* cHex = "0123456789ABCDEF";
int i=0;
for(int j =0; j < strlen(str); j++)
{
unsigned int a = (unsigned int) str[j];
hex[i++] = cHex[(a & 0xf0) >> 4];
hex[i++] = cHex[(a & 0x0f)];
}
hex[i] = '\0';
}
在main.c中编写以下代码
/* USER CODE BEGIN 0 */
#define REQUEST_METHOD 0 //0:GET请求,1:POST请求
/* USER CODE END 0 */
int main(void)
{
/* USER CODE BEGIN 1 */
unsigned char* dataPtr = NULL;
uint32_t request_time = 0;
uint32_t netErr_time = 0;
ESP8266_RETTYPE uwRet = ESP8266_NOK;
uint32_t total_len=0;
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_DMA_Init();
MX_LPUART1_UART_Init();
MX_USART1_UART_Init();
MX_TIM2_Init();
/* USER CODE BEGIN 2 */
TIM_Interupt_Enable(); //使能串口中断
USART_Interupt_Enable(); //使能定时器中断
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
if(!netDeviceInfo.netWork) //如果网络未连接
{
if(NET_DEVICE_Init() == ESP8266_OK)
{
printf("初始化成功\r\n");
}
}
if(time2Count - request_time >= 10000) //(1ms * 2000)相当于延时2秒钟
{
if(netDeviceInfo.netWork)//如果网络连接成功
{
#if REQUEST_METHOD
webclient_get_method();
#else
webclient_post_method();
#endif
}
}
}
/* USER CODE END 3 */
}
4、实验现象
实现的功能 |
---|
1、上电自动连接WIFI |
2、如果是POST请求,则请求POST接口(我这里用的是ONENET的POST接口) |
3、如果是GET请求,则请求GET接口(我这里用的是心知天气的GET接口) |
POST请求(Onenet物联网平台)
GET请求(心知天气)
更多推荐
已为社区贡献2条内容
所有评论(0)