2025年6月5日 星期四

ESP32 Arduino程式 將台中市36小時天氣資料輸出到Telegram

ESP32 Arduino程式 將台中市36小時天氣資料輸出到Telegram









https://opendata.cwa.gov.tw/api/v1/rest/datastore/F-C0032-001?Authorization=CWB-40C25FFF-1224-4250-B9D9-3735AAE17DBF&format=JSON&locationName=%E8%87%BA%E4%B8%AD%E5%B8%82

%E8%87%BA%E4%B8%AD%E5%B8%82 = 台中市

{
  "success": "true",
  "result": {
    "resource_id": "F-C0032-001",
    "fields": [
      {
        "id": "datasetDescription",
        "type": "String"
      },
      {
        "id": "locationName",
        "type": "String"
      },
      {
        "id": "parameterName",
        "type": "String"
      },
      {
        "id": "parameterValue",
        "type": "String"
      },
      {
        "id": "parameterUnit",
        "type": "String"
      },
      {
        "id": "startTime",
        "type": "Timestamp"
      },
      {
        "id": "endTime",
        "type": "Timestamp"
      }
    ]
  },
  "records": {
    "datasetDescription": "三十六小時天氣預報",
    "location": [
      {
        "locationName": "臺中市",
        "weatherElement": [
          {
            "elementName": "Wx",
            "time": [
              {
                "startTime": "2025-06-05 18:00:00",
                "endTime": "2025-06-06 06:00:00",
                "parameter": {
                  "parameterName": "陰短暫陣雨或雷雨",
                  "parameterValue": "18"
                }
              },
              {
                "startTime": "2025-06-06 06:00:00",
                "endTime": "2025-06-06 18:00:00",
                "parameter": {
                  "parameterName": "多雲午後短暫雷陣雨",
                  "parameterValue": "22"
                }
              },
              {
                "startTime": "2025-06-06 18:00:00",
                "endTime": "2025-06-07 06:00:00",
                "parameter": {
                  "parameterName": "多雲時晴",
                  "parameterValue": "3"
                }
              }
            ]
          },
          {
            "elementName": "PoP",
            "time": [
              {
                "startTime": "2025-06-05 18:00:00",
                "endTime": "2025-06-06 06:00:00",
                "parameter": {
                  "parameterName": "50",
                  "parameterUnit": "百分比"
                }
              },
              {
                "startTime": "2025-06-06 06:00:00",
                "endTime": "2025-06-06 18:00:00",
                "parameter": {
                  "parameterName": "30",
                  "parameterUnit": "百分比"
                }
              },
              {
                "startTime": "2025-06-06 18:00:00",
                "endTime": "2025-06-07 06:00:00",
                "parameter": {
                  "parameterName": "20",
                  "parameterUnit": "百分比"
                }
              }
            ]
          },
          {
            "elementName": "MinT",
            "time": [
              {
                "startTime": "2025-06-05 18:00:00",
                "endTime": "2025-06-06 06:00:00",
                "parameter": {
                  "parameterName": "25",
                  "parameterUnit": "C"
                }
              },
              {
                "startTime": "2025-06-06 06:00:00",
                "endTime": "2025-06-06 18:00:00",
                "parameter": {
                  "parameterName": "25",
                  "parameterUnit": "C"
                }
              },
              {
                "startTime": "2025-06-06 18:00:00",
                "endTime": "2025-06-07 06:00:00",
                "parameter": {
                  "parameterName": "26",
                  "parameterUnit": "C"
                }
              }
            ]
          },
          {
            "elementName": "CI",
            "time": [
              {
                "startTime": "2025-06-05 18:00:00",
                "endTime": "2025-06-06 06:00:00",
                "parameter": {
                  "parameterName": "舒適至悶熱"
                }
              },
              {
                "startTime": "2025-06-06 06:00:00",
                "endTime": "2025-06-06 18:00:00",
                "parameter": {
                  "parameterName": "舒適至悶熱"
                }
              },
              {
                "startTime": "2025-06-06 18:00:00",
                "endTime": "2025-06-07 06:00:00",
                "parameter": {
                  "parameterName": "舒適至悶熱"
                }
              }
            ]
          },
          {
            "elementName": "MaxT",
            "time": [
              {
                "startTime": "2025-06-05 18:00:00",
                "endTime": "2025-06-06 06:00:00",
                "parameter": {
                  "parameterName": "28",
                  "parameterUnit": "C"
                }
              },
              {
                "startTime": "2025-06-06 06:00:00",
                "endTime": "2025-06-06 18:00:00",
                "parameter": {
                  "parameterName": "31",
                  "parameterUnit": "C"
                }
              },
              {
                "startTime": "2025-06-06 18:00:00",
                "endTime": "2025-06-07 06:00:00",
                "parameter": {
                  "parameterName": "29",
                  "parameterUnit": "C"
                }
              }
            ]
          }
        ]
      }
    ]
  }
}




