上一节通过HTTP的GET操作,我们获取了天气相关的信息。但相关信息是以JSON格式发下来的,所以需要对数据进行解析。解析之前先明确几点:



基于idf的esp32获取心知天气信息 esp8266天气_基于idf的esp32获取心知天气信息

1、JSON是一种数据格式,不是通信协议。有专门的解析函数,但如果懒得用,可以用特定的方法获取想要的数据。

2、解析数据的前提是了解数据的结构,而不是说对一组结构陌生的数据进行解析。

以上一节得到的数据为例:

{"results":[{"location":{"id":"WX4BZD6KEJFY","name":"Langfang","country":"CN","path":"Langfang,Langfang,Hebei,China","timezone":"Asia/Shanghai","timezone_offset":"+08:00"},"now":{"text":"Overcast","code":"9","temperature":"20"},"last_update":"2019-09-19T20:15:00+08:00"}]}

这样看有点乱,整理一下:

{"results": [{"location": {"id": "WX4BZD6KEJFY","name": "Langfang","country": "CN","path": "Langfang,Langfang,Hebei,China","timezone": "Asia/Shanghai","timezone_offset": "+08:00"},"now": {"text": "Overcast","code": "9","temperature": "20"},"last_update": "2019-09-19T20:15:00+08:00"}]}

这样是不是整齐多了?

你会发现,这组数据貌似有个名字:results,它由三部分组成:location,now和last_update。也就是位置、当前天气和最后更新时间。剩下就是一些标点,可以最后考虑。

以“"id": "WX4BZD6KEJFY"”为例,前面的是“键”,后面的是“值”,合在一起,叫键值对。

