Nhà kính mini với ESP32: đo nhiệt độ, độ ẩm đất, ánh sáng; tự tưới, bật quạt, đèn trồng cây

Làm nhà kính mini với ESP32: DHT22, cảm biến độ ẩm đất, quang trở; relay điều khiển bơm, quạt, đèn trồng cây 12 V có khoảng trễ, tưới từng đợt, hạn mức bơm, trang web tự động và bằng tay.

Nhà kính mini (hay tủ trồng rau, chậu cây trên ban công có mái che) là dự án nông nghiệp nhỏ hợp với ESP32: đo môi trường, tự tưới, tự thông gió, tự bổ sung ánh sáng. Bài này ghép bốn cảm biến (nhiệt độ, độ ẩm không khí, độ ẩm đất, ánh sáng) và ba thiết bị 12 V (bơm, quạt, đèn trồng cây) qua module relay, điều khiển tự động có khoảng trễ, có giới hạn thời gian bơm để không ngập chậu khi cảm biến hỏng, và một trang web để xem số đo, chuyển từng thiết bị sang bật tắt bằng tay.

Bài tưới cây tự động với ESP32 đã nói kỹ về cảm biến độ ẩm đất và bơm; bài này mở rộng thành cả một hệ thống.

1. Thiết bị và cảm biến

PhầnLinh kiệnChân ESP32Dùng để
Nhiệt độ, độ ẩm không khíDHT22D4Bật quạt khi nóng hoặc ẩm quá
Độ ẩm đấtCảm biến điện dungD34Quyết định tưới
Ánh sángQuang trở + điện trở 10 kΩD35Bật đèn khi trời tối trong khung giờ ban ngày
Bơm 12 VRelay K1D32Tưới từng đợt ngắn
Quạt 12 VRelay K2D33Thông gió, giảm nóng, giảm ẩm
Đèn LED trồng cây 12 VRelay K3D26Bổ sung ánh sáng

2. Chuẩn bị

3. Nối dây

Sơ đồ nối: DHT22 VCC 3V3, DATA D4, GND; cảm biến đất VCC 3V3, GND, AO D34; quang trở điểm giữa D35; relay 4 kênh IN1 bơm D32, IN2 quạt D33, IN3 đèn D26, VCC VIN, GND
Cảm biến và relay; chân relay theo quy ước các bài relay trong loạt ESP32 với điện thoại.
Adapter 12 V cấp cho mạch hạ áp ra 5 V nuôi ESP32 và cấp +12 V vào chân COM của ba relay; chân NO của K1 nối bơm, K2 nối quạt, K3 nối đèn LED trồng cây; cực âm các thiết bị về GND chung; diode ngược song song với bơm và quạt
Nguồn 12 V cho bơm, quạt, đèn qua relay.
  • Chỉ dùng thiết bị 12 V một chiều trong nhà kính: môi trường ẩm ướt, có nước tưới, điện 220 V ở đây rất dễ gây giật.
  • Bơm và quạt là động cơ: đặt diode 1N4007 ngược song song ở hai đầu mỗi cái để dập xung khi relay nhả.
  • Mạch ESP32, relay đặt trong hộp kín; cảm biến đất cắm sâu tới phần cảm ứng, không ngập phần mạch điện tử ở đầu cảm biến.

4. Hiệu chuẩn cảm biến đất

Cảm biến điện dung cho điện áp càng thấp khi đất càng ướt. Mỗi cảm biến một khác, nên:

  1. Để cảm biến ngoài không khí, đọc mV ở D34 (in thử bằng analogReadMilliVolts(34)): điền vào DAT_KHO_MV.
  2. Nhúng cảm biến vào cốc nước tới vạch giới hạn: điền số đọc vào DAT_UOT_MV.
  3. Cắm vào đất vừa tưới đẫm và đất đã khô, xem phần trăm, chỉnh DAT_TUOI theo loại cây: rau ăn lá cần đất ẩm hơn cây chịu hạn.

