Node-Red程式
[{"id":"f55075677ed35293","type":"mqtt in","z":"bb5d172d168249ce","name":"Temperature In","topic":"esp32/dht/temperature","qos":"0","datatype":"auto-detect","broker":"450dc18180d64bb4","nl":false,"rap":true,"rh":0,"inputs":0,"x":140,"y":400,"wires":[["61b4d08432d56a31","c0d12e12613d965f"]]},{"id":"e44d567086ce7b54","type":"mqtt in","z":"bb5d172d168249ce","name":"Humidity In","topic":"esp32/dht/humidity","qos":"0","datatype":"auto-detect","broker":"450dc18180d64bb4","nl":false,"rap":true,"rh":0,"inputs":0,"x":120,"y":480,"wires":[["8929e5e3ae1f7377","2547cb81ca53ca94"]]},{"id":"b3e02f06b6b797fc","type":"ui_button","z":"bb5d172d168249ce","name":"LED ON","group":"2708b73a218d6a6a","order":1,"width":2,"height":1,"passthru":false,"label":"ON","tooltip":"","color":"","bgcolor":"green","icon":"","payload":"on","payloadType":"str","topic":"esp32/led/control","topicType":"str","x":100,"y":60,"wires":[["6b653a6976693822","2b611e3b624f923c"]]},{"id":"6b653a6976693822","type":"mqtt out","z":"bb5d172d168249ce","name":"LED Control Out","topic":"esp32/led/control","qos":"","retain":"","respTopic":"","contentType":"","userProps":"","correl":"","expiry":"","broker":"450dc18180d64bb4","x":340,"y":160,"wires":[]},{"id":"61b4d08432d56a31","type":"ui_text","z":"bb5d172d168249ce","group":"c1f20dd979d554a9","order":1,"width":4,"height":1,"name":"Temperature","label":"Temperature:","format":"{{msg.payload}} °C","layout":"row-spread","className":"","x":330,"y":440,"wires":[]},{"id":"8929e5e3ae1f7377","type":"ui_text","z":"bb5d172d168249ce","group":"c1f20dd979d554a9","order":2,"width":4,"height":1,"name":"Humidity","label":"Humidity:","format":"{{msg.payload}} %","layout":"row-spread","className":"","x":320,"y":520,"wires":[]},{"id":"b025d57d54e6015b","type":"ui_button","z":"bb5d172d168249ce","name":"LED OFF","group":"2708b73a218d6a6a","order":2,"width":2,"height":1,"passthru":false,"label":"OFF","tooltip":"","color":"","bgcolor":"red","icon":"","payload":"off","payloadType":"str","topic":"esp32/led/control","topicType":"str","x":100,"y":120,"wires":[["6b653a6976693822","dfa07159c3a3780a"]]},{"id":"1894d30b65f3f00e","type":"ui_button","z":"bb5d172d168249ce","name":"LED FLASH","group":"2708b73a218d6a6a","order":3,"width":2,"height":1,"passthru":false,"label":"FLASH","tooltip":"","color":"","bgcolor":"blue","icon":"","payload":"flash","payloadType":"str","topic":"esp32/led/control","topicType":"str","x":110,"y":180,"wires":[["6b653a6976693822","d9061099684784cf"]]},{"id":"764ef1a25ee82103","type":"ui_button","z":"bb5d172d168249ce","name":"LED TIMER","group":"2708b73a218d6a6a","order":4,"width":2,"height":1,"passthru":false,"label":"TIMER (10s)","tooltip":"","color":"","bgcolor":"orange","icon":"","payload":"timer","payloadType":"str","topic":"esp32/led/control","topicType":"str","x":110,"y":260,"wires":[["6b653a6976693822","e9569651a243e8ea"]]},{"id":"6f028889988229b4","type":"ui_text","z":"bb5d172d168249ce","group":"c711a31b7e5108ce","order":1,"width":8,"height":1,"name":"Current Time","label":"","format":"<span style=\"font-weight:bold;\">{{msg.payload}}</span>","layout":"row-spread","className":"","x":510,"y":680,"wires":[]},{"id":"fb5896a7d5705300","type":"inject","z":"bb5d172d168249ce","name":"Current Time","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"1","crontab":"","once":true,"onceDelay":"0.1","topic":"","payload":"","payloadType":"date","x":100,"y":680,"wires":[["b4482b5d4f208c14"]]},{"id":"b4482b5d4f208c14","type":"function","z":"bb5d172d168249ce","name":"Format Date/Time","func":"const now = new Date(msg.payload);\nconst date = now.toLocaleDateString();\nconst time = now.toLocaleTimeString();\nmsg.payload = `${date} ${time}`;\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":310,"y":680,"wires":[["6f028889988229b4"]]},{"id":"2b611e3b624f923c","type":"function","z":"bb5d172d168249ce","name":"Log LED ON","func":"const now = new Date();\nconst date = now.toISOString().split('T')[0];\nconst time = now.toTimeString().split(' ')[0];\nvar ledon=\"LED ON command sent\";\nmsg.topic = \"INSERT INTO events (date, time, event) VALUES ($date, $time ,$ledon)\";\nmsg.payload = [date, time, ledon];\nreturn msg;\n","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":330,"y":60,"wires":[["c060205f27bb1c2c","a19001309855523f"]]},{"id":"dfa07159c3a3780a","type":"function","z":"bb5d172d168249ce","name":"Log LED OFF","func":"const now = new Date();\nconst date = now.toISOString().split('T')[0];\nconst time = now.toTimeString().split(' ')[0];\nvar ledoff = \"LED OFF command sent\";\nmsg.topic = \"INSERT INTO events (date, time, event) VALUES ($date, $time, $ledoff)\";\nmsg.payload = [date, time, ledoff];\nreturn msg;","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":340,"y":120,"wires":[["c060205f27bb1c2c","a19001309855523f"]]},{"id":"d9061099684784cf","type":"function","z":"bb5d172d168249ce","name":"Log LED FLASH","func":"const now = new Date();\nconst date = now.toISOString().split('T')[0];\nconst time = now.toTimeString().split(' ')[0];\nvar ledflash = \"LED FLASH command sent\";\nmsg.topic = \"INSERT INTO events (date, time, event) VALUES ($date, $time, $ledflash)\";\nmsg.payload = [date, time, ledflash];\nreturn msg;","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":340,"y":220,"wires":[["c060205f27bb1c2c","a19001309855523f"]]},{"id":"e9569651a243e8ea","type":"function","z":"bb5d172d168249ce","name":"Log LED TIMER","func":"const now = new Date();\nconst date = now.toISOString().split('T')[0];\nconst time = now.toTimeString().split(' ')[0];\nvar ledtime = \"LED TIMER (10s) command sent\";\nmsg.topic = \"INSERT INTO events (date, time, event) VALUES ($date, $time, $ledtime )\";\nmsg.payload = [date, time, ledtime ];\nreturn msg;","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":340,"y":280,"wires":[["c060205f27bb1c2c","a19001309855523f"]]},{"id":"c0d12e12613d965f","type":"function","z":"bb5d172d168249ce","name":"Log Temperature","func":"const now = new Date();\nconst date = now.toISOString().split('T')[0];\nconst time = now.toTimeString().split(' ')[0];\nvar temp=\"Received Temperature: \" + msg.payload + \"°C\";\nmsg.topic = \"INSERT INTO events (date, time, event) VALUES ($date, $time, $temp)\";\nmsg.payload = [date, time, temp];\nreturn msg;","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":350,"y":400,"wires":[["a19001309855523f","2baf2855da70384e"]]},{"id":"f8101413a17e0b5a","type":"function","z":"bb5d172d168249ce","name":"Log Humidity","func":"const now = new Date();\nconst date = now.toISOString().split('T')[0];\nconst time = now.toTimeString().split(' ')[0];\nvar humid = \"Received Humidity:\"+msg.payload+\"%\" ;\nmsg.topic = \"INSERT INTO events (date, time, event) VALUES ($date, $time, $humid)\";\nmsg.payload = [date, time, humid ];\nreturn msg;","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":450,"y":480,"wires":[["a19001309855523f","2baf2855da70384e"]]},{"id":"c060205f27bb1c2c","type":"sqlite","z":"bb5d172d168249ce","mydb":"8e0edb919c30c852","sqlquery":"msg.topic","sql":"{{msg.topic}}","name":"SQLite DB","x":650,"y":340,"wires":[["6f2abbdf8483d7d0"]]},{"id":"a19001309855523f","type":"ui_text","z":"bb5d172d168249ce","group":"c711a31b7e5108ce","order":2,"width":8,"height":2,"name":"Event Log Display","label":"","format":"<pre>{{msg.payload}}</pre>","layout":"row-spread","className":"","x":630,"y":200,"wires":[]},{"id":"1f8e6c7020a6e2e5","type":"inject","z":"bb5d172d168249ce","name":"Refresh DB","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":true,"onceDelay":"0.1","topic":"SELECT id, date, time, event FROM events ORDER BY id DESC","payload":"","payloadType":"date","x":110,"y":580,"wires":[["4b600969d2753a47"]]},{"id":"63f4581ed6b567b4","type":"ui_table","z":"bb5d172d168249ce","group":"d0554c9c222ff4ed","name":"Database View","order":1,"width":10,"height":8,"columns":[{"field":"id","title":"ID碼","width":"","align":"left","formatter":"plaintext","formatterParams":{"target":"_blank"}},{"field":"date","title":"日期","width":"","align":"left","formatter":"plaintext","formatterParams":{"target":"_blank"}},{"field":"time","title":"時間","width":"","align":"left","formatter":"plaintext","formatterParams":{"target":"_blank"}},{"field":"event","title":"事件","width":"","align":"left","formatter":"plaintext","formatterParams":{"target":"_blank"}}],"outputs":0,"cts":false,"x":680,"y":580,"wires":[]},{"id":"4b600969d2753a47","type":"sqlite","z":"bb5d172d168249ce","mydb":"8e0edb919c30c852","sqlquery":"msg.topic","sql":"{{msg.topic}}","name":"Query DB","x":440,"y":580,"wires":[["63f4581ed6b567b4"]]},{"id":"060d4b1a8d05ddac","type":"inject","z":"bb5d172d168249ce","name":"Create Table Once","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":true,"onceDelay":"2","topic":"CREATE TABLE IF NOT EXISTS events (id INTEGER PRIMARY KEY AUTOINCREMENT, date TEXT NOT NULL, time TEXT NOT NULL, event TEXT NOT NULL)","payload":"","payloadType":"date","x":150,"y":340,"wires":[["c060205f27bb1c2c"]]},{"id":"dca2454b18f096ba","type":"comment","z":"bb5d172d168249ce","name":"esp32.db TABLE","info":"CREATE TABLE events \n(id INTEGER PRIMARY KEY AUTOINCREMENT, \ndate TEXT NOT NULL, \ntime TEXT NOT NULL, \nevent TEXT NOT NULL)","x":640,"y":300,"wires":[]},{"id":"6f2abbdf8483d7d0","type":"debug","z":"bb5d172d168249ce","name":"debug 344","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":810,"y":340,"wires":[]},{"id":"2547cb81ca53ca94","type":"delay","z":"bb5d172d168249ce","name":"","pauseType":"delay","timeout":"3","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"allowrate":false,"outputs":1,"x":300,"y":480,"wires":[["f8101413a17e0b5a"]]},{"id":"2baf2855da70384e","type":"sqlite","z":"bb5d172d168249ce","mydb":"8e0edb919c30c852","sqlquery":"msg.topic","sql":"{{msg.topic}}","name":"SQLite DB","x":650,"y":400,"wires":[[]]},{"id":"2a4e76d0edf315a6","type":"inject","z":"bb5d172d168249ce","name":"Current Time","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"5","crontab":"","once":true,"onceDelay":"0.1","topic":"","payload":"","payloadType":"date","x":100,"y":620,"wires":[["e1de99b9f8718bcf"]]},{"id":"e1de99b9f8718bcf","type":"function","z":"bb5d172d168249ce","name":"function query","func":"msg.topic= \"SELECT id, date, time, event FROM events ORDER BY id DESC\";\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":280,"y":620,"wires":[["4b600969d2753a47"]]},{"id":"450dc18180d64bb4","type":"mqtt-broker","name":"broker.mqttgo.io","broker":"broker.mqttgo.io","port":"1883","clientid":"","autoConnect":true,"usetls":false,"protocolVersion":"4","keepalive":"60","cleansession":true,"autoUnsubscribe":true,"birthTopic":"","birthQos":"0","birthPayload":"","birthMsg":{},"closeTopic":"","closeQos":"0","closePayload":"","closeMsg":{},"willTopic":"","willQos":"0","willPayload":"","willMsg":{},"userProps":"","sessionExpiry":""},{"id":"2708b73a218d6a6a","type":"ui_group","name":"LED Control","tab":"273a502422f98f98","order":1,"disp":true,"width":4,"collapse":false,"className":""},{"id":"c1f20dd979d554a9","type":"ui_group","name":"DHT22 Sensor Data","tab":"273a502422f98f98","order":2,"disp":true,"width":4,"collapse":false,"className":""},{"id":"c711a31b7e5108ce","type":"ui_group","name":"Event Log","tab":"273a502422f98f98","order":3,"disp":true,"width":8,"collapse":false,"className":""},{"id":"8e0edb919c30c852","type":"sqlitedb","db":"esp32.db","mode":"RWC"},{"id":"d0554c9c222ff4ed","type":"ui_group","name":"Database Browser","tab":"273a502422f98f98","order":4,"disp":true,"width":10,"collapse":false,"className":""},{"id":"273a502422f98f98","type":"ui_tab","name":"ESP32 IoT Dashboard","icon":"dashboard","order":1,"disabled":false,"hidden":false}]
Node-RED 流程的 JSON 配置,清晰地展示了 Node-RED 與 ESP32 之間透過 MQTT Broker 進行的 雙向控制與數據交換關係。
Node-RED 與 ESP32 的控制關係分析
這個 Node-RED 流程的核心是利用 MQTT (Message Queuing Telemetry Transport) 協定作為 ESP32 和 Node-RED 之間溝通的橋樑。MQTT 是一種輕量級的發布/訂閱 (Publish/Subscribe) 訊息協定,非常適合 IoT 應用。
1. MQTT Broker 配置
id: "450dc18180d64bb4"type: "mqtt-broker"name: "broker.mqttgo.io"broker: "broker.mqttgo.io"port: "1883"這是整個系統的中央通訊樞紐。Node-RED 和你的 ESP32 都會連接到這個 MQTT Broker (
broker.mqttgo.io,預設端口 1883)。MQTT Broker 負責接收來自發布者的訊息,並將這些訊息轉發給所有訂閱了相關主題的客戶端。
ESP32 的角色: 作為 MQTT 客戶端,它會發布 (Publish) DHT22 感測器數據到特定主題,並訂閱 (Subscribe) 特定主題來接收 LED 控制命令。
Node-RED 的角色: 同樣作為 MQTT 客戶端,它會訂閱 ESP32 發布的感測器數據主題,並發布 LED 控制命令到 ESP32 訂閱的主題。
2. 從 ESP32 接收數據 (感測器數據)
id: "f55075677ed35293" (Temperature In)id: "e44d567086ce7b54" (Humidity In)type: "mqtt in"topic: "esp32/dht/temperature" / "esp32/dht/humidity"這兩個是 MQTT 輸入 (mqtt in) 節點,它們負責從 MQTT Broker 接收訊息。
Temperature In節點訂閱了esp32/dht/temperature這個 MQTT 主題。當 ESP32 模擬器中的 DHT22 感測器發布溫度數據到這個主題時,Node-RED 就會接收到這個訊息。Humidity In節點訂閱了esp32/dht/humidity這個 MQTT 主題,接收濕度數據。控制關係 (數據流):
ESP32 發布 (Publish) 數據: ESP32 讀取 DHT22 溫濕度後,會將數據分別發布到
esp32/dht/temperature和esp32/dht/humidity。Node-RED 接收數據: Node-RED 中的這些
mqtt in節點會訂閱這些主題,從而接收到 ESP32 發送過來的溫濕度值。
後續處理:
收到的數據會傳遞給
ui_text節點 (61b4d08432d56a31和8929e5e3ae1f7377),用於在 Node-RED Dashboard 上實時顯示溫濕度數值。同時,數據也會傳遞給
function節點 (c0d12e12613d965f和f8101413a17e0b5a),這些function節點會處理數據並準備 SQL 語句,以便將溫濕度事件記錄到 SQLite 資料庫中(透過sqlite節點)。
3. 控制 ESP32 (LED 控制)
id: "b3e02f06b6b797fc" (LED ON)id: "b025d57d54e6015b" (LED OFF)id: "1894d30b65f3f00e" (LED FLASH)id: "764ef1a25ee82103" (LED TIMER)type: "ui_button"topic: "esp32/led/control"payload: "on" / "off" / "flash" / "timer"這些是 Node-RED Dashboard 上的 按鈕 (ui_button) 節點。當用戶在網頁儀表板上點擊這些按鈕時,它們會生成一個訊息 (
msg)。每個按鈕都配置了特定的
payload(例如 "on", "off", "flash", "timer") 和相同的topic(esp32/led/control)。
id: "6b653a6976693822" (LED Control Out)type: "mqtt out"topic: "esp32/led/control"這是一個 MQTT 輸出 (mqtt out) 節點,它負責將訊息發布到 MQTT Broker。
當任何一個 LED 控制按鈕被點擊時,其訊息會傳遞到這個
mqtt out節點。mqtt out節點會將訊息的payload(即 "on", "off", "flash", "timer") 發布到esp32/led/control這個主題上。控制關係 (控制流):
Node-RED 發布 (Publish) 命令: 用戶在 Node-RED Dashboard 上點擊按鈕,Node-RED 將對應的控制命令(例如 "on")發布到
esp32/led/control主題。ESP32 訂閱 (Subscribe) 命令: ESP32 的 Arduino 程式碼會訂閱
esp32/led/control這個 MQTT 主題。當它收到此主題的訊息時,會解析payload(例如 "on"),然後根據內容執行相應的操作,例如點亮 LED、關閉 LED、讓 LED 閃爍或定時器操作。
後續處理:
按鈕被點擊後,除了發送 MQTT 命令,訊息還會傳遞給不同的
function節點 (2b611e3b624f923c,dfa07159c3a3780a等)。這些function節點會創建對應的日誌訊息,並將其記錄到 SQLite 資料庫中,同時顯示在 Dashboard 的事件日誌上。
4. 資料庫記錄與瀏覽 (輔助 ESP32 控制的長期監控)
雖然這部分不直接控制 ESP32,但它與 ESP32 的數據和命令流緊密相關,提供了監控和歷史查詢的能力。
function節點 (Log LED ON/OFF/FLASH/TIMER, Log Temperature, Log Humidity):這些節點負責將 MQTT 訊息(無論是控制命令還是感測器數據)轉換為標準的事件日誌格式,並準備好插入 SQLite 資料庫的 SQL 語句和參數。
sqlite節點 (c060205f27bb1c2c,2baf2855da70384e):接收來自
function節點的 SQL 語句和數據,將其寫入esp32.db檔案中的events表格。
ui_text節點 (a19001309855523f- Event Log Display):接收
function節點生成的日誌文本,並在 Node-RED Dashboard 上實時顯示最新的事件。
inject節點 (1f8e6c7020a6e2e5- Refresh DB) 和function節點 (e1de99b9f8718bcf- function query):當用戶點擊 "Refresh DB" 按鈕時,
inject節點會觸發一個流程。function query節點會設置一個 SQLSELECT語句 (SELECT id, date, time, event FROM events ORDER BY id DESC),用於查詢events表格中的所有歷史數據。
sqlite節點 (4b600969d2753a47- Query DB):執行
SELECT語句,從esp32.db中讀取歷史事件數據。
ui_table節點 (63f4581ed6b567b4- Database View):接收
Query DB節點返回的數據,並以格式化的表格形式顯示在 Node-RED Dashboard 上,實現了數據庫瀏覽功能。
inject節點 (060d4b1a8d05ddac- Create Table Once):在 Node-RED 啟動時執行一次,用於確保
events表格在 SQLite 資料庫中已經存在。
總結控制關係
這整個 Node-RED 流程與 ESP32 之間建立了基於 MQTT 的完整雙向通訊:
Node-RED 控制 ESP32 (命令下發):
Node-RED Dashboard 上的按鈕 (例如 "LED ON") -> 發布 MQTT 訊息到
esp32/led/control主題 -> ESP32 訂閱此主題並執行相應的 LED 操作。
ESP32 向 Node-RED 報告 (數據上傳):
ESP32 感測器 (例如 DHT22) 讀取數據 -> 發布 MQTT 訊息到
esp32/dht/temperature或esp32/dht/humidity主題 -> Node-RED 訂閱這些主題並在 Dashboard 上顯示數據,同時記錄到資料庫。
這種架構實現了一個健壯且可擴展的 IoT 系統,Node-RED 充當了用戶界面、數據處理和持久化層,而 ESP32 則作為邊緣設備負責物理世界的互動和數據採集。














沒有留言:
張貼留言