2026年10月1日 星期四

基於 ESP32 與環境部 API 之微型空氣品質監測與警報系統 (即時 AQI 與 PM2.5 監測系統 含 I2C LCD 顯示與異常警報)

基於 ESP32 與環境部 API 之微型空氣品質監測與警報系統 (即時 AQI 與 PM2.5 監測系統  含 I2C LCD 顯示與異常警報)


Node-RED 與 Wokwi ESP32 目前已建立起基於 MQTT 的雙向邊緣運算連動架構。整個系統透過 HiveMQ Broker (broker.hivemq.com) 作為中間訊息轉運站,實現了「UI 控制發令」與「設備邊緣採集/反饋」的完整閉環。

以下為兩者的實時運作狀況與互動流程說明:

[ Node-RED Dashboard ]                                                       [ Wokwi ESP32 ]

  │                                                                                                         │

  ├── 1. 下拉選單選擇測站 (例: "汐止")                                         │

  │    └─► 發布至 MQTT Topic: alex9ufo/sitename ───────┐ │

  │                                                                                                     │ │

  │                                                                                                     ▼ │

  │                                        2. 接收切換指令: "汐止"

  │                                      └─► 主動向環境部 API 抓取資料

  │                                      └─► 本地比對並更新 20x4 LCD & LED

  │                                                                                                         │

  │                        3. 接收 24 欄位完整 JSON Payload                       │

  │    ◄─ 接收自 MQTT Topic: alex9ufo/weather ──────────┘

  │

  └── 4. 解析 JSON 並繪製雙語 HTML 表格顯示

二、Node-RED 與 ESP32 的分工與運作細節

1. 控制發令端:Node-RED Dashboard 運作狀況

  • 初始化選單:Node-RED 啟動時(或每 15 分鐘)會主動向環境部 API 抓取全台測站清單,並在 Dashboard 上繪製出選單與統計數量。

  • 發布控制指令 (alex9ufo/sitename):

    • 當使用者在 Node-RED 選單上切換測站(如從「大里」切換至「汐止」)時,ui_dropdown 節點會觸發 Function,將站名純文字字串發布至 MQTT Topic alex9ufo/sitename。

    • 特點:Node-RED 不直接傳送詳細天氣資料給 ESP32,而是只傳送指令,將採集運算交給邊緣端。

2. 邊緣採集與硬體控制端:Wokwi ESP32 運作狀況

  • 隨時監聽指令:ESP32 的 loop() 持續執行 client.loop()。當收到 alex9ufo/sitename 傳來的「汐止」時,callback() 函式會被觸發。

  • 獨立 HTTP API 採集:

    • ESP32 收到新站名後,會立刻使用 WiFiClientSecure 向環境部 API 發送 HTTP GET 請求。

    • 為了防範嵌入式系統上的中文 URL 轉碼失敗,ESP32 採用「全台資料一次抓取 + 本地比對 (Local Match)」策略,能 100% 穩定鎖定「汐止」的數據。

  • 硬體實體反應:

    • 20x4 LCD:第 1 行顯示 AQI 與站名、第 2 行顯示 PM2.5、第 3 行顯示狀態(Air OK 或 Air Bad!)、第 4 行顯示發布時間。

    • 警報 LED (GPIO 15):當判定 $AQI > 50$ 或 $PM2.5 > 15.4$ 時,自動點亮紅燈警報。

  • 數據反饋廣播 (alex9ufo/weather):

    • ESP32 提取該測站包含 sitename, aqi, pm2.5, so2, co, wind_speed... 等全部 24 個欄位,重新打包成 JSON 字串。

    • 將此 JSON 發布至 MQTT Topic alex9ufo/weather。

3. 數據展示端:Node-RED 表格渲染狀況

  • 監聽反饋:Node-RED 的 mqtt in 節點訂閱了 alex9ufo/weather,隨時接收來自 ESP32 廣播的回傳資料。

  • 容錯解析與表格繪製:

    • Function 節點接收到 JSON 後,進行自動轉碼防護(避免 String / Object 格式錯置)。

    • 逐一讀取 24 個欄位並對照中英文名稱,自動將 aqi 以紅色加粗、pm2.5 以綠色加粗呈現。

    • 最終將組裝好的 HTML 碼傳給 ui_template 節點,在 Node-RED 畫面上呈現出排版整齊的 24 欄位明細表格。

三、系統亮點與穩定機制

  1. 事件驅動 + 定時雙保險:

    • 事件驅動:一旦在 Node-RED(或 Python)選擇新測站,ESP32 在 1 秒內就會切換並更新硬體 LCD/LED。

    • 定時刷新:ESP32 內部設有 FETCH_INTERVAL(15 分鐘計時器),即使無人手動切換,也會自動定時重新向 API 請求當前測站的最新氣象數據。

  2. 高容錯防護:

    • 記憶體釋放:ESP32 每次抓完 HTTP 後會主動 delete secureClient 釋放記憶體,確保 MQTT 通訊不卡死。

    • 格式相容:Node-RED Function 節點能同時相容 JSON 字串與 JSON 物件,確保即使無數據時也能以 --- 正常顯示不報錯。

Wokwi esp32程式



#include <WiFi.h>
#include <HTTPClient.h>
#include <WiFiClientSecure.h>
#include <PubSubClient.h>
#include <ArduinoJson.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>

// --- 硬體腳位與 LCD 設定 ---
#define LED_PIN 15  // 警報 LED 腳位

LiquidCrystal_I2C lcd(0x27, 20, 4);

// --- 網路、API 與 MQTT 設定 ---
const char* ssid = "Wokwi-GUEST";
const char* password = "";

const char* api_key = "aa1bc2bf-24c2-4c55-980f-a12075c2caec";
const char* mqtt_broker = "broker.hivemq.com";
const int mqtt_port = 1883;

const char* SUB_TOPIC = "alex9ufo/sitename"; // 訂閱測站切換指令
const char* PUB_TOPIC = "alex9ufo/weather";  // 發行 24 欄位完整 JSON

// --- 狀態控制變數 ---
String currentSite = "大里";                   // 記錄當前測站名稱
unsigned long lastFetchTime = 0;              // 上次抓取 API 的時間點 (ms)
const unsigned long FETCH_INTERVAL = 900000;  // 自動定時更新時間:15分鐘 (900,000 ms)

WiFiClient espClient;
PubSubClient client(espClient);

// --- 輔助函式:LCD 20 字元格式化輸出 ---
void printLCDLine(int row, String text) {
  lcd.setCursor(0, row);
  if (text.length() > 20) {
    text = text.substring(0, 20);
  }
  while (text.length() < 20) {
    text += " ";
  }
  lcd.print(text);
}

// --- 向環境部 API 抓取全台資料,並在本地尋找目標測站 ---
void fetchAndPublishAirData(String siteName) {
  if (WiFi.status() != WL_CONNECTED) {
    Serial.println("[WiFi] Disconnected!");
    return;
  }

  WiFiClientSecure *secureClient = new WiFiClientSecure();
  if (!secureClient) {
    Serial.println("[Memory] Failed to allocate secure client!");
    return;
  }
  secureClient->setInsecure(); // 繞過 SSL 憑證驗證

  HTTPClient http;
  // 直接抓取最新數據(不透過 URL 進行中文 filter 篩選,避免中文轉碼失敗)
  String url = "https://data.moenv.gov.tw/api/v2/aqx_p_432?limit=100&api_key=" + String(api_key);

  Serial.println("\n------------------------------------------------");
  Serial.println("[HTTP GET] Fetching All Sites API...");
  Serial.println("Target Site: " + siteName);

  http.begin(*secureClient, url);
  http.addHeader("User-Agent", "ESP32-Air-Monitor");
  int httpCode = http.GET();

  if (httpCode == HTTP_CODE_OK) {
    String payload = http.getString();
    Serial.println("[HTTP Success] Data received!");

    // 全台資料約需要較大記憶體,分配 DynamicJsonDocument
    DynamicJsonDocument doc(32768);
    DeserializationError error = deserializeJson(doc, payload);

    if (error) {
      Serial.print("[JSON Error] Deserialize failed: ");
      Serial.println(error.f_str());
      http.end();
      delete secureClient;
      return;
    }

    // 取得 records 陣列
    JsonArray records;
    if (doc.is<JsonArray>()) {
      records = doc.as<JsonArray>();
    } else if (doc.containsKey("records")) {
      records = doc["records"].as<JsonArray>();
    }

    JsonObject targetRecord;
    bool found = false;

    // 在 ESP32 本地尋找符合 siteName 的測站
    for (JsonObject r : records) {
      const char* sName = r["sitename"] | "";
      if (String(sName) == siteName) {
        targetRecord = r;
        found = true;
        break;
      }
    }

    if (!found) {
      Serial.println("[API Warning] Station '" + siteName + "' not found in records!");
      printLCDLine(0, "Site: " + siteName);
      printLCDLine(1, "AQI: ---");
      printLCDLine(2, "Not Found!");
      digitalWrite(LED_PIN, LOW);
      http.end();
      delete secureClient;
      return;
    }

    Serial.println("[Match Success] Found data for site: " + siteName);

    // 1. 解析重點數據給 LCD 與 LED
    const char* sitename = targetRecord["sitename"] | siteName.c_str();
    const char* aqiStr   = targetRecord["aqi"] | "---";
    const char* pm25Str  = targetRecord["pm2.5"] | "---";
    const char* pubtime  = targetRecord["publishtime"] | "--:--:--";

    int aqiVal = atoi(aqiStr);
    float pm25Val = atof(pm25Str);
    bool hasValidData = (String(aqiStr) != "---" && String(aqiStr) != "");
    bool isBad = (hasValidData && aqiVal > 50) || (pm25Val > 15.4);

    // 2. 控制硬體警報 LED
    digitalWrite(LED_PIN, isBad ? HIGH : LOW);

    // 3. 更新模擬 20x4 LCD
    String timeStr = String(pubtime);
    if (timeStr.length() >= 19) {
      timeStr = timeStr.substring(11);
    }
    printLCDLine(0, "AQI: " + String(aqiStr) );
    printLCDLine(1, "PM2.5: " + String(pm25Str) + " ug/m3");
    printLCDLine(2, isBad ? "Air Bad! (Alarm)" : (hasValidData ? "Air OK" : "No Data!"));
    printLCDLine(3, "Time: " + timeStr);

    // 4. 發布至 MQTT Topic alex9ufo/weather
    String weatherJsonStr;
    serializeJson(targetRecord, weatherJsonStr);

    if (client.connected()) {
      client.publish(PUB_TOPIC, weatherJsonStr.c_str());
      Serial.println("[MQTT Tx] Successfully published data to " + String(PUB_TOPIC));
      Serial.println("Payload: " + weatherJsonStr);
    }

  } else {
    Serial.printf("[HTTP Error] GET failed, error code: %d\n", httpCode);
    printLCDLine(2, "HTTP Error: " + String(httpCode));
  }

  http.end();
  delete secureClient;
  lastFetchTime = millis(); // 重設 15 分鐘計時器
}

