Công tắc cảm ứng thông minh âm tường tự làm với ESP32: dây trung tính, chống bật nhầm, an toàn 220 V

Tự làm công tắc cảm ứng thông minh bằng ESP32: kiểm tra dây trung tính trong hộp âm tường, sơ đồ khối an toàn 220 V, thuật toán chạm theo giá trị nền, nhớ trạng thái, điều khiển Wi-Fi.

Công tắc cảm ứng thông minh là công tắc đèn không có nút bấm cơ, chạm tay lên mặt nhựa là đổi trạng thái, và điều khiển được qua điện thoại. Tự làm bằng ESP32 là một dự án thú vị, nhưng có hai điểm phải biết trước khi mua linh kiện: hộp âm tường nhà bạn có dây trung tính không, và phần 220 V phải làm đúng an toàn. Bài này đi qua hai điểm đó, thuật toán chạm chống bật nhầm kèm mô phỏng, và chương trình đầy đủ cho ESP32.

Cảnh báo an toàn: điện 220 V gây chết người và cháy nổ. Luôn cắt aptomat và kiểm tra lại bằng bút thử điện trước khi đụng vào dây. Không cắm USB vào ESP32 khi mạch đang nối với lưới điện. Mạch tự làm không có chứng nhận an toàn như sản phẩm thương mại; nếu chưa quen làm điện, hãy thử phần điện áp thấp với LED trước và nhờ thợ điện cho phần lắp đặt. Nên đọc bài điện một chiều, xoay chiều và an toàn điện 220 V trước.

1. Kiểm tra dây trung tính trước tiên

So sánh hộp công tắc âm tường có dây trung tính N làm được công tắc thông minh, và hộp chỉ có 2 dây L vào L ra đèn không có nguồn khi tắt đèn
Công tắc cơ chỉ ngắt dây pha, nên nhiều hộp âm tường không có dây N.

Công tắc cơ chỉ nối tiếp vào dây pha, nên hộp âm tường trong nhiều nhà chỉ có 2 dây: L đi vào và L đi ra đèn. ESP32 cần nguồn liên tục kể cả khi đèn tắt, mà hai dây này không cho nguồn đó. Có ba hướng:

  • Hộp có dây N: làm theo bài này.
  • Chỉ có 2 dây: kéo thêm dây N từ hộp nối. Đây là cách đúng.
  • Đặt mạch tại đèn: gắn cạnh bóng đèn, nơi có đủ L và N, còn công tắc tường để luôn đóng. Cách này không cần đụng hộp âm tường.

Công tắc thông minh loại "không dây N" bán trên thị trường lấy một dòng rất nhỏ rò qua bóng đèn. Cách đó cần mạch nguồn chuyên dụng và chip tiêu thụ cực thấp, không làm được với mạch ESP32 thông thường vì dòng Wi-Fi quá lớn. Đừng thử nối nối tiếp với bóng đèn.

2. Sơ đồ khối

Sơ đồ khối công tắc cảm ứng: dây L N qua cầu chì và varistor vào module nguồn 5 V, ESP32 đọc tấm đồng ở GPIO4, điều khiển cuộn relay ở GPIO32, tiếp điểm relay ra đèn
Hai vùng điện áp phải tách rõ trên mạch.
KhốiLinh kiệnLưu ý
NguồnModule AC-DC cách ly 5 V (loại HLK-PM01 hoặc tương đương)Cầu chì 1 A trên dây L, varistor 470 V giữa L và N
Điều khiểnESP32. Mạch DevKit dài, chỉ để thử trên bàn; lắp thật cần module nhỏ hơn hoặc đặt mạch tại hộp nổiHộp âm tường rất chật, tính kích thước trước
Đóng cắtModule relay 1 kênh 5 VChỉ dùng cho đèn, quạt nhỏ; để dư dòng
Cảm ứngTấm đồng hoặc băng đồng khoảng 25 × 25 mm dán sau mặt nhựa 2–3 mmDây ngắn dưới 10 cm, xa dây 220 V
  • Không làm mặt công tắc bằng kim loại. Người dùng chỉ chạm vào lớp nhựa cách điện.
  • Giữ khoảng cách giữa vùng 220 V và vùng điện áp thấp tối thiểu 6 mm trên mạch.
  • Module nguồn và relay tỏa nhiệt trong không gian kín. Không dùng cho bình nóng lạnh, máy lạnh hay tải động cơ lớn.

