2025年10月9日 星期四

Node-RED安裝與啟動 與 節點安裝 範例

 Node-RED安裝與啟動 與 節點安裝 範例






一、 Node-RED 核心安裝與啟動

Node-RED 運行於 Node.js 環境,最常見的安裝方式是使用 npm (Node.js 套件管理器)。

步驟 1: 安裝 Node.js 和 npm

  1. 下載 Node.js: 前往 Node.js 官方網站

  2. 選擇 LTS 版: 通常建議下載 LTS (長期支援版) 以確保穩定性。

  3. 執行安裝程式: 運行下載的安裝程式 (Windows/macOS) 或依照 Linux 指示安裝。保持預設設定,一路點擊「Next」即可。

  4. 驗證安裝: 打開終端機 (macOS/Linux) 或命令提示字元/PowerShell (Windows),輸入並檢查版本號:

    Bash
    node -v
    npm -v
    

步驟 2: 安裝 Node-RED

在終端機或命令提示字元中,使用 npm 進行全域安裝

Bash
npm install -g --unsafe-perm node-red

--unsafe-perm 在某些系統上可避免全域安裝時的權限問題。

步驟 3: 啟動 Node-RED

安裝完成後,直接在終端機中輸入:

Bash
node-red

當看到類似 Server now running at http://127.0.0.1:1880/ 的訊息時,表示啟動成功。

步驟 4: 訪問 Node-RED 編輯器

打開網頁瀏覽器,輸入網址:

http://localhost:1880

你將會看到 Node-RED 的網頁編輯器介面。


二、 節點 (Node) 安裝與管理

Node-RED 的功能強大,得益於其豐富的節點生態系統

安裝特定節點 (以 Dashboard 和 SQLite 為例)

  1. 打開管理調色盤: 在 Node-RED 編輯器介面,點擊右上角的 選單圖示 (三條橫線) 選擇 Palette Manager (管理調色盤)。

  2. 切換到安裝頁面: 點擊上方的 Install (安裝) 選項卡。

  3. 搜尋並安裝節點:

    • 在搜尋框輸入 node-red-dashboard 點擊找到的結果右側的 Install 確認安裝。

    • 在搜尋框輸入 node-red-node-sqlite 點擊找到的結果右側的 Install 確認安裝。

  4. 安裝完成後,新的節點會出現在左側的節點列表中。


三、 專案流程與檔案操作

匯入流程 JSON 檔案

如果你有他人分享的 Node-RED 流程 (JSON 格式):

  1. 在 Node-RED 介面,點擊右上角的 選單圖示 Import (匯入) From Clipboard (從剪貼簿)。

  2. 將 JSON 程式碼貼到文字框中,點擊 Import

部署流程

匯入或修改流程後,必須執行部署才能生效:

  1. 點擊右上角的 Deploy (部署) 紅色按鈕

訪問 Dashboard 儀表板

安裝 node-red-dashboard 後,可透過以下網址訪問視覺化儀表板:

http://localhost:1880/ui

四、 核心節點與概念簡介

1. Dashboard 節點

用於建立視覺化介面。

  • ui_tab: 代表一個主頁面或標籤頁。

  • ui_group: 在頁面中創建一個區塊,用於組織 UI 控制元件。

  • ui_button, ui_text, ui_table: 各式圖形化控制與顯示元件。

2. SQLite 節點 (node-red-node-sqlite)

用於與 SQLite 資料庫互動。

  • sqlite-db (配置節點):

    • 一個共享的配置,用於設定 SQLite 資料庫檔案的路徑 (例如:esp32.db)。

    • 在拖曳第一個 sqlite 節點到流程中時,點擊鉛筆圖示配置它。

  • sqlite (操作節點):

    • 接收包含 SQL 查詢語句的 msg.topic

    • 執行 SQL 操作(INSERT, SELECT, UPDATE, DELETE 等)。

3. MQTT 節點 (mqtt in / mqtt out)

用於連接 MQTT Broker 進行訊息收發。

  • mqtt in: 用於訂閱特定 Topic 的訊息 (例如:接收感測器數據)。

  • mqtt out: 用於發布訊息到特定 Topic (例如:發送 LED 控制命令)。

  • 重要: 在配置 MQTT Broker 節點時,務必修改 Client ID 為獨特的 ID,避免連線衝突。


