เริ่มใช้งาน MQTT กับ COWTH Broker ด้วย ESP32: ส่งค่า Sensor และรับข้อมูลแบบ Real-time

เขียนโดย YuTTYL เมื่อวันที่ 29/05/2026 | ใช้เวลาอ่าน 15 นาที
0

เริ่มใช้งาน MQTT กับ COWTH Broker ด้วย ESP32: ส่งค่า Sensor และรับข้อมูลแบบ Real-time

COWTH คือ MQTT platform made in Thailand สำหรับคนไทยที่อยากเริ่มทำ IoT ได้ง่ายขึ้น ในบทความนี้เราจะลองใช้งาน broker.cowth.dev กับบอร์ด ESP32 จำนวน 2 ตัว โดยตัวแรกทำหน้าที่ส่งข้อมูล และอีกตัวทำหน้าที่รับข้อมูล

COWTH MQTT ESP32 Cover

เป้าหมายของบทความนี้

บทความนี้ผมจะพาทดลองใช้งาน MQTT ของ COWTH แบบง่ายที่สุด แต่เป็น flow ที่ใช้จริงในงาน IoT ได้เลย

เราจะมีอุปกรณ์ 2 ฝั่ง

  1. ESP32 Publisher
    อ่านค่า Analog จาก LDR และ Potentiometer แล้วส่งขึ้น MQTT Broker

  2. ESP32-S3 Subscriber
    Subscribe topic เดียวกัน แล้วรับข้อมูลที่ Publisher ส่งมาแสดงบน Serial Monitor

ภาพรวมการทำงานจะเป็นแบบนี้

flowchart TD
    sensor["LDR + Potentiometer<br/>Analog Sensor Input"]
    pub["ESP32 Publisher<br/>Read sensor and publish MQTT"]
    broker["broker.cowth.dev<br/>COWTH MQTT Broker"]
    sub["ESP32-S3 Subscriber<br/>Subscribe MQTT topic"]
    serial["Serial Monitor<br/>Display received payload"]

    sensor --> pub
    pub -->|MQTT Publish| broker
    broker -->|MQTT Subscribe| sub
    sub --> serial

หรือพูดง่าย ๆ คือ ESP32 ตัวหนึ่งส่งค่า sensor เข้า COWTH Broker แล้ว ESP32-S3 อีกตัวรอฟังข้อมูลแบบ real-time


รูปอุปกรณ์ที่ใช้ทดสอบ

รูปบอร์ด ESP32 ฝั่ง Publisher

ESP32 Publisher Board

รูปบอร์ด ESP32-S3 ฝั่ง Subscriber

ESP32-S3 Subscriber Board

รูป Terminal ทดสอบจริง

COWTH MQTT Terminal Result


MQTT คืออะไรแบบสั้น ๆ

MQTT เป็น protocol สำหรับส่งข้อมูลระหว่างอุปกรณ์ IoT ผ่านแนวคิดแบบ Publish / Subscribe

แทนที่อุปกรณ์จะต้องคุยกันโดยตรง อุปกรณ์ทุกตัวจะคุยผ่านตัวกลางที่เรียกว่า MQTT Broker

ตัวอย่างเช่น

ESP32 A publish ข้อมูลไปที่ topic:
sandbox/your_user/esp_sensor/

ESP32 B subscribe topic เดียวกัน:
sandbox/your_user/esp_sensor/

เมื่อ ESP32 A ส่งข้อมูล ESP32 B ก็จะได้รับทันที

ข้อดีคืออุปกรณ์ไม่จำเป็นต้องรู้ IP ของกันและกัน รู้แค่ broker, username, password และ topic ที่ใช้ร่วมกันก็พอ


COWTH Broker Endpoint ที่ใช้ในตัวอย่างนี้

ในการทดลองนี้ใช้ MQTT Broker ของ COWTH ที่ endpoint:

Host: broker.cowth.dev
Port: 1883
Protocol: MQTT over TCP

หมายเหตุ: ตัวอย่างนี้เป็นการใช้งาน MQTT TCP port 1883 สำหรับการทดลองพื้นฐาน ถ้าใช้งานจริงใน production ควรพิจารณาเรื่อง TLS, authentication policy, topic policy และ credential rotation เพิ่มเติม


Library ที่ต้องติดตั้งใน Arduino IDE หรือ PlatformIO

ตัวอย่างนี้ใช้ Arduino framework และ library หลัก ๆ คือ

