在分析代碼之前,首先介紹 ArduinoJson 庫的安裝及“心知天氣”的ID申請
一、安裝 ArduinoJson 庫
進入 Arduino 開發環境后,選擇菜單欄-->工具-->管理庫,搜索“arduinojson”。盡量使用 ArduinoJson 5.x 版本,因為 6.x 版本有很大的改動。
二、申請“心知天氣”的個人APIKEY
首先進入“心知天氣”主頁,點擊此處訪問
注冊並登陸后,點擊“立即免費試用”
申請免費版
獲取自己的API密鑰
三、“心知天氣”API簡介
本部分內容參考自:https://www.jianshu.com/p/fd8c84e40994
1. API 請求參數
參數名 | 參數類型 | 參數意義 | 是否必選 |
key | string | 個人API密鑰 | true |
location | string | 查詢的地理位置 | true |
language | string | 結果表示的語言 | false,默認簡體中文 |
unit | string | 結果表示的單位 | false,默認攝氏度 |
本實驗中,language 的參數值選用“zh-Hans”簡體中文,unit 的參數選用“c”。(想了解更多參數范圍請看參考鏈接)
2. API 響應參數
參數名 | 參數類型 |
location | 對象:包括 id, name, country, path, timezone, timezone_offset |
now | 對象:包括 text, code, temperature |
last_update | 日期 |
參考一個請求的 Json 數據的實例,應該能較直觀地理解 Json 數據包的格式:
{
"results":[
{
"location":{
"id":"WTMKQ069CCJ7",
"name":"杭州",
"country":"CN",
"path":"杭州,杭州,浙江,中國",
"timezone":"Asia/Shanghai",
"timezone_offset":"+08:00"
},
"now":{
"text":"晴",
"code":"1",
"temperature":"18"
},
"last_update":"2020-11-07T21:25:00+08:00"
}
]
}
引用數據時,用類似數組的引用方式。
例如,若想引用地區名 “杭州”,則使用:
json["results"][0]["location"]["name"]
四、代碼分析
本次實驗代碼已上傳至本人 github 賬號:點擊此處查看完整代碼
1.變量設置
#include<ESP8266WiFi.h> #include<ArduinoJson.h> const char* ssid ="";//輸入熱點名稱 const char* password ="";//輸入熱點密碼 const char* host ="api.seniverse.com"; const char* APIKEY ="";//輸入自己申請的知心天氣私鑰 const char* city ="hangzhou";//可根據需要改為其余城市的拼音 const char* language ="zh-Hans"; const unsigned long BAUD_RATE=115200; const unsigned long HTTP_TIMEOUT=5000; const size_t MAX_CONTENT_SIZE=1000; struct WeatherData{//存儲天氣數據的結構體,可根據需要自行添加成員變量 char city[16]; char weather[32]; char temp[16]; char udate[32]; }; WiFiClient client;//創建了一個網絡對象 char response[MAX_CONTENT_SIZE]; char endOfHeaders[]="\r\n\r\n";
2.初始化
在這一步中 設置串口的波特率,連接WiFi,設置客戶端超時時間
void setup() { Serial.begin(BAUD_RATE); wifiConnect();//連接WiFi client.setTimeout(HTTP_TIMEOUT); }
3.循環體
首先判斷 tcp client 是否處於連接狀態,若不是,則嘗試建立連接。連接成功后,發送 http 請求,並且跳過響應頭,直接獲取響應 body。
void loop() { while(!client.connected()){ if(!client.connect(host,80)){//嘗試建立連接 Serial.println("connection...."); delay(500); } } //連接成功,發送GET請求 if(sendRequest(host,city,APIKEY)&&skipResponseHeaders()){//發送http請求 並且跳過響應頭 clrEsp8266ResponseBuffer();//清除緩存 readReponseContent(response,sizeof(response));//從HTTP服務器響應中讀取正文 WeatherData weatherData; if(parseUserData(response,&weatherData)){//判斷Json數據包是否分析成功 printUserData(&weatherData);//輸出讀取到的天氣信息 } }
stopConnect(); delay(5000); }
4.自定義函數詳解
(1) 連接到WiFi
void wifiConnect(){ WiFi.mode(WIFI_STA);//設置esp8266工作模式 Serial.print("Connecting to"); Serial.println(ssid); WiFi.begin(ssid,password);//連接WiFi WiFi.setAutoConnect(true); while(WiFi.status()!=WL_CONNECTED){//該函數返回WiFi的連接狀態 delay(500); Serial.print("."); } Serial.println(""); Serial.println("WiFi connected"); delay(500); Serial.println("IP address:"); Serial.println(WiFi.localIP()); }
(2) 發送 http 請求
bool sendRequest(const char* host,const char* cityid,const char* apiKey){ String GetUrl="/v3/weather/now.json?key="; GetUrl+=APIKEY; GetUrl+="&location="; GetUrl+=city; GetUrl+="&language="; GetUrl+=language; GetUrl+="&unit=c "; client.print(String("GET ")+GetUrl+"HTTP/1.1\r\n"+"Host:"+host+"\r\n"+"Connection:close\r\n\r\n"); Serial.println("creat a request:"); Serial.println(String("GET ")+GetUrl+"HTTP/1.1\r\n"+"Host:"+host+"\r\n"+"Connection:close\r\n\r\n"); delay(1000); return true; }
(3) 跳過響應頭
bool skipResponseHeaders(){ bool ok=client.find(endOfHeaders); if(!ok){ Serial.println("No response of invalid response!"); } return ok; }
(4) 讀取響應的正文信息
void readReponseContent(char* content,size_t maxSize){ size_t length=client.readBytes(content,maxSize); delay(100); Serial.println("Get the data from Internet"); content[length]=0; Serial.println(content);//輸出讀取到的數據 Serial.println("Read data Over!"); client.flush();//刷新客戶端 }
(5) 分析 Json 數據包
bool parseUserData(char* content,struct WeatherData* weatherData){ DynamicJsonBuffer jsonBuffer;//創建一個動態緩沖區實例 JsonObject&root=jsonBuffer.parseObject(content);//根據需要解析的數據來計算緩沖區的大小 if(!root.success()){ Serial.println("JSON parsing failed!"); return false; } //復制數據包中所需的字符串 strcpy(weatherData->city,root["results"][0]["location"]["name"]); strcpy(weatherData->weather,root["results"][0]["now"]["text"]); strcpy(weatherData->temp,root["results"][0]["now"]["temperature"]); strcpy(weatherData->udate,root["results"][0]["last_update"]); return true; }
(6) 打印數據
void printUserData(const struct WeatherData* weatherData){ Serial.println("Print parsed data:"); Serial.print("City:"); Serial.print(weatherData->city); Serial.print(" Weather:"); Serial.print(weatherData->weather); Serial.print(" Temp:"); Serial.print(weatherData->temp); Serial.print("℃"); Serial.print(" UpdateTime:"); Serial.println(weatherData->udate); }
(7) 停止客戶端訪問
void stopConnect(){ Serial.println("Disconnect"); client.stop();//停止客戶端訪問 }
(8) 清除緩存
void clrEsp8266ResponseBuffer(void){ memset(response,0,MAX_CONTENT_SIZE); }
五、串口輸出樣例
六、總結
在本次實踐中,我初步理解了 Json 數據包的格式及用法。希望以后可以做出更為復雜的應用。