WOKWI程式

#include <WiFi.h>
#include <PubSubClient.h>
#include <DHT.h>
#include <DHT_U.h> // 需要同時包含 DHT_U.h

// --- Wi-Fi 設定 ---
const char* ssid = "Wokwi-GUEST";
const char* password = "";

// --- MQTT 設定 ---
const char* mqtt_server = "broker.mqttgo.io"; // 或 "mqtt.eclipseprojects.io"
const int mqtt_port = 1883;
const char* mqtt_client_id = "ESP32_Wokwi_Client";

// MQTT 主題
const char* mqtt_topic_led_control = "wokwi/led/control";
const char* mqtt_topic_temperature = "wokwi/dht/temperature";
const char* mqtt_topic_humidity = "wokwi/dht/humidity";

WiFiClient espClient;
PubSubClient client(espClient);

// --- LED 設定 ---
const int ledPin = 2; // 連接到 GPIO 2
enum LedMode { ON, OFF, FLASH, TIMER };
volatile LedMode currentLedMode = OFF;
volatile unsigned long timerStartTime = 0;
volatile bool ledState = false; // 用於閃爍模式

// --- DHT22 設定 ---
#define DHTPIN 4      // 連接到 GPIO 4
#define DHTTYPE DHT22 // DHT 22  (AM2302), AM2321
DHT dht(DHTPIN, DHTTYPE);

// --- 任務句柄 ---
TaskHandle_t TaskLEDControl = NULL;
TaskHandle_t TaskDHTSensor = NULL;

// --- 函式宣告 ---
void setup_wifi();
void reconnect_mqtt();
void callback(char* topic, byte* payload, unsigned int length);

void ledControlTask(void *pvParameters);
void dhtSensorTask(void *pvParameters);

void setup() {
  Serial.begin(115200);
  pinMode(ledPin, OUTPUT);
  digitalWrite(ledPin, LOW); // 確保初始是關閉的

  setup_wifi();
  client.setServer(mqtt_server, mqtt_port);
  client.setCallback(callback);

  dht.begin(); // 初始化 DHT 感測器

  // 創建 LED 控制任務,運行在 Core 0
  xTaskCreatePinnedToCore(
    ledControlTask,   /* 任務函式 */
    "LED Control",    /* 任務名稱 */
    2048,             /* 堆疊大小 (字節) */
    NULL,             /* 任務參數 */
    1,                /* 任務優先級 */
    &TaskLEDControl,  /* 任務句柄 */
    0                 /* 運行在 Core 0 */
  );

  // 創建 DHT 感測器任務,運行在 Core 1
  xTaskCreatePinnedToCore(
    dhtSensorTask,    /* 任務函式 */
    "DHT Sensor",     /* 任務名稱 */
    4096,             /* 堆疊大小 (字節) */
    NULL,             /* 任務參數 */
    1,                /* 任務優先級 */
    &TaskDHTSensor,   /* 任務句柄 */
    1                 /* 運行在 Core 1 */
  );
}

void loop() {
  // 主循環中只負責維持 MQTT 連線
  if (!client.connected()) {
    reconnect_mqtt();
  }
  client.loop(); // 處理 MQTT 訊息
  delay(10); // 短暫延遲,避免佔用太多 CPU
}

// --- Wi-Fi 連線函式 ---
void setup_wifi() {
  delay(10);
  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(ssid);

  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  Serial.println("");
  Serial.println("WiFi connected");
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());
}

// --- MQTT 重連函式 ---
void reconnect_mqtt() {
  while (!client.connected()) {
    Serial.print("Attempting MQTT connection...");
    // 嘗試連線
    if (client.connect(mqtt_client_id)) {
      Serial.println("connected");
      // 訂閱 LED 控制主題
      client.subscribe(mqtt_topic_led_control);
      Serial.print("Subscribed to: ");
      Serial.println(mqtt_topic_led_control);
    } else {
      Serial.print("failed, rc=");
      Serial.print(client.state());
      Serial.println(" try again in 5 seconds");
      // 等待 5 秒後重試
      delay(5000);
    }
  }
}

