Node-RED
Node-RED
。如果未安裝,可訪問 Node-Red 官網 進行安裝。Node-RED
並打開其 Web
界面。MQTT Broker
MQTT Broker
,可以安裝如 Mosquitto
。Broker
正在運行。ESP32
溫濕度感測器或是實體 ESP32
溫濕度感測器
ESP32
,並編程讓它定期發送溫濕度數據。MQTT
協議發送到 Broker
。Node-RED
中配置 MQTT
Node-RED
的流中,添加一個 MQTT
輸入節點。MQTT
輸入節點以連接到你的 MQTT Broker
,並訂閱 ESP32
發送的主題。API
HTTP
請求節點以連接到所需的公開 API
。MQTT
和 API
收到的數據進行處理。dashboard
節點(如:chart
節點)以製作圖表。Node-RED Dashboard
查看圖表,檢視實時數據。MQTT
設置、函數邏輯或圖表配置。為了開始使用 Node-RED
,您首先需要安裝 Node.js
。Node.js
是一個執行 JavaScript
代碼所必需的環境,它是 Node-RED
運行的基礎。按照以下步驟進行安裝:
訪問 Node.js 官網 並下載適合您系統的版本。
完成下載後,按照指示安裝 Node.js。
完成 Node.js 安裝後,您可以透過命令行界面安裝 Node-Red。請執行以下指令:
npm install -g --unsafe-perm node-red
這條指令將會全局安裝 Node-Red。
安裝完成後,輸入以下指令以啟動 Node-RED
:
node-red
成功執行此指令後,Node-RED
會在您的本地機器上運行。
啟動 Node-RED
後,打開瀏覽器並訪問 http://127.0.0.1:1880/。您將看到 Node-RED
的使用者界面,如下圖所示:
透過這個界面,您可以開始創建自己的流程和集成不同的模塊,進行物聯網和其他自動化項目的開發。
為了在 Node-RED
中使用 MQTT
功能,您需要先安裝一個 MQTT broker
。這裡我們將使用 npm install node-red-contrib-aedes,這是一個流行的 MQTT broker
實現。您可以按照以下步驤進行安裝:
在您的命令行界面中,執行以下指令來安裝 npm install node-red-contrib-aedes:
npm install node-red-contrib-aedes
安裝完成後,您可以在 Node-RED
中匯入下面的 JSON
配置以測試 broker
的設置:
[
{
"id": "4752af02ad2b6936",
"type": "tab",
"label": "Flow 1",
"disabled": false,
"info": "",
"env": []
},
{
"id": "602db9fdcf17624e",
"type": "mqtt out",
"z": "4752af02ad2b6936",
"name": "",
"topic": "sensors/livingroom/temp",
"qos": "",
"retain": "",
"respTopic": "",
"contentType": "",
"userProps": "",
"correl": "",
"expiry": "",
"broker": "e9d558a0a2f1767d",
"x": 490,
"y": 220,
"wires": []
},
{
"id": "11c103420d64f70c",
"type": "inject",
"z": "4752af02ad2b6936",
"name": "",
"repeat": "",
"crontab": "",
"once": false,
"topic": "",
"payload": "22",
"payloadType": "num",
"x": 310,
"y": 220,
"wires": [
[
"602db9fdcf17624e"
]
]
},
{
"id": "81bd7474e62ab78c",
"type": "mqtt in",
"z": "4752af02ad2b6936",
"name": "",
"topic": "sensors/livingroom/temp",
"qos": "2",
"datatype": "auto-detect",
"broker": "e9d558a0a2f1767d",
"nl": false,
"rap": false,
"inputs": 0,
"x": 330,
"y": 300,
"wires": [
[
"674106a32fadc368"
]
]
},
{
"id": "674106a32fadc368",
"type": "debug",
"z": "4752af02ad2b6936",
"name": "",
"active": true,
"console": "false",
"complete": "false",
"x": 530,
"y": 300,
"wires": []
},
{
"id": "a5d723ab8d11088b",
"type": "aedes broker",
"z": "4752af02ad2b6936",
"name": "",
"mqtt_port": "1884",
"mqtt_ws_bind": "port",
"mqtt_ws_port": "",
"mqtt_ws_path": "",
"cert": "",
"key": "",
"certname": "",
"keyname": "",
"dburl": "",
"usetls": false,
"x": 310,
"y": 120,
"wires": [
[
"7caf4243938b959e"
],
[
"d5f9898904f6d9b9"
]
]
},
{
"id": "7caf4243938b959e",
"type": "debug",
"z": "4752af02ad2b6936",
"name": "debug 19",
"active": false,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "false",
"statusVal": "",
"statusType": "auto",
"x": 520,
"y": 80,
"wires": []
},
{
"id": "d5f9898904f6d9b9",
"type": "debug",
"z": "4752af02ad2b6936",
"name": "debug 20",
"active": false,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "false",
"statusVal": "",
"statusType": "auto",
"x": 520,
"y": 140,
"wires": []
},
{
"id": "e9d558a0a2f1767d",
"type": "mqtt-broker",
"name": "localhost:1884",
"broker": "localhost",
"port": "1884",
"clientid": "",
"autoConnect": true,
"usetls": false,
"protocolVersion": "4",
"keepalive": "60",
"cleansession": true,
"autoUnsubscribe": true,
"birthTopic": "",
"birthQos": "0",
"birthRetain": "false",
"birthPayload": "",
"birthMsg": {},
"closeTopic": "",
"closeQos": "0",
"closeRetain": "false",
"closePayload": "",
"closeMsg": {},
"willTopic": "",
"willQos": "0",
"willRetain": "false",
"willPayload": "",
"willMsg": {},
"userProps": "",
"sessionExpiry": ""
}
]
匯入並部署配置後,如果您看到以下界面,則表示您已成功安裝並配置了 MQTT broker
:
這樣,您就完成了在 Node-RED
中安裝 MQTT broker
的步驟,並可以開始使用 MQTT
進行消息傳輸。
MQTT(Message Queuing Telemetry Transport)
是一種輕量級的消息傳輸協議,常用於物聯網(IoT)應用。在 MQTT
架構中,「MQTT Broker」
和「MQTT Subscriber」
扮演著關鍵角色,它們之間的關聯非常重要:
MQTT Broker
是一個消息服務器,負責接收、處理和轉發消息。Broker
接收來自發布者(publishers)的消息,然後根據訂閱者(subscribers)的訂閱來分發這些消息。MQTT
網絡中的中心節點,所有的消息交換都透過Broker進行。Subscriber
是消息的接收者。Subscriber
向Broker
訂閱一個或多個主題(topics)。這意味著它表明了對特定主題的消息感興趣。Broker
會將這些消息轉發給相應的Subscriber
。在這個架構中,Broker
充當中介,確保消息從發布者有效地傳遞給訂閱者。這種設計使得系統具有高度的解耦性和擴展性,非常適合於分散式和動態變化的環境,如物聯網。若想模擬 ESP32
溫濕度感測器的工作情形,您可以訪問 Wokwi
網站。Wokwi
是一個強大的在線模擬平台,它可以讓您模擬各種微控制器和電子元件,包括 ESP32
和 DHT
溫濕度感測器。
為了開始模擬,請跟隨以下步驟:
ESP32
讀取 DHT
感測器的溫濕度數據,並通過 MQTT
協議發送這些數據。一旦您打開了範例項目,您將能夠查看源代碼、進行修改、並直接在瀏覽器中運行模擬。這是一種快速且有效的方式來測試和驗證您的 IoT
解決方案,而無需實際硬件。
使用實體的ESP32微控制器和DHT11溫濕度感測器,通過WiFi連接和MQTT協議將感測器數據發布到互聯網。我們將提供硬件連接指南以及Arduino IDE的設置步驟。
請參考下圖以完成硬件連接:
將ESP32和DHT11感測器連接如圖所示。確保連接正確,以確保感測器可以正常運作。
ESP32 DEV MODULE
作為目標開發板。ESP32
開發板套件以支持您的設置。如果在安裝過程中遇到缺少套件的錯誤,請複製套件名稱並在Arduino IDE中進行套件安裝。
下面是 Arduino
程式碼,詳細解釋在程式碼中的每個部分:
#include <WiFi.h>
#include <PubSubClient.h>
#include <DHT.h>
// 定義 DHT 傳感器引腳
#define DHTPIN 23
// 定義 DHT 類型
#define DHTTYPE DHT11
// 建立 DHT 物件
DHT dht(DHTPIN, DHTTYPE);
const char* ssid = "CSIE-WLAN"; // WiFi SSID
const char* password = "wificsie"; // WiFi 密碼
const char* mqtt_server = "test.mosquitto.org"; // MQTT 服務器地址
WiFiClient espClient;
PubSubClient client(espClient);
unsigned long lastMsg = 0; // 記錄上一次消息發送的時間
float temp = 0; // 溫度變量
float hum = 0; // 濕度變量
// 設置 WiFi 連接
void setup_wifi() {
delay(10);
Serial.println();
Serial.print("Connecting to ");
Serial.println(ssid);
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
// 等待 WiFi 連接
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
randomSeed(micros());
Serial.println("");
Serial.println("WiFi connected");
Serial.println("IP address: ");
Serial.println(WiFi.localIP());
}
// MQTT 消息回調函數
void callback(char* topic, byte* payload, unsigned int length) {
Serial.print("Message arrived [");
Serial.print(topic);
Serial.print("] ");
for (int i = 0; i < length; i++) {
Serial.print((char)payload[i]);
}
}
// 重連 MQTT 服務器
void reconnect() {
while (!client.connected()) {
Serial.print("Attempting MQTT connection...");
String clientId = "ESP32Client-";
clientId += String(random(0xffff), HEX);
if (client.connect(clientId.c_str())) {
Serial.println("Connected");
client.publish("/ThinkIOT/Publish", "Welcome");
client.subscribe("/ThinkIOT/Subscribe");
} else {
Serial.print("failed, rc=");
Serial.print(client.state());
Serial.println(" try again in 5 seconds");
delay(5000);
}
}
}
// 初始化設置
void setup() {
pinMode(2, OUTPUT);
Serial.begin(115200);
setup_wifi();
client.setServer(mqtt_server, 1883);
client.setCallback(callback);
// dht.setup(DHT_PIN, DHTesp::DHT22); // 初始化 DHT22
dht.begin();
}
// 主循環
void loop() {
// 如果未連接 MQTT,則嘗試重新連接
if (!client.connected()) {
reconnect();
}
client.loop();
unsigned long now = millis();
if (now - lastMsg > 2000) { // 每 2 秒發布一次數據
lastMsg = now;
// 讀取溫度和濕度
float temperature = dht.readTemperature();
float humidity = dht.readHumidity();
String temp = String(temperature, 2);
client.publish("/Thinkitive/temp", temp.c_str()); // 發布溫度數據
String hum = String(humidity, 1);
client.publish("/Thinkitive/hum", hum.c_str()); // 發布濕度數據
// 顯示溫度和濕度
Serial.print("溫度:");
Serial.print(temperature);
Serial.println(" 攝氏度");
Serial.print("濕度:");
Serial.print(humidity);
Serial.println(" %");
}
}
再經過後續的步驟,即可看到與連接虛擬ESP32一樣的畫面,就代表成功了
為了在 Node-RED
中接收來自 ESP32
的溫濕度數據,您需要首先配置 MQTT
並建立一個 Dashboard
。請按照以下步驟進行操作:
1.首先,安裝 Node-RED Dashboard 套件。這可以通過 Node-RED 的管理界面輕鬆完成。
1.在「管理面板」(Manage palette)中,搜索並安裝「node-red-dashboard」。
Node-RED
流程編輯器,這是用於構建 IoT
應用程式的工具。MQTT
的輸入節點。這個節點將用於接收來自您的 ESP32
微控制器的數據。MQTT
主題,它用於標識和接收特定類型的數據。ESP32
微控制器發布數據的主題。這個主題應該與您的 ESP32
上的程式碼中的主題一致,以確保數據能夠正確路由到這個節點。Dashboard
有關的節點。Dashboard
節點用於在 Web
介面上創建和顯示用戶界面,以便顯示來自 IoT
設備的數據。MQTT
輸入節點和 Dashboard
節點連接在一起,以便將接收到的數據顯示在 Dashboard
上。Node-RED
畫布上,使用滑鼠將連接線從 MQTT
輸入節點拖動到 Dashboard
節點。這將建立數據流動的連接。Dashboard
節點中進一步配置該節點,以指定要在 Dashboard
上顯示的數據類型和方式。MQTT IN
節點時,確保主題設定與您的 ESP32
程式碼中的 MQTT
主題匹配,以確保數據能夠正確路由到 Node-RED
。MQTT
服務提供商,請確保根據提供商的要求設置相應的伺服器地址、端口號以及可能的用戶名和密碼。
完成配置並部署後,打開您的瀏覽器並訪問 http://127.0.0.1:1880/ui。如果您看到類似下面的界面,則表示您已經成功配置了 Dashboard
並正接收來自 ESP32
的數據。
[
{
"id": "4752af02ad2b6936",
"type": "tab",
"label": "Flow 1",
"disabled": false,
"info": "",
"env": []
},
{
"id": "a5d723ab8d11088b",
"type": "aedes broker",
"z": "4752af02ad2b6936",
"name": "",
"mqtt_port": "1884",
"mqtt_ws_bind": "port",
"mqtt_ws_port": "",
"mqtt_ws_path": "",
"cert": "",
"key": "",
"certname": "",
"keyname": "",
"dburl": "",
"usetls": false,
"x": 310,
"y": 120,
"wires": [
[
"7caf4243938b959e"
],
[
"d5f9898904f6d9b9"
]
]
},
{
"id": "7caf4243938b959e",
"type": "debug",
"z": "4752af02ad2b6936",
"name": "debug 19",
"active": false,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "false",
"statusVal": "",
"statusType": "auto",
"x": 520,
"y": 80,
"wires": []
},
{
"id": "d5f9898904f6d9b9",
"type": "debug",
"z": "4752af02ad2b6936",
"name": "debug 20",
"active": false,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "false",
"statusVal": "",
"statusType": "auto",
"x": 520,
"y": 140,
"wires": []
},
{
"id": "0182fafab959f3ff",
"type": "mqtt in",
"z": "4752af02ad2b6936",
"name": "",
"topic": "/Thinkitive/temp",
"qos": "0",
"datatype": "auto-detect",
"broker": "2f00154a47567466",
"nl": false,
"rap": true,
"rh": 0,
"inputs": 0,
"x": 300,
"y": 220,
"wires": [
[
"0de022206403765d"
]
]
},
{
"id": "2c2a8fa3f3e459bd",
"type": "mqtt in",
"z": "4752af02ad2b6936",
"name": "",
"topic": "/Thinkitive/hum",
"qos": "0",
"datatype": "auto-detect",
"broker": "2f00154a47567466",
"nl": false,
"rap": true,
"rh": 0,
"inputs": 0,
"x": 300,
"y": 280,
"wires": [
[
"07a3231ef07f68af"
]
]
},
{
"id": "0de022206403765d",
"type": "ui_gauge",
"z": "4752af02ad2b6936",
"name": "TRMP",
"group": "70d1d491db52e4fe",
"order": 13,
"width": 0,
"height": 0,
"gtype": "gage",
"title": "ESP32溫度",
"label": "",
"format": "{{value}}",
"min": 0,
"max": "150",
"colors": [
"#00b500",
"#e6e600",
"#ec2727"
],
"seg1": "",
"seg2": "",
"diff": false,
"className": "",
"x": 510,
"y": 220,
"wires": []
},
{
"id": "07a3231ef07f68af",
"type": "ui_gauge",
"z": "4752af02ad2b6936",
"name": "",
"group": "70d1d491db52e4fe",
"order": 14,
"width": 0,
"height": 0,
"gtype": "gage",
"title": "ESP32濕度",
"label": "",
"format": "{{value}}",
"min": 0,
"max": "60",
"colors": [
"#15cb15",
"#e6e600",
"#ca3838"
],
"seg1": "",
"seg2": "",
"diff": false,
"className": "",
"x": 530,
"y": 280,
"wires": []
},
{
"id": "2f00154a47567466",
"type": "mqtt-broker",
"name": "",
"broker": "test.mosquitto.org",
"port": "1883",
"clientid": "",
"autoConnect": true,
"usetls": false,
"protocolVersion": "4",
"keepalive": "60",
"cleansession": true,
"autoUnsubscribe": true,
"birthTopic": "",
"birthQos": "0",
"birthRetain": "false",
"birthPayload": "",
"birthMsg": {},
"closeTopic": "",
"closeQos": "0",
"closeRetain": "false",
"closePayload": "",
"closeMsg": {},
"willTopic": "",
"willQos": "0",
"willRetain": "false",
"willPayload": "",
"willMsg": {},
"userProps": "",
"sessionExpiry": ""
},
{
"id": "70d1d491db52e4fe",
"type": "ui_group",
"name": "esp32圖表",
"tab": "2156c1cf7f043d72",
"order": 2,
"disp": true,
"width": "6",
"collapse": false,
"className": ""
},
{
"id": "2156c1cf7f043d72",
"type": "ui_tab",
"name": "天氣圖表",
"icon": "dashboard",
"disabled": false,
"hidden": false
}
]
透過這些步驟,您將能夠在 Node-RED
中實時監控來自 ESP32
的溫濕度數據,並透過一個視覺化的 Dashboard
展示這些信息。
首先前往氣象資料開放平臺,取得 TOKEN
https://opendata.cwa.gov.tw/api/v1/rest/datastore/O-A0001-001?Authorization=替換為你的Token&limit=10&format=JSON&StationName={{payload}}&WeatherElement=Weather,Now,WindSpeed,AirTemperature,RelativeHumidity,AirPressure&GeoInfo=CountyName
Authorization
參數換成你的 Token
。拉完節點的圖如下
將展示如何在 Node-RED 中連接到一個公開的氣象資料 API,並抓取各地區的溫度等氣象資訊。
API TOKEN
。Node-RED
中,拉取一個 HTTP
請求節點(http request)。HTTP
請求節點的 URL
欄位中,輸入以下網址,並將 Authorization
參數替換為您的 Token
:https://opendata.cwa.gov.tw/api/v1/rest/datastore/O-A0001-001?Authorization=替換為你的Token&limit=10&format=JSON&StationName={{payload}}&WeatherElement=Weather,Now,WindSpeed,AirTemperature,RelativeHumidity,AirPressure&GeoInfo=CountyName
JSON
節點,以及 MQTT
輸出(MQTT OUT)節點。HTTP
請求,從 API
獲取數據。JSON
節點,然後通過 MQTT 輸出節點發送。Dashboard
節點要解析傳入的資料,其他 Dashboard
舉一反三
下面為最終接線圖
部署流程後,您可以在 Node-RED Dashboard
上查看從 API
獲取的氣象數據。
透過這些步驟,您可以方便地在 Node-RED
中集成公開氣象 API
,並將數據實時顯示在 Dashboard
上。
[
{
"id": "4752af02ad2b6936",
"type": "tab",
"label": "Flow 1",
"disabled": false,
"info": "",
"env": []
},
{
"id": "a5d723ab8d11088b",
"type": "aedes broker",
"z": "4752af02ad2b6936",
"name": "",
"mqtt_port": "1883",
"mqtt_ws_bind": "port",
"mqtt_ws_port": "",
"mqtt_ws_path": "",
"cert": "",
"key": "",
"certname": "",
"keyname": "",
"dburl": "",
"usetls": false,
"x": 330,
"y": 220,
"wires": [
[
"7caf4243938b959e"
],
[
"d5f9898904f6d9b9"
]
]
},
{
"id": "7caf4243938b959e",
"type": "debug",
"z": "4752af02ad2b6936",
"name": "debug 19",
"active": false,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "false",
"statusVal": "",
"statusType": "auto",
"x": 540,
"y": 180,
"wires": []
},
{
"id": "d5f9898904f6d9b9",
"type": "debug",
"z": "4752af02ad2b6936",
"name": "debug 20",
"active": false,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "false",
"statusVal": "",
"statusType": "auto",
"x": 540,
"y": 240,
"wires": []
},
{
"id": "d3a027d3c2c251dd",
"type": "mqtt out",
"z": "4752af02ad2b6936",
"name": "",
"topic": "sensors/livingroom/temp",
"qos": "",
"retain": "",
"respTopic": "",
"contentType": "",
"userProps": "",
"correl": "",
"expiry": "",
"broker": "407a01e4.6b637",
"x": 870,
"y": 320,
"wires": []
},
{
"id": "f6f3f0550c2e3ee5",
"type": "mqtt in",
"z": "4752af02ad2b6936",
"name": "",
"topic": "sensors/livingroom/temp",
"qos": "2",
"datatype": "auto-detect",
"broker": "407a01e4.6b637",
"nl": false,
"rap": false,
"inputs": 0,
"x": 870,
"y": 380,
"wires": [
[
"456b638badb6cfa3",
"2650a0e7c32347e6",
"c40492dd854991ca",
"f4fdb780144bd0ef",
"5dd6d4f8c2f31a7f"
]
]
},
{
"id": "456b638badb6cfa3",
"type": "debug",
"z": "4752af02ad2b6936",
"name": "mqTT_In",
"active": false,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "payload",
"targetType": "msg",
"statusVal": "",
"statusType": "auto",
"x": 1140,
"y": 380,
"wires": []
},
{
"id": "510543f154dc735e",
"type": "ui_dropdown",
"z": "4752af02ad2b6936",
"name": "",
"label": "選擇地區",
"tooltip": "",
"place": "點擊選擇",
"group": "dcd3673081b04935",
"order": 1,
"width": 0,
"height": 0,
"passthru": true,
"multiple": false,
"options": [
{
"label": "桃園",
"value": "桃園",
"type": "str"
},
{
"label": "苗栗",
"value": "苗栗",
"type": "str"
},
{
"label": "南投",
"value": "南投",
"type": "str"
},
{
"label": "彰化",
"value": "埤頭",
"type": "str"
},
{
"label": "台中",
"value": "中坑",
"type": "str"
},
{
"label": "雲林",
"value": "斗六",
"type": "str"
},
{
"label": "嘉義",
"value": "水上",
"type": "str"
},
{
"label": "台南",
"value": "安南",
"type": "str"
},
{
"label": "屏東",
"value": "九如",
"type": "str"
},
{
"label": "台東",
"value": "延平",
"type": "str"
},
{
"label": "花蓮",
"value": "豐濱",
"type": "str"
},
{
"label": "宜蘭",
"value": "三星",
"type": "str"
},
{
"label": "高雄",
"value": "三民",
"type": "str"
},
{
"label": "台北",
"value": "平等",
"type": "str"
}
],
"payload": "",
"topic": "topic",
"topicType": "msg",
"className": "",
"x": 300,
"y": 320,
"wires": [
[
"062dcad06a00311c"
]
]
},
{
"id": "062dcad06a00311c",
"type": "http request",
"z": "4752af02ad2b6936",
"name": "",
"method": "GET",
"ret": "txt",
"paytoqs": "ignore",
"url": "https://opendata.cwa.gov.tw/api/v1/rest/datastore/O-A0001-001?Authorization=替換為你的Token&limit=10&format=JSON&StationName={{payload}}&WeatherElement=Weather,Now,WindSpeed,AirTemperature,RelativeHumidity,AirPressure&GeoInfo=CountyName",
"tls": "",
"persist": false,
"proxy": "",
"insecureHTTPParser": false,
"authType": "",
"senderr": false,
"headers": [],
"x": 490,
"y": 320,
"wires": [
[
"a8d2a1161cc2740f"
]
]
},
{
"id": "a8d2a1161cc2740f",
"type": "json",
"z": "4752af02ad2b6936",
"name": "",
"property": "payload",
"action": "",
"pretty": false,
"x": 670,
"y": 320,
"wires": [
[
"d3a027d3c2c251dd"
]
]
},
{
"id": "2650a0e7c32347e6",
"type": "ui_gauge",
"z": "4752af02ad2b6936",
"name": "",
"group": "dcd3673081b04935",
"order": 3,
"width": "0",
"height": "0",
"gtype": "gage",
"title": "氣溫",
"label": "度",
"format": "{{payload.records.Station[0].WeatherElement.AirTemperature}}",
"min": 0,
"max": "40",
"colors": [
"#00b500",
"#e6e600",
"#ca3838"
],
"seg1": "",
"seg2": "",
"diff": false,
"className": "",
"x": 1130,
"y": 420,
"wires": []
},
{
"id": "c40492dd854991ca",
"type": "ui_gauge",
"z": "4752af02ad2b6936",
"name": "",
"group": "dcd3673081b04935",
"order": 3,
"width": 0,
"height": 0,
"gtype": "gage",
"title": "風速",
"label": "m/s",
"format": "{{payload.records.Station[0].WeatherElement.WindSpeed}}",
"min": 0,
"max": "10",
"colors": [
"#00b500",
"#e6e600",
"#ca3838"
],
"seg1": "",
"seg2": "",
"diff": false,
"className": "",
"x": 1130,
"y": 460,
"wires": []
},
{
"id": "f4fdb780144bd0ef",
"type": "ui_gauge",
"z": "4752af02ad2b6936",
"name": "",
"group": "dcd3673081b04935",
"order": 3,
"width": 0,
"height": 0,
"gtype": "gage",
"title": "相對濕度",
"label": "%",
"format": "{{payload.records.Station[0].WeatherElement.RelativeHumidity}}",
"min": "50",
"max": "100",
"colors": [
"#00b500",
"#e6e600",
"#ca3838"
],
"seg1": "",
"seg2": "",
"diff": false,
"className": "",
"x": 1140,
"y": 500,
"wires": []
},
{
"id": "5dd6d4f8c2f31a7f",
"type": "ui_gauge",
"z": "4752af02ad2b6936",
"name": "",
"group": "dcd3673081b04935",
"order": 3,
"width": 0,
"height": 0,
"gtype": "gage",
"title": "空氣壓力",
"label": "atm",
"format": "{{payload.records.Station[0].WeatherElement.AirPressure}}",
"min": "900",
"max": "1100",
"colors": [
"#00b500",
"#e6e600",
"#ca3838"
],
"seg1": "",
"seg2": "",
"diff": false,
"className": "",
"x": 1140,
"y": 540,
"wires": []
},
{
"id": "407a01e4.6b637",
"type": "mqtt-broker",
"name": "",
"broker": "localhost",
"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": "dcd3673081b04935",
"type": "ui_group",
"name": "天氣",
"tab": "2156c1cf7f043d72",
"order": 1,
"disp": true,
"width": "6",
"collapse": false,
"className": ""
},
{
"id": "2156c1cf7f043d72",
"type": "ui_tab",
"name": "天氣圖表",
"icon": "dashboard",
"disabled": false,
"hidden": false
}
]
將上述公開 API
以及 ESP32
合在一起顯示,下圖為接線圖
部屬成功後查看Dashboard
[
{
"id": "4752af02ad2b6936",
"type": "tab",
"label": "Flow 1",
"disabled": false,
"info": "",
"env": []
},
{
"id": "d87bfba30f52a787",
"type": "mqtt out",
"z": "4752af02ad2b6936",
"name": "",
"topic": "sensors/livingroom/temp",
"qos": "",
"retain": "",
"respTopic": "",
"contentType": "",
"userProps": "",
"correl": "",
"expiry": "",
"broker": "407a01e4.6b637",
"x": 1250,
"y": 480,
"wires": []
},
{
"id": "46497999a0b9e240",
"type": "mqtt in",
"z": "4752af02ad2b6936",
"name": "",
"topic": "sensors/livingroom/temp",
"qos": "2",
"datatype": "auto-detect",
"broker": "407a01e4.6b637",
"nl": false,
"rap": false,
"inputs": 0,
"x": 1250,
"y": 540,
"wires": [
[
"014d24a654cd8f4c",
"912cb41e2b707cde",
"fb13611218084b5d",
"b9f9e902f028cef0",
"2ece70af46bcc0cc"
]
]
},
{
"id": "014d24a654cd8f4c",
"type": "debug",
"z": "4752af02ad2b6936",
"name": "mqTT_In",
"active": false,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "payload",
"targetType": "msg",
"statusVal": "",
"statusType": "auto",
"x": 1600,
"y": 440,
"wires": []
},
{
"id": "8f23f6566f4d9758",
"type": "aedes broker",
"z": "4752af02ad2b6936",
"name": "",
"mqtt_port": 1883,
"mqtt_ws_bind": "port",
"mqtt_ws_port": "",
"mqtt_ws_path": "",
"cert": "",
"key": "",
"certname": "",
"keyname": "",
"dburl": "",
"usetls": false,
"x": 1230,
"y": 340,
"wires": [
[
"6292b4f38426117d"
],
[
"4ae27ff101d2cb77"
]
]
},
{
"id": "6292b4f38426117d",
"type": "debug",
"z": "4752af02ad2b6936",
"name": "debug 19",
"active": false,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "payload",
"targetType": "msg",
"statusVal": "",
"statusType": "auto",
"x": 1480,
"y": 280,
"wires": []
},
{
"id": "4ae27ff101d2cb77",
"type": "debug",
"z": "4752af02ad2b6936",
"name": "debug 20",
"active": false,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "false",
"statusVal": "",
"statusType": "auto",
"x": 1480,
"y": 400,
"wires": []
},
{
"id": "3bdc30ad14914136",
"type": "ui_dropdown",
"z": "4752af02ad2b6936",
"name": "",
"label": "選擇地區",
"tooltip": "",
"place": "點擊選擇",
"group": "dcd3673081b04935",
"order": 1,
"width": 0,
"height": 0,
"passthru": true,
"multiple": false,
"options": [
{
"label": "桃園",
"value": "桃園",
"type": "str"
},
{
"label": "苗栗",
"value": "苗栗",
"type": "str"
},
{
"label": "南投",
"value": "南投",
"type": "str"
},
{
"label": "彰化",
"value": "埤頭",
"type": "str"
},
{
"label": "台中",
"value": "中坑",
"type": "str"
},
{
"label": "雲林",
"value": "斗六",
"type": "str"
},
{
"label": "嘉義",
"value": "水上",
"type": "str"
},
{
"label": "台南",
"value": "安南",
"type": "str"
},
{
"label": "屏東",
"value": "九如",
"type": "str"
},
{
"label": "台東",
"value": "延平",
"type": "str"
},
{
"label": "花蓮",
"value": "豐濱",
"type": "str"
},
{
"label": "宜蘭",
"value": "三星",
"type": "str"
},
{
"label": "高雄",
"value": "三民",
"type": "str"
},
{
"label": "台北",
"value": "平等",
"type": "str"
}
],
"payload": "",
"topic": "topic",
"topicType": "msg",
"className": "",
"x": 500,
"y": 480,
"wires": [
[
"7224c1d51afb7b29",
"ed9c5fc4953866d2"
]
]
},
{
"id": "ed9c5fc4953866d2",
"type": "http request",
"z": "4752af02ad2b6936",
"name": "",
"method": "GET",
"ret": "txt",
"paytoqs": "ignore",
"url": "https://opendata.cwa.gov.tw/api/v1/rest/datastore/O-A0001-001?Authorization=your-token&limit=10&format=JSON&StationName={{payload}}&WeatherElement=Weather,Now,WindSpeed,AirTemperature,RelativeHumidity,AirPressure&GeoInfo=CountyName",
"tls": "",
"persist": false,
"proxy": "",
"insecureHTTPParser": false,
"authType": "",
"senderr": false,
"headers": [],
"x": 770,
"y": 480,
"wires": [
[
"f313640322f03c6b"
]
]
},
{
"id": "f313640322f03c6b",
"type": "json",
"z": "4752af02ad2b6936",
"name": "",
"property": "payload",
"action": "",
"pretty": false,
"x": 950,
"y": 480,
"wires": [
[
"d87bfba30f52a787"
]
]
},
{
"id": "912cb41e2b707cde",
"type": "ui_gauge",
"z": "4752af02ad2b6936",
"name": "",
"group": "dcd3673081b04935",
"order": 3,
"width": "0",
"height": "0",
"gtype": "gage",
"title": "氣溫",
"label": "度",
"format": "{{payload.records.Station[0].WeatherElement.AirTemperature}}",
"min": 0,
"max": "40",
"colors": [
"#00b500",
"#e6e600",
"#ca3838"
],
"seg1": "",
"seg2": "",
"diff": false,
"className": "",
"x": 1590,
"y": 480,
"wires": []
},
{
"id": "fb13611218084b5d",
"type": "ui_gauge",
"z": "4752af02ad2b6936",
"name": "",
"group": "dcd3673081b04935",
"order": 3,
"width": 0,
"height": 0,
"gtype": "gage",
"title": "風速",
"label": "m/s",
"format": "{{payload.records.Station[0].WeatherElement.WindSpeed}}",
"min": 0,
"max": "10",
"colors": [
"#00b500",
"#e6e600",
"#ca3838"
],
"seg1": "",
"seg2": "",
"diff": false,
"className": "",
"x": 1590,
"y": 520,
"wires": []
},
{
"id": "b9f9e902f028cef0",
"type": "ui_gauge",
"z": "4752af02ad2b6936",
"name": "",
"group": "dcd3673081b04935",
"order": 3,
"width": 0,
"height": 0,
"gtype": "gage",
"title": "相對濕度",
"label": "%",
"format": "{{payload.records.Station[0].WeatherElement.RelativeHumidity}}",
"min": "50",
"max": "100",
"colors": [
"#00b500",
"#e6e600",
"#ca3838"
],
"seg1": "",
"seg2": "",
"diff": false,
"className": "",
"x": 1600,
"y": 560,
"wires": []
},
{
"id": "2ece70af46bcc0cc",
"type": "ui_gauge",
"z": "4752af02ad2b6936",
"name": "",
"group": "dcd3673081b04935",
"order": 3,
"width": 0,
"height": 0,
"gtype": "gage",
"title": "空氣壓力",
"label": "atm",
"format": "{{payload.records.Station[0].WeatherElement.AirPressure}}",
"min": "900",
"max": "1100",
"colors": [
"#00b500",
"#e6e600",
"#ca3838"
],
"seg1": "",
"seg2": "",
"diff": false,
"className": "",
"x": 1600,
"y": 600,
"wires": []
},
{
"id": "7224c1d51afb7b29",
"type": "debug",
"z": "4752af02ad2b6936",
"name": "debug 22",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "false",
"statusVal": "",
"statusType": "auto",
"x": 760,
"y": 400,
"wires": []
},
{
"id": "e0f13a621b9c6c39",
"type": "http in",
"z": "4752af02ad2b6936",
"name": "",
"url": "/weather-app",
"method": "get",
"upload": false,
"swaggerDoc": "",
"x": 530,
"y": 580,
"wires": [
[
"a5155f909f85ea60"
]
]
},
{
"id": "a5155f909f85ea60",
"type": "template",
"z": "4752af02ad2b6936",
"name": "JS版本氣象html",
"field": "payload",
"fieldType": "msg",
"format": "handlebars",
"syntax": "mustache",
"template": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>Weather Display</title>\n <style>\n body {\n font-family: 'Arial', sans-serif;\n margin: 0;\n padding: 0;\n background-color: #f4f4f4;\n color: #333;\n }\n\n .weather-container {\n margin-top: 20px;\n background-color: white;\n padding: 20px;\n border-radius: 8px;\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);\n width: 300px; /* 固定寬度 */\n }\n\n #stationSelect {\n padding: 8px;\n border-radius: 4px;\n border: 1px solid #ddd;\n margin-top: 20px;\n }\n\n h1, p {\n margin: 10px 0;\n }\n\n #city {\n font-size: 1.5em;\n color: #0275d8;\n }\n\n #weather {\n font-size: 1.2em;\n color: #5cb85c;\n }\n\n #temperature {\n font-size: 1.4em;\n color: #f0ad4e;\n }\n\n /* 響應式布局 */\n @media (max-width: 600px) {\n .weather-container {\n width: 90%;\n margin: 20px auto;\n }\n }\n\n </style>\n</head>\n<body>\n <div>\n <label for=\"stationSelect\">Choose a station:</label>\n <select id=\"stationSelect\">\n <option value=\"桃園\">桃園</option>\n <option value=\"苗栗\">苗栗</option>\n <option value=\"南投\">南投</option>\n <option value=\"埤頭\">彰化</option>\n <option value=\"中坑\">台中</option>\n <option value=\"安南\">台南</option>\n <!-- 更多選項 -->\n </select>\n </div>\n <div class=\"weather-container\">\n <h1 id=\"city\"></h1>\n <p id=\"weather\"></p>\n <p id=\"temperature\"></p>\n </div>\n <script>\n document.getElementById('stationSelect').addEventListener('change', (event) => {\n fetchWeatherData(event.target.value);\n });\n\n function fetchWeatherData(stationName) {\n const apiUrl = `https://opendata.cwa.gov.tw/api/v1/rest/datastore/O-A0001-001?Authorization=your-token&limit=10&format=JSON&StationName=${stationName}&WeatherElement=Weather,Now,WindSpeed,AirTemperature,RelativeHumidity,AirPressure&GeoInfo=CountyName`;\n\n fetch(apiUrl)\n .then(response => {\n if (!response.ok) {\n throw new Error('Network response was not ok');\n }\n return response.json();\n })\n .then(data => {\n console.log(data)\n updateWeatherDisplay(data);\n })\n .catch(error => {\n console.error('There has been a problem with your fetch operation:', error);\n });\n }\n\n function updateWeatherDisplay(data) {\n const temperature = data.records.Station[0]['WeatherElement']['AirTemperature'];\n document.getElementById('temperature').textContent = temperature;\n const weather = data.records.Station[0]['WeatherElement']['Weather'];\n document.getElementById('weather').textContent = weather;\n const city = data.records.Station[0]['GeoInfo']['CountyName'];\n document.getElementById('city').textContent = city;\n }\n\n // 初始化,載入頁面時自動加載預設站點的天氣數據\n fetchWeatherData(document.getElementById('stationSelect').value);\n\n </script>\n</body>\n</html>",
"output": "str",
"x": 780,
"y": 580,
"wires": [
[
"cacd3e3f65e86660"
]
]
},
{
"id": "cacd3e3f65e86660",
"type": "http response",
"z": "4752af02ad2b6936",
"name": "",
"statusCode": "",
"headers": {},
"x": 950,
"y": 580,
"wires": []
},
{
"id": "5974891f76b40263",
"type": "mqtt in",
"z": "4752af02ad2b6936",
"name": "",
"topic": "/Thinkitive/temp",
"qos": "0",
"datatype": "auto-detect",
"broker": "2f00154a47567466",
"nl": false,
"rap": true,
"rh": 0,
"inputs": 0,
"x": 1220,
"y": 640,
"wires": [
[
"8c8b14b4220552e7"
]
]
},
{
"id": "99b0ab903e745406",
"type": "mqtt in",
"z": "4752af02ad2b6936",
"name": "",
"topic": "/Thinkitive/hum",
"qos": "0",
"datatype": "auto-detect",
"broker": "2f00154a47567466",
"nl": false,
"rap": true,
"rh": 0,
"inputs": 0,
"x": 1220,
"y": 700,
"wires": [
[
"d3cad417a4b59315"
]
]
},
{
"id": "8c8b14b4220552e7",
"type": "ui_gauge",
"z": "4752af02ad2b6936",
"name": "TRMP",
"group": "70d1d491db52e4fe",
"order": 13,
"width": 0,
"height": 0,
"gtype": "gage",
"title": "ESP32溫度",
"label": "",
"format": "{{value}}",
"min": 0,
"max": "150",
"colors": [
"#00b500",
"#e6e600",
"#ec2727"
],
"seg1": "",
"seg2": "",
"diff": false,
"className": "",
"x": 1430,
"y": 640,
"wires": []
},
{
"id": "d3cad417a4b59315",
"type": "ui_gauge",
"z": "4752af02ad2b6936",
"name": "",
"group": "70d1d491db52e4fe",
"order": 14,
"width": 0,
"height": 0,
"gtype": "gage",
"title": "ESP32濕度",
"label": "",
"format": "{{value}}",
"min": 0,
"max": "60",
"colors": [
"#15cb15",
"#e6e600",
"#ca3838"
],
"seg1": "",
"seg2": "",
"diff": false,
"className": "",
"x": 1450,
"y": 700,
"wires": []
},
{
"id": "407a01e4.6b637",
"type": "mqtt-broker",
"name": "",
"broker": "localhost",
"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": "dcd3673081b04935",
"type": "ui_group",
"name": "天氣",
"tab": "2156c1cf7f043d72",
"order": 1,
"disp": true,
"width": "6",
"collapse": false,
"className": ""
},
{
"id": "2f00154a47567466",
"type": "mqtt-broker",
"name": "",
"broker": "test.mosquitto.org",
"port": "1883",
"clientid": "",
"autoConnect": true,
"usetls": false,
"protocolVersion": "4",
"keepalive": "60",
"cleansession": true,
"autoUnsubscribe": true,
"birthTopic": "",
"birthQos": "0",
"birthRetain": "false",
"birthPayload": "",
"birthMsg": {},
"closeTopic": "",
"closeQos": "0",
"closeRetain": "false",
"closePayload": "",
"closeMsg": {},
"willTopic": "",
"willQos": "0",
"willRetain": "false",
"willPayload": "",
"willMsg": {},
"userProps": "",
"sessionExpiry": ""
},
{
"id": "70d1d491db52e4fe",
"type": "ui_group",
"name": "esp32圖表",
"tab": "2156c1cf7f043d72",
"order": 2,
"disp": true,
"width": "6",
"collapse": false,
"className": ""
},
{
"id": "2156c1cf7f043d72",
"type": "ui_tab",
"name": "天氣圖表",
"icon": "dashboard",
"disabled": false,
"hidden": false
}
]