Cách đấu module nguồn 220 V có trong bài cùng cụm về HLK-PM01. Relay kích mức thấp và chân JD-VCC xem ở bài module relay 1, 2, 4 kênh. Nếu relay làm mạch khởi động lại thì xem đóng relay làm ESP32 bị reset.

3. Thuật toán chạm chống bật nhầm

ESP32 có sẵn các kênh cảm ứng điện dung. Hàm touchRead() trả một con số thay đổi khi ngón tay tới gần. Bài chạm tay bật đèn giới thiệu cách dùng. Với công tắc treo tường chạy nhiều năm, không nên so với ngưỡng cố định kiểu if (touchRead(4) < 60), vì độ ẩm, nhiệt độ và tuổi của keo dán làm giá trị nền trôi dần. Khi nền tụt tới gần ngưỡng, đèn sẽ tự bật tắt lúc nửa đêm.

Đồ thị mô phỏng 60 giây: giá trị touchRead tụt dần, ngưỡng cố định 60 bị vượt liên tục gây bật tắt sai, đường nền tự theo chỉ nhận đúng 3 lần chạm và bỏ qua xung nhiễu
Số đo giả định, thời gian rút ngắn để dễ thấy.

Chương trình dùng bốn biện pháp, và mô phỏng cho thấy chúng nhận đúng 3 lần chạm trong khi ngưỡng cố định báo 80 lần:

  1. So theo tỉ lệ với nền: lệch quá 25 % mới tính. Tỉ lệ dùng được cho cả hai thang giá trị của touchRead() và cả hai chiều tăng giảm.
  2. Nền tự theo: chỉ cập nhật rất chậm khi không chạm, nên trôi theo môi trường nhưng không trôi theo ngón tay.
  3. Hai ngưỡng: vào ở 25 %, ra ở 12 %, tránh rung quanh một ngưỡng.
  4. Xác nhận 4 mẫu liên tiếp (80 ms) để bỏ qua xung nhiễu do relay hay thiết bị điện khác.

Script mô phỏng dưới đây dùng đúng các hằng số của chương trình. Đổi số trong đó để xem tác dụng trước khi sửa code.

# PYWORLD.VN - Mô phỏng phát hiện chạm: so ngưỡng cố định với cách của chương trình trong bài (lệch theo nền, hai ngưỡng, xác nhận 4 mẫu)
# PYWORLD.VN - Số đo giả định: nền 80 có nhiễu, độ ẩm làm nền tụt dần, một xung nhiễu ngắn, 3 lần chạm thật; lấy mẫu 20 ms
# PYWORLD.VN - Chạy: python mo_phong_cham.py          (chỉ dùng thư viện chuẩn)
"""Hằng số TY_LE_CHAM, TY_LE_NHA, SO_MAU_XAC_NHAN, HE_SO_NEN giống hệt file .ino."""
import random

BUOC_S = 0.02
TY_LE_CHAM, TY_LE_NHA, SO_MAU_XAC_NHAN, HE_SO_NEN = 0.25, 0.12, 4, 0.002
NGUONG_CO_DINH = 60                    # kiểu hay viết: if (touchRead(4) < 60)


def tin_hieu(tong_s: float = 60.0) -> list[float]:
    rng = random.Random(7)
    ra = []
    for i in range(int(tong_s / BUOC_S)):
        t = i * BUOC_S
        nen = 80 - 26 * (t / tong_s)   # nền tụt từ 80 xuống 54 khi ẩm lên, mô phỏng rút ngắn thời gian
        v = nen + rng.gauss(0, 1.2)
        if 10 <= t < 10.3 or 30 <= t < 30.3 or 50 <= t < 50.3:
            v = nen * 0.5 + rng.gauss(0, 1.2)
        if 20 <= t < 20.03:            # xung nhiễu một mẫu
            v = nen * 0.45
        ra.append(v)
    return ra


def co_dinh(mau: list[float]) -> list[float]:
    """Mỗi lần xuống dưới ngưỡng tính một lần chạm, trả về các thời điểm."""
    su_kien, truoc = [], False
    for i, v in enumerate(mau):
        nay = v < NGUONG_CO_DINH
        if nay and not truoc:
            su_kien.append(i * BUOC_S)
        truoc = nay
    return su_kien


