โปรเจกต์ ESP32: สร้างเครื่องวิเคราะห์สุขภาพอัจฉริยะ (วัดอุณหภูมิ + SpO2 + ชีพจร) พร้อมระบบ Cloud

Multi-Sensor Health Analyzer

จะเป็นยังไงถ้าเซนเซอร์ตัวเล็กๆ ไม่ได้ทำหน้าที่แค่ "แสดงตัวเลข" แต่สามารถ "ตีความสุขภาพ" ของคุณได้แบบเรียลไทม์?

ในโปรเจกต์นี้ เราจะพาทุกคนมาสร้างระบบติดตามสุขภาพอัจฉริยะ (Smart Health Monitoring System) ด้วยบอร์ด ESP32 ที่สามารถวัดอุณหภูมิร่างกาย อัตราการเต้นของหัวใจ และระดับออกซิเจนในเลือด (SpO2) ได้พร้อมกัน แถมยังนำข้อมูลทั้งหมดมารวมกันเพื่อวิเคราะห์หาความเสี่ยงต่างๆ เช่น ความเครียด, ความเสี่ยงเป็นไข้, หรือปัญหาทางระบบหายใจได้ด้วยครับ!

โปรเจกต์นี้ต่างจากการทำ Arduino ธรรมดาตรงที่เราไม่ได้อ่านค่าแค่อย่างเดียว แต่มันคือระบบ Full-Stack ที่เชื่อมต่อแบบครบวงจร: ESP32 → Cloud → Dashboard เหมาะมากๆ สำหรับนำไปประยุกต์ใช้ในการดูแลผู้สูงอายุ, ติดตามผู้ป่วยทางไกล, หรือทำเป็น Fitness Tracker ล้ำๆ ของตัวเองครับ

อุปกรณ์และซอฟต์แวร์ที่ต้องใช้ (Supplies)

ฮาร์ดแวร์ที่ใช้มีไม่เยอะครับ เน้นไปที่เซนเซอร์ทางการแพทย์ที่มีความแม่นยำสูง (และแน่นอนว่า ถ้าเพื่อนๆ กำลังหาบอร์ด ESP32, เซนเซอร์สุดล้ำ หรืออุปกรณ์อิเล็กทรอนิกส์คุณภาพเยี่ยม แวะไปช้อปได้ที่ Globalbyte เลยครับ ครบจบในที่เดียว!)

  • ESP32 Development Board (รุ่น 38-pin หรือรุ่นไหนก็ได้)
  • เซนเซอร์วัดอุณหภูมิความแม่นยำสูง TMP117 (แบบ I2C ของ SparkFun หรือ Adafruit)
  • โมดูลวัดชีพจรและออกซิเจนในเลือด MAX30102
  • Breadboard, สายจัมเปอร์, สาย Micro USB และ Power Bank 5V
Hardware Components
Libraries Needed

ฝั่งซอฟต์แวร์และคลาวด์ (ใช้ฟรีทั้งหมด):

  • Arduino IDE (ลง ESP32 Board Support ไว้ด้วย) และไลบรารี: SparkFun_MAX3010x, Adafruit TMP117, WiFiManager
  • Node.js v18+ สำหรับรัน Backend ในคอมพิวเตอร์
  • MongoDB Atlas สำหรับใช้ทำฐานข้อมูลคลาวด์ (ฟรีแพ็กเกจ M0)
  • Render.com สำหรับโฮสต์ Backend และ Dashboard (ฟรี)
  • GitHub Account สำหรับดึงโค้ดไป Deploy บน Render

Step 1: ภาพรวมสถาปัตยกรรมระบบ (How It All Fits Together)

System Architecture Diagram

ก่อนจะเริ่มต่อสาย เรามาดูภาพรวมกันก่อนครับ ข้อมูลทั้งหมดจะไหลไปในทิศทางเดียว คือ:
Sensors → ESP32 → Cloud Backend → Dashboard