// --- MQTT 訊息回調函式 ---
void callback(char* topic, byte* payload, unsigned int length) {
  Serial.print("Message arrived [");
  Serial.print(topic);
  Serial.print("] ");
  String message = "";
  for (int i = 0; i < length; i++) {
    message += (char)payload[i];
  }
  Serial.println(message);

  if (String(topic) == mqtt_topic_led_control) {
    if (message == "on") {
      currentLedMode = ON;
      digitalWrite(ledPin, HIGH);
      Serial.println("LED Mode: ON");
    } else if (message == "off") {
      currentLedMode = OFF;
      digitalWrite(ledPin, LOW);
      Serial.println("LED Mode: OFF");
    } else if (message == "flash") {
      currentLedMode = FLASH;
      Serial.println("LED Mode: FLASH");
    } else if (message == "timer") {
      currentLedMode = TIMER;
      digitalWrite(ledPin, HIGH); // 定時模式開始時先開啟 LED
      timerStartTime = millis();
      Serial.println("LED Mode: TIMER (10s)");
    } else {
      Serial.println("Unknown LED command.");
    }
  }
}

// --- LED 控制任務 (運行在 Core 0) ---
void ledControlTask(void *pvParameters) {
  (void) pvParameters; // 避免編譯器警告

  for (;;) { // 無限循環
    switch (currentLedMode) {
      case ON:
        // LED 保持亮著,由 callback 函式設置
        break;
      case OFF:
        // LED 保持熄滅,由 callback 函式設置
        break;
      case FLASH:
        digitalWrite(ledPin, ledState);
        ledState = !ledState;
        vTaskDelay(pdMS_TO_TICKS(500)); // 每 500ms 改變一次狀態
        break;
      case TIMER:
        if (millis() - timerStartTime >= 10000) { // 10 秒
          digitalWrite(ledPin, LOW);
          currentLedMode = OFF; // 定時結束後轉為 OFF 模式
          Serial.println("LED Timer finished. LED OFF.");
        }
        vTaskDelay(pdMS_TO_TICKS(10)); // 短暫延遲
        break;
      default:
        digitalWrite(ledPin, LOW); // 預設為關閉
        break;
    }
    vTaskDelay(pdMS_TO_TICKS(10)); // 短暫延遲,讓其他任務有機會執行
  }
}

// --- DHT 感測器任務 (運行在 Core 1) ---
void dhtSensorTask(void *pvParameters) {
  (void) pvParameters; // 避免編譯器警告

  for (;;) { // 無限循環
    delay(2000); // 每 2 秒讀取一次數據,避免頻繁讀取導致錯誤

    float h = dht.readHumidity();
    float t = dht.readTemperature(); // 讀取攝氏溫度

    // 檢查是否讀取失敗,如果是則嘗試重讀
    if (isnan(h) || isnan(t)) {
      Serial.println(F("Failed to read from DHT sensor!"));
    } else {
      Serial.print(F("Humidity: "));
      Serial.print(h);
      Serial.print(F("%  Temperature: "));
      Serial.print(t);
      Serial.println(F("°C"));

      // 發布溫度
      char tempString[8];
      dtostrf(t, 4, 2, tempString); // 浮點數轉字串
      client.publish(mqtt_topic_temperature, tempString);

      // 發布濕度
      char humString[8];
      dtostrf(h, 4, 2, humString); // 浮點數轉字串
      delay(250);
      client.publish(mqtt_topic_humidity, humString);
    }
    vTaskDelay(pdMS_TO_TICKS(5000)); // 每 5 秒執行一次,避免 MQTT 發布過於頻繁
  }
}


Node-Red程式