def theo_nen(mau: list[float]) -> tuple[list[float], list[float]]:
    """Cùng logic xuLyCham() trong .ino; trả về (các lần chạm, đường nền theo thời gian)."""
    nen = sum(mau[:32]) / 32
    dang_cham, dem, su_kien, duong_nen = False, 0, [], []
    for i, v in enumerate(mau):
        lech = abs(v - nen) / nen
        if not dang_cham:
            dem = dem + 1 if lech > TY_LE_CHAM else 0
            if dem >= SO_MAU_XAC_NHAN:
                dang_cham, dem = True, 0
                su_kien.append(i * BUOC_S)
            elif lech < TY_LE_NHA:
                nen += HE_SO_NEN * (v - nen)
        else:
            dem = dem + 1 if lech < TY_LE_NHA else 0
            if dem >= SO_MAU_XAC_NHAN:
                dang_cham, dem = False, 0
        duong_nen.append(nen)
    return su_kien, duong_nen


if __name__ == "__main__":
    m = tin_hieu()
    a = co_dinh(m)
    b, _ = theo_nen(m)
    print(f"Nguong co dinh: {len(a)} lan, lan dau {a[0]:.2f} s, lan cuoi {a[-1]:.2f} s")
    print("Theo nen:", [round(x, 2) for x in b])

4. Chương trình ESP32

Tấm cảm ứng nối GPIO4, IN của relay nối GPIO32. Sửa tên và mật khẩu Wi-Fi rồi nạp khi chưa nối 220 V, nguồn từ cổng USB. Thử bằng LED hoặc nghe tiếng relay trước.

// PYWORLD.VN - Công tắc cảm ứng tự làm: chạm tấm đồng ở GPIO4 để đảo relay GPIO32 kích mức thấp, nhớ trạng thái khi mất điện
// PYWORLD.VN - So độ lệch với giá trị nền (nền tự theo độ ẩm, nhiệt độ) thay vì ngưỡng cố định; cần vài mẫu liên tiếp mới nhận là chạm
// PYWORLD.VN - Có Wi-Fi thì bật tắt qua trình duyệt; mất Wi-Fi công tắc vẫn chạy. Phần 220 V phải theo đúng cảnh báo trong bài

#include <Preferences.h>
#include <WebServer.h>
#include <WiFi.h>

const char *TEN_MANG = "TEN_WIFI";
const char *MAT_KHAU = "MAT_KHAU_WIFI";

const int CHAN_CHAM = 4;                 // GPIO4 là kênh cảm ứng T0
const int CHAN_RELAY = 32;               // IN của module relay
const int MUC_BAT = LOW;                 // module kích mức thấp
const int MUC_TAT = HIGH;

const uint32_t CHU_KY_MAU_MS = 20;
const float TY_LE_CHAM = 0.25;           // lệch quá 25 % so với nền thì coi là đang chạm
const float TY_LE_NHA = 0.12;            // về dưới 12 % mới coi là đã nhả (trễ hai ngưỡng)
const int SO_MAU_XAC_NHAN = 4;           // 4 mẫu liên tiếp = 80 ms
const float HE_SO_NEN = 0.002;           // nền chạy rất chậm, chỉ cập nhật khi không chạm

Preferences boNho;
WebServer mayChu(80);
float nen = 0;
bool dangCham = false;
bool relayBat = false;
int demMau = 0;
uint32_t lucLayMau = 0;

void datRelay(bool bat, const char *nguon) {
  relayBat = bat;
  digitalWrite(CHAN_RELAY, bat ? MUC_BAT : MUC_TAT);
  boNho.putBool("bat", bat);             // chỉ ghi khi đổi trạng thái để giữ tuổi thọ flash
  Serial.printf("Relay %s (%s)\n", bat ? "BAT" : "TAT", nguon);
}

float docTrungBinh(int soLan) {
  uint32_t tong = 0;
  for (int i = 0; i < soLan; i++) {
    tong += touchRead(CHAN_CHAM);
    delay(5);
  }
  return (float)tong / soLan;
}