โดยเซนเซอร์ทั้ง 2 ตัวจะใช้สาย I2C ร่วมกัน (SDA พิน 21, SCL พิน 22) จากนั้น ESP32 จะอ่านค่าและส่งข้อมูล (POST Request) เป็น JSON ไปที่ Node.js Backend ทุกๆ 5 วินาที ตัว Backend จะทำหน้าที่เซฟข้อมูลลงฐานข้อมูล MongoDB และมี "Sensor Fusion Engine" ที่คอยวิเคราะห์ความเสี่ยงสุขภาพ แล้วส่งข้อมูลไปแสดงผลสวยๆ บนหน้าเว็บ Dashboard ครับ

Step 2-3: การต่อวงจรและติดตั้งไลบรารี

เซนเซอร์ TMP117 และ MAX30102 ทำงานด้วยไฟ 3.3V และใช้อินเทอร์เฟซ I2C ทั้งคู่ เราสามารถต่อสายพ่วงกันได้เลย ใช้สายแค่ 4 เส้นเท่านั้นครับ:

  • 3.3V → VCC ของเซนเซอร์ทั้งสอง
  • GND → GND ของเซนเซอร์ทั้งสอง
  • GPIO 21 → SDA (สายข้อมูล I2C)
  • GPIO 22 → SCL (สายนาฬิกา I2C)

เซนเซอร์ 2 ตัวนี้มี Address ของ I2C ต่างกัน (0x48 และ 0x57) จึงไม่ตีกันแน่นอนครับ จากนั้นให้เปิดโปรแกรม Arduino IDE ไปที่เมนู Manage Libraries แล้วค้นหาติดตั้ง Adafruit TMP117, SparkFun MAX3010x, และ WiFiManager ให้เรียบร้อยครับ

Wiring Diagram
Breadboard Wiring Library 1 Library 2 Library 3

Step 4-5: อัปโหลดโค้ด ESP32 และตั้งค่า WiFi

Arduino Serial Monitor

ก๊อปปี้โค้ดด้านล่างนี้ไปวางใน Arduino IDE ครับ สิ่งเดียวที่คุณต้องแก้คือตัวแปร serverURL ให้เปลี่ยนเป็น URL ของคุณเองหลังจากนำ Backend ไปฝากไว้บน Render แล้วครับ

C/C++ (ESP32 Firmware)
#include <WiFi.h>
#include <HTTPClient.h>
#include <Wire.h>
#include "MAX30105.h"
#include "spo2_algorithm.h"
#include <Adafruit_TMP117.h>
#include <WiFiManager.h>

String serverURL = "https://health-monitor-server-2pbh.onrender.com/data";

/* -------------------- OBJECTS -------------------- */
MAX30105 particleSensor;
Adafruit_TMP117 tmp117;

/* -------------------- MAX30102 VARIABLES -------------------- */
uint32_t irBuffer[100];
uint32_t redBuffer[100];
int32_t bufferLength = 100;

int32_t spo2;
int8_t validSPO2;
int32_t heartRate;
int8_t validHeartRate;

/* -------------------- TMP117 -------------------- */
float temperatureC = 0;

/* -------------------- TIMING -------------------- */
unsigned long lastSendTime = 0;
const unsigned long sendInterval = 5000;

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

  /* ---------- WIFI ---------- */
  WiFiManager wm;
  bool res = wm.autoConnect("HealthMonitor-Setup");
  if(!res) {
    Serial.println("WiFi Failed");
    ESP.restart();
  }
  Serial.println("WiFi Connected!");
  Serial.println(WiFi.localIP());

  /* ---------- I2C ---------- */
  Wire.begin(21, 22);

  /* ---------- TMP117 ---------- */
  if (!tmp117.begin()) {
    Serial.println("TMP117 not found");
    while (1);
  }
  Serial.println("TMP117 initialized");

  /* ---------- MAX30102 ---------- */
  if (!particleSensor.begin(Wire, I2C_SPEED_FAST)) {
    Serial.println(" MAX30102 not found");
    while (1);
  }
  particleSensor.setup(60, 4, 2, 100, 411, 4096);
  Serial.println(" MAX30102 initialized");
  Serial.println(" Place finger on sensor");
}