PubSubClient
Adafruit GFX Library
Adafruit SSD1306
WiFi
Wire

ถ้าใช้ PlatformIO สามารถใส่ใน platformio.ini ประมาณนี้

[env:esp32dev]
platform = espressif32
board = esp32dev
framework = arduino
monitor_speed = 115200
lib_deps =
  knolleary/PubSubClient
  adafruit/Adafruit GFX Library
  adafruit/Adafruit SSD1306

สำหรับฝั่ง Subscriber ถ้าไม่ได้ใช้จอ OLED จะใช้แค่ PubSubClient กับ WiFi ก็พอ


โครงสร้าง Topic ที่ใช้

ในตัวอย่างนี้ใช้ topic รูปแบบ sandbox:

💡 หมายเหตุ: นำ username และ password ที่ได้รับในเมลที่ทำการกรอกผ่านหน้าเว็ป broker.cowth.dev

sandbox/<username>/esp_sensor/

ตัวอย่างเช่น

sandbox/sbx_xxxxxxxx/esp_sensor/

แนวคิดคือให้แต่ละ user มี prefix ของตัวเอง เพื่อลดโอกาส topic ชนกันระหว่างผู้ใช้งานหลายคน

สำหรับบทความนี้ผมจะใช้ placeholder แบบนี้แทน credential จริง

const char *MQTT_USERNAME = "YOUR_COWTH_USERNAME";
const char *MQTT_PASSWORD = "YOUR_COWTH_PASSWORD";
const char *MQTT_TOPIC = "sandbox/YOUR_COWTH_USERNAME/esp_sensor/";

ฝั่งที่ 1: ESP32 Publisher

ฝั่ง Publisher ทำหน้าที่อ่านค่า Analog จาก

  • LDR ที่ GPIO35
  • Potentiometer ที่ GPIO36
  • แสดงค่าบนจอ OLED SSD1306
  • ส่งข้อมูลขึ้น MQTT ทุก 1 วินาที

Payload ที่ส่งจะเป็น string ง่าย ๆ แบบนี้

LDR_VALUE,POT_VALUE

ตัวอย่าง payload:

2380,1542

Wiring ตัวอย่าง

ESP32 GPIO35  <--- LDR analog output
ESP32 GPIO36  <--- Potentiometer analog output
ESP32 GPIO21  <--- OLED SDA
ESP32 GPIO22  <--- OLED SCL
3.3V          <--- OLED VCC / Sensor VCC
GND           <--- Common GND

GPIO35 และ GPIO36 เป็น input-only pin เหมาะกับการอ่านค่า analog บน ESP32


Code: ESP32 Publisher

ก่อนนำไปใช้จริง ให้เปลี่ยน YOUR_WIFI_SSID, YOUR_WIFI_PASSWORD, YOUR_COWTH_USERNAME และ YOUR_COWTH_PASSWORD เป็นค่าของคุณเอง

#include <Arduino.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <PubSubClient.h>
#include <WiFi.h>
#include <Wire.h>

const char *WIFI_SSID = "YOUR_WIFI_SSID";
const char *WIFI_PASSWORD = "YOUR_WIFI_PASSWORD";

const char *MQTT_HOST = "broker.cowth.dev";
const uint16_t MQTT_PORT = 1883;
const char *MQTT_USERNAME = "YOUR_COWTH_USERNAME";
const char *MQTT_PASSWORD = "YOUR_COWTH_PASSWORD";
const char *MQTT_TOPIC = "sandbox/YOUR_COWTH_USERNAME/esp_sensor/";

const unsigned long PUBLISH_INTERVAL_MS = 1000;
const uint8_t LDR_PIN = 35;
const uint8_t POT_PIN = 36;
const uint8_t OLED_ADDRESS = 0x3C;
const uint8_t OLED_WIDTH = 128;
const uint8_t OLED_HEIGHT = 64;

WiFiClient wifiClient;
PubSubClient mqttClient(wifiClient);
Adafruit_SSD1306 display(OLED_WIDTH, OLED_HEIGHT, &Wire, -1);

unsigned long lastPublishAt = 0;
int lastLdrValue = 0;
int lastPotValue = 0;
bool displayReady = false;

