2026年8月11日 星期二

Toggle Swith in EasyBuilderPro HMI 控制 Wokwi 的 LED

 

Toggle Swith in EasyBuilderPro HMI  控制Wokwi 的 LED ON / OFF



參考來源 https://bermainwokwi.blogspot.com/p/1-koneksi-mqtt.html#delapan

const char* mqtt_server = "mqtt-dashboard.com";   // 設定 MQTT Broker 伺服器位址
const char* mqtt_topic = "alex9ufo/ebpro_wokwi/led/status";  // 設定訂閱的 MQTT 主題(Topic)名稱





Download EasyBuilder Pro (EBPro)  EasyBuilderPro_download

























WOKWI程式

#include <WiFi.h>          // 引入 ESP32 的 WiFi 模組函式庫,用於處理無線網路連線
#include <PubSubClient.h>    // 引入 MQTT 協定函式庫,用於傳輸與接收 MQTT 訊息

#define BUILTIN_LED 2      // 定義板載 LED 所連接的引腳(GPIO 2)

const char* ssid = "Wokwi-GUEST";                 // 設定 WiFi 的 SSID(名稱)
const char* password = "";                        // 設定 WiFi 的密碼(此處為無密碼)
const char* mqtt_server = "mqtt-dashboard.com";   // 設定 MQTT Broker 伺服器位址
const char* mqtt_topic = "alex9ufo/ebpro_wokwi/led/status";  // 設定訂閱的 MQTT 主題(Topic)名稱

WiFiClient espClient;          // 建立 ESP32 的網路客戶端物件
PubSubClient client(espClient); // 將網路客戶端傳入 MQTT 函式庫,建立 MQTT 客戶端實例

// 自定義函式:負責連接 WiFi
void setup_wifi() {
  Serial.print("Connecting to WiFi...");  // 在序列埠監控器輸出提示字串
  WiFi.begin(ssid, password);             // 開始連線至指定 SSID 與密碼的 WiFi
 
  // 檢查連線狀態,若尚未成功連線就每隔 0.5 秒印出一個點
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);                           // 延遲 500 毫秒
    Serial.print(".");                    // 輸出點點表示進度
  }
 
  // 連線成功後,顯示訊息並印出取得的局域網 IP 位址
  Serial.println("\nWiFi connected! IP: " + WiFi.localIP().toString());
}


// MQTT 回呼函式:當訂閱的主題收到新訊息時會自動執行
void callback(char* topic, byte* payload, unsigned int length) {
  String message;  // 宣告一個字串變數用來儲存接收到的訊息內容
 
  // 將 byte 陣列類型的 payload 逐字轉成字串
  for (unsigned int i = 0; i < length; i++) {
    message += (char)payload[i];
  }
  // 印出訊息發生的主題與具體內容
  Serial.printf("Message arrived [%s]: %s\n", topic, message.c_str());

  // 判斷 JSON 內容:若字串中包含 "true" (忽略大小寫) 則點亮 LED,否則關閉
  if (message.indexOf("true") != -1) {
    digitalWrite(BUILTIN_LED, HIGH);  // 點亮 LED
  } else if (message.indexOf("false") != -1) {
    digitalWrite(BUILTIN_LED, LOW);   // 熄滅 LED
  }
}

// 自定義函式:負責 MQTT 斷線重連
void reconnect() {
  // 當 MQTT 處於未連線狀態時持續嘗試
  while (!client.connected()) {
    Serial.print("Attempting MQTT connection...");
   
    // 產生一個隨機的 Client ID(例如 ESP32Client-A3F1),避免多台裝置使用相同 ID 斷線
    String clientId = "ESP32Client-" + String(random(0xffff), HEX);
   
    // 嘗試連線至 MQTT Broker
    if (client.connect(clientId.c_str())) {
      Serial.println("connected");     // 連線成功提示
      client.subscribe(mqtt_topic);    // 重新訂閱指定主題以接收訊息
    } else {
      // 連線失敗時印出錯誤代碼(rc)並等待 5 秒後重試
      Serial.printf("failed, rc=%d try again in 5 seconds\n", client.state());
      delay(5000);                     // 等待 5000 毫秒
    }
  }
}

// ESP32 初始化設定函式(只會在開機或重置時執行一次)
void setup() {
  pinMode(BUILTIN_LED, OUTPUT);  // 設定 BUILTIN_LED 引腳為輸出模式
  Serial.begin(115200);          // 初始化序列埠通訊,波特率設定為 115200
 
  setup_wifi();                  // 呼叫 setup_wifi 連接網路
 
  client.setServer(mqtt_server, 1883);  // 設定 MQTT 伺服器位址與 Port(預設 1883)
  client.setCallback(callback);         // 指定接收訊息時處理的回呼函式
}