void loop() {
  /* ---------- COLLECT MAX30102 SAMPLES ---------- */
  for (byte i = 0; i < bufferLength; i++) {
    while (!particleSensor.available()) {
      particleSensor.check();
    }
    redBuffer[i] = particleSensor.getRed();
    irBuffer[i] = particleSensor.getIR();
    particleSensor.nextSample();
  }

  /* ---------- CALCULATE HR & SPO2 ---------- */
  maxim_heart_rate_and_oxygen_saturation(
    irBuffer, bufferLength,
    redBuffer,
    &spo2, &validSPO2,
    &heartRate, &validHeartRate
  );

  /* ---------- READ TMP117 ---------- */
  sensors_event_t tempEvent;
  tmp117.getEvent(&tempEvent);
  temperatureC = tempEvent.temperature;

  /* ---------- VALIDATED VALUES ---------- */
  int finalHR = validHeartRate ? heartRate : -1;
  int finalSpO2 = validSPO2 ? spo2 : -1;

  Serial.print("Temp: ");
  Serial.print(temperatureC, 2);
  Serial.print(" °C | HR: ");
  Serial.print(finalHR);
  Serial.print(" | SpO2: ");
  Serial.println(finalSpO2);

  /* ---------- SEND TO DB EVERY 5s ---------- */
  if (millis() - lastSendTime >= sendInterval && WiFi.status() == WL_CONNECTED) {
    lastSendTime = millis();
    HTTPClient http;
    http.begin(serverURL);
    http.addHeader("Content-Type", "application/json");

    String jsonData = "{";
    jsonData += "\"temperature\":" + String(temperatureC, 2) + ",";
    jsonData += "\"heartRate\":" + String(finalHR) + ",";
    jsonData += "\"spo2\":" + String(finalSpO2) + ",";
    jsonData += "\"deviceId\":\"ESP32_01\"";
    jsonData += "}";

    int response = http.POST(jsonData);
    Serial.print(" HTTP Response: ");
    Serial.println(response);
    http.end();
  }
}

การตั้งค่า WiFi: ความเจ๋งของโปรเจกต์นี้คือเราใช้ไลบรารี WiFiManager ทำให้ไม่ต้องไปฮาร์ดโค้ดรหัสผ่าน WiFi ในโค้ดครับ พอเปิดบอร์ดปุ๊บ ให้ใช้มือถือเชื่อมต่อ WiFi ชื่อ HealthMonitor-Setup มันจะเด้งหน้าเว็บให้เราเลือก WiFi บ้านและใส่รหัสผ่านได้เลย สะดวกสุดๆ!

WiFi Setup 1 WiFi Setup 2

Step 6: สร้าง Backend (Node.js + MongoDB Atlas)

Render Dashboard

ถึงเวลาของระบบคลาวด์แล้วครับ เริ่มจากไปสมัคร MongoDB Atlas (ฟรี) เพื่อสร้างฐานข้อมูลสำหรับเก็บข้อมูลสุขภาพ และคัดลอก Connection String มาเก็บไว้ จากนั้นให้สร้างโครงสร้างไฟล์ Node.js ตามนี้ครับ:

JavaScript (models/SensorData.js) - กำหนดโครงสร้างฐานข้อมูล
const mongoose = require("mongoose");
const SensorSchema = new mongoose.Schema({
  temperature: Number,
  heartRate: Number,
  spo2: Number,
  deviceId: String,
  time: { type: Date, default: Date.now }
});
module.exports = mongoose.model("SensorData", SensorSchema);