5. Chương trình

// PYWORLD.VN - Nhà kính mini ESP32: đo nhiệt độ, độ ẩm không khí (DHT22), độ ẩm đất, ánh sáng; tự bật bơm, quạt, đèn trồng cây
// PYWORLD.VN - DHT22 D4; cảm biến độ ẩm đất điện dung AO D34; quang trở D35; relay kích mức thấp: bơm IN1 D32, quạt IN2 D33, đèn IN3 D26
// PYWORLD.VN - Trang web xem số đo, chuyển từng thiết bị giữa tự động và bật/tắt tay; bơm có giới hạn thời gian để không ngập chậu

#include <WiFi.h>
#include <WebServer.h>
#include <time.h>
#include <stdarg.h>

const char *TEN_WIFI = "TEN_WIFI";
const char *MAT_KHAU_WIFI = "MAT_KHAU_WIFI";
const int CHAN_DHT = 4, CHAN_DAT = 34, CHAN_SANG = 35;
const int CHAN_RELAY[3] = {32, 33, 26};    // bơm, quạt, đèn
const char *TEN_TB[3] = {"Bom", "Quat", "Den"};
const bool RELAY_KICH_MUC_THAP = true;
// Hiệu chuẩn cảm biến đất: đọc mV khi cảm biến để ngoài không khí (khô) và nhúng nước (ướt) rồi điền vào đây.
const int DAT_KHO_MV = 2600, DAT_UOT_MV = 1100;
// Ngưỡng điều khiển, mỗi cặp có khoảng trễ.
const float DAT_TUOI = 35, DAT_DU = 60;            // % độ ẩm đất: dưới 35 thì tưới
const uint32_t BOM_MOI_LAN_MS = 15000;             // mỗi lần bơm 15 giây
const uint32_t CHO_THAM_MS = 600000;               // rồi chờ 10 phút cho nước thấm mới đo lại
const uint32_t BOM_TOI_DA_NGAY_MS = 180000;        // tối đa 3 phút bơm mỗi ngày: hỏng cảm biến cũng không ngập
const float QUAT_BAT_C = 32, QUAT_TAT_C = 30;      // nhiệt độ
const float QUAT_BAT_RH = 90, QUAT_TAT_RH = 80;    // độ ẩm không khí cao dễ sinh nấm
const float TOI_PHAN_TRAM = 25;                    // ánh sáng dưới 25 % coi là tối
const int DEN_TU_GIO = 5, DEN_DEN_GIO = 21;        // chỉ bổ sung đèn trong khung giờ này, ban đêm cây cần tối

// Các struct khai báo trước mọi hàm.
struct ThietBi { bool bat, tuDong; };
struct SuKien { char noiDung[64]; };

WebServer may(80);
portMUX_TYPE khoaNgat = portMUX_INITIALIZER_UNLOCKED;
ThietBi tb[3] = {{false, true}, {false, true}, {false, true}};
float nhietDo = NAN, doAm = NAN, datPhanTram = NAN, sangPhanTram = NAN;
uint32_t batDauBom = 0, lanBomCuoi = 0, bomHomNay = 0;
int ngayBom = -1;
SuKien suKien[10];
int soSuKien = 0, viTriSuKien = -1;

// ---- DHT22 (như bài DHT11/DHT22) ----
static int32_t choHetMuc(int muc, uint32_t toiDa) {
  uint32_t batDau = micros();
  while (digitalRead(CHAN_DHT) == muc) {
    if (micros() - batDau > toiDa) return -1;
  }
  return (int32_t)(micros() - batDau);
}

