คู่มือใบงาน ESP32 IoT MQTT (HiveMQ)

ยินดีต้อนรับสู่ชุดใบงานสำหรับเรียนรู้ ESP32 ระดับมืออาชีพ จำนวน 10 ใบงาน เรียนรู้ตั้งแต่พื้นฐานจนถึงการสร้าง Web Dashboard ร่วมกับ MQTT

🔌 การต่อสายฮาร์ดแวร์ (Pinout)

Inputs (สวิตช์แบบ Active Low - ใช้งานแบบ Pull-up):

  • SW1 = GPIO 4
  • SW2 = GPIO 5
  • SW3 = GPIO 18
  • SW4 = GPIO 19
  • SW Reset WiFi = GPIO 15

Outputs (โหลดหรือ LED แบบ Active High):

  • O1 = GPIO 13
  • O2 = GPIO 12
  • O3 = GPIO 14
  • O4 = GPIO 27

กรุณาเลือกใบงานจากแถบเมนูด้านซ้ายเพื่อเริ่มการเรียนรู้

Lab 01: Basic I/O & Hardware Test

ทดสอบการอ่านค่าอินพุต (สวิตช์) และเขียนค่าออกเอาต์พุต (LED/Relay) เพื่อให้มั่นใจว่าฮาร์ดแวร์ทำงานได้ถูกต้อง โดยใช้คำสั่ง INPUT_PULLUP เพื่อไม่ต้องต่อตัวต้านทานภายนอก


// Lab 01: Basic I/O & Hardware Test
#define SW1 4
#define SW2 5
#define SW3 18
#define SW4 19

#define O1 13
#define O2 12
#define O3 14
#define O4 27

void setup() {
  Serial.begin(115200);
  pinMode(SW1, INPUT_PULLUP);
  pinMode(SW2, INPUT_PULLUP);
  pinMode(SW3, INPUT_PULLUP);
  pinMode(SW4, INPUT_PULLUP);
  
  pinMode(O1, OUTPUT);
  pinMode(O2, OUTPUT);
  pinMode(O3, OUTPUT);
  pinMode(O4, OUTPUT);
}

void loop() {
  digitalWrite(O1, !digitalRead(SW1));
  digitalWrite(O2, !digitalRead(SW2));
  digitalWrite(O3, !digitalRead(SW3));
  digitalWrite(O4, !digitalRead(SW4));
  delay(50);
}
            

Lab 02: Smart WiFi Connection (WiFiManager)

เชื่อมต่อ WiFi โดยไม่ฮาร์ดโค้ดรหัสผ่านลงในโค้ด ใช้ไลบรารี WiFiManager โดยหากหาเน็ตไม่เจอ ESP32 จะปล่อย AP ออกมา และสามารถกดปุ่มที่ขา 15 เพื่อล้างค่าได้


// Lab 02: Smart WiFi Connection
#include <WiFi.h>
#include <WiFiManager.h> 

#define WIFI_RESET_PIN 15

void setup() {
  Serial.begin(115200);
  pinMode(WIFI_RESET_PIN, INPUT_PULLUP);
  
  WiFiManager wm;
  if (digitalRead(WIFI_RESET_PIN) == LOW) {
    wm.resetSettings();
    ESP.restart();
  }

  wm.autoConnect("ESP32_Setup", "password"); 
  Serial.println("Connected to WiFi!");
}

void loop() {
  if (digitalRead(WIFI_RESET_PIN) == LOW) {
    delay(100); 
    if (digitalRead(WIFI_RESET_PIN) == LOW) {
      WiFiManager wm;
      wm.resetSettings();
      ESP.restart();
    }
  }
}
            

Lab 03: Hello HiveMQ (MQTT Connection)

การตั้งค่าและเชื่อมต่อ ESP32 เข้ากับระบบ Cloud ผ่าน HiveMQ Public Broker โดยใช้ไลบรารี PubSubClient


// Lab 03: Hello HiveMQ
#include <WiFi.h>
#include <WiFiManager.h>
#include <PubSubClient.h>