void xuLyCham() {
  const float giaTri = touchRead(CHAN_CHAM);
  const float lech = fabs(giaTri - nen) / nen;
  if (!dangCham) {
    demMau = (lech > TY_LE_CHAM) ? demMau + 1 : 0;
    if (demMau >= SO_MAU_XAC_NHAN) {
      dangCham = true;
      demMau = 0;
      datRelay(!relayBat, "cham");
    } else if (lech < TY_LE_NHA) {
      nen += HE_SO_NEN * (giaTri - nen);  // nền theo độ ẩm, nhiệt độ
    }
  } else {
    demMau = (lech < TY_LE_NHA) ? demMau + 1 : 0;
    if (demMau >= SO_MAU_XAC_NHAN) {
      dangCham = false;
      demMau = 0;
    }
  }
}

void trangChu() {
  String html = "<!doctype html><meta name=viewport content='width=device-width'><h2>Den: ";
  html += relayBat ? "BAT" : "TAT";
  html += "</h2><a href=/dao>Dao trang thai</a>";
  mayChu.send(200, "text/html", html);
}

void daoQuaWeb() {
  datRelay(!relayBat, "web");
  mayChu.sendHeader("Location", "/");
  mayChu.send(303);
}

void setup() {
  Serial.begin(115200);
  boNho.begin("cong-tac", false);
  relayBat = boNho.getBool("bat", false);

  // Gói esp32 3.x bỏ qua digitalWrite trước pinMode: đặt mức ở INPUT rồi mới chuyển OUTPUT.
  pinMode(CHAN_RELAY, INPUT);
  digitalWrite(CHAN_RELAY, relayBat ? MUC_BAT : MUC_TAT);
  pinMode(CHAN_RELAY, OUTPUT);

  delay(300);
  nen = docTrungBinh(32);                // không chạm tay vào tấm cảm ứng lúc cấp điện
  Serial.printf("Gia tri nen: %.1f\n", nen);

  WiFi.mode(WIFI_STA);
  WiFi.setAutoReconnect(true);
  WiFi.begin(TEN_MANG, MAT_KHAU);        // không chờ: công tắc phải chạy được khi chưa có Wi-Fi
  mayChu.on("/", trangChu);
  mayChu.on("/dao", daoQuaWeb);
  mayChu.begin();
}

void loop() {
  mayChu.handleClient();
  if (millis() - lucLayMau >= CHU_KY_MAU_MS) {
    lucLayMau = millis();
    xuLyCham();
  }
  static bool daBaoIp = false;
  if (!daBaoIp && WiFi.status() == WL_CONNECTED) {
    daBaoIp = true;
    Serial.print("Dia chi: http://");
    Serial.println(WiFi.localIP());
  }
}
  • Đừng chạm vào tấm cảm ứng trong lúc cấp điện, vì chương trình đo nền ở bước này.
  • Preferences nhớ trạng thái đèn khi mất điện. Lệnh ghi chỉ chạy khi trạng thái đổi, nên flash không bị ghi liên tục.
  • Ba dòng pinMode trong setup() giữ relay ở đúng mức ngay từ đầu: gói esp32 3.x bỏ qua digitalWrite() gọi trước pinMode().
  • WiFi.begin() không chờ kết nối. Mất Wi-Fi hay router hỏng, chạm tay vẫn bật được đèn.
  • Trang web chỉ dùng trong mạng nội bộ, không có mật khẩu. Không mở cổng ra Internet.
Serial Monitor in giá trị nền, địa chỉ IP, Relay BAT do chạm, Relay TAT do chạm, Relay BAT do web
Hình minh họa kết quả mong đợi.

Tiếp theo có thể nối công tắc vào nhà thông minh: thay trang web bằng MQTT theo bài đưa ESP32 và relay vào Home Assistant.

5. Xử lý lỗi thường gặp