bool docDht22(float &t, float &rh) {
  pinMode(CHAN_DHT, OUTPUT);
  digitalWrite(CHAN_DHT, LOW);
  delay(2);
  pinMode(CHAN_DHT, INPUT_PULLUP);
  uint16_t cao[40];
  bool duoc = true;
  portENTER_CRITICAL(&khoaNgat);
  if (choHetMuc(HIGH, 100) < 0 || choHetMuc(LOW, 100) < 0 || choHetMuc(HIGH, 100) < 0) duoc = false;
  for (int i = 0; i < 40 && duoc; i++) {
    int32_t c = choHetMuc(LOW, 80) < 0 ? -1 : choHetMuc(HIGH, 100);
    if (c < 0) duoc = false;
    else cao[i] = c;
  }
  portEXIT_CRITICAL(&khoaNgat);
  if (!duoc) return false;
  uint8_t du[5] = {0, 0, 0, 0, 0};
  for (int i = 0; i < 40; i++) du[i / 8] = (du[i / 8] << 1) | (cao[i] > 48 ? 1 : 0);
  if (((du[0] + du[1] + du[2] + du[3]) & 0xFF) != du[4]) return false;
  rh = ((du[0] << 8) | du[1]) * 0.1f;
  t = (((du[2] & 0x7F) << 8) | du[3]) * 0.1f;
  if (du[2] & 0x80) t = -t;
  return true;
}

// ---- Cảm biến analog ----
int docMv(int chan) {
  uint32_t tong = 0;
  for (int i = 0; i < 32; i++) tong += analogReadMilliVolts(chan);
  return tong / 32;
}

void doCamBien() {
  float t, rh;
  if (docDht22(t, rh)) {
    nhietDo = t;
    doAm = rh;
  }
  int mv = docMv(CHAN_DAT);                // đất càng ướt, điện áp ra của cảm biến điện dung càng thấp
  datPhanTram = constrain(100.0f * (DAT_KHO_MV - mv) / (DAT_KHO_MV - DAT_UOT_MV), 0.0f, 100.0f);
  sangPhanTram = 100.0f * docMv(CHAN_SANG) / 3300;   // quang trở lên 3V3, điện trở 10 kΩ xuống GND: sáng thì điện áp cao
}

// ---- Thiết bị, sự kiện ----
void ghiSuKien(const char *dang, ...) {
  viTriSuKien = (viTriSuKien + 1) % 10;
  if (soSuKien < 10) soSuKien++;
  char chu[48];
  va_list ds;
  va_start(ds, dang);
  vsnprintf(chu, sizeof(chu), dang, ds);
  va_end(ds);
  struct tm t;
  if (getLocalTime(&t, 0)) snprintf(suKien[viTriSuKien].noiDung, sizeof(suKien[0].noiDung), "%02d:%02d %s", t.tm_hour, t.tm_min, chu);
  else snprintf(suKien[viTriSuKien].noiDung, sizeof(suKien[0].noiDung), "--:-- %s", chu);
  Serial.println(suKien[viTriSuKien].noiDung);
}

void dat(int i, bool bat, const char *lyDo) {
  if (tb[i].bat == bat) return;
  tb[i].bat = bat;
  digitalWrite(CHAN_RELAY[i], (bat == RELAY_KICH_MUC_THAP) ? LOW : HIGH);
  ghiSuKien("%s %s (%s)", TEN_TB[i], bat ? "BAT" : "TAT", lyDo);
}