#define WIFI_RESET_PIN 15
const char* mqtt_server = "broker.hivemq.com";
String clientId = "ESP32_Client_" + String(random(0xffff), HEX);

WiFiClient espClient;
PubSubClient client(espClient);

void connectMQTT() {
  while (!client.connected()) {
    if (client.connect(clientId.c_str())) {
      Serial.println("MQTT Connected!");
    } else {
      delay(5000);
    }
  }
}

void setup() {
  Serial.begin(115200);
  pinMode(WIFI_RESET_PIN, INPUT_PULLUP);
  WiFiManager wm;
  wm.autoConnect("ESP32_Setup", "password");
  client.setServer(mqtt_server, 1883);
}

void loop() {
  if (!client.connected()) connectMQTT();
  client.loop();
}
            

Lab 04: Single Input Publish

อ่านค่าการกดปุ่ม SW1 (Debounce ป้องกันสวิตช์เบิ้ล) และส่งข้อมูล "ON" หรือ "OFF" ขึ้นไปยัง Topic บน MQTT


// Lab 04: Single Input Publish
// อ่านค่าสวิตช์ SW1 (Pin 4) แล้วส่งไปที่ HiveMQ (Publish)

#include <WiFi.h>
#include <WiFiManager.h>
#include <PubSubClient.h>

#define WIFI_RESET_PIN 15
#define SW1 4

const char* mqtt_server = "broker.hivemq.com";
const int mqtt_port = 1883;
String clientId = "ESP32_Client_IOT_" + String(random(0xffff), HEX);
const char* topic_pub_sw1 = "myiot/student1/sw1"; // เปลี่ยน student1 เป็นชื่อคุณ

WiFiClient espClient;
PubSubClient client(espClient);

int lastSw1State = HIGH;

void connectMQTT() {
  while (!client.connected()) {
    Serial.print("กำลังเชื่อมต่อ MQTT...");
    if (client.connect(clientId.c_str())) {
      Serial.println("เชื่อมต่อสำเร็จ!");
    } else {
      Serial.print("ล้มเหลว ลองใหม่ใน 5 วินาที");
      delay(5000);
    }
  }
}

void setup() {
  Serial.begin(115200);
  pinMode(WIFI_RESET_PIN, INPUT_PULLUP);
  pinMode(SW1, INPUT_PULLUP);
  
  WiFiManager wm;
  if (digitalRead(WIFI_RESET_PIN) == LOW) { wm.resetSettings(); ESP.restart(); }
  wm.autoConnect("ESP32_Setup", "password");
  
  client.setServer(mqtt_server, mqtt_port);
}

void loop() {
  if (!client.connected()) connectMQTT();
  client.loop();

  // อ่านค่า SW1 (LOW = กด, HIGH = ปล่อย)
  int currentSw1State = digitalRead(SW1);
  
  // ตรวจสอบว่ามีการเปลี่ยนแปลงสถานะหรือไม่ (Edge Detection)
  if (currentSw1State != lastSw1State) {
    delay(50); // Debounce
    currentSw1State = digitalRead(SW1);
    if (currentSw1State != lastSw1State) {
      lastSw1State = currentSw1State;
      
      // ส่งข้อมูล Publish ไปที่ MQTT
      if (currentSw1State == LOW) {
        client.publish(topic_pub_sw1, "ON");
        Serial.println("SW1 Pressed -> Sent: ON");
      } else {
        client.publish(topic_pub_sw1, "OFF");
        Serial.println("SW1 Released -> Sent: OFF");
      }
    }
  }

  if (digitalRead(WIFI_RESET_PIN) == LOW) {
    delay(100);
    if(digitalRead(WIFI_RESET_PIN) == LOW) {
      WiFiManager wm;
      wm.resetSettings();
      ESP.restart();
    }
  }
}
            

Lab 05: Single Output Subscribe

การรับคำสั่งจาก MQTT มาเปิด/ปิด โหลด O1 โดยใช้ Callback Function


