1、用网络调试助手GET天气预报


1.1、设置域名和端口,建立TCP连接

域名:pi.seniverse.com,端口:80

esp8266程序读取 esp8266 get_json


1.2、GET请求

1.2.1、先了解下HTTP请求报文格式

esp8266程序读取 esp8266 get_esp8266_02

 1.2.2、分析下这个GET请求

  • 第一行:请求行
  • GET URL链接 HTTP协议版本
  • 第二、三、四、五行:请求头
  • 冒号前面是头部键,冒号后面是值,然后回车换行到下一行
  • 第六、七行:空行
  • Connection行末尾有回车换行,然后一个回车换行
GET /v3/weather/daily.json?key=rrpd2zmqkpwlsckt&location=guangzhou&language=en&unit=c&start=0&days=3 HTTP/1.1
Content-Type: text/html;charset=utf-8
Accept: */*
Host: api.seniverse.com
Connection: Keep-Alive

1.3、收到HTTP的响应

1.3.1、先看HTTP响应报文格式

esp8266程序读取 esp8266 get_esp8266_03

 1.3.2、实际收到的HTTP响应报文

esp8266程序读取 esp8266 get_http_04

2、心知天气、API的说明

上面的HTTP响应报文,人眼看是没问题,但是这收到的是json数据,我复制到一些在线解析json的网站都解析不成功,好几个地方少了大括号;不知道是不是GET的格式不对,所以自己去心知天气网站注册了账号,看看有没有什么文档说明,而且毕竟上面的GET格式我也不知道他是哪里来的,自己研究下了。

2.1、注册账号,选择免费的版本

2.2、进入文档,选择“天气预报”,可以看到右边有API接口

esp8266程序读取 esp8266 get_json_05


 2.3、查看API接口、参数说明、返回数据说明

这个接口示例就是我们用GET请求中需要的,密钥在“控制台”→“我的产品”→“免费版”,点进去,有显示公钥、私钥;

language改成en,英文显示,因为esp8266又不能解析中文;最后day指的是查询的天数,花钱可以查15天,否则最多3天,我只想只要今天而已,day=1,只查今天的就好了;

esp8266程序读取 esp8266 get_esp8266_06

 

esp8266程序读取 esp8266 get_json_07


3、关于JSON格式的错误的解决

3.1、换一个网络调试工具

对比了,与之前的GET请求格式并无不同,利用这个接口和自己的密钥再试试,果然,返回的json格式还是不对;

可能是这个网络调试助手不行,可是为什么就只有大括号没有呢,好几个大括号,而且还是在数据的中间部分,也并不是开头结尾呀,莫名其妙的,不管怎么说算换一个试试,还真就是......

esp8266程序读取 esp8266 get_HTTP_08

3.2、在线解析json

这下没问题了,可以开始下一步在ESP8266上发送GET请求并接收数据了,唉,折腾,13:31了,看看之前那个调试工具收到的HTTP响应报文还是11:44,煮饭去,肚子痛;

esp8266程序读取 esp8266 get_esp8266程序读取_09


4、用ESP8266发送GET请求、打印HTTP响应报文

调试助手里,GET请求这么写;

GET https://api.seniverse.com/v3/weather/daily.json?key=SJo8p9FiiIu6gDNz4&location=guangzhou&language=en&unit=c&start=0&days=1 HTTP/1.1
Content-Type: text/html;charset=utf-8
Accept: */*
Host: api.seniverse.com
Connection: Keep-Alive

但是作为espconn_send发送的字符串,GET请求按调试助手这么写肯定不行呀,首先要手动加入\r\n,这个HTTP请求报文写成一个宏,不然直接放在函数里,肯定不好看,一开始我是这么写的,嗯,不行,一个""括起来了是一个字符串,可是我还要换行呢,这样宏的换行就在字符串里面了,不换行看着又不清晰,百度下宏定义字符串怎么换行,没百度到不过看到有字符串里直接写\换行的,试一下先,返回HTTP/1.1 400 Bad Request,也就是请求的格式错误.....