void dieuKhien() {
  uint32_t bay = millis();
  struct tm t;
  bool coGio = getLocalTime(&t, 0);
  if (coGio && t.tm_yday != ngayBom) {      // sang ngày mới: đặt lại hạn mức bơm
    ngayBom = t.tm_yday;
    bomHomNay = 0;
  }
  // Bơm: tưới từng đợt ngắn, chờ thấm, có hạn mức mỗi ngày.
  if (tb[0].bat) {
    if (bay - batDauBom >= BOM_MOI_LAN_MS || (!tb[0].tuDong && bomHomNay + (bay - batDauBom) >= BOM_TOI_DA_NGAY_MS)) {
      bomHomNay += bay - batDauBom;
      lanBomCuoi = bay;
      dat(0, false, "het dot bom");
    }
  } else if (tb[0].tuDong && !isnan(datPhanTram) && datPhanTram < DAT_TUOI) {
    if (bomHomNay + BOM_MOI_LAN_MS > BOM_TOI_DA_NGAY_MS) {
      static int ngayDaBao = -2;
      if (ngayDaBao != ngayBom) {
        ngayDaBao = ngayBom;
        ghiSuKien("Dat van kho nhung da bom du han muc: kiem tra cam bien, nuoc");
      }
    } else if (lanBomCuoi == 0 || bay - lanBomCuoi >= CHO_THAM_MS) {
      batDauBom = bay;
      dat(0, true, "dat kho");
    }
  }
  // Quạt: nóng hoặc ẩm quá thì bật, có khoảng trễ.
  if (tb[1].tuDong && !isnan(nhietDo)) {
    if (nhietDo >= QUAT_BAT_C || doAm >= QUAT_BAT_RH) dat(1, true, nhietDo >= QUAT_BAT_C ? "nong" : "am cao");
    else if (nhietDo <= QUAT_TAT_C && doAm <= QUAT_TAT_RH) dat(1, false, "da diu");
  }
  // Đèn: chỉ trong khung giờ ban ngày, khi trời tối (mây, chiều muộn).
  if (tb[2].tuDong) {
    bool trongKhung = coGio && t.tm_hour >= DEN_TU_GIO && t.tm_hour < DEN_DEN_GIO;
    if (!trongKhung) dat(2, false, "ngoai gio");
    else if (sangPhanTram < TOI_PHAN_TRAM) dat(2, true, "troi toi");
    else if (sangPhanTram > TOI_PHAN_TRAM + 10) dat(2, false, "du sang");
  }
}

// ---- Trang web ----
void trangChu() {
  String s;
  s.reserve(2500);
  s += F("<!doctype html><html lang='vi'><head><meta charset='utf-8'><meta name='viewport' content='width=device-width,initial-scale=1'>"
         "<meta http-equiv='refresh' content='10'><title>Nhà kính mini</title><style>body{font-family:sans-serif;margin:14px;background:#f1f8f1}"
         ".o{display:inline-block;background:#fff;border-radius:10px;padding:10px 14px;margin:4px;min-width:120px}.o b{display:block;font-size:24px}"
         "table{background:#fff;border-collapse:collapse}td{padding:8px 10px;border-bottom:1px solid #ddd}a{margin-right:8px}</style></head><body><h2>Nhà kính mini</h2>");
  char d[200];
  snprintf(d, sizeof(d), "<div class='o'>Nhiệt độ<b>%.1f °C</b></div><div class='o'>Độ ẩm không khí<b>%.0f %%</b></div>"
           "<div class='o'>Độ ẩm đất<b>%.0f %%</b></div><div class='o'>Ánh sáng<b>%.0f %%</b></div>", nhietDo, doAm, datPhanTram, sangPhanTram);
  s += d;
  s += F("<h3>Thiết bị</h3><table>");
  const char *tenVn[3] = {"Bơm tưới", "Quạt", "Đèn trồng cây"};
  for (int i = 0; i < 3; i++) {
    snprintf(d, sizeof(d), "<tr><td>%s</td><td><b>%s</b> (%s)</td><td><a href='/dk?tb=%d&cd=tu'>Tự động</a><a href='/dk?tb=%d&cd=bat'>Bật</a><a href='/dk?tb=%d&cd=tat'>Tắt</a></td></tr>",
             tenVn[i], tb[i].bat ? "BẬT" : "tắt", tb[i].tuDong ? "tự động" : "tay", i, i, i);
    s += d;
  }
  snprintf(d, sizeof(d), "</table><p>Bơm hôm nay: %lu / %lu giây</p><h3>Gần đây</h3><ul>", (unsigned long)(bomHomNay / 1000), (unsigned long)(BOM_TOI_DA_NGAY_MS / 1000));
  s += d;
  for (int k = 0; k < soSuKien; k++) {
    s += "<li>";
    s += suKien[(viTriSuKien - k + 10) % 10].noiDung;
    s += "</li>";
  }
  s += F("</ul></body></html>");
  may.send(200, "text/html; charset=utf-8", s);
}