// --- MQTT 收到訊息時的回呼函式 ---
void callback(char* topic, byte* payload, unsigned int length) {
  String newSite = "";
  for (unsigned int i = 0; i < length; i++) {
    newSite += (char)payload[i];
  }
  newSite.trim();

  Serial.println("\n[MQTT Rx] Topic: '" + String(topic) + "' | Received sitename: " + newSite);

  if (newSite.length() > 0) {
    currentSite = newSite;
    printLCDLine(2, "Loading " + currentSite + "...");
    fetchAndPublishAirData(currentSite);
  }
}

// --- MQTT 重新連線機制 ---
void reconnectMQTT() {
  while (!client.connected()) {
    Serial.print("Attempting MQTT connection...");
    String clientId = "ESP32-AirNode-" + String(random(0xffff), HEX);
   
    if (client.connect(clientId.c_str())) {
      Serial.println("connected!");
      client.subscribe(SUB_TOPIC);
      Serial.println("Subscribed to " + String(SUB_TOPIC));
    } else {
      Serial.print("failed, rc=");
      Serial.print(client.state());
      Serial.println(" try again in 5 seconds");
      delay(5000);
    }
  }
}

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

  pinMode(LED_PIN, OUTPUT);
  digitalWrite(LED_PIN, LOW);

  Wire.begin(21, 22);
  lcd.init();
  lcd.backlight();

  printLCDLine(0, "ESP32 AQI Node");
  printLCDLine(1, "Connecting WiFi...");

  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("\nWiFi Connected!");
  printLCDLine(1, "WiFi Connected!");
  printLCDLine(2, "Connecting MQTT...");

  client.setServer(mqtt_broker, mqtt_port);
  client.setCallback(callback);
  client.setBufferSize(4096);

  reconnectMQTT();

  // 啟動時自動載入第一次(預設:大里)
  fetchAndPublishAirData(currentSite);
}

void loop() {
  // 1. 維持 MQTT 連線與隨時接收 alex9ufo/sitename 指令
  if (!client.connected()) {
    reconnectMQTT();
  }
  client.loop();

  // 2. 每隔 15 分鐘 (FETCH_INTERVAL) 自動重新呼叫 API 抓取當前站名 (currentSite) 最新數據
  if (millis() - lastFetchTime >= FETCH_INTERVAL) {
    Serial.println("\n[Timer Trigger] 15 minutes elapsed. Auto-refreshing current site: " + currentSite);
    fetchAndPublishAirData(currentSite);
  }
}

這支適用於 Wokwi 模擬環境與 ESP32 的 Arduino 程式,將 ESP32 打造成一個兼具 MQTT 雙向通訊、HTTP 雲端資料抓取、本地 JSON 解析、硬體 LED 警報與 20x4 LCD 狀態顯示的智慧物聯網邊緣節點。

程式的主要核心模組與運作流程如下:

1. 硬體介面與網路初始化 (setup)

  • LCD 20x4 (LiquidCrystal_I2C):透過 I2C 通訊協定(ESP32 腳位 GPIO 21 為 SDA、GPIO 22 為 SCL)初始化 4 行 20 字元的液晶螢幕,用來即時顯示 AQI 指標、測站名稱、PM2.5 濃度、空氣品質狀態與更新時間。

  • 警報 LED 燈 (GPIO 15):設定為輸出腳位,當空氣品質不良時會點亮紅燈進行硬體警報。

  • Wi-Fi 與 MQTT 連線:連線至 Wokwi 模擬的 Wi-Fi (Wokwi-GUEST),並連接至公用 MQTT Broker (broker.hivemq.com:1883),同時訂閱控制主題 alex9ufo/sitename。

2. MQTT 事件驅動:切換測站 (callback)

  • 當您透過 Python GUI 或 Node-RED Dashboard 切換測站(例如發布 汐止 或 士林 至 alex9ufo/sitename)時,ESP32 會立刻透過 callback() 函式接收到該站名,並更新當前追蹤的 currentSite 變數。

  • 接收到新站名後,會立刻呼叫 fetchAndPublishAirData() 進行資料抓取。

3. 本地資料抓取與智慧過濾 (fetchAndPublishAirData)

  • 全台資料一次抓取:為了解決中文網址 URL 轉碼在嵌入式系統上容易失敗的問題,程式改向環境部 API 請求不帶過濾條件的全台測站總表 (limit=100)。

  • ESP32 本地比對 (Local Search):利用 ArduinoJson 將接收到的龐大 JSON 資料載入記憶體(配置 32KB JSON Document),並在 for 迴圈中逐一比對 sitename。只要找到符合 siteName(例如「汐止」)的記錄,就會立刻鎖定該筆資料。

4. 數據解析、硬體觸發與 MQTT 廣播

  • LED 警報邏輯:

    • 檢查該測站的 aqi 是否大於 50 或 pm2.5 是否大於 15.4。

    • 若超過標準,將 LED_PIN 設為 HIGH(亮燈警報),否則保持 LOW(熄滅)。

  • LCD 畫面更新:

    • 第 1 行:顯示 AQI 數值與測站名稱(例:AQI: 54 (大里))。

    • 第 2 行:顯示細懸浮微粒濃度(例:PM2.5: 10 ug/m3)。

    • 第 3 行:顯示判定狀態(Air Bad! (Alarm) 或 Air OK)。

    • 第 4 行:截取並顯示發布時間的時分秒(例:Time: 16:00:00)。

  • 發布至 MQTT (alex9ufo/weather):

    • 將該測站完整的 24 個欄位資料重新序列化(Serialize)成 JSON 字串,並透過 MQTT 發布到 alex9ufo/weather 主題。這能讓 Node-RED 上的 24 欄位 HTML 明細表格同步獲得最新數據。

5. 主迴圈與定時更新 (loop)

  • client.loop():持續維持 MQTT 連線,確保隨時能接收來自外部的測站切換指令。

  • 15 分鐘定時刷新機制 (FETCH_INTERVAL):

    • 利用 millis() 計算時間差。即使使用者沒有手動切換測站,系統每隔 15 分鐘也會自動對當前的 currentSite 重新發動一次 API 查詢,確保監控板上的環境數據隨時保持最新狀態。

Node-red程式