Hiện tượngNguyên nhânCách xử lý
Đèn tự bật tắtDây cảm ứng dài, đi cạnh dây 220 V; nguồn nhiễuRút ngắn dây, tách xa dây điện; tăng SO_MAU_XAC_NHAN
Chạm không ănMặt nhựa quá dày, tấm đồng nhỏDùng mặt 2–3 mm, tấm đồng lớn hơn; giảm TY_LE_CHAM
Luôn nhận là đang chạmTay đặt trên tấm lúc cấp điệnNhấn EN, không chạm trong 1 giây đầu
Đóng relay thì ESP32 khởi động lạiNguồn yếu, xung nhiễu khi relay đóngTụ 470 µF ở 5 V, mạch RC dập tia lửa cho tải cảm
Module nguồn nóng, có tiếng rítQuá tải, không thoángKiểm tra tổng dòng; module 3 W chỉ cho khoảng 600 mA ở 5 V
Trang web không vào đượcKhác mạng, sai IPXem IP trên Serial Monitor, điện thoại phải cùng Wi-Fi

6. Xem thêm

Muốn điều khiển relay không cần mở hộp điện, xem ESP32 làm web server điều khiển relay và điều khiển relay bằng Blynk.

Sản phẩm dùng trong bài: ESP32 DevKit V1 30 chân, module relay 1, 2, 4 kênh.

Bài cùng cụm chủ đề: HLK-PM01; an toàn khi làm mạch 220 V.


Tin tức liên quan

Giám sát điện trong nhà bằng ESP32 và nhiều PZEM-004T: từng nhánh, quá tải, quên tắt, công suất nền
Giám sát điện trong nhà bằng ESP32 và nhiều PZEM-004T: từng nhánh, quá tải, quên tắt, công suất nền

Dùng ESP32 đọc nhiều PZEM-004T chung một đường Modbus để giám sát điện từng nhánh: cảnh báo quá tải, điện áp thấp, bình nóng lạnh quên tắt, điện năng từng ngày, công suất nền.

Hướng dẫn bắt đầu MicroPython trên ESP32 Lite với Thonny
Hướng dẫn bắt đầu MicroPython trên ESP32 Lite với Thonny

Hướng dẫn cài driver, cài Thonny và nạp firmware MicroPython cho mạch ESP32 Lite, dùng REPL, nháy LED, lưu main.py, đọc nút nhấn, quét I2C và xử lý lỗi thường gặp.

Cảm biến nhịp tim MAX30102 với ESP32: tín hiệu PPG, đếm nhịp không cần thư viện và giới hạn của SpO2
Cảm biến nhịp tim MAX30102 với ESP32: tín hiệu PPG, đếm nhịp không cần thư viện và giới hạn của SpO2

Nguyên lý PPG, nối MAX30102 với ESP32, cấu hình thanh ghi, lọc tín hiệu và đếm nhịp tim không cần thư viện, tỉ số R của SpO2 và vì sao kết quả tự làm chỉ để học.

Mạch ESP32 tích hợp OLED 0.96 và pin 18650: sơ đồ chân, sạc pin, đo % pin và chương trình đầu tiên
Mạch ESP32 tích hợp OLED 0.96 và pin 18650: sơ đồ chân, sạc pin, đo % pin và chương trình đầu tiên

Mạch ESP32 tích hợp OLED 0.96 inch và đế pin 18650: chân OLED GPIO5, GPIO4, LED GPIO16, sạc pin, công tắc, gắn cầu phân áp đo phần trăm pin và chương trình hiện lên màn hình.

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
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.

DHT11 và DHT22 với ESP32: đọc nhiệt độ, độ ẩm không cần thư viện, giao thức 40 bit và điểm sương
DHT11 và DHT22 với ESP32: đọc nhiệt độ, độ ẩm không cần thư viện, giao thức 40 bit và điểm sương

So sánh DHT11 và DHT22, nối dây với ESP32, giải thích giao thức một dây 40 bit và checksum, chương trình đọc nhiệt độ, độ ẩm không cần thư viện, kèm điểm sương và bảng lỗi.

LED ma trận MAX7219 và LED 7 đoạn TM1637 với ESP32: chữ chạy 32 × 8 và đồng hồ, không cần thư viện
LED ma trận MAX7219 và LED 7 đoạn TM1637 với ESP32: chữ chạy 32 × 8 và đồng hồ, không cần thư viện

So sánh LED ma trận MAX7219 và LED 7 đoạn TM1637, nối với ESP32 an toàn mức điện áp, giải thích khung 16 bit và ba lệnh TM1637, hai chương trình chữ chạy và đồng hồ không cần thư viện.

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.


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