第十九章 ESP32 HTTP获取城市温度

 

学习目的及目标

  1. 掌握HTTP原理和工作过程
  2. 掌握乐鑫ESP32HTTP获取服务器温度的程序设计

HTTP原理

HTTP是一套计算机网络通讯规则。下面只讲下请求格式,其他原理

HTTP请求格式

  HTTP请求是客户端往服务端发送请求动作,告知服务器自己的要求。其中信息由三部分组成:

  1. 请求方法,URI协议/版本:包括请求方式Method、资源路径URL、协议版本Version
  2. 请求头:包括一些访问的域名、用户代理、Cookie等信息
  3. 请求正文:就是HTTP请求的数据

备注:请求方式Method一般有GET、POST、PUT、DELETE,含义分别是获取、修改、上传、删除,其中GET方式仅仅为获取服务器资源,方式较为简单,因此在请求方式为GET的HTTP请求数据中,请求正文部分可以省略,直接将想要获取的资源添加到URL中。下图所示就是GET的请求,没有请求正文。详细的说明在下边。

现在大多数协议版本为http/1.1

1

2

3

4

5

6

7

8

9

GET/sample.jspHTTP/1.1

Accept:image/gif.image/jpeg,*/*

Accept-Language:zh-cn

Connection:Keep-Alive

Host:localhost

User-Agent:Mozila/4.0(compatible;MSIE5.01;Window NT5.0)

Accept-Encoding:gzip,deflate

 

username=jinqiao&password=1234

 

服务器收到了客户端发来的HTTP请求后,根据HTTP请求中的动作要求,服务端做出具体的动作,将结果回应给客户端,称为HTTP响应。数据主要由三部分组成:HTTP 应答格式

  1. 协议状态:包括协议版本Version、状态码Status Code、回应短语
  2. 响应头:包括搭建服务器的软件,发送响应的时间,回应数据的格式等信息
  3. 响应正文:就是响应的具体数据

1

2

3

4

5

6

HTTP/1.1 200 OK

Server:Apache Tomcat/5.0.12

Date:Mon,6Oct2003 13:23:42 GMT

Content-Length:112

 

data(返回数据)

备注:我们主要关心并且能够在客户端浏览器看得到的是三位数的状态码,不同的状态

码代表不同的含义,其中

1xx

表示HTTP请求已经接受,继续处理请求

2xx

表示HTTP请求已经处理完成

3xx

表示把请求访问的URL重定向到其他目录

4xx

表示客户端出现错误

5xx

表示服务端出现错误

常见状态码的含义

200---OK/请求已经正常处理完毕

301---/请求永久重定向

302---/请求临时重定向

304---/请求被重定向到客户端本地缓存

400---/客户端请求存在语法错误

401---/客户端请求没有经过授权

403---/客户端的请求被服务器拒绝,一般为客户端没有访问权限

404---/客户端请求的URL在服务端不存在

500---/服务端永久错误

503---/服务端发生临时错误

HTTP报文格式(原文

HTTP报文是HTTP应用程序之间传输的数据块,HTTP报文分为HTTP请求报文和HTTP响应报文,但是无论哪种报文,他的整体格式是类似的,大致都是由起始、首部、主体三部分组成,起始说明报文的动作,首部说明报文的属性,主体则是报文的数据。接下来具体说明。

HTTP请求报文

请求报文的起始由请求行构成(有些资料称为状态行,名字不一样而已,都是指的一个东西),用来说明该请求想要做什么,由<Method>、<URL>、<Version> 三个字段组成,注意每个字段之间都有一个空格。

其中<Method>字段有不同的值:

GET   --- 访问服务器的资源

POST  --- 向服务器发送要修改的数据

HEAD  --- 获取服务器文档的首部

PUT   --- 向服务器上传资源

DELETE--- 删除服务器的资源

<URL>字段表示服务器的资源目录定位

<Version>字段表示使用的http协议版本

首部部分由多个请求头(也叫首部行)构成,那些首部字段名有如下,不全:

Accept     指定客户端能够接收的内容格式类型

Accept-Language 指定客户端能够接受的语言类型

Accept-Ecoding  指定客户端能够接受的编码类型

User-Agent      用户代理,向服务器说明自己的操作系统、浏览器等信息

Connection      是否开启持久连接(keepalive)

Host            服务器域名

...

主体部分就是报文的具体数据。       

HTTP响应报文

 

响应报文的起始由状态行构成,用来说明服务器做了什么,由<Version>、<Status-Code>、<Phrase>三个字段组成,同样的每个字段之间留有空格;

<Status-Code> 上边已经说明; 

首部由多个响应头(也叫首部行)组成, 首部字段名如下,不全:

Server    服务器软件名,Apache/Nginx

Date      服务器发出响应报文的时间

Last-Modified   请求资源的最后的修改时间

...

主体部分是响应报文的具体数据。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

{

  "results": [{

    "location": {

      "id": "WX4FBXXFKE4F",

      "name": "北京",

      "country": "CN",

      "path": "北京,北京,中国",

      "timezone": "Asia/Shanghai",

      "timezone_offset": "+08:00"

    },

    "daily": [{                         //返回指定days天数的结果

      "date": "2015-09-20",             //日期

      "text_day": "多云",               //白天天气现象文字

      "code_day": "4",                  //白天天气现象代码

      "text_night": "晴",               //晚间天气现象文字

      "code_night": "0",                //晚间天气现象代码

      "high": "26",                     //当天最高温度

      "low": "17",                      //当天最低温度

      "precip": "0",                    //降水概率,范围0~100,单位百分比

      "wind_direction": "",             //风向文字

      "wind_direction_degree": "255",   //风向角度,范围0~360

      "wind_speed": "9.66",             //风速,单位km/h(当unit=c时)、mph(当unit=f时)

      "wind_scale": ""                  //风力等级

    }, {

      "date": "2015-09-21",

      "text_day": "晴",

      "code_day": "0",

      "text_night": "晴",

      "code_night": "0",

      "high": "27",

      "low": "17",

      "precip": "0",

      "wind_direction": "",

      "wind_direction_degree": "157",

      "wind_speed": "17.7",

      "wind_scale": "3"

    }, {

      ...                               //更多返回结果

    }],

    "last_update": "2015-09-20T18:00:00+08:00" //数据更新时间(该城市的本地时间)

  }]

}

JSON解析

此处HTTP获取城市的温度是访问心知天气的服务器,心知天气返回的数据是json格式,那么我们就需要使用到第三方的开源库cJSON了。ESP32的SDK已经自带这些移植好的库了,我们只需要直接使用即可。天气预报的数据格式如下所示:

关于cjson的使用,可参考

 

软件设计

HTTP获取城市温度的主逻辑

 

 

ESP32的HTTP详细过程逻辑

 

 

ESP32的HTTP接口介绍

同TCP接口,因为此处是使用TCP数据包模拟HTTP包,完成发送和读取。

更多更详细接口请参考官方指南

 

ESP32的HTTP总结

初始化wifi配置后,程序会根据WIFI的实时状态,在回调函数中给出状态返回,所以只需要在回调中进行相关操作,STA开始事件触发SC开始进行一键配置,在SC的回调中处理SC配置过程的事件,sc完成后,WIFI连上网后,就开始了HTTP的工作过程,很简单。

 

HTTP任务编写

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

void http_get_task(void *pvParameters)

{

    const struct addrinfo hints = {

        .ai_family = AF_INET,

        .ai_socktype = SOCK_STREAM,

    };

    struct addrinfo *res;

    struct in_addr *addr;

    int s, r;

    char recv_buf[1024];

    char mid_buf[1024];

    int index;

    while(1) {

        

        //DNS域名解析

        int err = getaddrinfo(WEB_SERVER, WEB_PORT, &hints, &res);

        if(err != 0 || res == NULL) {

            ESP_LOGE(HTTP_TAG, "DNS lookup failed err=%d res=%p\r\n", err, res);

            vTaskDelay(1000 / portTICK_PERIOD_MS);

            continue;

        }

 

        //打印获取的IP

        addr = &((struct sockaddr_in *)res->ai_addr)->sin_addr;

        //ESP_LOGI(HTTP_TAG, "DNS lookup succeeded. IP=%s\r\n", inet_ntoa(*addr));

 

        //新建socket

        s = socket(res->ai_family, res->ai_socktype, 0);

        if(s < 0) {

            ESP_LOGE(HTTP_TAG, "... Failed to allocate socket.\r\n");

            close(s);

            freeaddrinfo(res);

            vTaskDelay(1000 / portTICK_PERIOD_MS);

            continue;

        }

        //连接ip

        if(connect(s, res->ai_addr, res->ai_addrlen) != 0) {

            ESP_LOGE(HTTP_TAG, "... socket connect failed errno=%d\r\n", errno);

            close(s);

            freeaddrinfo(res);

            vTaskDelay(4000 / portTICK_PERIOD_MS);

            continue;

        }

        freeaddrinfo(res);

        //发送http包

        if (write(s, REQUEST, strlen(REQUEST)) < 0) {

            ESP_LOGE(HTTP_TAG, "... socket send failed\r\n");

            close(s);

            vTaskDelay(4000 / portTICK_PERIOD_MS);

            continue;

        }

        //清缓存

        memset(mid_buf,0,sizeof(mid_buf));

        //获取http应答包

        do {

            bzero(recv_buf, sizeof(recv_buf));

            r = read(s, recv_buf, sizeof(recv_buf)-1);

            strcat(mid_buf,recv_buf);

        } while(r > 0);

        //json解析

        cjson_to_struct_info(mid_buf);

        //关闭socket,http是短连接

        close(s);

 

        //延时一会

        for(int countdown = 10; countdown >= 0; countdown--) {

            vTaskDelay(1000 / portTICK_PERIOD_MS);

        }

    }

}

 

温度数据JSON解析

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

void cjson_to_struct_info(char *text)

{

    cJSON *root,*psub;

    cJSON *arrayItem;

    //截取有效json

    char *index=strchr(text,'{');

    strcpy(text,index);

 

    root = cJSON_Parse(text);

    

    if(root!=NULL)

    {

        psub = cJSON_GetObjectItem(root, "results");

        arrayItem = cJSON_GetArrayItem(psub,0);

 

        cJSON *locat = cJSON_GetObjectItem(arrayItem, "location");

        cJSON *now = cJSON_GetObjectItem(arrayItem, "now");

        if((locat!=NULL)&&(now!=NULL))

        {

            psub=cJSON_GetObjectItem(locat,"name");

            sprintf(weathe.cit,"%s",psub->valuestring);

            ESP_LOGI(HTTP_TAG,"city:%s",weathe.cit);

 

            psub=cJSON_GetObjectItem(now,"text");

            sprintf(weathe.weather_text,"%s",psub->valuestring);

            ESP_LOGI(HTTP_TAG,"weather:%s",weathe.weather_text);

            

            psub=cJSON_GetObjectItem(now,"code");

            sprintf(weathe.weather_code,"%s",psub->valuestring);

            //ESP_LOGI(HTTP_TAG,"%s",weathe.weather_code);

 

            psub=cJSON_GetObjectItem(now,"temperature");

            sprintf(weathe.temperatur,"%s",psub->valuestring);

            ESP_LOGI(HTTP_TAG,"temperatur:%s",weathe.temperatur);

 

            //ESP_LOGI(HTTP_TAG,"--->city %s,weather %s,temperature %s<---\r\n",weathe.cit,weathe.weather_text,weathe.temperatur);

        }

    }

    //ESP_LOGI(HTTP_TAG,"%s 222",__func__);

    cJSON_Delete(root);

}

 

效果展示

测试流程

  1. SmartConfig快配账号密码
  2. 自动连服务器获取温度
  3. 串口打开即可看获取的温度

效果展示

SmartConfig配置

获取温度

 

HTTP总结

  1. 此处HTTP是使用TCP模拟的,所以过程和TCP章类似。
  2. HTTP部分参考官方的源码,sc参考官方源码。
  3. Sc没有保存密码。
  4. 源码地址:https://github.com/xiaolongba/wireless-tech

点我->更多ESP32开发指南系列目录

  • 7
    点赞
  • 53
    收藏
    觉得还不错? 一键收藏
  • 7
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值