[{"id":"7be861652bc8e89c","type":"inject","z":"2c5f3bfb45d6165e","name":"每15分鐘自動更新","props":[],"repeat":"900","crontab":"","once":true,"onceDelay":0.1,"topic":"","x":150,"y":60,"wires":[["46eb09a75d14a750"]]},{"id":"46eb09a75d14a750","type":"http request","z":"2c5f3bfb45d6165e","name":"抓取環境部 AQI API","method":"GET","ret":"obj","paytoqs":"ignore","url":"https://data.moenv.gov.tw/api/v2/aqx_p_432?api_key=aa1bc2bf-24c2-4c55-980f-a12075c2caec&limit=1000&sort=ImportDate%20desc&format=JSON","tls":"","persist":false,"proxy":"","insecureHTTPParser":false,"authType":"","senderr":false,"headers":[],"x":380,"y":60,"wires":[["e5193a3ede46a1c6"]]},{"id":"e5193a3ede46a1c6","type":"function","z":"2c5f3bfb45d6165e","name":"解析站名列表與總數","func":"var records = msg.payload.records || msg.payload || [];\nflow.set('aqiData', records);\n\nvar siteOptions = [];\nfor (var i = 0; i < records.length; i++) {\n    if (records[i].sitename) {\n        var item = {};\n        var labelName = records[i].sitename + \" (\" + (records[i].county || \"\") + \")\";\n        item[labelName] = records[i].sitename;\n        siteOptions.push(item);\n    }\n}\n\nvar msgCount = { payload: \"全台共有 \" + records.length + \" 個觀測站\" };\nvar msgDropdown = { options: siteOptions };\nvar defaultSite = records.length > 0 ? records[0].sitename : \"基隆\";\nvar msgDefaultSite = { payload: defaultSite };\n\nreturn [msgCount, msgDropdown, msgDefaultSite];","outputs":3,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":630,"y":60,"wires":[["5fc2106fbad4dc8e"],["3d02b496fa9d4c93"],["207ed3d6797b6f04"]]},{"id":"5fc2106fbad4dc8e","type":"ui_text","z":"2c5f3bfb45d6165e","group":"974fdf1f6cb5cb4f","order":1,"width":6,"height":1,"name":"測站總數","label":"統計資訊:","format":"{{msg.payload}}","layout":"row-spread","className":"","style":false,"font":"","fontSize":"","color":"#000000","x":860,"y":20,"wires":[]},{"id":"3d02b496fa9d4c93","type":"ui_dropdown","z":"2c5f3bfb45d6165e","name":"選擇測站","label":"選擇觀測站:","tooltip":"","place":"請選擇測站","group":"974fdf1f6cb5cb4f","order":2,"width":6,"height":1,"passthru":false,"multiple":false,"options":[],"payload":"","topic":"topic","topicType":"msg","className":"","x":860,"y":60,"wires":[["207ed3d6797b6f04"]]},{"id":"207ed3d6797b6f04","type":"function","z":"2c5f3bfb45d6165e","name":"設定 alex9ufo 發行主題","func":"var site = msg.payload || \"基隆\";\nmsg.topic = \"alex9ufo/sitename\";\nmsg.payload = site;\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":890,"y":160,"wires":[["f5e591d78bb7673f"]]},{"id":"f5e591d78bb7673f","type":"mqtt out","z":"2c5f3bfb45d6165e","name":"發行至 HiveMQ","topic":"","qos":"1","retain":"false","respTopic":"","contentType":"","userProps":"","correl":"","expiry":"","broker":"hivemq_broker","x":1120,"y":160,"wires":[]},{"id":"cd94185a1981d9d8","type":"mqtt in","z":"2c5f3bfb45d6165e","name":"接收 alex9ufo/weather","topic":"alex9ufo/weather","qos":"1","datatype":"auto-detect","broker":"hivemq_broker","nl":false,"rap":true,"rh":0,"inputs":0,"x":140,"y":300,"wires":[["c9344815d20ea895","17502c2f447671ec"]]},{"id":"c9344815d20ea895","type":"json","z":"2c5f3bfb45d6165e","name":"解析 JSON 物件","property":"payload","action":"","pretty":false,"x":380,"y":300,"wires":[["95d863494a7b805f","c23f44a14202ebb5"]]},{"id":"95d863494a7b805f","type":"function","z":"2c5f3bfb45d6165e","name":"組裝 24 欄位 HTML 表格","func":"// 1. 自動解析與相容性防護 (處理 JSON 字串或物件)\nvar d = msg.payload;\nif (typeof d === \"string\") {\n    try {\n        d = JSON.parse(d);\n    } catch (e) {\n        d = {};\n    }\n}\nd = d || {};\n\n// 2. 中文名稱對照表\nvar fieldNames = {\n    \"sitename\": \"測站名稱\",\n    \"county\": \"縣市\",\n    \"aqi\": \"AQI 指標\",\n    \"pollutant\": \"主要污染物\",\n    \"status\": \"狀態\",\n    \"so2\": \"二氧化硫 (SO2 ppb)\",\n    \"co\": \"一氧化碳 (CO ppm)\",\n    \"o3\": \"臭氧 (O3 ppb)\",\n    \"o3_8hr\": \"臭氧8小時 (O3 8hr ppb)\",\n    \"pm10\": \"懸浮微粒 (PM10 µg/m³)\",\n    \"pm2.5\": \"細懸浮微粒 (PM2.5 µg/m³)\",\n    \"no2\": \"二氧化氮 (NO2 ppb)\",\n    \"nox\": \"氮氧化物 (NOx ppb)\",\n    \"no\": \"一氧化氮 (NO ppb)\",\n    \"wind_speed\": \"風速 (m/s)\",\n    \"wind_direc\": \"風向 (deg)\",\n    \"publishtime\": \"發布時間\",\n    \"co_8hr\": \"一氧化碳8小時 (CO 8hr)\",\n    \"pm2.5_avg\": \"PM2.5 移動平均\",\n    \"pm10_avg\": \"PM10 移動平均\",\n    \"so2_avg\": \"SO2 移動平均\",\n    \"longitude\": \"經度\",\n    \"latitude\": \"緯度\",\n    \"siteid\": \"測站代碼\"\n};\n\n// 3. 定義欲顯示的欄位順序\nvar keys = [\n    \"sitename\", \"county\", \"aqi\", \"status\", \"pollutant\", \"publishtime\",\n    \"pm2.5\", \"pm2.5_avg\", \"pm10\", \"pm10_avg\",\n    \"o3\", \"o3_8hr\", \"co\", \"co_8hr\", \"so2\", \"so2_avg\",\n    \"no2\", \"nox\", \"no\", \"wind_speed\", \"wind_direc\",\n    \"longitude\", \"latitude\", \"siteid\"\n];\n\n// 4. 組裝 HTML 表格內容\nvar html = '<table style=\"width:100%; border-collapse:collapse; font-size:14px;\">';\nhtml += '<tr style=\"background-color:#2b3e50; color:white;\"><th style=\"padding:8px; border:1px solid #ddd; text-align:left;\">欄位名稱 (英/中)</th><th style=\"padding:8px; border:1px solid #ddd; text-align:left;\">數據內容</th></tr>';\n\nfor (var i = 0; i < keys.length; i++) {\n    var k = keys[i];\n    var label = fieldNames[k] || k;\n\n    // 嚴格判斷值是否存在 (允許 0 或 false,僅排除 undefined, null, 空字串)\n    var rawVal = d[k];\n    var val = (rawVal !== undefined && rawVal !== null && rawVal !== \"\") ? rawVal : \"---\";\n    var bg = (i % 2 === 0) ? \"#f9f9f9\" : \"#ffffff\";\n\n    // 強調關鍵數據 (僅在非 --- 時套用樣式)\n    if (k === \"aqi\" && val !== \"---\") {\n        val = '<b style=\"color:#e74c3c; font-size:16px;\">' + val + '</b>';\n    } else if (k === \"pm2.5\" && val !== \"---\") {\n        val = '<b style=\"color:#27ae60; font-size:15px;\">' + val + ' µg/m³</b>';\n    }\n\n    html += '<tr style=\"background-color:' + bg + ';\">';\n    html += '<td style=\"padding:6px 8px; border:1px solid #ddd; font-weight:bold; color:#333;\">' + k + ' <span style=\"color:#7f8c8d; font-weight:normal;\">(' + label + ')</span></td>';\n    html += '<td style=\"padding:6px 8px; border:1px solid #ddd; color:#2c3e50;\">' + val + '</td>';\n    html += '</tr>';\n}\n\nhtml += '</table>';\n\nmsg.payload = html;\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":630,"y":300,"wires":[["1ad78c572ab65271","74dc6bb79666d52a"]]},{"id":"1ad78c572ab65271","type":"ui_template","z":"2c5f3bfb45d6165e","group":"974fdf1f6cb5cb4f","name":"24欄位明細表格","order":3,"width":"12","height":"12","format":"<div ng-bind-html=\"msg.payload\"></div>","storeOutMessages":true,"fwdInMessages":true,"resendOnRefresh":true,"templateScope":"local","className":"","x":880,"y":300,"wires":[[]]},{"id":"17502c2f447671ec","type":"debug","z":"2c5f3bfb45d6165e","name":"debug 9","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":360,"y":360,"wires":[]},{"id":"c23f44a14202ebb5","type":"debug","z":"2c5f3bfb45d6165e","name":"debug 10","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":600,"y":360,"wires":[]},{"id":"74dc6bb79666d52a","type":"debug","z":"2c5f3bfb45d6165e","name":"debug 11","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":860,"y":360,"wires":[]},{"id":"974fdf1f6cb5cb4f","type":"ui_group","name":"空氣品質監測板","tab":"78b8909ef20c482a","order":1,"disp":true,"width":"12","collapse":false},{"id":"hivemq_broker","type":"mqtt-broker","name":"HiveMQ Broker","broker":"broker.hivemq.com","port":"1883","clientid":"","autoConnect":true,"usetls":false,"protocolVersion":"4","keepalive":"60","cleansession":true,"autoUnsubscribe":true,"birthTopic":"","birthQos":"0","birthPayload":"","birthMsg":{},"closeTopic":"","closePayload":"","closeMsg":{},"willTopic":"","willQos":"0","willPayload":"","willMsg":{},"userProps":"","sessionExpiry":""},{"id":"78b8909ef20c482a","type":"ui_tab","name":"環境監測 Dashboard","icon":"dashboard","order":1},{"id":"7b33920c3da821eb","type":"global-config","env":[],"modules":{"node-red-dashboard":"3.6.6"}}]

這段 Node-RED Flow 程式碼是針對「空氣品質監測儀表板 (Dashboard)」所設計的整合流程,主要實現了選單連動廣播與接收 MQTT 數據並渲染 HTML 表格兩大核心功能。

以下為您詳細拆解整支流程的架構與各個節點運作原理:

核心運作架構圖

整個流程主要分為上、下兩條獨立但互相配合的管線(Pipeline):

[上半部:選單控制與 MQTT 發行管線]

Inject(15分) ──> HTTP Request ──> Function(解析站名列表) ┬─> UI Text(總數)

                                                         ├─> UI Dropdown(選單) ──> Function(設定Topic) ──> MQTT Out(發行站名)

                                                         └───────────────────────┘


[下半部:接收 MQTT 數據與 HTML 表格渲染管線]

MQTT In(接收Weather) ──> JSON 解析 ──> Function(組裝HTML表格) ──> UI Template(表格顯示)

一、上半部:下拉選單初始化與 MQTT 控制發行

這部分負責向環境部抓取全台測站清單,將選單繪製在 Dashboard 上,並將使用者選擇的測站廣播給 Python / ESP32。

  1. 每15分鐘自動更新 (Inject 節點)

    • 作用:啟動時自動觸發一次(once: true),之後每隔 15 分鐘(900 秒)自動執行一次,確保測站選單列表保持最新。

  2. 抓取環境部 AQI API (HTTP Request 節點)

    • 作用:向環境部 API (data.moenv.gov.tw) 發送 GET 請求,取得包含全台所有觀測站的最新完整 JSON 資料。

  3. 解析站名列表與總數 (Function 節點)

    • 作用:

      • 將 API 回傳的測站資料陣列儲存在 flow.set('aqiData', records) 全域變數中。

      • 走訪所有測站,組裝成 ui_dropdown 支援的選單格式(顯示為 測站 (縣市),值為 測站)。

      • 三向輸出:

        • 輸出 1:傳送「全台共有 XX 個觀測站」給 UI Text 顯示統計資訊。

        • 輸出 2:傳送選單選項陣列給 UI Dropdown 下拉選單。

        • 輸出 3:傳送預設的第一個測站名稱(如「基隆」)直接傳給主題設定節點。

  4. 選擇測站 (UI Dropdown 節點)

    • 作用:在 Dashboard 提供使用者下拉選取測站。使用者一旦點擊切換,就會輸出選擇的測站名稱(例如:「大里」)。

  5. 設定 alex9ufo 發行主題 (Function 節點)

    • 作用:接收來自預設或下拉選單的測站名稱,將 MQTT 主題包裝為 alex9ufo/sitename,準備進行 MQTT 廣播。

  6. 發行至 HiveMQ (MQTT Out 節點)

    • 作用:連線至 broker.hivemq.com,將選擇的測站名稱發布至 alex9ufo/sitename。Python GUI 與 ESP32 收到後便會開始抓取該測站的資料。

二、下半部:接收數據與渲染 24 欄位明細表格

這部分負責監聽 Python / ESP32 發布回來的完整空氣品質數據,並渲染出雙語表格。

  1. 接收 alex9ufo/weather (MQTT In 節點)

    • 作用:持續監聽 broker.hivemq.com 上的 alex9ufo/weather 主題。只要 Python 或 ESP32 將單一測站的 24 欄位 JSON 發送出來,此節點就會立刻接收。

  2. 解析 JSON 物件 (JSON 節點)

    • 作用:自動將傳入的 JSON 字串轉換為 JavaScript 的 Object 物件,方便後續讀取屬性。

  3. 組裝 24 欄位 HTML 表格 (Function 節點)

    • 作用:(即我們剛剛優化的關鍵節點)

      • 相容性防護:自動檢查 Payload 是字串還是物件,避免格式不符讀不到屬性。

      • 中英對照與順序:建立 24 個欄位的中英文對照表(fieldNames)與固定顯示順序(keys)。

      • 資料清洗:嚴格判斷數值,若無數據則顯示為 ---。

      • 關鍵字突出:自動尋找 aqi(紅色加粗)與 pm2.5(綠色加粗並補上單位 µg/m³)。

      • HTML 繪製:透過 for 迴圈動態繪製雙色交錯(#f9f9f9 與 #ffffff)的 CSS 表格,並輸出為 msg.payload。

  4. 24欄位明細表格 (UI Template 節點)

    • 作用:使用 Angular 的 <div ng-bind-html="msg.payload"></div> 指令,將 Function 節點組裝好的 HTML 表格渲染到 Dashboard 畫面上。

三、Debug 輔助節點

  • debug 9:用來印出 mqtt in 剛收到的原始 Payload。

  • debug 10:用來印出過完 json 節點解析後的物件。

  • debug 11:用來印出最終組裝完成的 HTML 字串,方便開發者進行除錯。


apiKey = "a1bc2bf-24c2-455-98f-a205c2aec"

https://data.moenv.gov.tw/api/v2/aqx_p_432?offset=0&limit=1000&api_key= apiKey


[ { "sitename": "基隆", "county": "基隆市", "aqi": "61", "pollutant": "細懸浮微粒", "status": "普通", "so2": "2.6", "co": "0.28", "o3": "73", "o3_8hr": "54", "pm10": "32", "pm2.5": "19", "no2": "4", "nox": "4.9", "no": "0.8", "wind_speed": "2.6", "wind_direc": "85", "publishtime": "2026\/09\/28 15:00:00", "co_8hr": "0.3", "pm2.5_avg": "16.1", "pm10_avg": "28", "so2_avg": "1", "longitude": "121.760056", "latitude": "25.129168", "siteid": "1" }, { "sitename": "汐止", "county": "新北市", "aqi": "56", "pollutant": "細懸浮微粒", "status": "普通", "so2": "3.6", "co": "0.22", "o3": "62", "o3_8hr": "46", "pm10": "23", "pm2.5": "11", "no2": "7", "nox": "8.5", "no": "1.1", "wind_speed": "2.4", "wind_direc": "55", "publishtime": "2026\/09\/28 15:00:00", "co_8hr": "0.2", "pm2.5_avg": "14.4", "pm10_avg": "25", "so2_avg": "1", "longitude": "121.64081", "latitude": "25.06624", "siteid": "2" }, { "sitename": "新店", "county": "新北市", "aqi": "38", "pollutant": "", "status": "良好", "so2": "0.7", "co": "0.17", "o3": "52", "o3_8hr": "41", "pm10": "8", "pm2.5": "3", "no2": "3", "nox": "4.1", "no": "0.5", "wind_speed": "1.5", "wind_direc": "249", "publishtime": "2026\/09\/28 15:00:00", "co_8hr": "0.2", "pm2.5_avg": "7", "pm10_avg": "13", "so2_avg": "0", "longitude": "121.53778", "latitude": "24.977222", "siteid": "4" }, { "sitename": "土城", "county": "新北市", "aqi": "46", "pollutant": "", "status": "良好", "so2": "1.2", "co": "0.2", "o3": "55", "o3_8hr": "38", "pm10": "15", "pm2.5": "9", "no2": "4", "nox": "4.8", "no": "0.4", "wind_speed": "2.1", "wind_direc": "299", "publishtime": "2026\/09\/28 15:00:00", "co_8hr": "0.2", "pm2.5_avg": "11.4", "pm10_avg": "21", "so2_avg": "1", "longitude": "121.45186", "latitude": "24.982529", "siteid": "5" }, { "sitename": "板橋", "county": "新北市", "aqi": "40", "pollutant": "", "status": "良好", "so2": "1", "co": "0.22", "o3": "63", "o3_8hr": "43", "pm10": "20", "pm2.5": "6", "no2": "4", "nox": "5.3", "no": "0.6", "wind_speed": "3.1", "wind_direc": "295", "publishtime": "2026\/09\/28 15:00:00", "co_8hr": "0.2", "pm2.5_avg": "9.4", "pm10_avg": "18", "so2_avg": "0", "longitude": "121.458664", "latitude": "25.012972", "siteid": "6" }, { "sitename": "新莊", "county": "新北市", "aqi": "44", "pollutant": "", "status": "良好", "so2": "1.2", "co": "0.16", "o3": "64", "o3_8hr": "43", "pm10": "18", "pm2.5": "8", "no2": "4", "nox": "5", "no": "0.9", "wind_speed": "2", "wind_direc": "271", "publishtime": "2026\/09\/28 15:00:00", "co_8hr": "0.2", "pm2.5_avg": "10.8", "pm10_avg": "22", "so2_avg": "1", "longitude": "121.4325", "latitude": "25.037971", "siteid": "7" }, { "sitename": "菜寮", "county": "新北市", "aqi": "41", "pollutant": "", "status": "良好", "so2": "0.7", "co": "0.22", "o3": "64", "o3_8hr": "44", "pm10": "16", "pm2.5": "8", "no2": "5", "nox": "5.7", "no": "0", "wind_speed": "3.4", "wind_direc": "276", "publishtime": "2026\/09\/28 15:00:00", "co_8hr": "0.2", "pm2.5_avg": "8.6", "pm10_avg": "16", "so2_avg": "0", "longitude": "121.481026", "latitude": "25.06895", "siteid": "8" }, { "sitename": "林口", "county": "新北市", "aqi": "39", "pollutant": "", "status": "良好", "so2": "0.8", "co": "0.18", "o3": "53", "o3_8hr": "40", "pm10": "31", "pm2.5": "13", "no2": "4", "nox": "4.9", "no": "0.5", "wind_speed": "3", "wind_direc": "272", "publishtime": "2026\/09\/28 15:00:00", "co_8hr": "0.2", "pm2.5_avg": "9.7", "pm10_avg": "20", "so2_avg": "1", "longitude": "121.365486", "latitude": "25.07799", "siteid": "9" }, { "sitename": "淡水", "county": "新北市", "aqi": "42", "pollutant": "", "status": "良好", "so2": "1.2", "co": "0.19", "o3": "45", "o3_8hr": "40", "pm10": "21", "pm2.5": "7", "no2": "5", "nox": "6.9", "no": "1.6", "wind_speed": "", "wind_direc": "", "publishtime": "2026\/09\/28 15:00:00", "co_8hr": "0.2", "pm2.5_avg": "10.3", "pm10_avg": "21", "so2_avg": "0", "longitude": "121.44924", "latitude": "25.1645", "siteid": "10" }, { "sitename": "士林", "county": "臺北市", "aqi": "46", "pollutant": "", "status": "良好", "so2": "1.2", "co": "0.22", "o3": "62", "o3_8hr": "46", "pm10": "27", "pm2.5": "11", "no2": "6", "nox": "8.4", "no": "1.6", "wind_speed": "3.5", "wind_direc": "326", "publishtime": "2026\/09\/28 15:00:00", "co_8hr": "0.2", "pm2.5_avg": "11.5", "pm10_avg": "23", "so2_avg": "1", "longitude": "121.51666", "latitude": "25.10334", "siteid": "11" }, { "sitename": "中山", "county": "臺北市", "aqi": "43", "pollutant": "", "status": "良好", "so2": "0.8", "co": "0.27", "o3": "59", "o3_8hr": "41", "pm10": "40", "pm2.5": "13", "no2": "10", "nox": "12.6", "no": "1.7", "wind_speed": "3.2", "wind_direc": "7", "publishtime": "2026\/09\/28 15:00:00", "co_8hr": "0.3", "pm2.5_avg": "10.7", "pm10_avg": "22", "so2_avg": "0", "longitude": "121.52653", "latitude": "25.06236", "siteid": "12" }, { "sitename": "萬華", "county": "臺北市", "aqi": "40", "pollutant": "", "status": "良好", "so2": "1.5", "co": "0.26", "o3": "63", "o3_8hr": "43", "pm10": "15", "pm2.5": "5", "no2": "6", "nox": "7.8", "no": "0.8", "wind_speed": "1.5", "wind_direc": "352", "publishtime": "2026\/09\/28 15:00:00", "co_8hr": "0.2", "pm2.5_avg": "9.2", "pm10_avg": "17", "so2_avg": "0", "longitude": "121.50797", "latitude": "25.046503", "siteid": "13" }, { "sitename": "古亭", "county": "臺北市", "aqi": "42", "pollutant": "", "status": "良好", "so2": "1.2", "co": "0.2", "o3": "62", "o3_8hr": "45", "pm10": "20", "pm2.5": "5", "no2": "4", "nox": "5.6", "no": "1.1", "wind_speed": "1.5", "wind_direc": "311", "publishtime": "2026\/09\/28 15:00:00", "co_8hr": "0.2", "pm2.5_avg": "8.9", "pm10_avg": "18", "so2_avg": "1", "longitude": "121.52956", "latitude": "25.020609", "siteid": "14" }, { "sitename": "松山", "county": "臺北市", "aqi": "43", "pollutant": "", "status": "良好", "so2": "1.1", "co": "0.23", "o3": "64", "o3_8hr": "45", "pm10": "28", "pm2.5": "8", "no2": "6", "nox": "9.3", "no": "2.5", "wind_speed": "2.1", "wind_direc": "309", "publishtime": "2026\/09\/28 15:00:00", "co_8hr": "0.2", "pm2.5_avg": "10.6", "pm10_avg": "21", "so2_avg": "1", "longitude": "121.57861", "latitude": "25.05", "siteid": "15" }, { "sitename": "大同", "county": "臺北市", "aqi": "48", "pollutant": "", "status": "良好", "so2": "0.8", "co": "0.47", "o3": "52", "o3_8hr": "28", "pm10": "37", "pm2.5": "10", "no2": "14", "nox": "17", "no": "2.1", "wind_speed": "-", "wind_direc": "-", "publishtime": "2026\/09\/28 15:00:00", "co_8hr": "0.8", "pm2.5_avg": "12", "pm10_avg": "25", "so2_avg": "0", "longitude": "121.51342", "latitude": "25.063314", "siteid": "16" }, { "sitename": "桃園", "county": "桃園市", "aqi": "38", "pollutant": "", "status": "良好", "so2": "1.6", "co": "0.16", "o3": "58", "o3_8hr": "41", "pm10": "22", "pm2.5": "7", "no2": "3", "nox": "4.9", "no": "1.1", "wind_speed": "2.8", "wind_direc": "297", "publishtime": "2026\/09\/28 15:00:00", "co_8hr": "0.1", "pm2.5_avg": "8.2", "pm10_avg": "16", "so2_avg": "1", "longitude": "121.30501", "latitude": "24.99471", "siteid": "17" }, { "sitename": "大園", "county": "桃園市", "aqi": "42", "pollutant": "", "status": "良好", "so2": "1", "co": "0.15", "o3": "51", "o3_8hr": "39", "pm10": "26", "pm2.5": "9", "no2": "2", "nox": "3", "no": "0.4", "wind_speed": "4.7", "wind_direc": "248", "publishtime": "2026\/09\/28 15:00:00", "co_8hr": "0.1", "pm2.5_avg": "10.3", "pm10_avg": "23", "so2_avg": "0", "longitude": "121.202515", "latitude": "25.061003", "siteid": "18" }, { "sitename": "觀音", "county": "桃園市", "aqi": "37", "pollutant": "", "status": "良好", "so2": "0.2", "co": "0.12", "o3": "42", "o3_8hr": "37", "pm10": "23", "pm2.5": "8", "no2": "2", "nox": "3.6", "no": "1.4", "wind_speed": "4.9", "wind_direc": "252", "publishtime": "2026\/09\/28 15:00:00", "co_8hr": "0.1", "pm2.5_avg": "8.5", "pm10_avg": "22", "so2_avg": "0", "longitude": "121.08283", "latitude": "25.035568", "siteid": "19" }, { "sitename": "平鎮", "county": "桃園市", "aqi": "37", "pollutant": "", "status": "良好", "so2": "1", "co": "0.17", "o3": "53", "o3_8hr": "40", "pm10": "23", "pm2.5": "8", "no2": "4", "nox": "5", "no": "0.8", "wind_speed": "2.6", "wind_direc": "280", "publishtime": "2026\/09\/28 15:00:00", "co_8hr": "0.1", "pm2.5_avg": "9.1", "pm10_avg": "19", "so2_avg": "0", "longitude": "121.20399", "latitude": "24.952785", "siteid": "20" }, { "sitename": "龍潭", "county": "桃園市", "aqi": "35", "pollutant": "", "status": "良好", "so2": "1.4", "co": "0.18", "o3": "59", "o3_8hr": "38", "pm10": "19", "pm2.5": "7", "no2": "4", "nox": "4.1", "no": "0", "wind_speed": "2.8", "wind_direc": "254", "publishtime": "2026\/09\/28 15:00:00", "co_8hr": "0.1", "pm2.5_avg": "6.4", "pm10_avg": "17", "so2_avg": "1", "longitude": "121.21646", "latitude": "24.864", "siteid": "21" }, { "sitename": "湖口", "county": "新竹縣", "aqi": "36", "pollutant": "", "status": "良好", "so2": "1.2", "co": "0.14", "o3": "46", "o3_8hr": "39", "pm10": "16", "pm2.5": "9", "no2": "2", "nox": "2.8", "no": "0.2", "wind_speed": "4.3", "wind_direc": "264", "publishtime": "2026\/09\/28 15:00:00", "co_8hr": "0.1", "pm2.5_avg": "8.7", "pm10_avg": "18", "so2_avg": "1", "longitude": "121.03887", "latitude": "24.900097", "siteid": "22" }, { "sitename": "竹東", "county": "新竹縣", "aqi": "31", "pollutant": "", "status": "良好", "so2": "1.1", "co": "0.18", "o3": "54", "o3_8hr": "34", "pm10": "26", "pm2.5": "8", "no2": "2", "nox": "3.4", "no": "0.6", "wind_speed": "3", "wind_direc": "274", "publishtime": "2026\/09\/28 15:00:00", "co_8hr": "0.1", "pm2.5_avg": "7.6", "pm10_avg": "15", "so2_avg": "1", "longitude": "121.08896", "latitude": "24.740913", "siteid": "23" }, { "sitename": "新竹", "county": "新竹市", "aqi": "39", "pollutant": "", "status": "良好", "so2": "2.6", "co": "0.21", "o3": "39", "o3_8hr": "35", "pm10": "22", "pm2.5": "11", "no2": "7", "nox": "8.5", "no": "1.4", "wind_speed": "4.8", "wind_direc": "245", "publishtime": "2026\/09\/28 15:00:00", "co_8hr": "0.2", "pm2.5_avg": "9.7", "pm10_avg": "20", "so2_avg": "1", "longitude": "120.97237", "latitude": "24.805635", "siteid": "24" }, { "sitename": "頭份", "county": "苗栗縣", "aqi": "34", "pollutant": "", "status": "良好", "so2": "1.6", "co": "0.15", "o3": "41", "o3_8hr": "37", "pm10": "22", "pm2.5": "5", "no2": "1", "nox": "3.4", "no": "1.5", "wind_speed": "5.1", "wind_direc": "277", "publishtime": "2026\/09\/28 15:00:00", "co_8hr": "0.1", "pm2.5_avg": "4.2", "pm10_avg": "19", "so2_avg": "1", "longitude": "120.89869", "latitude": "24.696907", "siteid": "25" }, { "sitename": "苗栗", "county": "苗栗縣", "aqi": "37", "pollutant": "", "status": "良好", "so2": "1.9", "co": "0.15", "o3": "45", "o3_8hr": "40", "pm10": "13", "pm2.5": "5", "no2": "4", "nox": "5.2", "no": "1.2", "wind_speed": "2.2", "wind_direc": "269", "publishtime": "2026\/09\/28 15:00:00", "co_8hr": "0.1", "pm2.5_avg": "6.5", "pm10_avg": "13", "so2_avg": "1", "longitude": "120.820114", "latitude": "24.564991", "siteid": "26" }, { "sitename": "三義", "county": "苗栗縣", "aqi": "37", "pollutant": "", "status": "良好", "so2": "0.8", "co": "0.18", "o3": "51", "o3_8hr": "40", "pm10": "23", "pm2.5": "10", "no2": "2", "nox": "4", "no": "1.6", "wind_speed": "2.5", "wind_direc": "199", "publishtime": "2026\/09\/28 15:00:00", "co_8hr": "0.1", "pm2.5_avg": "7", "pm10_avg": "18", "so2_avg": "1", "longitude": "120.75957", "latitude": "24.382484", "siteid": "27" }, { "sitename": "豐原", "county": "臺中市", "aqi": "37", "pollutant": "", "status": "良好", "so2": "0.9", "co": "0.17", "o3": "52", "o3_8hr": "40", "pm10": "16", "pm2.5": "5", "no2": "3", "nox": "4.3", "no": "0.7", "wind_speed": "3.6", "wind_direc": "208", "publishtime": "2026\/09\/28 15:00:00", "co_8hr": "0.1", "pm2.5_avg": "4.4", "pm10_avg": "13", "so2_avg": "0", "longitude": "120.74252", "latitude": "24.256998", "siteid": "28" }, { "sitename": "沙鹿", "county": "臺中市", "aqi": "34", "pollutant": "", "status": "良好", "so2": "0.7", "co": "0.15", "o3": "38", "o3_8hr": "37", "pm10": "18", "pm2.5": "6", "no2": "3", "nox": "4.5", "no": "1.5", "wind_speed": "3.9", "wind_direc": "253", "publishtime": "2026\/09\/28 15:00:00", "co_8hr": "0.1", "pm2.5_avg": "5.6", "pm10_avg": "18", "so2_avg": "0", "longitude": "120.568794", "latitude": "24.225628", "siteid": "29" }, { "sitename": "大里", "county": "臺中市", "aqi": "33", "pollutant": "", "status": "良好", "so2": "1.1", "co": "0.15", "o3": "47", "o3_8hr": "36", "pm10": "19", "pm2.5": "7", "no2": "3", "nox": "4", "no": "0.1", "wind_speed": "3.7", "wind_direc": "254", "publishtime": "2026\/09\/28 15:00:00", "co_8hr": "0.1", "pm2.5_avg": "5.3", "pm10_avg": "12", "so2_avg": "1", "longitude": "120.678444", "latitude": "24.099611", "siteid": "30" }, { "sitename": "忠明", "county": "臺中市", "aqi": "35", "pollutant": "", "status": "良好", "so2": "1.4", "co": "0.19", "o3": "47", "o3_8hr": "38", "pm10": "17", "pm2.5": "8", "no2": "3", "nox": "4.7", "no": "1.2", "wind_speed": "2", "wind_direc": "197", "publishtime": "2026\/09\/28 15:00:00", "co_8hr": "0.1", "pm2.5_avg": "6.1", "pm10_avg": "11", "so2_avg": "1", "longitude": "120.64109", "latitude": "24.151958", "siteid": "31" }, { "sitename": "西屯", "county": "臺中市", "aqi": "38", "pollutant": "", "status": "良好", "so2": "1.2", "co": "0.14", "o3": "45", "o3_8hr": "36", "pm10": "22", "pm2.5": "12", "no2": "2", "nox": "3.1", "no": "0.8", "wind_speed": "4", "wind_direc": "220", "publishtime": "2026\/09\/28 15:00:00", "co_8hr": "0.1", "pm2.5_avg": "9.5", "pm10_avg": "17", "so2_avg": "1", "longitude": "120.61692", "latitude": "24.162197", "siteid": "32" }, { "sitename": "彰化", "county": "彰化縣", "aqi": "36", "pollutant": "", "status": "良好", "so2": "1.4", "co": "0.13", "o3": "42", "o3_8hr": "39", "pm10": "18", "pm2.5": "4", "no2": "3", "nox": "5.8", "no": "1.9", "wind_speed": "3.1", "wind_direc": "238", "publishtime": "2026\/09\/28 15:00:00", "co_8hr": "0.1", "pm2.5_avg": "4.5", "pm10_avg": "15", "so2_avg": "1", "longitude": "120.54152", "latitude": "24.066", "siteid": "33" }, { "sitename": "線西", "county": "彰化縣", "aqi": "32", "pollutant": "", "status": "良好", "so2": "1.1", "co": "0.14", "o3": "37", "o3_8hr": "35", "pm10": "20", "pm2.5": "4", "no2": "2", "nox": "2.4", "no": "0.3", "wind_speed": "5.2", "wind_direc": "250", "publishtime": "2026\/09\/28 15:00:00", "co_8hr": "0.1", "pm2.5_avg": "5.7", "pm10_avg": "15", "so2_avg": "0", "longitude": "120.46906", "latitude": "24.131672", "siteid": "34" }, { "sitename": "二林", "county": "彰化縣", "aqi": "36", "pollutant": "", "status": "良好", "so2": "1.2", "co": "0.15", "o3": "33", "o3_8hr": "31", "pm10": "16", "pm2.5": "4", "no2": "0", "nox": "1.5", "no": "0.8", "wind_speed": "3.9", "wind_direc": "256", "publishtime": "2026\/09\/28 15:00:00", "co_8hr": "0.1", "pm2.5_avg": "9", "pm10_avg": "20", "so2_avg": "1", "longitude": "120.40965", "latitude": "23.925175", "siteid": "35" }, { "sitename": "南投", "county": "南投縣", "aqi": "34", "pollutant": "", "status": "良好", "so2": "1.2", "co": "0.14", "o3": "49", "o3_8hr": "37", "pm10": "20", "pm2.5": "3", "no2": "1", "nox": "2.8", "no": "1", "wind_speed": "2.6", "wind_direc": "262", "publishtime": "2026\/09\/28 15:00:00", "co_8hr": "0.1", "pm2.5_avg": "4.3", "pm10_avg": "14", "so2_avg": "1", "longitude": "120.6853", "latitude": "23.913", "siteid": "36" }, { "sitename": "斗六", "county": "雲林縣", "aqi": "34", "pollutant": "", "status": "良好", "so2": "0.9", "co": "0.15", "o3": "37", "o3_8hr": "37", "pm10": "16", "pm2.5": "5", "no2": "3", "nox": "4.1", "no": "0.8", "wind_speed": "2.7", "wind_direc": "244", "publishtime": "2026\/09\/28 15:00:00", "co_8hr": "0.1", "pm2.5_avg": "6", "pm10_avg": "14", "so2_avg": "1", "longitude": "120.54499", "latitude": "23.711853", "siteid": "37" }, { "sitename": "崙背", "county": "雲林縣", "aqi": "37", "pollutant": "", "status": "良好", "so2": "0.9", "co": "0.13", "o3": "34", "o3_8hr": "30", "pm10": "32", "pm2.5": "6", "no2": "1", "nox": "2.4", "no": "0.6", "wind_speed": "2.2", "wind_direc": "216", "publishtime": "2026\/09\/28 15:00:00", "co_8hr": "0.1", "pm2.5_avg": "5.3", "pm10_avg": "22", "so2_avg": "1", "longitude": "120.34874", "latitude": "23.757547", "siteid": "38" }, { "sitename": "新港", "county": "嘉義縣", "aqi": "31", "pollutant": "", "status": "良好", "so2": "1.5", "co": "0.13", "o3": "36", "o3_8hr": "34", "pm10": "15", "pm2.5": "5", "no2": "1", "nox": "1.9", "no": "0.8", "wind_speed": "3.5", "wind_direc": "247", "publishtime": "2026\/09\/28 15:00:00", "co_8hr": "0.1", "pm2.5_avg": "6", "pm10_avg": "14", "so2_avg": "1", "longitude": "120.34553", "latitude": "23.554838", "siteid": "39" }, { "sitename": "朴子", "county": "嘉義縣", "aqi": "28", "pollutant": "", "status": "良好", "so2": "1.1", "co": "0.15", "o3": "36", "o3_8hr": "30", "pm10": "15", "pm2.5": "4", "no2": "2", "nox": "4.3", "no": "1.7", "wind_speed": "2.4", "wind_direc": "257", "publishtime": "2026\/09\/28 15:00:00", "co_8hr": "0.1", "pm2.5_avg": "3.3", "pm10_avg": "17", "so2_avg": "1", "longitude": "120.2478", "latitude": "23.46538", "siteid": "40" }, { "sitename": "臺西", "county": "雲林縣", "aqi": "30", "pollutant": "", "status": "良好", "so2": "1.5", "co": "0.15", "o3": "40", "o3_8hr": "32", "pm10": "17", "pm2.5": "6", "no2": "0", "nox": "1.9", "no": "1.3", "wind_speed": "4.9", "wind_direc": "225", "publishtime": "2026\/09\/28 15:00:00", "co_8hr": "0.1", "pm2.5_avg": "7.2", "pm10_avg": "17", "so2_avg": "1", "longitude": "120.19933", "latitude": "23.702175", "siteid": "41" }, { "sitename": "嘉義", "county": "嘉義市", "aqi": "31", "pollutant": "", "status": "良好", "so2": "0.9", "co": "0.13", "o3": "36", "o3_8hr": "33", "pm10": "13", "pm2.5": "4", "no2": "2", "nox": "3.2", "no": "0.6", "wind_speed": "3.8", "wind_direc": "250", "publishtime": "2026\/09\/28 15:00:00", "co_8hr": "0.1", "pm2.5_avg": "4.8", "pm10_avg": "16", "so2_avg": "1", "longitude": "120.44125", "latitude": "23.464779", "siteid": "42" }, { "sitename": "新營", "county": "臺南市", "aqi": "29", "pollutant": "", "status": "良好", "so2": "0.6", "co": "0.13", "o3": "33", "o3_8hr": "31", "pm10": "14", "pm2.5": "9", "no2": "3", "nox": "3.5", "no": "0.3", "wind_speed": "2.6", "wind_direc": "263", "publishtime": "2026\/09\/28 15:00:00", "co_8hr": "0.1", "pm2.5_avg": "7", "pm10_avg": "15", "so2_avg": "0", "longitude": "120.31725", "latitude": "23.305634", "siteid": "43" }, { "sitename": "善化", "county": "臺南市", "aqi": "28", "pollutant": "", "status": "良好", "so2": "0.6", "co": "0.13", "o3": "32", "o3_8hr": "30", "pm10": "12", "pm2.5": "2", "no2": "3", "nox": "4.8", "no": "1.1", "wind_speed": "3.5", "wind_direc": "254", "publishtime": "2026\/09\/28 15:00:00", "co_8hr": "0.1", "pm2.5_avg": "4.8", "pm10_avg": "13", "so2_avg": "0", "longitude": "120.29741", "latitude": "23.113377", "siteid": "44" }, { "sitename": "安南", "county": "臺南市", "aqi": "30", "pollutant": "", "status": "良好", "so2": "0.6", "co": "0.18", "o3": "34", "o3_8hr": "32", "pm10": "12", "pm2.5": "4", "no2": "1", "nox": "3", "no": "1.5", "wind_speed": "2.7", "wind_direc": "279", "publishtime": "2026\/09\/28 15:00:00", "co_8hr": "0.1", "pm2.5_avg": "5.1", "pm10_avg": "13", "so2_avg": "0", "longitude": "120.2175", "latitude": "23.048197", "siteid": "45" }, { "sitename": "臺南", "county": "臺南市", "aqi": "30", "pollutant": "", "status": "良好", "so2": "1", "co": "0.15", "o3": "33", "o3_8hr": "32", "pm10": "12", "pm2.5": "8", "no2": "2", "nox": "3.6", "no": "0.8", "wind_speed": "2.7", "wind_direc": "273", "publishtime": "2026\/09\/28 15:00:00", "co_8hr": "0.1", "pm2.5_avg": "7.2", "pm10_avg": "14", "so2_avg": "1", "longitude": "120.21948", "latitude": "22.989283", "siteid": "46" }, { "sitename": "美濃", "county": "高雄市", "aqi": "31", "pollutant": "", "status": "良好", "so2": "1.2", "co": "0.16", "o3": "40", "o3_8hr": "33", "pm10": "37", "pm2.5": "2", "no2": "2", "nox": "3", "no": "0.6", "wind_speed": "2.5", "wind_direc": "282", "publishtime": "2026\/09\/28 15:00:00", "co_8hr": "0.1", "pm2.5_avg": "3.3", "pm10_avg": "17", "so2_avg": "1", "longitude": "120.53054", "latitude": "22.883583", "siteid": "47" }, { "sitename": "橋頭", "county": "高雄市", "aqi": "31", "pollutant": "", "status": "良好", "so2": "1", "co": "0.12", "o3": "34", "o3_8hr": "33", "pm10": "15", "pm2.5": "6", "no2": "2", "nox": "3.6", "no": "0.7", "wind_speed": "3", "wind_direc": "279", "publishtime": "2026\/09\/28 15:00:00", "co_8hr": "0.1", "pm2.5_avg": "6.7", "pm10_avg": "15", "so2_avg": "1", "longitude": "120.30569", "latitude": "22.757505", "siteid": "48" }, { "sitename": "仁武", "county": "高雄市", "aqi": "29", "pollutant": "", "status": "良好", "so2": "1", "co": "0.15", "o3": "34", "o3_8hr": "31", "pm10": "13", "pm2.5": "9", "no2": "0", "nox": "1.3", "no": "1", "wind_speed": "2.3", "wind_direc": "307", "publishtime": "2026\/09\/28 15:00:00", "co_8hr": "0.1", "pm2.5_avg": "7.2", "pm10_avg": "14", "so2_avg": "0", "longitude": "120.332634", "latitude": "22.689056", "siteid": "49" }, { "sitename": "鳳山", "county": "高雄市", "aqi": "34", "pollutant": "", "status": "良好", "so2": "0.3", "co": "0.28", "o3": "30", "o3_8hr": "29", "pm10": "8", "pm2.5": "4", "no2": "3", "nox": "5.9", "no": "2", "wind_speed": "1.4", "wind_direc": "299", "publishtime": "2026\/09\/28 15:00:00", "co_8hr": "0.2", "pm2.5_avg": "8.5", "pm10_avg": "14", "so2_avg": "0", "longitude": "120.35742", "latitude": "22.628126", "siteid": "50" }, { "sitename": "大寮", "county": "高雄市", "aqi": "32", "pollutant": "", "status": "良好", "so2": "0.5", "co": "0.13", "o3": "28", "o3_8hr": "32", "pm10": "19", "pm2.5": "10", "no2": "3", "nox": "5.1", "no": "1.1", "wind_speed": "3.2", "wind_direc": "302", "publishtime": "2026\/09\/28 15:00:00", "co_8hr": "0.1", "pm2.5_avg": "6.8", "pm10_avg": "19", "so2_avg": "1", "longitude": "120.42531", "latitude": "22.564137", "siteid": "51" }, { "sitename": "林園", "county": "高雄市", "aqi": "37", "pollutant": "", "status": "良好", "so2": "1.2", "co": "0.15", "o3": "36", "o3_8hr": "33", "pm10": "27", "pm2.5": "5", "no2": "2", "nox": "3.5", "no": "1", "wind_speed": "3.9", "wind_direc": "287", "publishtime": "2026\/09\/28 15:00:00", "co_8hr": "0.1", "pm2.5_avg": "7.3", "pm10_avg": "22", "so2_avg": "0", "longitude": "120.41175", "latitude": "22.4795", "siteid": "52" }, { "sitename": "楠梓", "county": "高雄市", "aqi": "31", "pollutant": "", "status": "良好", "so2": "0.7", "co": "0.14", "o3": "34", "o3_8hr": "30", "pm10": "20", "pm2.5": "7", "no2": "3", "nox": "4.6", "no": "1", "wind_speed": "2.7", "wind_direc": "286", "publishtime": "2026\/09\/28 15:00:00", "co_8hr": "0.1", "pm2.5_avg": "7.7", "pm10_avg": "17", "so2_avg": "0", "longitude": "120.328285", "latitude": "22.733667", "siteid": "53" }, { "sitename": "左營", "county": "高雄市", "aqi": "32", "pollutant": "", "status": "良好", "so2": "0.5", "co": "0.15", "o3": "36", "o3_8hr": "30", "pm10": "17", "pm2.5": "10", "no2": "2", "nox": "3.2", "no": "1", "wind_speed": "3", "wind_direc": "282", "publishtime": "2026\/09\/28 15:00:00", "co_8hr": "0.1", "pm2.5_avg": "7.9", "pm10_avg": "13", "so2_avg": "0", "longitude": "120.292915", "latitude": "22.674862", "siteid": "54" }, { "sitename": "前金", "county": "高雄市", "aqi": "29", "pollutant": "", "status": "良好", "so2": "0.4", "co": "0.17", "o3": "35", "o3_8hr": "31", "pm10": "18", "pm2.5": "5", "no2": "2", "nox": "2.7", "no": "0.5", "wind_speed": "4.3", "wind_direc": "314", "publishtime": "2026\/09\/28 15:00:00", "co_8hr": "0.1", "pm2.5_avg": "5.2", "pm10_avg": "14", "so2_avg": "0", "longitude": "120.28676", "latitude": "22.633904", "siteid": "56" }, { "sitename": "前鎮", "county": "高雄市", "aqi": "30", "pollutant": "", "status": "良好", "so2": "1.3", "co": "0.15", "o3": "37", "o3_8hr": "32", "pm10": "12", "pm2.5": "7", "no2": "4", "nox": "6.2", "no": "1.8", "wind_speed": "1.6", "wind_direc": "342", "publishtime": "2026\/09\/28 15:00:00", "co_8hr": "0.1", "pm2.5_avg": "7", "pm10_avg": "13", "so2_avg": "1", "longitude": "120.308334", "latitude": "22.60445", "siteid": "57" }, { "sitename": "小港", "county": "高雄市", "aqi": "31", "pollutant": "", "status": "良好", "so2": "0.6", "co": "0.15", "o3": "35", "o3_8hr": "29", "pm10": "19", "pm2.5": "6", "no2": "5", "nox": "8.1", "no": "2.7", "wind_speed": "3.9", "wind_direc": "274", "publishtime": "2026\/09\/28 15:00:00", "co_8hr": "0.1", "pm2.5_avg": "7.8", "pm10_avg": "16", "so2_avg": "0", "longitude": "120.33774", "latitude": "22.565832", "siteid": "58" }, { "sitename": "屏東", "county": "屏東縣", "aqi": "32", "pollutant": "", "status": "良好", "so2": "1", "co": "0.18", "o3": "38", "o3_8hr": "35", "pm10": "24", "pm2.5": "4", "no2": "4", "nox": "4.9", "no": "0.9", "wind_speed": "3.2", "wind_direc": "281", "publishtime": "2026\/09\/28 15:00:00", "co_8hr": "0.1", "pm2.5_avg": "5.2", "pm10_avg": "18", "so2_avg": "0", "longitude": "120.48803", "latitude": "22.67308", "siteid": "59" }, { "sitename": "潮州", "county": "屏東縣", "aqi": "34", "pollutant": "", "status": "良好", "so2": "1.1", "co": "0.14", "o3": "42", "o3_8hr": "37", "pm10": "30", "pm2.5": "8", "no2": "2", "nox": "3.7", "no": "1", "wind_speed": "4.6", "wind_direc": "279", "publishtime": "2026\/09\/28 15:00:00", "co_8hr": "0.1", "pm2.5_avg": "8", "pm10_avg": "19", "so2_avg": "1", "longitude": "120.56117", "latitude": "22.523108", "siteid": "60" }, { "sitename": "恆春", "county": "屏東縣", "aqi": "31", "pollutant": "", "status": "良好", "so2": "0.6", "co": "0.12", "o3": "34", "o3_8hr": "34", "pm10": "16", "pm2.5": "7", "no2": "1", "nox": "2.1", "no": "0.9", "wind_speed": "3.4", "wind_direc": "311", "publishtime": "2026\/09\/28 15:00:00", "co_8hr": "0.1", "pm2.5_avg": "4.4", "pm10_avg": "9", "so2_avg": "0", "longitude": "120.788925", "latitude": "21.958069", "siteid": "61" }, { "sitename": "臺東", "county": "臺東縣", "aqi": "35", "pollutant": "", "status": "良好", "so2": "1.1", "co": "0.15", "o3": "30", "o3_8hr": "29", "pm10": "13", "pm2.5": "11", "no2": "2", "nox": "4", "no": "1.2", "wind_speed": "2.3", "wind_direc": "69", "publishtime": "2026\/09\/28 15:00:00", "co_8hr": "0.1", "pm2.5_avg": "8.7", "pm10_avg": "16", "so2_avg": "0", "longitude": "121.15045", "latitude": "22.755358", "siteid": "62" }, { "sitename": "花蓮", "county": "花蓮縣", "aqi": "34", "pollutant": "", "status": "良好", "so2": "0.2", "co": "0.21", "o3": "43", "o3_8hr": "36", "pm10": "18", "pm2.5": "9", "no2": "1", "nox": "2.2", "no": "0.8", "wind_speed": "3.1", "wind_direc": "111", "publishtime": "2026\/09\/28 15:00:00", "co_8hr": "0.2", "pm2.5_avg": "8.5", "pm10_avg": "19", "so2_avg": "0", "longitude": "121.59977", "latitude": "23.971306", "siteid": "63" }, { "sitename": "陽明", "county": "臺北市", "aqi": "44", "pollutant": "", "status": "良好", "so2": "1.6", "co": "0.21", "o3": "61", "o3_8hr": "48", "pm10": "21", "pm2.5": "8", "no2": "3", "nox": "3.6", "no": "0.4", "wind_speed": "", "wind_direc": "", "publishtime": "2026\/09\/28 15:00:00", "co_8hr": "0.2", "pm2.5_avg": "8.2", "pm10_avg": "21", "so2_avg": "1", "longitude": "121.52959", "latitude": "25.182722", "siteid": "64" }, { "sitename": "宜蘭", "county": "宜蘭縣", "aqi": "40", "pollutant": "", "status": "良好", "so2": "1", "co": "0.21", "o3": "51", "o3_8hr": "40", "pm10": "13", "pm2.5": "10", "no2": "2", "nox": "3.6", "no": "1", "wind_speed": "2.3", "wind_direc": "107", "publishtime": "2026\/09\/28 15:00:00", "co_8hr": "0.2", "pm2.5_avg": "10", "pm10_avg": "15", "so2_avg": "0", "longitude": "121.74639", "latitude": "24.747917", "siteid": "65" }, { "sitename": "冬山", "county": "宜蘭縣", "aqi": "33", "pollutant": "", "status": "良好", "so2": "1.1", "co": "0.2", "o3": "45", "o3_8hr": "36", "pm10": "25", "pm2.5": "9", "no2": "6", "nox": "8.9", "no": "2.6", "wind_speed": "2.7", "wind_direc": "69", "publishtime": "2026\/09\/28 15:00:00", "co_8hr": "0.1", "pm2.5_avg": "6.7", "pm10_avg": "18", "so2_avg": "0", "longitude": "121.79293", "latitude": "24.632202", "siteid": "66" }, { "sitename": "三重", "county": "新北市", "aqi": "53", "pollutant": "細懸浮微粒", "status": "普通", "so2": "1.3", "co": "0.41", "o3": "42", "o3_8hr": "30", "pm10": "38", "pm2.5": "13", "no2": "24", "nox": "37.6", "no": "12.8", "wind_speed": "-", "wind_direc": "-", "publishtime": "2026\/09\/28 15:00:00", "co_8hr": "0.5", "pm2.5_avg": "13.1", "pm10_avg": "23", "so2_avg": "1", "longitude": "121.493805", "latitude": "25.07261", "siteid": "67" }, { "sitename": "中壢", "county": "桃園市", "aqi": "39", "pollutant": "", "status": "良好", "so2": "1.3", "co": "0.44", "o3": "40", "o3_8hr": "31", "pm10": "23", "pm2.5": "11", "no2": "16", "nox": "32.9", "no": "16", "wind_speed": "1.4", "wind_direc": "31", "publishtime": "2026\/09\/28 15:00:00", "co_8hr": "0.4", "pm2.5_avg": "9.6", "pm10_avg": "20", "so2_avg": "1", "longitude": "121.221664", "latitude": "24.953278", "siteid": "68" }, { "sitename": "竹山", "county": "南投縣", "aqi": "32", "pollutant": "", "status": "良好", "so2": "1.1", "co": "0.14", "o3": "47", "o3_8hr": "35", "pm10": "19", "pm2.5": "10", "no2": "2", "nox": "3.8", "no": "0.9", "wind_speed": "2.5", "wind_direc": "250", "publishtime": "2026\/09\/28 15:00:00", "co_8hr": "0.1", "pm2.5_avg": "6.7", "pm10_avg": "13", "so2_avg": "1", "longitude": "120.67731", "latitude": "23.75639", "siteid": "69" }, { "sitename": "永和", "county": "新北市", "aqi": "45", "pollutant": "", "status": "良好", "so2": "1.5", "co": "0.44", "o3": "49", "o3_8hr": "37", "pm10": "17", "pm2.5": "8", "no2": "15", "nox": "19.9", "no": "4.4", "wind_speed": "1.4", "wind_direc": "354", "publishtime": "2026\/09\/28 15:00:00", "co_8hr": "0.3", "pm2.5_avg": "11.1", "pm10_avg": "17", "so2_avg": "1", "longitude": "121.516304", "latitude": "25.017", "siteid": "70" }, { "sitename": "復興", "county": "高雄市", "aqi": "29", "pollutant": "", "status": "良好", "so2": "0.3", "co": "0.13", "o3": "35", "o3_8hr": "30", "pm10": "19", "pm2.5": "6", "no2": "3", "nox": "5.4", "no": "1.5", "wind_speed": "1.3", "wind_direc": "270", "publishtime": "2026\/09\/28 15:00:00", "co_8hr": "0.1", "pm2.5_avg": "7.2", "pm10_avg": "16", "so2_avg": "0", "longitude": "120.31202", "latitude": "22.608711", "siteid": "71" }, { "sitename": "埔里", "county": "南投縣", "aqi": "29", "pollutant": "", "status": "良好", "so2": "1.2", "co": "0.15", "o3": "38", "o3_8hr": "30", "pm10": "7", "pm2.5": "3", "no2": "0", "nox": "2.6", "no": "1.7", "wind_speed": "3.5", "wind_direc": "284", "publishtime": "2026\/09\/28 15:00:00", "co_8hr": "0.1", "pm2.5_avg": "7.2", "pm10_avg": "11", "so2_avg": "0", "longitude": "120.9679", "latitude": "23.968842", "siteid": "72" }, { "sitename": "馬祖", "county": "連江縣", "aqi": "51", "pollutant": "懸浮微粒", "status": "普通", "so2": "0", "co": "0.18", "o3": "61", "o3_8hr": "49", "pm10": "30", "pm2.5": "14", "no2": "1", "nox": "2.2", "no": "0.8", "wind_speed": "5.9", "wind_direc": "181", "publishtime": "2026\/09\/28 15:00:00", "co_8hr": "0.1", "pm2.5_avg": "12", "pm10_avg": "31", "so2_avg": "1", "longitude": "119.95272", "latitude": "26.153736", "siteid": "75" }, { "sitename": "金門", "county": "金門縣", "aqi": "85", "pollutant": "細懸浮微粒", "status": "普通", "so2": "1.3", "co": "0.28", "o3": "88", "o3_8hr": "60", "pm10": "37", "pm2.5": "28", "no2": "6", "nox": "7.5", "no": "0.7", "wind_speed": "5", "wind_direc": "181", "publishtime": "2026\/09\/28 15:00:00", "co_8hr": "0.3", "pm2.5_avg": "24.9", "pm10_avg": "39", "so2_avg": "1", "longitude": "118.312256", "latitude": "24.432133", "siteid": "77" }, { "sitename": "馬公", "county": "澎湖縣", "aqi": "33", "pollutant": "", "status": "良好", "so2": "0.6", "co": "0.18", "o3": "33", "o3_8hr": "36", "pm10": "16", "pm2.5": "10", "no2": "3", "nox": "4.8", "no": "1.3", "wind_speed": "2.1", "wind_direc": "184", "publishtime": "2026\/09\/28 15:00:00", "co_8hr": "0.1", "pm2.5_avg": "8", "pm10_avg": "18", "so2_avg": "0", "longitude": "119.566154", "latitude": "23.56903", "siteid": "78" }, { "sitename": "關山", "county": "臺東縣", "aqi": "20", "pollutant": "", "status": "良好", "so2": "0.5", "co": "0.13", "o3": "30", "o3_8hr": "22", "pm10": "18", "pm2.5": "5", "no2": "1", "nox": "1.6", "no": "0.6", "wind_speed": "2.4", "wind_direc": "189", "publishtime": "2026\/09\/28 15:00:00", "co_8hr": "0.1", "pm2.5_avg": "5", "pm10_avg": "11", "so2_avg": "0", "longitude": "121.161934", "latitude": "23.045082", "siteid": "80" }, { "sitename": "麥寮", "county": "雲林縣", "aqi": "31", "pollutant": "", "status": "良好", "so2": "1.1", "co": "0.15", "o3": "41", "o3_8hr": "33", "pm10": "15", "pm2.5": "7", "no2": "3", "nox": "3.2", "no": "0.1", "wind_speed": "3.8", "wind_direc": "237", "publishtime": "2026\/09\/28 15:00:00", "co_8hr": "0.1", "pm2.5_avg": "6.3", "pm10_avg": "17", "so2_avg": "1", "longitude": "120.25182", "latitude": "23.753506", "siteid": "83" }, { "sitename": "富貴角", "county": "新北市", "aqi": "41", "pollutant": "", "status": "良好", "so2": "1.6", "co": "0.1", "o3": "50", "o3_8hr": "44", "pm10": "21", "pm2.5": "9", "no2": "0", "nox": "0.5", "no": "0", "wind_speed": "3.2", "wind_direc": "240", "publishtime": "2026\/09\/28 15:00:00", "co_8hr": "0.1", "pm2.5_avg": "9.5", "pm10_avg": "18", "so2_avg": "1", "longitude": "121.53657", "latitude": "25.296818", "siteid": "84" }, { "sitename": "大城", "county": "彰化縣", "aqi": "38", "pollutant": "", "status": "良好", "so2": "2.7", "co": "0.16", "o3": "33", "o3_8hr": "30", "pm10": "40", "pm2.5": "7", "no2": "7", "nox": "10.4", "no": "3.4", "wind_speed": "4.7", "wind_direc": "259", "publishtime": "2026\/09\/28 15:00:00", "co_8hr": "0.1", "pm2.5_avg": "8.5", "pm10_avg": "23", "so2_avg": "1", "longitude": "120.26964", "latitude": "23.85493", "siteid": "85" }, { "sitename": "臺灣大道", "county": "臺中市", "aqi": "34", "pollutant": "", "status": "良好", "so2": "0.7", "co": "0.26", "o3": "47", "o3_8hr": "37", "pm10": "19", "pm2.5": "8", "no2": "6", "nox": "9.7", "no": "3.3", "wind_speed": "1.3", "wind_direc": "27", "publishtime": "2026\/09\/28 15:00:00", "co_8hr": "0.3", "pm2.5_avg": "8.5", "pm10_avg": "16", "so2_avg": "0", "longitude": "120.65671", "latitude": "24.1589", "siteid": "88" }, { "sitename": "員林", "county": "彰化縣", "aqi": "35", "pollutant": "", "status": "良好", "so2": "1.2", "co": "0.15", "o3": "40", "o3_8hr": "38", "pm10": "17", "pm2.5": "6", "no2": "1", "nox": "2.5", "no": "0.8", "wind_speed": "4.6", "wind_direc": "260", "publishtime": "2026\/09\/28 15:00:00", "co_8hr": "0.1", "pm2.5_avg": "8.2", "pm10_avg": "16", "so2_avg": "1", "longitude": "120.56319", "latitude": "23.961594", "siteid": "139" }, { "sitename": "林森", "county": "臺南市", "aqi": "32", "pollutant": "", "status": "良好", "so2": "0.4", "co": "0.16", "o3": "32", "o3_8hr": "29", "pm10": "12", "pm2.5": "6", "no2": "2", "nox": "3.4", "no": "0.6", "wind_speed": "1.9", "wind_direc": "183", "publishtime": "2026\/09\/28 15:00:00", "co_8hr": "0.2", "pm2.5_avg": "8", "pm10_avg": "15", "so2_avg": "0", "longitude": "120.22216", "latitude": "22.985226", "siteid": "140" }, { "sitename": "南投(鹿谷)", "county": "南投縣", "aqi": "30", "pollutant": "", "status": "良好", "so2": "1.6", "co": "0.13", "o3": "43", "o3_8hr": "32", "pm10": "3", "pm2.5": "9", "no2": "1", "nox": "3", "no": "1", "wind_speed": "1.3", "wind_direc": "293", "publishtime": "2026\/09\/28 15:00:00", "co_8hr": "0.1", "pm2.5_avg": "6.4", "pm10_avg": "7", "so2_avg": "1", "longitude": "120.79448", "latitude": "23.718676", "siteid": "203" }, { "sitename": "屏東(琉球)", "county": "屏東縣", "aqi": "32", "pollutant": "", "status": "良好", "so2": "0", "co": "0.13", "o3": "33", "o3_8hr": "35", "pm10": "19", "pm2.5": "5", "no2": "0", "nox": "1.5", "no": "0.7", "wind_speed": "3.1", "wind_direc": "322", "publishtime": "2026\/09\/28 15:00:00", "co_8hr": "0.1", "pm2.5_avg": "6.3", "pm10_avg": "15", "so2_avg": "0", "longitude": "120.37722", "latitude": "22.35222", "siteid": "204" }, { "sitename": "新北(樹林)", "county": "新北市", "aqi": "39", "pollutant": "", "status": "良好", "so2": "0.8", "co": "0.15", "o3": "60", "o3_8hr": "42", "pm10": "13", "pm2.5": "4", "no2": "4", "nox": "4.6", "no": "0.5", "wind_speed": "0.4", "wind_direc": "254", "publishtime": "2026\/09\/28 15:00:00", "co_8hr": "0.1", "pm2.5_avg": "8.6", "pm10_avg": "15", "so2_avg": "0", "longitude": "121.38353", "latitude": "24.949028", "siteid": "311" }, { "sitename": "屏東(枋山)", "county": "屏東縣", "aqi": "36", "pollutant": "", "status": "良好", "so2": "0", "co": "0.13", "o3": "37", "o3_8hr": "39", "pm10": "25", "pm2.5": "9", "no2": "0", "nox": "0.5", "no": "0", "wind_speed": "5.8", "wind_direc": "296", "publishtime": "2026\/09\/28 15:00:00", "co_8hr": "0.1", "pm2.5_avg": "5.8", "pm10_avg": "18", "so2_avg": "0", "longitude": "120.651474", "latitude": "22.260899", "siteid": "313" } ]


https://data.moenv.gov.tw/api/v2/aqx_p_432?offset=0&limit=1000&api_key="u'r api key"&filters=sitename,eq, 大里

[
    {
        "sitename": "大里",
        "county": "臺中市",
        "aqi": "38",
        "pollutant": "",
        "status": "良好",
        "so2": "1.5",
        "co": "0.24",
        "o3": "37",
        "o3_8hr": "41",
        "pm10": "22",
        "pm2.5": "12",
        "no2": "6",
        "nox": "6.9",
        "no": "0.2",
        "wind_speed": "2.2",
        "wind_direc": "242",
        "publishtime": "2026\/09\/28 18:00:00",
        "co_8hr": "0.1",
        "pm2.5_avg": "7.7",
        "pm10_avg": "16",
        "so2_avg": "1",
        "longitude": "120.678444",
        "latitude": "24.099611",
        "siteid": "30"
    }
]


沒有留言:

張貼留言

ESP32-Flutter-MQTT-IoT-Controller

 ESP32-Flutter-MQTT-IoT-Controller 這套系統包含了 Flutter 跨平台 App 、 ESP32 韌體 與 MQTT 雲端傳輸機制 。 專案中採用的 功能命名原則與對應關係 整理如下: 1. 核心功能名稱對照表 功能區塊 Python / ...