void updateDisplay() {
  if (!displayReady) {
    return;
  }

  display.clearDisplay();
  display.setTextSize(1);
  display.setTextColor(SSD1306_WHITE);

  display.setCursor(0, 0);
  display.print("IP: ");
  display.println(WiFi.localIP());

  display.setCursor(0, 22);
  display.print("LDR: ");
  display.println(lastLdrValue);

  display.setCursor(0, 42);
  display.print("POTENTIOMETER: ");
  display.println(lastPotValue);

  display.display();
}

void connectWiFi() {
  if (WiFi.status() == WL_CONNECTED) {
    return;
  }

  WiFi.mode(WIFI_STA);
  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);

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

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

void connectMqtt() {
  while (!mqttClient.connected()) {
    String clientId = "esp32-sensor-pub-";
    clientId += String((uint32_t)ESP.getEfuseMac(), HEX);

    Serial.print("Connecting MQTT...");
    if (mqttClient.connect(clientId.c_str(), MQTT_USERNAME, MQTT_PASSWORD)) {
      Serial.println("connected");
      return;
    }

    Serial.print("failed, rc=");
    Serial.print(mqttClient.state());
    Serial.println(" retrying in 5 seconds");
    delay(5000);
  }
}

void publishSensorData() {
  lastLdrValue = analogRead(LDR_PIN);
  lastPotValue = analogRead(POT_PIN);

  char payload[32];
  snprintf(payload, sizeof(payload), "%d,%d", lastLdrValue, lastPotValue);

  if (mqttClient.publish(MQTT_TOPIC, payload)) {
    Serial.print("Published: ");
    Serial.println(payload);
  } else {
    Serial.println("Publish failed");
  }

  updateDisplay();
}

void setup() {
  Serial.begin(115200);
  delay(1000);

  Wire.begin();
  if (!display.begin(SSD1306_SWITCHCAPVCC, OLED_ADDRESS)) {
    Serial.println("OLED not found. Check I2C wiring/address.");
  } else {
    displayReady = true;
    updateDisplay();
  }

  analogReadResolution(12);
  analogSetPinAttenuation(LDR_PIN, ADC_11db);
  analogSetPinAttenuation(POT_PIN, ADC_11db);

  connectWiFi();
  mqttClient.setServer(MQTT_HOST, MQTT_PORT);
  connectMqtt();
}

void loop() {
  connectWiFi();
  connectMqtt();
  mqttClient.loop();

  unsigned long now = millis();
  if (now - lastPublishAt >= PUBLISH_INTERVAL_MS) {
    lastPublishAt = now;
    publishSensorData();
  }
}

จุดที่ผมอยากเสริมให้นะครับ Publisher

1. Client ID ไม่ควรซ้ำกัน

ในโค้ดนี้ใช้ MAC จาก ESP32 มาช่วยสร้าง client id

String clientId = "esp32-sensor-pub-";
clientId += String((uint32_t)ESP.getEfuseMac(), HEX);

เพราะ MQTT client แต่ละตัวควรมี client id ที่ไม่ซ้ำกัน ถ้า client id ซ้ำกัน broker อาจมองว่าเป็น client เดียวกัน และเกิดการเตะ connection เก่าออกได้

2. ใช้ mqttClient.loop() เสมอ

ใน loop หลักต้องเรียก

mqttClient.loop();

เพื่อให้ PubSubClient จัดการ MQTT packet ภายใน เช่น keep alive และ packet ที่รอรับจาก broker

3. ส่งข้อมูลทุก 1 วินาทีโดยไม่ใช้ delay() ยาว ๆ

โค้ดใช้ millis() เพื่อจับเวลา publish

if (now - lastPublishAt >= PUBLISH_INTERVAL_MS) {
  lastPublishAt = now;
  publishSensorData();
}

วิธีนี้ดีกว่าการใช้ delay(1000) ตรง ๆ เพราะยังเปิดโอกาสให้ mqttClient.loop() ทำงานต่อเนื่องได้


ฝั่งที่ 2: ESP32-S3 Subscriber

ฝั่ง Subscriber ทำหน้าที่เชื่อมต่อ Wi-Fi, เชื่อมต่อ MQTT Broker แล้ว subscribe topic เดียวกับ Publisher

เมื่อมีข้อมูลเข้ามา function callback จะถูกเรียก

void onMqttMessage(char *topic, byte *payload, unsigned int length)

จากนั้นข้อมูลจะถูกพิมพ์ออก Serial Monitor


Code: ESP32-S3 Subscriber

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

const char *WIFI_SSID = "YOUR_WIFI_SSID";
const char *WIFI_PASSWORD = "YOUR_WIFI_PASSWORD";