#define GET_TODAY_WEATHER		"GET https://api.seniverse.com/v3/weather/daily.json?key=SJo8p9FiiIu6gDNz4&location=guangzhou&language=en&unit=c&start=0&days=1 HTTP/1.1\r\n\
								Content-Type: text/html;charset=utf-8\r\n			\
								Accept: */*\r\n									\
								Host: api.seniverse.com\r\n						\
								Connection: Keep-Alive\r\n\r\n"

继续查找资料吧,有发现,当字符串过长的时候,直接写多个字符串就可以了,中间以空白、制表符、换行符隔开,这个换行符就符合现在我的需要;

esp8266程序读取 esp8266 get_HTTP_10

 于是,GET请求这样写,每一行呢都加""好似一个单独的字符串,行末加换行符,果然可以了;

#define GET_TODAY_WEATHER		"GET https://api.seniverse.com/v3/weather/daily.json?key=SJo8p9FiiIu6gDNz4&location=guangzhou&language=en&unit=c&start=0&days=1 HTTP/1.1\r\n"\
								"Content-Type: text/html;charset=utf-8\r\n"			\
								"Accept: */*\r\n"									\
								"Host: api.seniverse.com\r\n"						\
								"Connection: Keep-Alive\r\n\r\n"

esp8266程序读取 esp8266 get_esp8266程序读取_11

5、json解析

先放链接为敬,分析得很深入,讲解清晰,注意其中一段代码从下往上看跟着理解,博主也很细心标注了顺序,很有帮助;

因为之前没有理解过json这个东西,所以这里记录初学粗浅的一些想法。看完上面的链接之后就可以顺利解析json了;


5.1、总结下json的格式

json是一种特定的字符串格式,本质是一个对象

对象由大括号{}表示,所以最外层是大括号括起来的。

对象由一个或多个键值对组成,键值对之间用逗号,分隔,除最后一个键值对;

键值对的格式为:"键":值,即键的名字是字符串,用冒号:分隔键与值,值可以是字符串、数字、对象、数组、null、布尔值

数组用中括号[]括起来,数组中元素可以是字符串、数字、对象、数组、null、布尔值,数组中元素用逗号分隔除最后一个;


5.2、代码及结果

代码其实并不多,而且我只解析其中需要的一些数据,比如"location"中有"id","name","country","path","timezone","timezone_offset",我只保留了"name"和"country",只需要这些信息而且看着也清晰;

逻辑上,就是用JSONTREE_OBJECT相关的宏创建一个json树,最终键值对"值"的位置指向了一个回调函数,这样在解析到这个键值对的时候就会调用这个回调函数,由于多个键值对指向同一个回调函数,所以在回调函数内部要判断当前是哪一个分支;

记得添加user_json.c和user_json.h,并#include "user_json.h";

5.2.1、创建json树

省略了一些信息,更详细的内容请见上面的链接,还有比如"daily"如果查3日数据是要弄个数组的;

LOCAL struct jsontree_callback seniverse_handle_callback =
    JSONTREE_CALLBACK(NULL, seniverse_info_set);

//seniverse_location_tree里面关于"name""country"的回调函数
JSONTREE_OBJECT(seniverse_location_tree,
		 JSONTREE_PAIR("name", &seniverse_handle_callback),
		 JSONTREE_PAIR("country", &seniverse_handle_callback)
		 );

//seniverse_daily_par_tree里面关于"data""text_day"等的回调函数
JSONTREE_OBJECT(seniverse_daily_tree,
		 JSONTREE_PAIR("date", &seniverse_handle_callback),
		 JSONTREE_PAIR("text_day", &seniverse_handle_callback),
		 JSONTREE_PAIR("text_night", &seniverse_handle_callback),
		 JSONTREE_PAIR("high", &seniverse_handle_callback),
		 JSONTREE_PAIR("low", &seniverse_handle_callback)
		 );

JSONTREE_OBJECT(seniverse_info_tree,
                JSONTREE_PAIR("location", &seniverse_location_tree),
				JSONTREE_PAIR("daily", &seniverse_daily_tree),
				JSONTREE_PAIR("last_update", &seniverse_handle_callback)
				);

5.2.2、回调函数

解析json树的时候,在"name"处,这两个判断都是成立的,也就是说jsonparse_strcmp_value还可以知道自己的上一级;

if(jsonparse_strcmp_value(parser, "location") == 0)

if (jsonparse_strcmp_value(parser, "name") == 0)

每次查到具体的键之后,都会有两个jsonparse_next,是为了跳过冒号和双引号;

jsonparse_next(parser);    jsonparse_next(parser); 