// 主程式無窮迴圈(持續重複執行)
void loop() {
  // 檢查 MQTT 連線狀態,若斷開則呼叫 reconnect() 重連
  if (!client.connected()) reconnect();
 
  client.loop();  // 保持 MQTT 客戶端的運作,處理心跳包(Keep-Alive)與接收新訊息
}

2026年8月8日 星期六

Node-RED 寫入到 Do-more PLC

    Node-RED 寫入到 Do-more PLC
    要將資料從 Node-RED 寫入到 Do-more PLC,最標準且穩定的做法是讓 Node-RED 扮演 Modbus TCP 客戶端(Client),使用 modbus-write 節點,將資料寫入 Do-more 內建的 Modbus 專用暫存器(如線圈 MC 或保持暫存器 MHR)。
    以下為您整理完整的設定步驟與不同資料型態(開關、16位元整數、32位元浮點數)的寫入方法。

    1. 基礎節點設定 (Modbus Write)
    在 Node-RED 中,拖曳 modbus-write 節點到畫布上,雙擊開啟並進行以下基礎設定:
    • Server:選擇您先前建立好的 Do-more 連線(確認 IP 正確、Port 為 502)。
    • Unit ID:輸入 1(或依 PLC 設定,通常預設為 1)。
    • Address:輸入 0-based 的相對位址(例如欲寫入 MHR0,此處請填 0)。

    2. 三種常見資料型態的寫入實作
    情況 A:寫入開關/布林值(對應 Do-more 的 MC 線圈)
    如果要控制 PLC 內部的某個開關接點(例如 MC0):
    1. modbus-write 節點設定
      • FC (Function Code):選擇 FC 5: Write Single Coil
      • Address0(代表 MC0)。
    2. 前段輸入(如 inject 節點)
      • 輸出的 msg.payload 必須是布林值 truefalse(或者是數字 10)。

    情況 B:寫入 16 位元整數(對應 Do-more 的 MHR 暫存器)
    如果要寫入一個 0 ~ 65535 之間的整數值(例如設定溫度、計時器目標值到 MHR0):
    1. modbus-write 節點設定
      • FC (Function Code):選擇 FC 6: Write Single Register
      • Address0(代表 MHR0)。
    2. 前段輸入
      • 輸出的 msg.payload 必須是一個標準整數(如 125)。

    情況 C:寫入 32 位元實數/浮點數(佔用 2 個 MHR 暫存器)
    當你要將一個帶有小數點的數值(如 36.5)從 Node-RED 傳入 Do-more(例如存入 MHR0MHR1)時,因為 Modbus 一個暫存器只能存 16 位元,你必須先用 function 節點將浮點數拆解成兩個 16 位元整數組成的陣列,再進行寫入。
    1. modbus-write 節點設定
      • FC (Function Code):選擇 FC 16: Write Multiple Registers ⚠️ (寫入多個暫存器必選此項)
      • Address0(代表從 MHR0 開始寫)。
      • Quantity2(代表連續寫入 2 個暫存器)。
    2. 前段接一個 function 節點,寫入以下轉換程式碼
    javascript
    // 假設要寫入的浮點數值來自 msg.payload (例如 36.5)
    let floatValue = Number(msg.payload);
    
    // 建立一個 4 內容值的 Buffer 區塊 (32 bits)
    const buf = Buffer.alloc(4);
    
    // 將浮點數以 Big-Endian 格式寫入 Buffer
    buf.writeFloatBE(floatValue, 0);
    
    // 將 4 個 Bytes 拆解組合成 2 個 16 位元的無正負號整數 (UInt16)
    let word1 = buf.readUInt16BE(0); // 高位字組 (High Word)
    let word2 = buf.readUInt16BE(2); // 低位字組 (Low Word)
    
    // 將準備好的陣列賦值給 msg.payload 輸出給 modbus-write 節點
    msg.payload = [word1, word2];
    
    return msg;