const char *MQTT_HOST = "broker.cowth.dev";
const uint16_t MQTT_PORT = 1883;
const char *MQTT_USERNAME = "YOUR_COWTH_USERNAME";
const char *MQTT_PASSWORD = "YOUR_COWTH_PASSWORD";
const char *MQTT_TOPIC = "sandbox/YOUR_COWTH_USERNAME/esp_sensor/";

WiFiClient wifiClient;
PubSubClient mqttClient(wifiClient);

void connectWiFi() {
  if (WiFi.status() == WL_CONNECTED) {
    return;
  }

  WiFi.mode(WIFI_STA);
  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);

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

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

void onMqttMessage(char *topic, byte *payload, unsigned int length) {
  Serial.print("Received [");
  Serial.print(topic);
  Serial.print("]: ");

  for (unsigned int index = 0; index < length; index++) {
    Serial.write(payload[index]);
  }

  Serial.println();
}

void connectMqtt() {
  while (!mqttClient.connected()) {
    String clientId = "esp32-sensor-sub-";
    clientId += String((uint32_t)ESP.getEfuseMac(), HEX);

    Serial.print("Connecting MQTT...");
    if (mqttClient.connect(clientId.c_str(), MQTT_USERNAME, MQTT_PASSWORD)) {
      Serial.println("connected");

      mqttClient.subscribe(MQTT_TOPIC);
      Serial.print("Subscribed: ");
      Serial.println(MQTT_TOPIC);
      return;
    }

    Serial.print("failed, rc=");
    Serial.print(mqttClient.state());
    Serial.println(" retrying in 5 seconds");
    delay(5000);
  }
}

void setup() {
  Serial.begin(115200);
  delay(1000);

  connectWiFi();
  mqttClient.setServer(MQTT_HOST, MQTT_PORT);
  mqttClient.setCallback(onMqttMessage);
  connectMqtt();
}

void loop() {
  connectWiFi();
  connectMqtt();
  mqttClient.loop();
}

ผลลัพธ์ที่คาดหวัง

เมื่อเปิด Serial Monitor ของฝั่ง Publisher จะเห็นประมาณนี้

Connecting WiFi...
WiFi connected, IP: 192.168.1.42
Connecting MQTT...connected
Published: 2350,1488
Published: 2361,1510
Published: 2372,1502

ฝั่ง Subscriber จะเห็นประมาณนี้

Connecting WiFi...
WiFi connected, IP: 192.168.1.56
Connecting MQTT...connected
Subscribed: sandbox/YOUR_COWTH_USERNAME/esp_sensor/
Received [sandbox/YOUR_COWTH_USERNAME/esp_sensor/]: 2350,1488
Received [sandbox/YOUR_COWTH_USERNAME/esp_sensor/]: 2361,1510
Received [sandbox/YOUR_COWTH_USERNAME/esp_sensor/]: 2372,1502

ถ้าเห็นข้อมูลวิ่งแบบนี้ แปลว่า ESP32 ทั้งสองตัวเชื่อมต่อผ่าน COWTH Broker ได้สำเร็จแล้ว


ทดสอบด้วย mosquitto CLI เพิ่มเติม

ถ้ามี mosquitto-clients สามารถทดสอบจากคอมพิวเตอร์ได้ด้วย

Subscribe

mosquitto_sub -h broker.cowth.dev -p 1883 \
  -u "YOUR_COWTH_USERNAME" \
  -P "YOUR_COWTH_PASSWORD" \
  -t "sandbox/YOUR_COWTH_USERNAME/esp_sensor/" \
  -v

Publish

mosquitto_pub -h broker.cowth.dev -p 1883 \
  -u "YOUR_COWTH_USERNAME" \
  -P "YOUR_COWTH_PASSWORD" \
  -t "sandbox/YOUR_COWTH_USERNAME/esp_sensor/" \
  -m "1234,2048"

ถ้าใช้คำสั่ง publish จากคอมพิวเตอร์ ฝั่ง ESP32-S3 Subscriber ก็ควรได้รับข้อความเช่นกัน


การแก้ปัญหา

1. ต่อ Wi-Fi ไม่ได้

ให้เช็ก SSID และ password ก่อน โดยเฉพาะถ้าใช้ ESP32 รุ่นที่รองรับเฉพาะ Wi-Fi 2.4GHz ต้องแน่ใจว่า router เปิด 2.4GHz อยู่