[ { "id": "131047bc20364361", "type": "tab", "label": "流程2", "disabled": false, "info": "", "env": [] }, { "id": "164767289eb7c78d", "type": "ui_button", "z": "131047bc20364361", "name": "ON", "group": "539f4088006e8909", "order": 1, "width": 0, "height": 0, "passthru": false, "label": "ON", "tooltip": "", "color": "", "bgcolor": "", "className": "", "icon": "", "payload": "on", "payloadType": "str", "topic": "wokwi/led/control", "topicType": "str", "x": 110, "y": 20, "wires": [ [ "3c076dc1c02bd5a8", "7ebc9ae2f8f61bdf" ] ] }, { "id": "3c076dc1c02bd5a8", "type": "mqtt out", "z": "131047bc20364361", "name": "LED Control", "topic": "wokwi/led/control", "qos": "0", "retain": "false", "respTopic": "", "contentType": "", "userProps": "", "correl": "", "expiry": "", "broker": "192c2b20bef1e71a", "x": 390, "y": 120, "wires": [] }, { "id": "1a607adac2bd1c5f", "type": "ui_button", "z": "131047bc20364361", "name": "OFF", "group": "539f4088006e8909", "order": 2, "width": 0, "height": 0, "passthru": false, "label": "OFF", "tooltip": "", "color": "", "bgcolor": "", "className": "", "icon": "", "payload": "off", "payloadType": "str", "topic": "wokwi/led/control", "topicType": "str", "x": 110, "y": 80, "wires": [ [ "3c076dc1c02bd5a8", "7ebc9ae2f8f61bdf" ] ] }, { "id": "20c5d848d874407c", "type": "ui_button", "z": "131047bc20364361", "name": "FLASH", "group": "539f4088006e8909", "order": 3, "width": 0, "height": 0, "passthru": false, "label": "FLASH", "tooltip": "", "color": "", "bgcolor": "", "className": "", "icon": "", "payload": "flash", "payloadType": "str", "topic": "wokwi/led/control", "topicType": "str", "x": 120, "y": 140, "wires": [ [ "3c076dc1c02bd5a8", "7ebc9ae2f8f61bdf" ] ] }, { "id": "5886d355af86e15f", "type": "ui_button", "z": "131047bc20364361", "name": "TIMER (10s)", "group": "539f4088006e8909", "order": 4, "width": 0, "height": 0, "passthru": false, "label": "TIMER (10s)", "tooltip": "", "color": "", "bgcolor": "", "className": "", "icon": "", "payload": "timer", "payloadType": "str", "topic": "wokwi/led/control", "topicType": "str", "x": 130, "y": 200, "wires": [ [ "3c076dc1c02bd5a8", "7ebc9ae2f8f61bdf" ] ] }, { "id": "2fad22cc617b2e3a", "type": "mqtt in", "z": "131047bc20364361", "name": "Temperature", "topic": "wokwi/dht/temperature", "qos": "1", "datatype": "utf8", "broker": "192c2b20bef1e71a", "nl": false, "rap": true, "rh": 0, "inputs": 0, "x": 130, "y": 340, "wires": [ [ "4096f840a6a15575", "0649251b181a8602" ] ] }, { "id": "66b7cccf837503a8", "type": "mqtt in", "z": "131047bc20364361", "name": "Humidity", "topic": "wokwi/dht/humidity", "qos": "1", "datatype": "utf8", "broker": "192c2b20bef1e71a", "nl": false, "rap": true, "rh": 0, "inputs": 0, "x": 110, "y": 420, "wires": [ [ "6fb678097cc1214a", "05a8523190b6f301" ] ] }, { "id": "c3bb780153f67b32", "type": "ui_chart", "z": "131047bc20364361", "name": "Temperature Chart", "group": "182b827e8d2e8b0b", "order": 1, "width": 5, "height": 4, "label": "Temperature (°C)", "chartType": "line", "legend": "true", "xformat": "HH:mm:ss", "interpolate": "step", "nodata": "", "dot": false, "ymin": "-40", "ymax": "80", "removeOlder": "10", "removeOlderPoints": "", "removeOlderUnit": "1", "cutout": "", "useOneColor": false, "useUTC": false, "colors": [ "#ff0000", "#aec7e8", "#ff7f0e", "#2ca02c", "#98df8a", "#d62728", "#ff9896", "#9467bd", "#c5b0d5" ], "outputs": 1, "useDifferentColor": false, "className": "", "x": 540, "y": 360, "wires": [ [] ] }, { "id": "a3022323c38ba706", "type": "ui_chart", "z": "131047bc20364361", "name": "Humidity Chart", "group": "182b827e8d2e8b0b", "order": 2, "width": 5, "height": 4, "label": "Humidity (%)", "chartType": "line", "legend": "true", "xformat": "HH:mm:ss", "interpolate": "step", "nodata": "", "dot": false, "ymin": "0", "ymax": "100", "removeOlder": "10", "removeOlderPoints": "", "removeOlderUnit": "1", "cutout": "", "useOneColor": false, "useUTC": false, "colors": [ "#1f77b4", "#aec7e8", "#ff7f0e", "#2ca02c", "#98df8a", "#d62728", "#ff9896", "#9467bd", "#c5b0d5" ], "outputs": 1, "useDifferentColor": false, "className": "", "x": 530, "y": 420, "wires": [ [] ] }, { "id": "0649251b181a8602", "type": "debug", "z": "131047bc20364361", "name": "Debug Temperature", "active": true, "tosidebar": true, "console": false, "tostatus": false, "complete": "payload", "targetType": "msg", "statusVal": "", "statusType": "auto", "x": 310, "y": 300, "wires": [] }, { "id": "05a8523190b6f301", "type": "debug", "z": "131047bc20364361", "name": "Debug Humidity", "active": true, "tosidebar": true, "console": false, "tostatus": false, "complete": "payload", "targetType": "msg", "statusVal": "", "statusType": "auto", "x": 320, "y": 480, "wires": [] }, { "id": "4096f840a6a15575", "type": "function", "z": "131047bc20364361", "name": "To Number (Temp)", "func": "msg.payload = parseFloat(msg.payload);\nreturn msg;", "outputs": 1, "timeout": "", "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 330, "y": 360, "wires": [ [ "c3bb780153f67b32", "f89803acf4bcc755" ] ] }, { "id": "6fb678097cc1214a", "type": "function", "z": "131047bc20364361", "name": "To Number (Hum)", "func": "msg.payload = parseFloat(msg.payload);\nreturn msg;", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 320, "y": 420, "wires": [ [ "a3022323c38ba706", "0c339df3ab928ff6" ] ] }, { "id": "f89803acf4bcc755", "type": "ui_gauge", "z": "131047bc20364361", "name": "", "group": "182b827e8d2e8b0b", "order": 3, "width": 5, "height": 4, "gtype": "gage", "title": "gauge", "label": "℃", "format": "{{value}}", "min": "-40", "max": "80", "colors": [ "#00b500", "#e6e600", "#ca3838" ], "seg1": "25", "seg2": "40", "diff": false, "className": "", "x": 510, "y": 320, "wires": [] }, { "id": "0c339df3ab928ff6", "type": "ui_gauge", "z": "131047bc20364361", "name": "", "group": "182b827e8d2e8b0b", "order": 4, "width": 5, "height": 4, "gtype": "gage", "title": "gauge", "label": "%", "format": "{{value}}", "min": 0, "max": "100", "colors": [ "#00b500", "#e6e600", "#ca3838" ], "seg1": "65", "seg2": "80", "diff": false, "className": "", "x": 510, "y": 460, "wires": [] }, { "id": "7ebc9ae2f8f61bdf", "type": "debug", "z": "131047bc20364361", "name": "debug ", "active": true, "tosidebar": true, "console": false, "tostatus": false, "complete": "payload", "targetType": "msg", "statusVal": "", "statusType": "auto", "x": 410, "y": 80, "wires": [] }, { "id": "539f4088006e8909", "type": "ui_group", "name": "LED Control", "tab": "31b64e525281561f", "order": 1, "disp": true, "width": 3, "collapse": false, "className": "" }, { "id": "192c2b20bef1e71a", "type": "mqtt-broker", "name": "mqttgo", "broker": "broker.mqttgo.io", "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": "182b827e8d2e8b0b", "type": "ui_group", "name": "DHT22 Sensor Data", "tab": "31b64e525281561f", "order": 2, "disp": true, "width": 10, "collapse": false, "className": "" }, { "id": "31b64e525281561f", "type": "ui_tab", "name": "ESP32 Control", "icon": "dashboard", "disabled": false, "hidden": false } ]

















沒有留言:

張貼留言

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

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