#include <WiFi.h>
#include <HTTPClient.h>
#include <ArduinoJson.h> // 請安裝此庫: 工具 -> 管理程式庫 -> 搜尋 "ArduinoJson"

// Wi-Fi 設定
const char* ssid = "Wokwi-GUEST"; // 請替換為您的Wi-Fi SSID
const char* password = ""; // 請替換為您的Wi-Fi 密碼

// 中央氣象署開放資料平台設定
const char* cwa_api_url = "https://opendata.cwa.gov.tw/api/v1/rest/datastore/F-C0032-001";
const char* cwa_authorization_code = "CWB-404C25FFF-1242F4-4250-B9D9-37635AAE177DBF"; // 您的CWB授權碼
const char* location_name = "臺中市"; // 要爬取資料的縣市

// Telegram Bot 設定
const char* telegram_bot_token = "773798940254:AAHbrWu9ovb1BKPQyW9sbNSjNxfCGCrEWU-o"; // 請替換為您的Telegram Bot Token
const char* telegram_chat_id = "7496152148469";   // 請替換為您的Telegram Chat ID

void setup() {
  Serial.begin(115200);

  // 連線到Wi-Fi
  Serial.print("Connecting to WiFi: ");
  Serial.println(ssid);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    Serial.print(".");
  }
  Serial.println("\nWiFi connected.");
  Serial.print("IP Address: ");
  Serial.println(WiFi.localIP());

  // 獲取天氣資料並發送到Telegram
  getWeatherAndSendToTelegram();
}

void loop() {
  // 您可以在這裡加入定時更新的邏輯,例如每小時更新一次
  delay(30000); // 每30Sec更新一次
  getWeatherAndSendToTelegram();
}

