WiFi練習:工作站模式—取得OpenWeatherMap天氣資料

OpenWeatherMap 是一個提供全球氣象資料的線上平台,可透過 API (Application Programming Interface) 取得即時天氣、預報、歷史天氣、空氣品質等資料,廣泛應用於網站、APP、IoT(物聯網)與學術研究。使用 API 前需註冊帳號,並取得 API 金鑰,資料為 JSON 格式,利用 JSON 函式庫解析後即可進一步應用。

該網站可使用的免費項目:Current weather and forecasts,基本語法如下:

https://api.openweathermap.org/data/2.5/weather?lat=緯度&lon=經度&units=metric&appid=API金鑰
https://api.openweathermap.org/data/2.5/weather?lat=25.03&lon=121.54&units=metric&appid=API金鑰
{"coord":{"lon":121.54,"lat":25.03},"weather":[{"id":803,"main":"Clouds","description":"broken clouds","icon":"04d"}],"base":"stations","main":{"temp":33.38,"feels_like":40.38,"temp_min":31.68,"temp_max":33.83,"pressure":1003,"humidity":62,"sea_level":1003,"grnd_level":985},"visibility":10000,"wind":{"speed":2.24,"deg":300,"gust":8.49},"clouds":{"all":75},"dt":1747985425,"sys":{"type":2,"id":266033,"country":"TW","sunrise":1747947985,"sunset":1747996502},"timezone":28800,"id":1675720,"name":"Xianeibu","cod":200}
{
    "coord": {
        "lon": 121.54, 
        "lat": 25.03
    }, 
    "weather": [
        {
            "id": 803, 
            "main": "Clouds", 
            "description": "broken clouds", 
            "icon": "04d"
        }
    ], 
    "base": "stations", 
    "main": {
        "temp": 33.38, 
        "feels_like": 40.38, 
        "temp_min": 31.68, 
        "temp_max": 33.83, 
        "pressure": 1003, 
        "humidity": 62, 
        "sea_level": 1003, 
        "grnd_level": 985
    }, 
    "visibility": 10000, 
    "wind": {
        "speed": 2.24, 
        "deg": 300, 
        "gust": 8.49
    }, 
    "clouds": {
        "all": 75
    }, 
    "dt": 1747985425, 
    "sys": {
        "type": 2, 
        "id": 266033, 
        "country": "TW", 
        "sunrise": 1747947985, 
        "sunset": 1747996502
    }, 
    "timezone": 28800, 
    "id": 1675720, 
    "name": "Xianeibu", 
    "cod": 200
}
https://api.openweathermap.org/data/2.5/weather?q=城市,國家或地區&units=metric&appid=API金鑰
https://api.openweathermap.org/data/2.5/weather?q=Taipei,TW&units=metric&appid=API金鑰

臺灣城市英文對應名稱

{"coord":{"lon":121.5319,"lat":25.0478},"weather":[{"id":803,"main":"Clouds","description":"broken clouds","icon":"04d"}],"base":"stations","main":{"temp":33.42,"feels_like":40.42,"temp_min":31.7,"temp_max":33.84,"pressure":1002,"humidity":62,"sea_level":1002,"grnd_level":985},"visibility":10000,"wind":{"speed":1.54,"deg":100},"clouds":{"all":75},"dt":1747984953,"sys":{"type":2,"id":266033,"country":"TW","sunrise":1747947985,"sunset":1747996506},"timezone":28800,"id":1668341,"name":"Taipei","cod":200}
{
    "coord": {
        "lon": 121.5319, 
        "lat": 25.0478
    }, 
    "weather": [
        {
            "id": 803, 
            "main": "Clouds", 
            "description": "broken clouds", 
            "icon": "04d"
        }
    ], 
    "base": "stations", 
    "main": {
        "temp": 33.42, 
        "feels_like": 40.42, 
        "temp_min": 31.7, 
        "temp_max": 33.84, 
        "pressure": 1002, 
        "humidity": 62, 
        "sea_level": 1002, 
        "grnd_level": 985
    }, 
    "visibility": 10000, 
    "wind": {
        "speed": 1.54, 
        "deg": 100
    }, 
    "clouds": {
        "all": 75
    }, 
    "dt": 1747984953, 
    "sys": {
        "type": 2, 
        "id": 266033, 
        "country": "TW", 
        "sunrise": 1747947985, 
        "sunset": 1747996506
    }, 
    "timezone": 28800, 
    "id": 1668341, 
    "name": "Taipei", 
    "cod": 200
}