2. MQTT connect ไม่ผ่าน

ให้ดูค่า mqttClient.state() จาก Serial Monitor

ค่าที่เจอบ่อย เช่น

failed, rc=-2

มักเกี่ยวกับ network connection หรือ broker endpoint ติดต่อไม่ได้

failed, rc=4

มักเกี่ยวกับ username/password ไม่ถูกต้อง

3. Subscriber ไม่ได้รับข้อมูล

ให้เช็ก 3 จุดนี้ก่อน

1. Topic ตรงกันไหม
2. Username/password ตรงกันไหม
3. Publisher publish สำเร็จไหม

โดย topic ต้องเหมือนกันทุกตัวอักษร เช่น

sandbox/YOUR_COWTH_USERNAME/esp_sensor/

ถ้าอีกฝั่งเขียนเป็น

sandbox/YOUR_COWTH_USERNAME/esp_sensor

โดยไม่มี / ท้ายสุด จะถือว่าเป็นคนละ topic

4. OLED ไม่ขึ้น

ให้เช็ก I2C address ว่าเป็น 0x3C หรือ 0x3D และเช็กสาย SDA/SCL ให้ถูกต้อง

ค่า default ในตัวอย่างนี้คือ

const uint8_t OLED_ADDRESS = 0x3C;

ข้อควรระวัง

ตัวอย่างนี้ตั้งใจให้เข้าใจ flow MQTT แบบเร็วที่สุด จึงยังมีหลายเรื่องที่ควรปรับก่อนใช้งานจริง เช่น

  • ไม่ควร hardcode Wi-Fi password และ MQTT password ใน firmware ที่แจกให้คนอื่น
  • ควรแยกไฟล์ config หรือใช้ provisioning mode สำหรับตั้งค่า Wi-Fi
  • ควรออกแบบ topic ให้เป็นระบบ เช่น แยก device id, sensor type และ command topic
  • ควรพิจารณา MQTT over TLS ถ้าส่งข้อมูลสำคัญ
  • ควรมีระบบ rotate credential ถ้า token หลุด
  • ควรเพิ่ม reconnect backoff แทนการ retry แบบ fixed delay

ตัวอย่าง topic ที่เป็นระบบมากขึ้น เช่น

sandbox/YOUR_COWTH_USERNAME/devices/esp32-001/telemetry
sandbox/YOUR_COWTH_USERNAME/devices/esp32-001/command
sandbox/YOUR_COWTH_USERNAME/devices/esp32-001/status

แบบนี้จะต่อยอดไปเป็น dashboard, device management หรือ OTA command ในอนาคตได้ง่ายกว่า


สรุป

บทความนี้เราได้ทดลองใช้งาน COWTH MQTT Broker กับ ESP32 แบบครบ flow ตั้งแต่

ESP32 Publisher -> COWTH Broker -> ESP32-S3 Subscriber

ฝั่ง Publisher อ่านค่า LDR และ Potentiometer แล้วส่งเป็น payload ผ่าน MQTT ส่วนฝั่ง Subscriber รับข้อมูลจาก topic เดียวกันและแสดงผลบน Serial Monitor

นี่คือพื้นฐานสำคัญของระบบ IoT หลายแบบ เช่น

  • ระบบ monitor sensor
  • ระบบควบคุมอุปกรณ์ระยะไกล
  • ระบบ dashboard แบบ real-time
  • ระบบแจ้งเตือนจากอุปกรณ์ภาคสนาม
  • ระบบ prototype สำหรับทดสอบ device ก่อนขึ้น production

สำหรับผม COWTH ไม่ได้อยากเป็นแค่ MQTT broker อีกตัวหนึ่ง แต่อยากเป็น platform ที่ทำให้คนไทยเริ่มทำ IoT ได้ง่ายขึ้น ทดสอบได้เร็วขึ้น และต่อยอดจาก prototype ไปสู่ product จริงได้ง่ายขึ้น

COWTH MQTT BROKER
จุดเริ่มต้นเล็ก ๆ ของ IoT platform ที่ทำขึ้นจากมุมมองของ embedded developer จริง ๆ

ขอบคุณทุกท่านที่อ่านมาถึงตรงนี้ ผมหวังเป็นอย่างยิ่งว่าบทความนี้จะช่วยให้ทุกท่านได้ความรู้ในสิ่งที่กำลังศึกษาอยู่