[{"id":"281052bfc8430391","type":"modbus-read","z":"940df9f68ff5f5f7","name":"","topic":"","showStatusActivities":false,"logIOActivities":false,"showErrors":false,"showWarnings":true,"unitid":"255","dataType":"Coil","adr":"0","quantity":"8","rate":"3000","rateUnit":"ms","delayOnStart":false,"enableDeformedMessages":false,"startDelayTime":"","server":"03e72e7fd9537ba2","useIOFile":false,"ioFile":"","useIOForPayload":false,"emptyMsgOnFail":false,"x":390,"y":440,"wires":[["01a0a871b0013f8d"],[]]},{"id":"01a0a871b0013f8d","type":"debug","z":"940df9f68ff5f5f7","name":"debug 1","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":580,"y":380,"wires":[]},{"id":"72834d90122f8f0c","type":"modbus-read","z":"940df9f68ff5f5f7","name":"","topic":"","showStatusActivities":false,"logIOActivities":false,"showErrors":false,"showWarnings":true,"unitid":"255","dataType":"HoldingRegister","adr":"0","quantity":"8","rate":"3000","rateUnit":"ms","delayOnStart":false,"enableDeformedMessages":false,"startDelayTime":"","server":"03e72e7fd9537ba2","useIOFile":false,"ioFile":"","useIOForPayload":false,"emptyMsgOnFail":false,"x":390,"y":320,"wires":[["01a0a871b0013f8d"],[]]},{"id":"02ac9ac5b37299ac","type":"modbus-write","z":"940df9f68ff5f5f7","name":"","showStatusActivities":false,"showErrors":false,"showWarnings":true,"unitid":"255","dataType":"Coil","adr":"8","quantity":"1","server":"03e72e7fd9537ba2","emptyMsgOnFail":false,"keepMsgProperties":false,"delayOnStart":false,"startDelayTime":"","x":540,"y":520,"wires":[["c23cc8f3685a03c3"],[]]},{"id":"8fa3a624ea874c80","type":"inject","z":"940df9f68ff5f5f7","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"true","payloadType":"bool","x":350,"y":520,"wires":[["02ac9ac5b37299ac"]]},{"id":"c23cc8f3685a03c3","type":"debug","z":"940df9f68ff5f5f7","name":"debug 2","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":720,"y":480,"wires":[]},{"id":"e8ec656f015766b6","type":"modbus-write","z":"940df9f68ff5f5f7","name":"","showStatusActivities":false,"showErrors":false,"showWarnings":true,"unitid":"255","dataType":"HoldingRegister","adr":"2","quantity":"1","server":"03e72e7fd9537ba2","emptyMsgOnFail":false,"keepMsgProperties":false,"delayOnStart":false,"startDelayTime":"","x":540,"y":620,"wires":[["c23cc8f3685a03c3"],[]]},{"id":"c85e74ca4374c2bb","type":"inject","z":"940df9f68ff5f5f7","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"127","payloadType":"num","x":350,"y":620,"wires":[["e8ec656f015766b6"]]},{"id":"03e72e7fd9537ba2","type":"modbus-client","name":"modbus-Do-more","clienttype":"tcp","bufferCommands":true,"stateLogEnabled":false,"queueLogEnabled":false,"failureLogEnabled":true,"tcpHost":"127.0.0.1","tcpPort":502,"tcpType":"DEFAULT","serialPort":"/dev/ttyUSB","serialType":"RTU-BUFFERD","serialBaudrate":9600,"serialDatabits":8,"serialStopbits":1,"serialParity":"none","serialConnectionDelay":100,"serialAsciiResponseStartDelimiter":"0x3A","unit_id":255,"commandDelay":1,"clientTimeout":1000,"reconnectOnTimeout":true,"reconnectTimeout":2000,"parallelUnitIdsAllowed":true,"showErrors":false,"showWarnings":true,"showLogs":true},{"id":"207b905784147db1","type":"global-config","env":[],"modules":{"node-red-contrib-modbus":"5.60.1"}}]


Do-more 轉 Modbus 記憶體對應表
在 Node-RED 的 Modbus 節點設定中,Address(位址) 通常採用 0 起始(0-based) 的相對位址(即協定內定址)。
Do-more 記憶體類型說明Modbus 類型 / 功能碼 (FC)Node-RED 位址 (0-based)備註 / 對應範例
MC0MC1023Modbus 專用線圈Coils (FC1 / FC5 / FC15)01023MC0 = 位址 0
MI0MI1023Modbus 專用輸入接點Discrete Inputs (FC2)01023MI0 = 位址 0
MHR0MHR2047Modbus 專用保持暫存器Holding Registers (FC3/FC4/FC6/FC16)02047MHR0 = 位址 0
MIR0MIR2047Modbus 專用輸入暫存器Input Registers (FC4)02047MIR0 = 位址 0

Toggle Swith in EasyBuilderPro HMI 控制 Wokwi 的 LED

  Toggle Swith in  EasyBuilder Pro HMI  控制Wokwi  的  LED ON / OFF 參考來源 https://bermainwokwi.blogspot.com/p/1-koneksi-mqtt.html#delapan const ...