JSON內容有經緯度座標、天氣概況、溫度、溼度、大氣壓力、風速等資料。

HTTP請求取得網站資料

ESP32可透過HTTPClient函式庫取得OpenWeatherMap網站的資料,方法如下:

//---------------------------------------------------------------
#include <WiFi.h>
const char *ssid = "SSID";       // 連上無線基地臺的SSID
const char *password = "密碼";   // 連上無線基地臺的密碼
//---------------------------------------------------------------
void connect_to_wifi()
{
  WiFi.begin(ssid, password);           // 啟動WiFi連線
  Serial.printf("Connecting to %s ", ssid);
  while(WiFi.status() != WL_CONNECTED)  // 只要WiFi連線狀態不正常
  {
    delay(500);                         // 每0.5秒印出一個點
    Serial.print(".");
  }
  Serial.println(" CONNECTED!");
  Serial.print("SSID: ");
  Serial.println(WiFi.SSID());          // 印出SSID
  Serial.print("IP: ");
  Serial.println(WiFi.localIP());       // 印出IP
  Serial.print("Subnet Mask IP: ");
  Serial.println(WiFi.subnetMask());    // 印出子網路遮罩
  Serial.print("Gateway IP: ");
  Serial.println(WiFi.gatewayIP());     // 印出閘道IP
  Serial.print("DNS IP: ");
  Serial.println(WiFi.dnsIP());         // 印出DNS IP  
}
//---------------------------------------------------------------
#include <HTTPClient.h>                  // 發送http請求,取得網站資料
HTTPClient http;
String url = "https://api.openweathermap.org/data/2.5/weather?q=Taipei,TW&units=metric&appid=API金鑰"; // 網址
//---------------------------------------------------------------

void setup()
{
  //------------------------------------------------------------------------------
  Serial.begin(9600);                   // 啟用串列埠監看視窗
  //------------------------------------------------------------------------------
  connect_to_wifi();                    // 連線到WiFi
  //------------------------------------------------------------------------------
  http.begin(url);                      // 開始連接網頁
  int httpCode = http.GET();            // 執行GET請求,回傳碼儲存於httpCode

  if (httpCode == HTTP_CODE_OK)         // 如果連線正常
  {
    String payload = http.getString();  // 傳回的網頁內容儲存於字串變數payload(承載量)
    Serial.println(payload);            // 印出傳回的網頁內容
  }
  else
  {
    Serial.print("HTTP GET failed, error code: ");      // 印出錯誤訊息
    Serial.println(httpCode);
  }   
  http.end();                           // 結束連線
  //------------------------------------------------------------------------------
}

void loop()
{
}

傳回結果與瀏覽器相同,只要再經過JSON解析之後,即可將各項資料進一步運用。

JSON格式解析

ESP32可透過ArduinoJson函式庫解析網站的JSON資料,方法如下:

//---------------------------------------------------------------
#include <WiFi.h>
const char *ssid = "SSID";              // 連上無線基地臺的SSID
const char *password = "密碼";          // 連上無線基地臺的密碼
//---------------------------------------------------------------
void connect_to_wifi()
{
  WiFi.begin(ssid, password);           // 啟動WiFi連線
  Serial.printf("Connecting to %s ", ssid);
  while(WiFi.status() != WL_CONNECTED)  // 只要WiFi連線狀態不正常
  {
    delay(500);                         // 每0.5秒印出一個點
    Serial.print(".");
  }
  Serial.println(" CONNECTED!");
  Serial.print("SSID: ");
  Serial.println(WiFi.SSID());          // 印出SSID
  Serial.print("IP: ");
  Serial.println(WiFi.localIP());       // 印出IP
  Serial.print("Subnet Mask IP: ");
  Serial.println(WiFi.subnetMask());    // 印出子網路遮罩
  Serial.print("Gateway IP: ");
  Serial.println(WiFi.gatewayIP());     // 印出閘道IP
  Serial.print("DNS IP: ");
  Serial.println(WiFi.dnsIP());         // 印出DNS IP  
}
//---------------------------------------------------------------
#include <HTTPClient.h>                 // 發送http請求,取得網站資料
HTTPClient http;
// String url = "https://api.openweathermap.org/data/2.5/weather?q=Taipei,TW&units=metric&appid=API金鑰";   // 網址