/******************************************************************************
 * FunctionName : seniverse_info_set
 * Description  : 将一个json格式解析成心知天气的参数
 * Parameters   : js_ctx -- 一个指向JSON设置的指针
 *                parser -- 指向JSON解析器状态的指针
 * Returns      : result
*******************************************************************************/
LOCAL int ICACHE_FLASH_ATTR
seniverse_info_set(struct jsontree_context *js_ctx, struct jsonparse_state *parser)
{
    int type;
    uint8 seniverse_tree=0;
    //解析 JSON 格式下⼀个元素
    while ((type = jsonparse_next(parser)) != 0){

        if (type == JSON_TYPE_PAIR_NAME) {

            char buffer[64];
            os_bzero(buffer, 64);		//buffer数组清零

			//比较解析 JSON 数据与特定字符串
            if(jsonparse_strcmp_value(parser, "location") == 0){			//location对象
            	seniverse_tree = 1;
            }else if (jsonparse_strcmp_value(parser, "daily") == 0){		//daily对象
            	seniverse_tree = 2;
            }else if (jsonparse_strcmp_value(parser, "last_update") == 0){	//last_update对象
				 jsonparse_next(parser);	jsonparse_next(parser);
				 jsonparse_copy_value(parser, buffer, sizeof(buffer));
				 os_printf("last_update:%s\n",buffer);		//数据更新时间
            }
            if(seniverse_tree == 1){						//解析location里面的信息
            	 if (jsonparse_strcmp_value(parser, "name") == 0){
                     jsonparse_next(parser);	jsonparse_next(parser);
            		 jsonparse_copy_value(parser, buffer, sizeof(buffer));
           		 os_printf("address:%s\n",buffer);		//地名:广州
            	 }else if (jsonparse_strcmp_value(parser, "country") == 0){
                     jsonparse_next(parser);	jsonparse_next(parser);
            		 jsonparse_copy_value(parser, buffer, sizeof(buffer));
            		 os_printf("country:%s\n",buffer);		//国家
            	 }
            }else if(seniverse_tree == 2){					//解析daily里面的信息
            	if (jsonparse_strcmp_value(parser, "date") == 0){
					 jsonparse_next(parser);	jsonparse_next(parser);
					 jsonparse_copy_value(parser, buffer, sizeof(buffer));
					 os_printf("date:%s\n",buffer);			//date:日期,比如2022-05-24
				 }else if (jsonparse_strcmp_value(parser, "text_day") == 0){
					 jsonparse_next(parser);	jsonparse_next(parser);
					 jsonparse_copy_value(parser, buffer, sizeof(buffer));
					 os_printf("day:%s\n",buffer);			//白天天气
				 }else if (jsonparse_strcmp_value(parser, "text_night") == 0){
					 jsonparse_next(parser);	jsonparse_next(parser);
					 jsonparse_copy_value(parser, buffer, sizeof(buffer));
					 os_printf("night:%s\n",buffer);		//夜晚天气
				 }else if (jsonparse_strcmp_value(parser, "high") == 0){
					 jsonparse_next(parser);	jsonparse_next(parser);
					 jsonparse_copy_value(parser, buffer, sizeof(buffer));
					 os_printf("high:%s\n",buffer);			//最高温度
				 }else if (jsonparse_strcmp_value(parser, "low") == 0){
					 jsonparse_next(parser);	jsonparse_next(parser);
					 jsonparse_copy_value(parser, buffer, sizeof(buffer));
					 os_printf("low:%s\n",buffer);			//最低温度
				 }
            }
        }
    }
    return 0;
}

5.2.3、TCP通信收到数据后的处理

void ICACHE_FLASH_ATTR TCP_recv_callback(void * arg, char * pdata, unsigned short len)
{
	struct espconn * T_arg = arg;		            // 缓存网络连接结构体指针
	char * pParse_json=NULL;

	os_printf("\nESP8266_Receive_Data = %s\n",pdata);// 串口打印接收到的数据

    //找到要解析的json字符串开头
    pParse_json=(char *)os_strstr(pdata, "{");		//指向"{"
    if(NULL ==  pParse_json){						//找不到"{"
    	return;
    }else{
		struct jsontree_context js;
		jsontree_setup(&js, (struct jsontree_value *)&seniverse_info_tree, json_putchar);
		json_parse(&js, pParse_json);
    }
}

5.2.4、结果

首先打印的是收到的HTTP响应报文,然后是解析完json后的数据;

  • address:Guangzhou                                         广州
  • country:CN                                                       中国
  • date:2022-05-24                                               2022年5月24日的天气
  • day:Cloudy                                                       白天多云
  • night:Thundershower                                       夜晚雷阵雨,雷阵雨原来是thundershower,看到shower只能想到洗澡~
  • high:30                                                             最高气温30℃
  • low:24                                                               最低气温24℃
  • last_update:2022-05-24T08:00+08:00             天气更新时间:2022年5月24日16:00

esp8266程序读取 esp8266 get_esp8266程序读取_12