可以看到,温度是27度。但程序没有眼睛,不能直接看,怎么办呢?偷懒的方式:从第一个符号“{”出现,计算数字“27”出现的位置,只要你的API链接不变,网站提供的数据格式不更新,一般来说也能行。

但这个方法有点low,所以,还是用JSON函数解析吧。

首先说一下,官方的SDK里面(例程IoT_Demo)提供了JSON相关的库,可以直接使用,所以这里的讲解主要参照官方,顺便百度了一下,如果有说的不对的地方,欢迎指正。

代码不少,但重复的比较多,所以理解起来不是很难。大致分6步:

1添加头文件和相关源文件


这个简单,主要是user_json.c和user_json.h,放到指定位置。不会的,看我工程文件。

2更新JSON数据


第二步就能获取数据了?

其实不是,因为数据的解析是一个整体的操作,这只是其中的一个环节。先看一下SDK中的例程文件user_webserver.c:

LOCAL int ICACHE_FLASH_ATTRlight_status_get(struct jsontree_context *js_ctx){    const char *path = jsontree_path_name(js_ctx, js_ctx->depth - 1);    if (os_strncmp(path, "red", 3) == 0) {        jsontree_write_int(js_ctx, user_light_get_duty(LIGHT_RED));    } else if (os_strncmp(path, "green", 5) == 0) {        jsontree_write_int(js_ctx, user_light_get_duty(LIGHT_GREEN));    } else if (os_strncmp(path, "blue", 4) == 0) {        jsontree_write_int(js_ctx, user_light_get_duty(LIGHT_BLUE));    }..........    return 0;}

因为篇幅原因,去掉了一些代码。从函数名字可以看出来,它有一个关键词“get”,也就是“获取”。所以,这个函数的功能是把解析好的数据更新到JSON结构里。

明白了函数的功能,先定义一个“location”的更新函数,代码如下:

int ICACHE_FLASH_ATTRjson_location_get(struct jsontree_context *js_ctx){    const char *path = jsontree_path_name(js_ctx, js_ctx->depth - 1);    if (os_strncmp(path, "id", os_strlen("id")) == 0) {     jsontree_write_string(js_ctx, "WX4BZD6KEJFY");    }    else if (os_strncmp(path, "name", os_strlen("name")) == 0) {     jsontree_write_string(js_ctx, "Langfang");    }    else if (os_strncmp(path, "country", os_strlen("country")) == 0) {     jsontree_write_string(js_ctx, "CN");    }    else if (os_strncmp(path, "path", os_strlen("path")) == 0) {     jsontree_write_string(js_ctx, "Langfang,Langfang,Hebei,China");    }    else if (os_strncmp(path, "timezone", os_strlen("timezone")) == 0) {         jsontree_write_string(js_ctx, "Asia/Shanghai");    }    else if (os_strncmp(path, "timezone_offset", os_strlen("timezone_offset")) == 0) {         jsontree_write_string(js_ctx, "+08:00");    }    return 0;}

同样的结构,同样的代码,就是把信息换一下。对比上下两个函数中的两行代码:

jsontree_write_int(js_ctx, user_light_get_duty(LIGHT_RED));jsontree_write_string(js_ctx, "WX4BZD6KEJFY");

第一行,因为要写入整形数据,所以使用函数“jsontree_write_int”。

第二行,因为要写入字符串,所以使用函数“jsontree_write_string”。

还有一点注意事项,第二行写入的数据是死的,即便后面更新了数据,新的数据也进不来。这里写成这样的方式是为了演示,理解方便。

同样的方式,创建now和last_update这两个类型。

3创建解析函数


参照SDK中的例程文件user_webserver.c(篇幅原因,去掉一些代码):

LOCAL int ICACHE_FLASH_ATTRlight_status_set(struct jsontree_context *js_ctx, struct jsonparse_state *parser){    while ((type = jsonparse_next(parser)) != 0) {        if (type == JSON_TYPE_PAIR_NAME) {            if (jsonparse_strcmp_value(parser, "red") == 0) {                uint32 status;                jsonparse_next(parser);                jsonparse_next(parser);                status = jsonparse_get_value_as_int(parser);                r=status;                os_printf("R: %d \n",status);            } else if (jsonparse_strcmp_value(parser, "green") == 0) {                uint32 status;                jsonparse_next(parser);                jsonparse_next(parser);                status = jsonparse_get_value_as_int(parser);                g=status;                os_printf("G: %d \n",status);            }...................        }    }    return 0;}

第2步的函数更新数据,那么数据从哪来?就是这里函数提供的。

程序中会分别判断red对应的值和green对应的值。如果有需要,可以进行相应的操作。不难理解吧?修改一下,换成我们需要的函数(篇幅有限,依然省略部分):

int ICACHE_FLASH_ATTRjson_location_parse(struct jsontree_context *js_ctx, struct jsonparse_state *parser){int type;char buffer[64];while ((type = jsonparse_next(parser)) != 0){if (type == JSON_TYPE_PAIR_NAME){if (jsonparse_strcmp_value(parser, "id") == 0){jsonparse_next(parser);jsonparse_next(parser);jsonparse_copy_value(parser, buffer, sizeof(buffer));os_printf("id is = %s\r\n", buffer);}else if(jsonparse_strcmp_value(parser, "name") == 0){jsonparse_next(parser);jsonparse_next(parser);jsonparse_copy_value(parser, buffer, sizeof(buffer));os_printf("name is = %s\r\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 is = %s\r\n", buffer);}.............................}}return 0;}

因为咱们获取的是字符串信息,所以这里使用了函数“jsonparse_copy_value”。同理,创建now和last_updata的解析函数。

4注册更新JSON数据和解析JSON数据的回调函数


参照SDK中的例程文件user_webserver.c中的代码,第394行:

LOCAL struct jsontree_callback light_callback =JSONTREE_CALLBACK(light_status_get, light_status_set);

light_status_get是生成,light_status_set是解析。回调函数以结构体的形式注册,light_callback是结构体的名字。

所以,参照这个格式往里套,location的代码如下:

LOCAL struct jsontree_callback json_location_Callback =JSONTREE_CALLBACK(json_location_get, json_location_parse);

now和last_updata同理:

LOCAL struct jsontree_callback json_now_Callback =JSONTREE_CALLBACK(json_now_get,json_now_parse);LOCAL struct jsontree_callback json_last_update_Callback =JSONTREE_CALLBACK(json_last_update_get, json_last_update_parse);

5生成JSON数的对象


参照SDK中的例程文件user_webserver.c中的代码,第397行:

JSONTREE_OBJECT(rgb_tree,                JSONTREE_PAIR("red", &light_callback),                JSONTREE_PAIR("green", &light_callback),                JSONTREE_PAIR("blue", &light_callback),                JSONTREE_PAIR("cwhite", &light_callback),                JSONTREE_PAIR("wwhite", &light_callback),                );

所以,先生成一个location的对象:

JSONTREE_OBJECT(location,                JSONTREE_PAIR("id", &json_location_Callback),                JSONTREE_PAIR("name", &json_location_Callback),                JSONTREE_PAIR("country", &json_location_Callback),                JSONTREE_PAIR("path", &json_location_Callback),                JSONTREE_PAIR("timezone", &json_location_Callback),                JSONTREE_PAIR("timezone_offset", &json_location_Callback));

然后,依次生成now和last_updata这两个对象。

现在分别生成了location、now和last_updata这三个结构,要把这三个结构组合起来:

JSONTREE_OBJECT(resultsArray,JSONTREE_PAIR("location",&location),JSONTREE_PAIR("now", &now),JSONTREE_PAIR("last_update", &json_last_update_Callback));

得到的是这样一个结构:

{"location": {"id": "WX4BZD6KEJFY","name": "Langfang","country": "CN","path": "Langfang,Langfang,Hebei,China","timezone": "Asia/Shanghai","timezone_offset": "+08:00"},"now": {"text": "Overcast","code": "9","temperature": "20"},"last_update": "2019-09-19T20:15:00+08:00"}

然后,加上方括号:

JSONTREE_ARRAY(jsonArray,JSONTREE_PAIR_ARRAY(&resultsArray));

再然后,生成名为“results”的对象:

JSONTREE_OBJECT(object,JSONTREE_PAIR("results", &jsonArray),);

至此,一个完整的数据结构已经创建好了,跟天气网站发给我们的结构是一样的。

再再然后,我们需要把这个结构放进一个对象里,方便调用:

JSONTREE_OBJECT(temp,JSONTREE_PAIR("object", &object),);

6解析天气数据


参照SDK中的例程文件user_webserver.c中的代码,第1606行:

jsontree_setup(&js, (struct jsontree_value *)&wifi_req_tree, json_putchar);json_parse(&js, pParseBuffer);

改为我们的代码:

struct jsontree_context js;jsontree_setup(&js, (struct jsontree_value *)&object, json_putchar);json_parse(&js,c);

函数“jsontree_setup”生成格式数据树,函数“json_parse”对数据进行解析,并打印出来。其中,“c”是指针,指向从“{”开始的JSON数据。具体可以看我工程里的代码,这里就不再贴出来了。

程序修改完成,保存、清理、编译、下载一条龙,然后重新上电。这里借助串口助手来查看效果。效果如下所示:

基于idf的esp32获取心知天气信息 esp8266天气_第一行代码酷欧天气_02

从截图上可以看到,程序通过解析函数,已经成功获取到了JSON结构中的所有信息,接下来,你就可以把天气信息显示在某些设备上了。


ESP8266_前言

ESP8266_01搭建开发环境

ESP8266_02程序的编译与下载

ESP8266_03 SDK与Makefile的基本用法

ESP8266_04管脚控制与软件定时器

ESP8266_05 ESP8266有几个串口?

ESP8266_06硬件定时器与IO中断

ESP8266_07基于PWM的呼吸灯

ESP8266_08基于flash的数据掉电保护

ESP8266_09基于IIC控制的OLED屏幕

ESP8266_10 ESP8266的STATION模式

ESP8266_11 ESP8266的UDP广播

ESP8266_12 ESP8266客户端模式下的TCP通信

ESP8266_13服务器端模式下的TCP通信

ESP8266_14 SOFTAP模式下的服务器端和客户端ESP8266_15天气预报之TCP的GET操作