ESP8266_16天氣預報之JSON數據的生成與解析

ESP8266_01搭建開發環境

ESP8266_02程序的編譯與下載

ESP8266_03SDK與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操作

ESP8266_16天氣預報之JSON數據的生成與解析

ESP8266_17簡單網絡時間協議-SNTP

ESP8266_18 MQTT協議接入ONENET平臺

ESP8266_19MQTT協議接入ONENET平臺_訂閱主題

ESP8266_20 基於ONENET平臺的數據上傳之TCP的POST操作

ESP8266_21基於ESP8266的一鍵配網

ESP8266_22基於自身ADC的電壓採樣

ESP8266_23基於硬件定時器的紅外遙控器解碼

上一節通過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_ATTR
light_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_ATTR
json_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_ATTR
light_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_ATTR
json_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);

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結構中的所有信息,接下來,你就可以把天氣信息顯示在某些設備上了。

 

鏈接:https://pan.baidu.com/s/1BOUrZWAGJxMjQAyBtxhyuQ

提取碼:gozo

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章