void dieuKhienTay() {
  int i = may.arg("tb").toInt();
  String cd = may.arg("cd");
  if (i >= 0 && i < 3) {
    tb[i].tuDong = cd == "tu";
    if (cd == "bat") {
      if (i == 0) batDauBom = millis();    // bật tay vẫn chỉ bơm một đợt rồi tự tắt
      dat(i, true, "web");
    } else if (cd == "tat") {
      if (i == 0 && tb[0].bat) bomHomNay += millis() - batDauBom;
      dat(i, false, "web");
    }
    ghiSuKien("%s: che do %s", TEN_TB[i], tb[i].tuDong ? "tu dong" : "tay");
  }
  may.sendHeader("Location", "/");
  may.send(303);
}

void setup() {
  Serial.begin(115200);
  for (int i = 0; i < 3; i++) {
    digitalWrite(CHAN_RELAY[i], RELAY_KICH_MUC_THAP ? HIGH : LOW);   // đặt mức tắt trước khi thành ngõ ra
    pinMode(CHAN_RELAY[i], OUTPUT);
  }
  pinMode(CHAN_DHT, INPUT_PULLUP);
  analogSetAttenuation(ADC_11db);
  WiFi.mode(WIFI_STA);
  WiFi.begin(TEN_WIFI, MAT_KHAU_WIFI);
  configTzTime("ICT-7", "pool.ntp.org", "time.google.com");
  may.on("/", trangChu);
  may.on("/dk", dieuKhienTay);
  may.begin();
  Serial.println("Nha kinh mini: cho 2 giay de doc cam bien");
}

void loop() {
  may.handleClient();
  static uint32_t lanDo = 0, lanIn = 0;
  static bool daInIp = false;
  if (millis() - lanDo >= 2000) {
    lanDo = millis();
    doCamBien();
    dieuKhien();
  }
  if (!daInIp && WiFi.status() == WL_CONNECTED) {
    daInIp = true;
    Serial.printf("Mo trinh duyet: http://%s\n", WiFi.localIP().toString().c_str());
  }
  if (millis() - lanIn >= 60000) {
    lanIn = millis();
    Serial.printf("%.1f C, %.0f %% | dat %.0f %% | sang %.0f %% | bom %s, quat %s, den %s\n", nhietDo, doAm, datPhanTram, sangPhanTram,
                  tb[0].bat ? "BAT" : "tat", tb[1].bat ? "BAT" : "tat", tb[2].bat ? "BAT" : "tat");
  }
}

Giải thích

  • Khoảng trễ: quạt bật khi nóng từ 32 °C hoặc ẩm từ 90 %, nhưng chỉ tắt khi đã xuống 30 °C và 80 %. Đèn bật khi ánh sáng dưới 25 %, tắt khi trên 35 %. Không có khoảng trễ, thiết bị sẽ bật tắt liên tục quanh ngưỡng, relay nhanh hỏng.
  • Tưới từng đợt: đất dưới 35 % thì bơm 15 giây rồi dừng, chờ 10 phút cho nước thấm tới cảm biến mới đo lại. Nếu bơm liên tục tới khi cảm biến báo đủ ẩm thì nước kịp tràn chậu trước khi cảm biến nhận ra.
  • Hạn mức 3 phút bơm mỗi ngày: cảm biến đất hỏng hay rút khỏi chậu sẽ luôn báo "khô"; hạn mức chặn không cho bơm cạn bể nước hay ngập chậu, và ghi một dòng nhắc kiểm tra.
  • Đèn theo khung giờ: chỉ bổ sung ánh sáng từ 5 giờ tới 21 giờ khi trời tối (sáng sớm, mây, chiều muộn); ban đêm cây cũng cần tối.
  • Tự động và bằng tay: trên trang web, mỗi thiết bị có ba nút. "Bật", "Tắt" chuyển thiết bị sang chế độ tay; "Tự động" trả lại cho chương trình. Bơm bật tay cũng chỉ chạy một đợt rồi tự tắt.
  • Khởi động, các chân relay được đặt mức "tắt" trước khi chuyển thành ngõ ra, nên relay không đóng thoáng qua lúc cấp điện.