void getWeatherAndSendToTelegram() {
  if (WiFi.status() == WL_CONNECTED) {
    HTTPClient http;
    String url = String(cwa_api_url) + "?Authorization=" + cwa_authorization_code + "&format=JSON&locationName=" + location_name;

    Serial.print("Fetching weather data from: ");
    Serial.println(url);

    http.begin(url);
    int httpCode = http.GET();

    if (httpCode > 0) {
      Serial.printf("HTTP Code: %d\n", httpCode);
      if (httpCode == HTTP_CODE_OK) {
        String payload = http.getString();
        // Serial.println(payload); // 暫時取消註解這行,用於調試原始JSON,確認是您提供的結構

        // 解析JSON
        DynamicJsonDocument doc(40000); // 確保足夠的緩衝區大小
        DeserializationError error = deserializeJson(doc, payload);

        if (error) {
          Serial.print(F("deserializeJson() failed: "));
          Serial.println(error.f_str());
          sendTelegramMessage("Failed to parse weather data. Error: " + String(error.f_str()));
          return;
        }

        sendTelegramMessage("✨ 臺中市36小時天氣預報 ✨"); // 先發送標題

        // *** 關鍵修正點:JSON 路徑從 doc["records"]["locations"][0]["location"] 改為 doc["records"]["location"] ***
        JsonArray locations = doc["records"]["location"];

        if (locations.isNull()) {
            Serial.println("Error: 'location' array not found in JSON.");
            sendTelegramMessage("無法找到天氣地區資料。");
            return;
        }

        for (JsonObject location : locations) {
          if (String(location["locationName"]) == location_name) {
            JsonArray weatherElements = location["weatherElement"];

            if (weatherElements.isNull()) {
                Serial.println("Error: 'weatherElement' array not found for 臺中市.");
                sendTelegramMessage("無法找到臺中市的天氣元素資料。");
                return;
            }

            // 先找到 Wx 元素,用它的時間來做主導遍歷
            JsonArray times_to_iterate;
            for (JsonObject element : weatherElements) {
              if (String(element["elementName"]) == "Wx") {
                times_to_iterate = element["time"];
                break; // 找到 Wx 元素後就跳出迴圈
              }
            }

            if (times_to_iterate.isNull()) {
                Serial.println("Error: Wx element or its time data not found.");
                sendTelegramMessage("無法取得天氣現象時間資料。");
                return;
            }

            for (JsonObject time_period : times_to_iterate) {
                String periodMessage = "--- 🗓️ 時段預報 ---\n";
                periodMessage += "時間: " + String(time_period["startTime"]) + " ~ " + String(time_period["endTime"]) + "\n";

                // 遍歷所有天氣元素,找到對應時間段的資料
                for (JsonObject element : weatherElements) {
                    String elementName = element["elementName"];
                    JsonArray innerTimes = element["time"];
                   
                    if (innerTimes.isNull()) continue; // 如果該元素沒有時間數據,跳過

                    for (JsonObject innerTime : innerTimes) {
                        // 比較時間區間
                        if (String(innerTime["startTime"]) == String(time_period["startTime"]) &&
                            String(innerTime["endTime"]) == String(time_period["endTime"])) {

                            if (elementName == "Wx") {
                                periodMessage += "天氣: " + String(innerTime["parameter"]["parameterName"]) + "\n";
                            } else if (elementName == "MaxT") {
                                periodMessage += "最高溫: " + String(innerTime["parameter"]["parameterName"]) + "°C\n";
                            } else if (elementName == "MinT") {
                                periodMessage += "最低溫: " + String(innerTime["parameter"]["parameterName"]) + "°C\n";
                            } else if (elementName == "CI") {
                                periodMessage += "舒適度: " + String(innerTime["parameter"]["parameterName"]) + "\n";
                            } else if (elementName == "PoP") {
                                periodMessage += "降雨機率: " + String(innerTime["parameter"]["parameterName"]) + "%\n";
                            }
                            // 如果有其他需要顯示的元素,可以在這裡添加
                            break; // 找到對應時間的元素就跳出內層迴圈
                        }
                    }
                }
                sendTelegramMessage(periodMessage); // 發送每個時間段的訊息
                delay(200); // 稍作延遲,避免連續發送過快
            }
          }
        }

      } else {
        Serial.printf("Error getting weather data. HTTP code: %d\n", httpCode);
        sendTelegramMessage("Error getting weather data. HTTP code: " + String(httpCode));
      }
    } else {
      Serial.printf("HTTP GET request failed. Error: %s\n", http.errorToString(httpCode).c_str());
      sendTelegramMessage("HTTP GET request failed. Error: " + String(http.errorToString(httpCode).c_str()));
    }
    http.end();
  } else {
    Serial.println("WiFi not connected. Cannot get weather data.");
    sendTelegramMessage("WiFi not connected. Cannot get weather data.");
  }
}

void sendTelegramMessage(String message) {
  HTTPClient http;
  String url = "https://api.telegram.org/bot" + String(telegram_bot_token) + "/sendMessage";

  http.begin(url);
  http.addHeader("Content-Type", "application/json");

  // 對訊息內容進行 JSON 安全編碼,特別是對於雙引號
  message.replace("\"", "\\\"");

  String httpRequestData = "{\"chat_id\": \"" + String(telegram_chat_id) + "\", \"text\": \"" + message + "\"}";
  Serial.print("Sending to Telegram: ");
  Serial.println(httpRequestData);

  int httpCode = http.POST(httpRequestData);

  if (httpCode > 0) {
    Serial.printf("Telegram API response code: %d\n", httpCode);
    if (httpCode == HTTP_CODE_OK) {
      String response = http.getString();
      Serial.println(response);
    }
  } else {
    Serial.printf("Error sending message to Telegram. HTTP code: %d\n", httpCode);
    Serial.printf("Error: %s\n", http.errorToString(httpCode).c_str());
  }
  http.end();
}

沒有留言:

張貼留言

ESP32 (ESP-IDF in VS Code) MFRC522 + MQTT + PYTHON TKinter +SQLite

 ESP32 (ESP-IDF in VS Code) MFRC522 + MQTT + PYTHON TKinter +SQLite  ESP32 VS Code 程式 ; PlatformIO Project Configuration File ; ;   Build op...