Dự án đầu tiên trong series: video doorbell — chuông cửa thông minh cho phép nhà chủ nhìn thấy và nói chuyện với khách từ bất cứ đâu, trực tiếp trên browser mà không cần cài app.
Tổng Quan Hệ Thống
[Camera ngoài cửa]
[Mic ngoài cửa] → ESP32-P4 → Wi-Fi → MediaMTX → Browser (nhà chủ)
[Nút bấm chuông] → Speaker (nhà chủ)
[Speaker ngoài cửa] ←─────────────────────────────────────
Khi khách nhấn nút, ESP32-P4 gửi WebRTC call → nhà chủ nhận trên browser → 2 chiều audio+video.
Danh Sách Linh Kiện
| Linh Kiện | Model | Ghi Chú |
|---|---|---|
| ESP32-P4 EV Board | Espressif | Board chính |
| Camera | OV2640 DVP | Góc rộng 160° nếu có |
| Microphone | INMP441 | Chống nước nếu dùng ngoài trời |
| Speaker | 8Ω 2W | Gắn trong hộp |
| Nút bấm | Momentary push button | Chống nước IP67 |
| Hộp | Hộp điện nhựa IP65 | Bảo vệ ngoài trời |
| Nguồn | 5V 2A adapter | Cấp từ trong nhà ra |
Sơ Đồ Kết Nối
Nút bấm chuông → GPIO_NUM_0 (INPUT_PULLUP)
LED trạng thái → GPIO_NUM_48 (OUTPUT)
Camera DVP → Xem bài 06
Mic INMP441 → Xem bài 06
Speaker I2S → I2S_NUM_1 qua NS4168 amp
Code: Main Application
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "driver/gpio.h"
#include "esp_webrtc.h"
#include "esp_capture.h"
#include "av_render.h"
#include "wifi_helper.h"
#define BUTTON_PIN GPIO_NUM_0
#define LED_PIN GPIO_NUM_48
#define CALL_TIMEOUT 30000 // 30 giây không ai bắt máy → tắt
static esp_webrtc_handle_t g_webrtc;
static bool g_call_active = false;
// Callback khi có sự kiện WebRTC
static void on_webrtc_event(esp_webrtc_event_t *event, void *ctx) {
switch (event->type) {
case ESP_WEBRTC_EVENT_CONNECTED:
g_call_active = true;
gpio_set_level(LED_PIN, 1); // LED sáng khi kết nối
ESP_LOGI("doorbell", "Browser answered, streaming...");
break;
case ESP_WEBRTC_EVENT_DISCONNECTED:
g_call_active = false;
gpio_set_level(LED_PIN, 0);
ESP_LOGI("doorbell", "Call ended");
break;
default:
break;
}
}
// Task theo dõi nút bấm chuông
static void button_task(void *arg) {
gpio_config_t btn_cfg = {
.pin_bit_mask = (1ULL << BUTTON_PIN),
.mode = GPIO_MODE_INPUT,
.pull_up_en = GPIO_PULLUP_ENABLE,
};
gpio_config(&btn_cfg);
while (1) {
// Nút bấm (active LOW)
if (gpio_get_level(BUTTON_PIN) == 0 && !g_call_active) {
ESP_LOGI("doorbell", "Button pressed — calling...");
// Nhấp nháy LED trong khi đổ chuông
for (int i = 0; i < 6; i++) {
gpio_set_level(LED_PIN, i % 2);
vTaskDelay(pdMS_TO_TICKS(300));
}
// Bắt đầu WebRTC call
esp_webrtc_start_call(g_webrtc);
// Timeout nếu không ai bắt máy
vTaskDelay(pdMS_TO_TICKS(CALL_TIMEOUT));
if (!g_call_active) {
esp_webrtc_stop_call(g_webrtc);
gpio_set_level(LED_PIN, 0);
ESP_LOGI("doorbell", "No answer, call ended");
}
}
vTaskDelay(pdMS_TO_TICKS(100));
}
}
void app_main(void) {
// LED output
gpio_set_direction(LED_PIN, GPIO_MODE_OUTPUT);
// Wi-Fi
wifi_init_sta(CONFIG_WIFI_SSID, CONFIG_WIFI_PASS);
wifi_wait_connected();
// Capture pipeline (camera + mic)
esp_capture_handle_t cap;
init_capture(&cap); // Hàm từ bài 06
// AV Render (speaker + LCD nếu có)
av_render_handle_t render;
init_av_render(&render);
// WebRTC
esp_webrtc_cfg_t cfg = {
.signaling = {
.type = ESP_WEBRTC_SIGNALING_WHIP,
.whip_url = CONFIG_WHIP_URL,
},
.ice_servers = {
{ .url = "stun:stun.l.google.com:19302" },
{ .url = "turn:" CONFIG_TURN_SERVER, .username = CONFIG_TURN_USER, .credential = CONFIG_TURN_PASS },
},
.ice_server_num = 2,
.audio_codec = ESP_WEBRTC_AUDIO_CODEC_OPUS,
.video_codec = ESP_WEBRTC_VIDEO_CODEC_H264,
};
esp_webrtc_init(&cfg, &g_webrtc);
esp_webrtc_set_av_source(g_webrtc, cap);
esp_webrtc_set_av_render(g_webrtc, render);
esp_webrtc_register_event_handler(g_webrtc, on_webrtc_event, NULL);
// Bắt đầu giám sát nút bấm
xTaskCreate(button_task, "button_task", 4096, NULL, 5, NULL);
ESP_LOGI("doorbell", "Video doorbell ready");
}
Trang Web Nhận Cuộc Gọi
Nhà chủ mở trang web đơn giản trên điện thoại/laptop:
<!DOCTYPE html>
<html>
<head><title>Chuông Cửa</title></head>
<body>
<h2>Video Doorbell</h2>
<video id="remoteVideo" autoplay playsinline style="width:100%"></video>
<button onclick="answer()">Bắt Máy</button>
<button onclick="hangup()">Cúp Máy</button>
<script>
let pc;
// Kiểm tra có cuộc gọi đến không (polling đơn giản)
async function checkCall() {
const resp = await fetch('http://SERVER/esp32-door/status');
const { calling } = await resp.json();
if (calling && !pc) {
document.querySelector('button').style.animation = 'pulse 1s infinite';
// Thông báo âm thanh
new Audio('ring.mp3').play();
}
}
setInterval(checkCall, 2000);
async function answer() {
pc = new RTCPeerConnection({
iceServers: [
{ urls: 'stun:stun.l.google.com:19302' },
{ urls: 'turn:YOUR_TURN_SERVER', username: 'esp32', credential: 'password123' }
]
});
pc.ontrack = e => document.getElementById('remoteVideo').srcObject = e.streams[0];
pc.addTransceiver('video', { direction: 'recvonly' });
pc.addTransceiver('audio', { direction: 'sendrecv' }); // 2 chiều audio
const offer = await pc.createOffer();
await pc.setLocalDescription(offer);
const resp = await fetch('http://SERVER:8889/esp32-door/whep', {
method: 'POST',
headers: { 'Content-Type': 'application/sdp' },
body: offer.sdp
});
const answer = await resp.text();
await pc.setRemoteDescription({ type: 'answer', sdp: answer });
}
async function hangup() {
pc?.close();
pc = null;
}
</script>
</body>
</html>
📷 [Hình minh hoạ: Ảnh demo: browser hiển thị video từ camera cửa, nút Bắt Máy/Cúp Máy]
Cài Đặt Trong Hộp Chống Nước
Gợi ý lắp đặt thực tế:
- Camera: gắn góc cao ~2.2m, hướng xuống
- Mic: dùng mic có windscreen (bảo vệ gió), đặt gần nút bấm
- Speaker: đặt trong hộp, khoét lỗ nhỏ cho âm thanh
- ESP32-P4 board: trong hộp IP65, cách ẩm bằng silica gel
- Cấp nguồn: dây nguồn 5V từ trong nhà ra (không dùng pin)
Tiết Kiệm Điện Ở Chế Độ Chờ
Khi không có cuộc gọi, ESP32-P4 chạy ở light sleep — vẫn giữ Wi-Fi nhưng giảm CPU:
// Sau khi setup xong, vào light sleep
esp_pm_config_t pm_config = {
.max_freq_mhz = 400,
.min_freq_mhz = 40, // CPU giảm xuống 40MHz khi idle
.light_sleep_enable = true,
};
esp_pm_configure(&pm_config);
Tiêu thụ: ~30mA khi chờ (Wi-Fi connected, light sleep), ~300mA khi stream video.
Bài tiếp theo: Dự Án Two-Way Intercom — Gọi Video Hai Chiều ESP32 và Browser