// Lab 05: Single Output Subscribe
// รับคำสั่งจาก HiveMQ (Subscribe) มาควบคุม LED/Output 1 (Pin 13)

#include <WiFi.h>
#include <WiFiManager.h>
#include <PubSubClient.h>

#define WIFI_RESET_PIN 15
#define O1 13

const char* mqtt_server = "broker.hivemq.com";
const int mqtt_port = 1883;
String clientId = "ESP32_Client_IOT_" + String(random(0xffff), HEX);
const char* topic_sub_o1 = "myiot/student1/o1"; 

WiFiClient espClient;
PubSubClient client(espClient);

// ฟังก์ชันทำงานเมื่อได้รับข้อมูลจาก MQTT
void callback(char* topic, byte* payload, unsigned int length) {
  String message = "";
  for (int i = 0; i < length; i++) {
    message += (char)payload[i];
  }
  
  Serial.print("ได้รับข้อความ Topic: ");
  Serial.print(topic);
  Serial.print(" -> ข้อมูล: ");
  Serial.println(message);

  // ควบคุม Output O1
  if (String(topic) == topic_sub_o1) {
    if (message == "ON") {
      digitalWrite(O1, HIGH);
    } else if (message == "OFF") {
      digitalWrite(O1, LOW);
    }
  }
}

void connectMQTT() {
  while (!client.connected()) {
    Serial.print("กำลังเชื่อมต่อ MQTT...");
    if (client.connect(clientId.c_str())) {
      Serial.println("เชื่อมต่อสำเร็จ!");
      // หลังจากเชื่อมต่อสำเร็จ ต้อง Subscribe Topic ทันที
      client.subscribe(topic_sub_o1);
    } else {
      delay(5000);
    }
  }
}

void setup() {
  Serial.begin(115200);
  pinMode(WIFI_RESET_PIN, INPUT_PULLUP);
  pinMode(O1, OUTPUT);
  digitalWrite(O1, LOW);
  
  WiFiManager wm;
  if (digitalRead(WIFI_RESET_PIN) == LOW) { wm.resetSettings(); ESP.restart(); }
  wm.autoConnect("ESP32_Setup", "password");
  
  client.setServer(mqtt_server, mqtt_port);
  client.setCallback(callback); // ผูกฟังก์ชัน callback รับข้อมูล
}

void loop() {
  if (!client.connected()) connectMQTT();
  client.loop();
}
            

Lab 06: Full I/O MQTT Control

