Chuông cửa thông minh với ESP32-CAM: chụp ảnh và gửi Telegram khi có người
Chuông cửa “thông minh” bán sẵn thường khóa bạn vào app riêng và server của hãng. Với một board ESP32-CAM giá chưa tới 100 nghìn đồng, một cảm biến PIR, và một bot Telegram tự tạo (miễn phí, không giới hạn), bạn có thể tự làm hệ thống báo động kèm ảnh chụp gửi thẳng vào điện thoại — dữ liệu hoàn toàn do bạn kiểm soát.
Nguyên lý hoạt động
- Cảm biến PIR phát hiện chuyển động (nhiệt hồng ngoại thay đổi) trước cửa.
- ESP32-CAM đọc chân PIR, nếu HIGH thì chụp một khung hình JPEG từ camera.
- ESP32-CAM kết nối WiFi, gửi ảnh đó lên Telegram Bot API bằng HTTPS POST (multipart) tới chat của bạn.
- Có cơ chế “cooldown” để không gửi ảnh liên tục khi người đứng lâu trước cửa.
Linh kiện
- Board ESP32-CAM (module AI-Thinker phổ biến nhất)
- Bộ chuyển USB-to-Serial (FTDI hoặc CP2102) để nạp code — ESP32-CAM không có cổng USB
- Cảm biến chuyển động PIR (HC-SR501)
- Dây nối, breadboard, nguồn 5V ổn định (camera + WiFi khá tốn dòng, nên tránh cấp nguồn qua cổng 3.3V yếu của FTDI)
- Tài khoản Telegram + bot tạo qua @BotFather, và chat ID của bạn
Code mẫu
#include "esp_camera.h"
#include <WiFi.h>
#include <WiFiClientSecure.h>
#include "camera_pins.h" // định nghĩa chân camera cho model AI-Thinker
const char* WIFI_SSID = "ten_wifi_cua_ban";
const char* WIFI_PASS = "mat_khau_wifi";
const String BOT_TOKEN = "123456789:AAExxxxxxxxxxxxxxxxxxxxxxxxxx";
const String CHAT_ID = "987654321";
const int PIR_PIN = 13;
unsigned long lastSent = 0;
const unsigned long COOLDOWN = 60000UL; // 60 giây giữa 2 lần gửi
WiFiClientSecure client;
void setupCamera() {
camera_config_t config;
config.ledc_channel = LEDC_CHANNEL_0;
config.ledc_timer = LEDC_TIMER_0;
config.pin_d0 = Y2_GPIO_NUM; config.pin_d1 = Y3_GPIO_NUM;
config.pin_d2 = Y4_GPIO_NUM; config.pin_d3 = Y5_GPIO_NUM;
config.pin_d4 = Y6_GPIO_NUM; config.pin_d5 = Y7_GPIO_NUM;
config.pin_d6 = Y8_GPIO_NUM; config.pin_d7 = Y9_GPIO_NUM;
config.pin_xclk = XCLK_GPIO_NUM;
config.pin_pclk = PCLK_GPIO_NUM;
config.pin_vsync = VSYNC_GPIO_NUM;
config.pin_href = HREF_GPIO_NUM;
config.pin_sccb_sda = SIOD_GPIO_NUM;
config.pin_sccb_scl = SIOC_GPIO_NUM;
config.pin_pwdn = PWDN_GPIO_NUM;
config.pin_reset = RESET_GPIO_NUM;
config.xclk_freq_hz = 20000000;
config.pixel_format = PIXFORMAT_JPEG;
config.frame_size = FRAMESIZE_VGA; // 640x480, đủ nét mà không quá nặng
config.jpeg_quality = 12;
config.fb_count = 1;
esp_camera_init(&config);
}
void sendPhotoToTelegram() {
camera_fb_t* fb = esp_camera_fb_get();
if (!fb) { Serial.println("Chup anh that bai"); return; }
client.setInsecure(); // bỏ qua kiểm tra chứng chỉ cho đơn giản
if (!client.connect("api.telegram.org", 443)) {
Serial.println("Khong ket noi duoc Telegram");
esp_camera_fb_return(fb);
return;
}
String boundary = "coderdiyBoundary";
String head = "--" + boundary + "\r\n"
"Content-Disposition: form-data; name=\"chat_id\"\r\n\r\n" + CHAT_ID + "\r\n"
"--" + boundary + "\r\n"
"Content-Disposition: form-data; name=\"photo\"; filename=\"cua.jpg\"\r\n"
"Content-Type: image/jpeg\r\n\r\n";
String tail = "\r\n--" + boundary + "--\r\n";
uint32_t contentLength = head.length() + fb->len + tail.length();
client.printf("POST /bot%s/sendPhoto HTTP/1.1\r\n", BOT_TOKEN.c_str());
client.println("Host: api.telegram.org");
client.println("Content-Type: multipart/form-data; boundary=" + boundary);
client.printf("Content-Length: %u\r\n\r\n", contentLength);
client.print(head);
client.write(fb->buf, fb->len);
client.print(tail);
esp_camera_fb_return(fb);
Serial.println("Da gui anh len Telegram");
}
void setup() {
Serial.begin(115200);
pinMode(PIR_PIN, INPUT);
setupCamera();
WiFi.begin(WIFI_SSID, WIFI_PASS);
while (WiFi.status() != WL_CONNECTED) {
delay(300);
Serial.print(".");
}
Serial.println("\nDa ket noi WiFi");
}
void loop() {
if (digitalRead(PIR_PIN) == HIGH && millis() - lastSent > COOLDOWN) {
Serial.println("Phat hien chuyen dong!");
sendPhotoToTelegram();
lastSent = millis();
}
}
Lấy
BOT_TOKENtừ @BotFather sau khi tạo bot mới, và lấyCHAT_IDbằng cách nhắn tin cho bot rồi mởhttps://api.telegram.org/bot<TOKEN>/getUpdatesđể đọc trườngchat.id.
Những lỗi thường gặp
- Quên tháo dây GPIO0-GND sau khi nạp code: ESP32-CAM dùng GPIO0 nối GND để vào chế độ nạp firmware. Nếu để nguyên dây này, board sẽ luôn khởi động vào bootloader và không bao giờ chạy chương trình chính — đây là lỗi phổ biến nhất khiến người mới tưởng board bị hỏng.
- Nguồn yếu: camera + WiFi có lúc kéo dòng đỉnh hơn 300mA, cấp qua cổng 3.3V của FTDI dễ gây brown-out reset liên tục. Nên dùng nguồn 5V riêng ổn định cắm vào chân 5V của board.
- PIR báo động giả do gió, ánh nắng thay đổi hoặc côn trùng bay qua — nên chỉnh biến trở độ nhạy trên module PIR và đặt thời gian trễ (delay) sau khi cấp nguồn khoảng 30-60 giây để cảm biến ổn định trước khi đọc.
FRAMESIZEquá lớn làm chậm quá trình chụp và gửi, có thể gây timeout kết nối — VGA (640x480) là điểm cân bằng tốt giữa chất lượng và tốc độ.
Mở rộng
Thêm nút bấm vật lý để khách có thể “bấm chuông” chủ động thay vì chỉ dựa vào PIR, hoặc lưu ảnh vào thẻ SD tích hợp trên board để xem lại lịch sử khi không có mạng. Nâng cấp xa hơn nữa: chạy nhận diện khuôn mặt đơn giản trên Edge Impulse để chỉ gửi thông báo khi phát hiện người lạ.
Bình luận