6. Kết quả

Đồ thị một ngày: nhiệt độ, độ ẩm không khí, ánh sáng có mây buổi chiều, độ ẩm đất giảm dần và tăng lên sau hai lần bơm lúc gần 12 giờ và 16 giờ; bên dưới là vạch thời gian bật của bơm, quạt, đèn
Một ngày trong nhà kính (mô hình minh họa, trạng thái thiết bị tính đúng như chương trình).
Trang web nhà kính mini: nhiệt độ 33,3 độ C, độ ẩm không khí 63 %, độ ẩm đất 40 %, ánh sáng 88 %; bảng ba thiết bị với trạng thái và nút tự động, bật, tắt; số giây bơm hôm nay; danh sách sự kiện
Trang web lúc 13 giờ (số liệu mô hình).
Serial Monitor in các sự kiện trong ngày: quạt bật vì ẩm cao lúc 0 giờ, đèn bật lúc 5 giờ vì trời tối, tắt lúc 7 giờ 35, quạt bật vì nóng lúc 11 giờ 18, bơm bật vì đất khô lúc 11 giờ 32 rồi tắt hết đợt, đèn bật khi mây che lúc 14 giờ
Sự kiện in ra Serial Monitor (theo mô hình một ngày).

7. Lỗi hay gặp

Hiện tượngNguyên nhân thường gặpCách xử lý
Độ ẩm đất luôn 0 % hoặc 100 %Chưa hiệu chuẩn, đọc nhầm chânĐo lại DAT_KHO_MV, DAT_UOT_MV
Bơm chạy nhiều đợt mà đất không ẩm thêmHết nước trong bể, ống tắc, cảm biến đặt xa chỗ tướiKiểm tra bể, ống; cắm cảm biến gần gốc cây nơi nước tới
"da bom du han muc"Cảm biến đất hỏng hoặc bị rút raKiểm tra cảm biến; hạn mức đặt lại lúc 0 giờ
ESP32 khởi động lại khi bơm chạyThiếu diode, nguồn 12 V yếu, chung dây GND dàiThêm diode, adapter đủ dòng; xem bài relay làm ESP32 bị reset
Đèn không bao giờ bậtChưa có giờ Internet (khung giờ tính theo giờ thật)Kiểm tra Wi-Fi; quang trở có đặt chỗ bị đèn chiếu vào không
Relay chạy ngượcModule kích mức caoĐặt RELAY_KICH_MUC_THAP = false

8. Hướng mở rộng

  • Kênh relay thứ tư (D27) cho máy phun sương: bật khi độ ẩm không khí quá thấp vào buổi trưa.
  • Thêm cảm biến mực nước bể để báo hết nước thay vì dựa vào hạn mức bơm.
  • Gửi số đo lên Google Sheets hoặc ThingSpeak để xem đồ thị cả mùa vụ, nhận cảnh báo qua Telegram khi nhiệt độ quá cao (các bài trong loạt ESP32 với điện thoại và dịch vụ đám mây).

9. Xem thêm


Tin tức liên quan

Đồng hồ và dự báo thời tiết trên OLED: ESP32 lấy dữ liệu Open-Meteo qua HTTP API
Đồng hồ và dự báo thời tiết trên OLED: ESP32 lấy dữ liệu Open-Meteo qua HTTP API