รวมระบบทั้ง 4 อินพุต และ 4 เอาต์พุต โดยใช้ Array ทำให้โค้ดสั้นลง และใช้ Wildcard (#) ในการ Subscribe


// Lab 06: Full I/O MQTT Control
// ควบคุม Input 4 ช่อง ส่ง Publish และ Output 4 ช่องรับ Subscribe

#include <WiFi.h>
#include <WiFiManager.h>
#include <PubSubClient.h>

#define WIFI_RESET_PIN 15

// พิน I/O
const int swPins[4] = {4, 5, 18, 19};
const int outPins[4] = {13, 12, 14, 27};
int lastSwState[4] = {HIGH, HIGH, HIGH, HIGH};

const char* mqtt_server = "broker.hivemq.com";
const int mqtt_port = 1883;
String clientId = "ESP32_Client_IOT_" + String(random(0xffff), HEX);

// Topics
String baseTopic = "myiot/student1/";
String subTopic = baseTopic + "out/#"; // Subscribe ทุก Topic ที่ขึ้นต้นด้วย out/

WiFiClient espClient;
PubSubClient client(espClient);

void callback(char* topic, byte* payload, unsigned int length) {
  String message = "";
  for (int i = 0; i < length; i++) message += (char)payload[i];
  
  String topicStr = String(topic);
  
  // เช็คว่า Topic ตรงกับ Output ไหน
  for(int i=0; i<4; i++) {
    String checkTopic = baseTopic + "out/" + String(i+1);
    if(topicStr == checkTopic) {
      if(message == "ON") digitalWrite(outPins[i], HIGH);
      else if(message == "OFF") digitalWrite(outPins[i], LOW);
    }
  }
}

void connectMQTT() {
  while (!client.connected()) {
    if (client.connect(clientId.c_str())) {
      client.subscribe(subTopic.c_str());
    } else {
      delay(5000);
    }
  }
}

void setup() {
  Serial.begin(115200);
  pinMode(WIFI_RESET_PIN, INPUT_PULLUP);
  
  for(int i=0; i<4; i++) {
    pinMode(swPins[i], INPUT_PULLUP);
    pinMode(outPins[i], OUTPUT);
    digitalWrite(outPins[i], LOW);
  }
  
  WiFiManager wm;
  if (digitalRead(WIFI_RESET_PIN) == LOW) { wm.resetSettings(); ESP.restart(); }
  wm.autoConnect("ESP32_Setup", "password");
  
  client.setServer(mqtt_server, mqtt_port);
  client.setCallback(callback);
}

void loop() {
  if (!client.connected()) connectMQTT();
  client.loop();

  // ตรวจสอบสวิตช์ทั้ง 4 ปุ่ม
  for(int i=0; i<4; i++) {
    int currentState = digitalRead(swPins[i]);
    if(currentState != lastSwState[i]) {
      delay(50);
      if(digitalRead(swPins[i]) == currentState) {
        lastSwState[i] = currentState;
        String pubTopic = baseTopic + "in/" + String(i+1);
        String msg = (currentState == LOW) ? "ON" : "OFF";
        client.publish(pubTopic.c_str(), msg.c_str());
      }
    }
  }
}
            

Lab 07: Professional Payload Structuring (JSON)

การส่งข้อมูลเป็น JSON String เช่น {"sw1":1, "sw2":0} ซึ่งเป็นมาตรฐานอุตสาหกรรม ต้องใช้ไลบรารี ArduinoJson


// Lab 07: Professional Payload Structuring (JSON)
// การใช้ JSON ในการแพ็คข้อมูลหลายๆ ค่าส่งไปใน Topic เดียว

#include <WiFi.h>
#include <WiFiManager.h>
#include <PubSubClient.h>
#include <ArduinoJson.h> // ติดตั้ง Library: ArduinoJson by Benoit Blanchon (version 6 or 7)

#define WIFI_RESET_PIN 15

const int swPins[4] = {4, 5, 18, 19};
const int outPins[4] = {13, 12, 14, 27};
int lastSwState[4] = {HIGH, HIGH, HIGH, HIGH};

const char* mqtt_server = "broker.hivemq.com";
const int mqtt_port = 1883;
String clientId = "ESP32_Client_IOT_" + String(random(0xffff), HEX);

String pubTopic = "myiot/student1/status";
String subTopic = "myiot/student1/command";

WiFiClient espClient;
PubSubClient client(espClient);

void callback(char* topic, byte* payload, unsigned int length) {
  // รับข้อมูล JSON และแยกวิเคราะห์ (Parse)
  StaticJsonDocument<256> doc;
  DeserializationError error = deserializeJson(doc, payload, length);

  if (error) {
    Serial.print("JSON Parse failed: ");
    Serial.println(error.c_str());
    return;
  }

  // คาดหวัง JSON: {"o1":1, "o2":0, "o3":1, "o4":0}
  if (doc.containsKey("o1")) digitalWrite(outPins[0], doc["o1"] ? HIGH : LOW);
  if (doc.containsKey("o2")) digitalWrite(outPins[1], doc["o2"] ? HIGH : LOW);
  if (doc.containsKey("o3")) digitalWrite(outPins[2], doc["o3"] ? HIGH : LOW);
  if (doc.containsKey("o4")) digitalWrite(outPins[3], doc["o4"] ? HIGH : LOW);
}

void connectMQTT() {
  while (!client.connected()) {
    if (client.connect(clientId.c_str())) {
      client.subscribe(subTopic.c_str());
    } else delay(5000);
  }
}

void setup() {
  Serial.begin(115200);
  pinMode(WIFI_RESET_PIN, INPUT_PULLUP);
  for(int i=0; i<4; i++) { pinMode(swPins[i], INPUT_PULLUP); pinMode(outPins[i], OUTPUT); }
  
  WiFiManager wm;
  if (digitalRead(WIFI_RESET_PIN) == LOW) { wm.resetSettings(); ESP.restart(); }
  wm.autoConnect("ESP32_Setup", "password");
  
  client.setServer(mqtt_server, mqtt_port);
  client.setCallback(callback);
}

void loop() {
  if (!client.connected()) connectMQTT();
  client.loop();

  bool changed = false;
  for(int i=0; i<4; i++) {
    int currentState = digitalRead(swPins[i]);
    if(currentState != lastSwState[i]) {
      delay(50);
      if(digitalRead(swPins[i]) == currentState) {
        lastSwState[i] = currentState;
        changed = true;
      }
    }
  }

  // ถอดสถานะมีการเปลี่ยนแปลง ให้ส่ง JSON ออกไป
  if (changed) {
    StaticJsonDocument<256> doc;
    doc["sw1"] = (lastSwState[0] == LOW) ? 1 : 0;
    doc["sw2"] = (lastSwState[1] == LOW) ? 1 : 0;
    doc["sw3"] = (lastSwState[2] == LOW) ? 1 : 0;
    doc["sw4"] = (lastSwState[3] == LOW) ? 1 : 0;
    
    char buffer[256];
    serializeJson(doc, buffer);
    client.publish(pubTopic.c_str(), buffer);
    Serial.println(buffer);
  }
}
            

Lab 08: Device Reliability (LWT & Retain)

ใช้ Last Will and Testament (LWT) เพื่อให้ Broker แจ้งเตือนเวลาอุปกรณ์เน็ตหลุดโดยอัตโนมัติ


// Lab 08: Device Reliability (LWT & Retain)
// การใช้ Last Will and Testament (LWT) แจ้งเตือนเมื่ออุปกรณ์ออฟไลน์ และ Retain ข้อความ

#include <WiFi.h>
#include <WiFiManager.h>
#include <PubSubClient.h>

#define WIFI_RESET_PIN 15
const int swPins[4] = {4, 5, 18, 19};

const char* mqtt_server = "broker.hivemq.com";
const int mqtt_port = 1883;
String clientId = "ESP32_Client_IOT_" + String(random(0xffff), HEX);

const char* topic_status = "myiot/student1/connection";

WiFiClient espClient;
PubSubClient client(espClient);

void connectMQTT() {
  while (!client.connected()) {
    Serial.print("Connecting to MQTT...");
    // กำหนด LWT (Topic, QoS, Retain, Message)
    // หาก ESP32 ขาดการเชื่อมต่อ Broker จะส่งข้อความ "offline" ออกไปแทน
    boolean retain = true;
    if (client.connect(clientId.c_str(), "", "", topic_status, 0, retain, "offline")) {
      Serial.println("Connected!");
      // เมื่อเชื่อมต่อสำเร็จ ให้ส่งสถานะ "online" พร้อมตั้ง Retain = true
      client.publish(topic_status, "online", retain);
    } else {
      delay(5000);
    }
  }
}

void setup() {
  Serial.begin(115200);
  pinMode(WIFI_RESET_PIN, INPUT_PULLUP);
  
  WiFiManager wm;
  if (digitalRead(WIFI_RESET_PIN) == LOW) { wm.resetSettings(); ESP.restart(); }
  wm.autoConnect("ESP32_Setup", "password");
  
  client.setServer(mqtt_server, mqtt_port);
}

void loop() {
  if (!client.connected()) connectMQTT();
  client.loop();
}
            

Lab 09: Local Web Server (SPIFFS/LittleFS)

เสิร์ฟไฟล์ HTML หน้าเว็บ (UI) โดยตรงจากหน่วยความจำ (SPIFFS) ของ ESP32 ไปยังเบราว์เซอร์


// Lab 09: Local Web Server (SPIFFS) & index.html
// การใช้งาน Web Server ภายใน ESP32 เพื่อเสิร์ฟไฟล์ index.html (หน้า Dashboard)

#include <WiFi.h>
#include <WiFiManager.h>
#include <WebServer.h>
#include <SPIFFS.h> // ใช้ระบบไฟล์ SPIFFS สำหรับเก็บหน้าเว็บ

#define WIFI_RESET_PIN 15

WebServer server(80); // สร้าง Web Server บนพอร์ต 80

void setup() {
  Serial.begin(115200);
  pinMode(WIFI_RESET_PIN, INPUT_PULLUP);
  
  // เริ่มต้นใช้งาน SPIFFS (หากไม่เคย format จะ format ให้)
  if (!SPIFFS.begin(true)) {
    Serial.println("เกิดข้อผิดพลาดในการเมานท์ SPIFFS (ตรวจสอบการตั้งค่า Partition Scheme)");
    return;
  }
  
  WiFiManager wm;
  if (digitalRead(WIFI_RESET_PIN) == LOW) { wm.resetSettings(); ESP.restart(); }
  wm.autoConnect("ESP32_Setup", "password");
  
  // ตั้งค่า Route หน้าแรก (Root) ให้ส่งไฟล์ /index.html ไปยัง Client
  server.on("/", HTTP_GET, [](){
    if (SPIFFS.exists("/index.html")) {
      File file = SPIFFS.open("/index.html", "r");
      server.streamFile(file, "text/html");
      file.close();
    } else {
      server.send(404, "text/plain", "File Not Found. กรุณาอัปโหลดไฟล์ผ่าน Sketch Data Upload");
    }
  });
  
  server.begin();
  Serial.print("Web Server Started! เปิดเบราว์เซอร์แล้วเข้าไปที่ http://");
  Serial.println(WiFi.localIP());
}

void loop() {
  server.handleClient(); // รอรับการเชื่อมต่อจากเบราว์เซอร์
}
            

Lab 10: Ultimate Pro IoT Node (Integration)

ใบงานขั้นสุดยอด: นำโค้ด ESP32 ทุกส่วนมารวมกัน และในไฟล์ index.html (Web Dashboard) จะมีการเขียน JavaScript เพื่อใช้ Paho MQTT เชื่อมต่อโดยตรงกับ WebSockets ของ HiveMQ

ตัวอย่างหน้าตา UI สำหรับควบคุม:


<!-- ตัวอย่างส่วนหนึ่งของ index.html ใน Lab 10 -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/paho-mqtt/1.0.1/mqttws31.min.js"></script>
<script>
  const client = new Paho.MQTT.Client("broker.hivemq.com", 8000, "WebClient_" + Date.now());
  
  client.onMessageArrived = function(message) {
      if(message.destinationName === "myiot/pro/lwt") {
          document.getElementById("deviceStatus").textContent = message.payloadString;
      }
      else if (message.destinationName === "myiot/pro/status") {
          const data = JSON.parse(message.payloadString);
          if (data.sw1 !== undefined) updateUI('sw1', data.sw1);
      }
  };

  client.connect({ onSuccess: () => {
      client.subscribe("myiot/pro/status");
      client.subscribe("myiot/pro/lwt");
  }});
  
  function sendCmd(outputId, state) {
      const payload = JSON.stringify({ [outputId]: state ? 1 : 0 });
      const msg = new Paho.MQTT.Message(payload);
      msg.destinationName = "myiot/pro/command";
      client.send(msg);
  }
</script>
            

เพื่อดูหน้าเว็บจริงแบบเต็มรูปแบบ คุณสามารถเปิดไฟล์ D:\antigravity_iot\Lab10_Ultimate_Pro_Node\data\index.html ได้เลยครับ