Sensor Fusion Engine (สมองของระบบ): ไฟล์ด้านล่างนี้คือตัวรับข้อมูลทั้ง 3 อย่างมาวิเคราะห์ร่วมกัน ถือเป็นไฮไลต์ของโปรเจกต์นี้เลยครับ

JavaScript (fusion/healthFusion.js)
function analyzeHealth(hr, spo2, temp) {
  let status = [];
  let riskScore = 0;

  if (hr > 100 && spo2 >= 95 && temp < 37.5) {
    status.push("Stress detected"); riskScore += 2;
  }
  if (temp >= 37.8 && hr > 95) {
    status.push("Fever risk"); riskScore += 3;
  }
  if (spo2 < 92 && hr <= 100) {
    status.push("Respiratory concern"); riskScore += 4;
  }
  if (status.length === 0) status.push("Normal");

  return { indicators: status, riskScore };
}
module.exports = analyzeHealth;

ส่วนไฟล์ routes/dashboard.js และ server.js จะทำหน้าที่รับ/ส่งข้อมูล API ครับ (สามารถดูโค้ดแบบเต็มๆ ใน Repository ได้เลย) เมื่อเตรียมไฟล์ครบแล้ว ให้อัปโหลดขึ้น GitHub แล้วนำไปผูกและสั่ง Deploy แบบฟรีๆ บน Render.com ได้เลยครับ สิ่งสำคัญคืออย่าลืมตั้งค่า Environment Variable MONGO_URI ให้เรียบร้อยด้วยนะครับ

Step 7: สร้าง Dashboard แสดงผล

ส่วนของหน้าเว็บ Dashboard เขียนด้วย HTML+CSS+JS ธรรมดาเลยครับ ไม่ต้องใช้ Framework ให้ยุ่งยาก หน้าเว็บนี้จะดึงข้อมูลผ่าน /api/dashboard ทุกๆ 3 วินาที เพื่อนำมาแสดงค่าอุณหภูมิ, ชีพจร, SpO2 และ "คำเตือนทางสุขภาพ" ที่คิดคำนวณมาจาก Sensor Fusion ของเราครับ

JavaScript (app.js - Dashboard Logic)
const API = "/api/dashboard";

function healthLabel(score) {
  if (score <= 1) return "Normal";
  if (score <= 3) return "Mild Risk";
  if (score <= 6) return "Moderate Risk";
  return "High Risk";
}

async function loadDashboard() {
  const res = await fetch(API);
  const data = await res.json();

  const label = healthLabel(data.fusion.riskScore);
  document.getElementById("status-label").innerText = label;
  document.getElementById("hr").innerText = data.vitals.heartRate;
  document.getElementById("spo2").innerText = data.vitals.spo2;
  document.getElementById("temp").innerText = data.vitals.temperature;

  // Render fusion insight cards
  document.getElementById("fusion").innerHTML = "";
  data.fusion.indicators.forEach(i => {
    const div = document.createElement("div");
    div.innerText = `${i} — ${explanations[i]}`;
    document.getElementById("fusion").appendChild(div);
  });

  document.getElementById("updated").innerText =
    `Last updated: ${new Date(data.time).toLocaleTimeString()}`;
}

setInterval(loadDashboard, 3000);
loadDashboard();
Dashboard Full View
Dashboard Mobile View

Step 8-9: ทดสอบการทำงาน และเจาะลึกการทำงานของเซนเซอร์

Testing Serial Monitor

เมื่อแฟลชเฟิร์มแวร์และ Deploy Backend เสร็จแล้ว ก็เริ่มทดสอบได้เลยครับ! วางปลายนิ้วให้แน่นสนิทกับเซนเซอร์ MAX30102 (ด้านที่มีไฟแดง/อินฟราเรด) และอยู่นิ่งๆ ประมาณ 5-10 วินาที เพื่อให้เซนเซอร์อ่านค่าได้เสถียร (ถ้าค่าโชว์ -1 แปลว่านิ้วยังวางไม่สนิทนะ)

