上一节通过HTTP的GET操作,我们获取了天气相关的信息。但相关信息是以JSON格式发下来的,所以需要对数据进行解析。解析之前先明确几点:
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数据。具体可以看我工程里的代码,这里就不再贴出来了。
程序修改完成,保存、清理、编译、下载一条龙,然后重新上电。这里借助串口助手来查看效果。效果如下所示:
从截图上可以看到,程序通过解析函数,已经成功获取到了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操作