ESP32 lấy giờ NTP và thời tiết Open-Meteo qua HTTPS mỗi 15 phút, đọc JSON bằng ArduinoJson, hiện nhiệt độ, độ ẩm, gió và dự báo 3 ngày trên OLED 0.96 inch.

ESP32 đọc MPU-6050 tính góc nghiêng bằng bộ lọc bù: nền tảng để flycam tự cân bằng
ESP32 đọc MPU-6050 tính góc nghiêng bằng bộ lọc bù: nền tảng để flycam tự cân bằng

Nối MPU-6050 với ESP32, đọc thanh ghi không cần thư viện, tính góc roll, pitch từ gia tốc kế, ghép với con quay bằng bộ lọc bù và kiểm tra dấu trước khi dùng cho flycam.

Cánh tay robot 4 servo với ESP32: điều khiển bằng biến trở, ghi nhớ và phát lại động tác
Cánh tay robot 4 servo với ESP32: điều khiển bằng biến trở, ghi nhớ và phát lại động tác

Làm cánh tay robot 4 servo với ESP32: điều khiển từng khớp bằng biến trở, nhấn nút ghi tư thế, phát lại chuỗi động tác mượt, lưu vào flash khi mất điện và gợi ý STEM.

Cấp nguồn cho ESP32 đúng cách: cổng USB, chân VIN, 3V3, pin 18650 và lỗi brownout khi bật Wi-Fi
Cấp nguồn cho ESP32 đúng cách: cổng USB, chân VIN, 3V3, pin 18650 và lỗi brownout khi bật Wi-Fi

Cấp nguồn cho ESP32 qua cổng USB, chân VIN, 3V3, từ adapter hay pin 18650; vì sao ESP32 tự khởi động lại khi bật Wi-Fi (brownout) và chương trình đo sụt áp nguồn.

GPS NEO-6M với ESP32: đọc câu NMEA, lấy tọa độ, giờ Việt Nam, tốc độ không cần thư viện
GPS NEO-6M với ESP32: đọc câu NMEA, lấy tọa độ, giờ Việt Nam, tốc độ không cần thư viện

GPS làm việc thế nào, module NEO-6M, nối với ESP32 qua UART2, đọc câu NMEA RMC và GGA, kiểm checksum, đổi tọa độ, giờ Việt Nam, tốc độ, tính khoảng cách, không cần TinyGPS.

Web điều khiển ESP32 cập nhật tức thời trên nhiều điện thoại: hỏi định kỳ bằng fetch hay WebSocket
Web điều khiển ESP32 cập nhật tức thời trên nhiều điện thoại: hỏi định kỳ bằng fetch hay WebSocket

Làm trang web trên ESP32 tự cập nhật trạng thái relay trên mọi điện thoại đang mở bằng fetch() và JSON, chỉ dùng WebServer có sẵn, so sánh với WebSocket.

ESP32 điều khiển máy CNC, máy laser với FluidNC: Wi-Fi, file cấu hình và giao diện web
ESP32 điều khiển máy CNC, máy laser với FluidNC: Wi-Fi, file cấu hình và giao diện web

Dùng ESP32 DevKit V1 chạy FluidNC điều khiển máy khắc laser 2 trục: sơ đồ nối hai A4988, cài firmware từ trình duyệt, file config.yaml mẫu giải thích từng dòng, lệnh hay dùng, Wi-Fi.

Hướng dẫn điều khiển relay bằng điện thoại qua Bluetooth với ESP32
Hướng dẫn điều khiển relay bằng điện thoại qua Bluetooth với ESP32

Hướng dẫn dùng Bluetooth Classic của ESP32 trên Arduino IDE: ghép đôi với điện thoại Android, gửi nhận chữ bằng BluetoothSerial và điều khiển module relay bằng lệnh ON, OFF.


Bình luận
Đã thêm vào giỏ hàng