ค่ามาตรฐานขณะพักผ่อน (Expected readings at rest):

  • อุณหภูมิร่างกาย: 36.0°C – 37.5°C (อุณหภูมิปลายนิ้วจะต่ำกว่าแกนกลางร่างกายนิดหน่อยครับ)
  • อัตราการเต้นของหัวใจ: 60–100 BPM
  • SpO2 (ออกซิเจนในเลือด): 95–100% (สำหรับคนสุขภาพปกติ)
  • สถานะ Fusion: Normal (Risk score 0-1)

ทำไมถึงเลือกเซนเซอร์ 2 ตัวนี้?
TMP117 ไม่ใช่เซนเซอร์วัดอุณหภูมิบ้านๆ ทั่วไปนะครับ แต่มันมีความแม่นยำถึง ±0.1°C ซึ่งเป็นสเปกเดียวกับที่ใช้ในอุปกรณ์ทางการแพทย์เลย! (เทียบกับ DHT22 ที่คลาดเคลื่อน ±0.5°C ถือว่าคนละชั้น) ส่วน MAX30102 จะใช้หลักการยิงแสงสีแดงและแสงอินฟราเรดทะลุปลายนิ้วเรา เพื่อวัดการสะท้อนกลับของฮีโมโกลบินในเลือด ทำให้วัดค่าออกซิเจนและจังหวะชีพจรได้อย่างแม่นยำครับ

ไอเดียพัฒนาต่อยอด (Possible Improvements)

โปรเจกต์นี้วางรากฐานไว้ดีมาก สามารถอัปเกรดความล้ำต่อได้สบายๆ เลย เช่น:

  • ต่อจอ OLED (SSD1306) เพิ่ม เพื่อให้ดูค่าได้โดยไม่ต้องเปิดมือถือ (I2C รองรับการต่อพ่วงอยู่แล้ว)
  • ตั้งค่าบอท Telegram แจ้งเตือนอัตโนมัติหาก SpO2 ต่ำกว่า 94% หรือมีไข้สูงเกิน 38°C
  • ปรับ Backend ให้รองรับบอร์ด ESP32 หลายๆ ตัว พร้อมกัน (สำหรับใช้งานในสถานพยาบาลหรือบ้านที่มีผู้สูงอายุหลายคน)
  • ออกแบบและ 3D Print เคสแบบสวมใส่ได้ (Wearable enclosure) (ถ้าใครอยากทำเคสแบบสวมข้อมือ หรือหนีบนิ้ว แนะนำให้ใช้เส้นพลาสติก 3D Print คุณภาพสูงจาก Globalbyte เลยครับ พิมพ์งานง่าย แข็งแรงทนทาน ใส่สบายแน่นอน!)

อ้างอิงข้อมูลจาก: Globalbyteshop Blog

ต้นฉบับโปรเจกต์โดย: sxmnath | Original Link

Source Code ต้นฉบับ: GitHub Repository

*คำเตือน: เนื้อหานี้เป็นการสรุปและเรียบเรียงจากบทความโปรเจกต์ต้นฉบับภาษาอังกฤษ โปรเจกต์นี้มีจุดประสงค์เพื่อการเรียนรู้และทดลอง "ไม่ใช่เครื่องมือทางการแพทย์ที่ได้รับการรับรอง" หากมีปัญหาสุขภาพโปรดปรึกษาแพทย์ผู้เชี่ยวชาญ สามารถตรวจสอบข้อมูลและโค้ดเชิงลึกได้ที่ ต้นฉบับภาษาอังกฤษ

 

แท็ก


Blog posts

เข้าสู่ระบบ

ลืมรหัสผ่านใช่ไหม?

ยังไม่มีบัญชีใช่ไหม?
สร้างบัญชี