// 將網址中的城市、地區、API金鑰設定為變數,以方便後續程式操控
String city = "Taipei";
String countryCode = "TW";
String ApiKey = "API金鑰";
String url = "https://api.openweathermap.org/data/2.5/weather?q=" + city + "," + countryCode + "&units=metric&appid=" + ApiKey; // 網址
//---------------------------------------------------------------
#include <ArduinoJson.h>                // 解析JSON資料
String weatherDescription;              // 天氣概況
String temp;                            // 溫度
String pressure;                        // 大氣壓力
String humidity;                        // 溼度
//---------------------------------------------------------------
void get_weather_data()
{
  http.begin(url);                      // 開始連接網頁
  int httpCode = http.GET();            // 執行GET請求,回傳碼儲存於httpCode

  if (httpCode == HTTP_CODE_OK)         // 如果連線正常
  {
    String payload = http.getString();  // 傳回的網頁內容儲存於字串變數payload(承載量)
    // Serial.println(payload);         // 印出傳回的網頁內容
    
    // -------------------------------------------------------------
    // OpenWeatherMap JSON格式解析
    // -------------------------------------------------------------
    DynamicJsonDocument WeatherJson(payload.length() * 2);    // 宣告一個Json文件,名稱為WeatherJson(陣列格式)
    deserializeJson(WeatherJson, payload);                    // 解析payload為JSON Array格式

    weatherDescription = WeatherJson["weather"][0]["description"].as<String>();   // 取得天氣概況,weather是陣列,[0]是索引值
    temp = WeatherJson["main"]["temp"].as<String>();          // 取得溫度
    pressure = WeatherJson["main"]["pressure"].as<String>();  // 取得氣壓
    humidity = WeatherJson["main"]["humidity"].as<String>();  // 取得溼度
    Serial.println("----------------------------------");
    Serial.print("Weather description: ");
    Serial.println(weatherDescription);
    Serial.print("Temp: ");
    Serial.print(temp);
    Serial.println(" °C");
    Serial.print("Pressure: ");
    Serial.print(pressure);
    Serial.println(" hPa");
    Serial.print("Humidity: ");
    Serial.print(humidity);
    Serial.println(" %");
    Serial.println("----------------------------------");
  }
  else
  {
    Serial.print("HTTP GET failed, error code: ");      // 印出錯誤訊息
    Serial.println(httpCode);
  } 
  
  http.end();                           // 結束連線
}

void setup()
{
  //-------------------------------------------
  Serial.begin(9600);     // 啟用串列埠監看視窗
  //-------------------------------------------
  connect_to_wifi();      // 連線到WiFi
  //-------------------------------------------
  get_weather_data();     // 取得天氣資料
  //-------------------------------------------
}

void loop()
{
}

作業練習1:請利用ESP32取得時間(每秒1次)、天氣資料(每10秒1次),將結果顯示在監看視窗上。

作業練習2:

  • 請利用ESP32的WiFi功能,連上網路取得NTP時間資料及OpenWeatherMap天氣資料,利用紅外線遙控器、紅外線接收器,可選擇臺灣不同的城市,將時間、天氣概況、溫度、溼度、大氣壓力等訊息呈現在LCD上。
  • 基本功能:
    • 請自行定義10個城市。
    • 按下0-9數字鍵,可對應定義的城市,並顯示時間及天氣訊息在LCD上,一個頁面不夠顯示時,可用二個頁面交替顯示。
    • 按下PREV、NEXT鍵,可切換前一個或下一個城市,可在城市0~9之間循環切換。
    • 按下VOL+、VOL-鍵,可切換當前城市的時間及天氣訊息。
    • 按下EQ鍵,可顯示當下日期時間,時間每秒更新一次。
  • 進階功能可自行補充,例如:
    • 超過10個以上的城市。
    • 不同天氣,可在RGB LED燈上顯示不同的顏色。
  • 請拍照、錄影,並完成實習報告,上傳Google Classroom作業。
  • 實習報告須包含下列內容:(1)摘要 (2)動機 (3)主題 (4)實作步驟 (5)實作結果 (6)心得與討論 (7)參考資料。

成果影片:

控制115乙 黃郁善
控制115乙 宋允升
控115甲 王承鈞
控115甲 梁宸嘉