# WiFiSense — ESP32-S3 + OLED Edition > Phát hiện chuyển động thụ động qua biến động tín hiệu RSSI WiFi, hiển thị trực tiếp trên màn hình OLED 0.96", tự động cấu hình WiFi qua Captive Portal. ![Platform](https://img.shields.io/badge/Platform-ESP32--S3-blue) ![IDE](https://img.shields.io/badge/IDE-Arduino-teal) ![License](https://img.shields.io/badge/License-MIT-green) --- ## Mục lục - [Nguyên lý hoạt động](#nguyên-lý-hoạt-động) - [Sơ đồ luồng hệ thống](#sơ-đồ-luồng-hệ-thống) - [Phần cứng](#phần-cứng) - [Cài đặt môi trường](#cài-đặt-môi-trường) - [Nạp code](#nạp-code) - [Cách sử dụng](#cách-sử-dụng) - [Màn hình OLED](#màn-hình-oled) - [Troubleshooting](#troubleshooting) --- ## Nguyên lý hoạt động WiFiSense **không dùng camera, microphone hay cảm biến vật lý nào**. Thay vào đó, nó khai thác một hiện tượng vật lý: khi có người/vật di chuyển trong phòng, sóng WiFi bị phản xạ và nhiễu loạn, khiến **chỉ số RSSI** (cường độ tín hiệu) dao động theo những mẫu đặc trưng. ### Pipeline xử lý tín hiệu ``` WiFi RSSI thô │ ▼ ┌─────────────────┐ │ Kalman Filter │ ← Loại bỏ nhiễu ngẫu nhiên ngắn hạn └────────┬────────┘ │ ▼ ┌──────────────────────┐ │ Exponential Smoothing │ ← Làm mượt tín hiệu (α = 0.15) └──────────┬───────────┘ │ ▼ ┌──────────────────────────────────────────────────┐ │ Cửa sổ trượt (40 mẫu ngắn + 100 mẫu dài) │ │ • Độ lệch chuẩn (Variance) → biên độ dao động │ │ • Rate of Change → tốc độ thay đổi │ │ • Peak Detection → đỉnh đột biến │ │ • Z-Score → bất thường thống │ └──────────────────────┬───────────────────────────┘ │ ▼ ┌────────────────────────────────┐ │ Tính Confidence Score (0–100) │ └──────────────┬─────────────────┘ │ ▼ ┌──────────────────────────────────────────────────┐ │ Persistence Counter (≥ 3 mẫu liên tiếp) │ │ → Kết luận: MOTION / CLEAR │ │ → Bật LED + hiển thị OLED │ └──────────────────────────────────────────────────┘ ``` ### Adaptive Baseline Baseline (đường cơ sở khi không có chuyển động) **tự thích nghi** theo thời gian thực với tốc độ rất chậm (α = 0.008), giúp hệ thống tự điều chỉnh khi môi trường thay đổi dần (người rời phòng, thay đổi vị trí router…). --- ## Sơ đồ luồng hệ thống ``` ┌──────────────┐ │ Khởi động │ └──────┬───────┘ │ ┌──────▼───────┐ │ Đọc NVS │ credentials đã lưu? └──────┬───────┘ Có ▼ ▼ Không ┌──────────────┐ ┌──────────────────────┐ │ Kết nối WiFi │ │ Mở AP WiFiSense-Setup│ └──────┬───────┘ │ DNS + Captive Portal │ OK ▼ ▼ Fail │ Người dùng chọn mạng │ ┌────────┐ ┌──────────┐ │ → Lưu NVS → Restart │ │Calibrate│ │Thử lại x3│ └──────────────────────┘ └────┬───┘ └────┬─────┘ │ Fail│ │ ┌────▼──────────────┐ │ │ Xóa NVS + Restart │ │ └───────────────────┘ ┌────▼──────────────────┐ │ Loop chính │ │ • Lấy RSSI mỗi 500ms │ │ • Kalman + Smooth │ │ • Tính Variance/Z │ │ • Cập nhật OLED │ │ • Bật/tắt LED │ └───────────────────────┘ ``` --- ## Phần cứng ### Linh kiện cần thiết | Linh kiện | Thông số | Ghi chú | |---|---|---| | Vi điều khiển | ESP32-S3 (bất kỳ board) | Đã test trên ESP32-S3-DevKitC-1 | | Màn hình OLED | 0.96" SSD1306 128×64 I2C | Địa chỉ I2C: `0x3C` | | LED | 3mm hoặc 5mm, bất kỳ màu | Kèm điện trở 220Ω–330Ω | | Dây jumper | Male-to-Male hoặc Male-to-Female | — | ### Sơ đồ đấu dây ``` ESP32-S3 SSD1306 OLED ───────── ──────────── GPIO 6 ───────── SDA GPIO 7 ───────── SCL 3.3V ───────── VCC GND ───────── GND ESP32-S3 LED ───────── ──────────── GPIO 2 ──[330Ω]── Anode (+) GND ───────── Cathode (-) ``` > **Lưu ý:** Một số board ESP32-S3 (ví dụ Waveshare) có thể có sẵn LED trên GPIO 2 — kiểm tra datasheet của board bạn đang dùng. --- ## Cài đặt môi trường ### 1. Arduino IDE Tải về tại [arduino.cc/en/software](https://www.arduino.cc/en/software) (phiên bản 2.x khuyến nghị). ### 2. Thêm ESP32 vào Board Manager Vào **File → Preferences → Additional boards manager URLs**, thêm: ``` https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json ``` Sau đó vào **Tools → Board → Boards Manager**, tìm **esp32** của Espressif và cài đặt. ### 3. Cài thư viện Vào **Tools → Manage Libraries** và cài đặt 2 thư viện sau: | Thư viện | Tác giả | Ghi chú | |---|---|---| | **Adafruit SSD1306** | Adafruit | Tìm đúng tên này | | **Adafruit GFX Library** | Adafruit | Dependency của SSD1306 | > Các thư viện còn lại (`WiFi`, `WebServer`, `DNSServer`, `Preferences`) đã có sẵn trong ESP32 Arduino core, **không cần cài thêm**. ### 4. Chọn board **Tools → Board → ESP32 Arduino → ESP32S3 Dev Module** Các thông số quan trọng: | Tùy chọn | Giá trị | |---|---| | USB Mode | Hardware CDC and JTAG | | Flash Size | 4MB (hoặc theo board của bạn) | | Partition Scheme | Default 4MB with spiffs | | PSRAM | Disabled (nếu board không có) | --- ## Nạp code 1. Kết nối ESP32-S3 với máy tính qua USB. 2. Mở file `WiFiSense.ino` trong Arduino IDE. 3. Chọn đúng **Port** tại **Tools → Port**. 4. Nhấn nút **Upload** (→) hoặc `Ctrl+U`. 5. Chờ quá trình biên dịch và nạp hoàn tất (~30–60 giây). > Nếu board không tự vào chế độ nạp: giữ nút **BOOT**, nhấn **RESET**, thả **RESET**, rồi thả **BOOT**. --- ## Cách sử dụng ### Lần đầu tiên (chưa có WiFi lưu) Khi khởi động và chưa có credentials trong NVS, ESP32 sẽ tự động mở **Captive Portal**: ``` OLED hiển thị: ┌──────────────────────────────┐ │ ** SETUP MODE ** │ │──────────────────────────────│ │ 1. Ket noi WiFi: │ │ WiFiSense-Setup │ │ 2. Mo trinh duyet: │ │ 192.168.4.1 │ └──────────────────────────────┘ ``` **Các bước cấu hình:** 1. Dùng điện thoại hoặc máy tính, kết nối vào WiFi **`WiFiSense-Setup`** (không mật khẩu). 2. Trình duyệt sẽ tự động mở trang cấu hình (Captive Portal). Nếu không tự mở, truy cập thủ công vào `http://192.168.4.1`. 3. Chọn mạng WiFi nhà bạn từ danh sách quét được. 4. Nhập mật khẩu WiFi → nhấn **Kết nối & Lưu**. 5. ESP32 lưu thông tin vào bộ nhớ NVS và **tự khởi động lại**. 6. Lần này ESP32 kết nối thẳng vào WiFi đã chọn và bắt đầu calibrate. ### Các lần sau (đã có WiFi lưu) ESP32 tự kết nối WiFi khi khởi động, không cần cấu hình lại. ### Đổi WiFi Nếu muốn kết nối sang mạng khác: xóa credentials bằng cách mất điện trong khi đang ở chế độ portal, hoặc thêm một nút bấm vật lý để gọi `clearCredentials()` + `ESP.restart()`. ### Calibration Sau khi kết nối WiFi thành công, ESP32 sẽ **calibrate trong ~6 giây** (200 mẫu × 30ms). Trong thời gian này: - **Giữ phòng trống** (không có người di chuyển). - OLED hiển thị tiến trình calibration. - Baseline được thiết lập dựa trên RSSI trung bình. --- ## Màn hình OLED Sau calibration, OLED hiển thị **1 trang duy nhất** cập nhật mỗi 500ms: ``` ┌──────────────────────────────┐ ← 128px │ WiFiSense [ MOTION ] │ dòng 1: tiêu đề + badge trạng thái │──────────────────────────────│ đường kẻ │ RSSI: -67dBm C: 82% │ dòng 2: RSSI thô + Confidence số │ Conf [████████████░░░░░░] │ dòng 3: thanh Confidence │ Qual [█████████░░░░░░░░░] │ dòng 4: thanh Signal Quality │──────────────────────────────│ đường kẻ │ Base:-66.1 Var:2.3 │ dòng 5: Baseline + Variance │ #15 WALKING │ dòng 6: tổng detections + cường độ └──────────────────────────────┘ ``` ### Ý nghĩa các chỉ số | Chỉ số | Mô tả | |---|---| | **RSSI** | Cường độ tín hiệu WiFi thô (dBm). Giá trị điển hình: -40 (gần) đến -90 (xa). | | **C (Confidence)** | Độ tin cậy phát hiện chuyển động, 0–100%. Trên ~50% là có người. | | **Conf bar** | Thanh trực quan hóa Confidence. | | **Qual bar** | Chất lượng tín hiệu tổng thể (dựa trên độ ổn định dài hạn). | | **Base** | Baseline RSSI hiện tại (tự thích nghi). | | **Var** | Variance của cửa sổ 40 mẫu gần nhất — số càng cao = dao động càng mạnh. | | **#N** | Tổng số lần phát hiện chuyển động kể từ khi khởi động. | | **Badge** | `MOTION` (đảo màu trắng) khi phát hiện, `CLEAR` khi yên tĩnh. | | **Cường độ** | `CALM` / `SLOW` / `WALKING` / `FAST` / `SPRINT` — ước lượng mức độ di chuyển. | --- ## Troubleshooting ### OLED không hiển thị gì | Nguyên nhân | Giải pháp | |---|---| | Sai địa chỉ I2C | Chạy sketch `I2C Scanner` để tìm địa chỉ thực. Nếu khác `0x3C`, sửa `#define OLED_ADDRESS` thành `0x3D`. | | Sai chân SDA/SCL | Kiểm tra lại: SDA → GPIO **6**, SCL → GPIO **7**. | | Nguồn không đủ | Đảm bảo OLED lấy nguồn 3.3V (không phải 5V đối với các module 3.3V). | | Thư viện chưa cài | Kiểm tra lại Library Manager: `Adafruit SSD1306` và `Adafruit GFX Library`. | ### Không thấy WiFi "WiFiSense-Setup" | Nguyên nhân | Giải pháp | |---|---| | ESP32 đang kết nối WiFi cũ | Chờ 30 giây timeout, ESP32 sẽ tự chuyển sang AP mode. | | Credentials cũ còn trong NVS | Upload lại firmware có `clearCredentials()` trong `setup()` (tạm thời), sau đó upload lại bản gốc. | ### Trang cấu hình không tự mở trên điện thoại Mt số thiết bị Android/iOS không tự kích hoạt captive portal với DNS redirect. Mở trình duyệt và truy cập thủ công: **`http://192.168.4.1`** ### Phát hiện chuyển động không chính xác | Vấn đề | Giải pháp | |---|---| | Phát hiện khi không có người | Giảm `FAST_MOVEMENT_THRESHOLD` và `SLOW_MOVEMENT_THRESHOLD`, hoặc tăng `PERSISTENCE_REQUIRED`. | | Không phát hiện khi có người | Đặt ESP32 gần router hơn; tăng `CALIBRATION_SAMPLES`; kiểm tra không có thiết bị khác gây nhiễu. | | Baseline trôi quá nhanh | Giảm `ADAPTIVE_ALPHA` (ví dụ từ `0.008` xuống `0.003`). | ### Mất kết nối WiFi liên tục - Kiểm tra khoảng cách tới router. RSSI nên ở mức > -75 dBm để ổn định. - Sau 3 lần mất kết nối liên tiếp (`MAX_RECONNECT_ATTEMPTS = 3`), ESP32 tự xóa NVS và mở lại Captive Portal — đây là hành vi thiết kế để người dùng có thể đổi mạng. - Tăng `MAX_RECONNECT_ATTEMPTS` nếu mạng nhà bạn hay bị ngắt tạm thời. ### Upload thất bại (Port not found / No device) 1. Kiểm tra cáp USB (phải là cáp data, không phải cáp sạc). 2. Giữ nút **BOOT** trên board, nhấn **RESET**, thả **RESET**, thả **BOOT**, rồi Upload ngay. 3. Thử port COM khác trong Arduino IDE. --- ## Cấu trúc project ``` WiFiSense/ └── WiFiSense.ino ← Toàn bộ code trong 1 file ``` --- ## Tác giả **Tony Trần** — [github.com/thanhtantran](https://github.com/thanhtantran) **Orange Pi Việt Nam** — [orangepi.vn](https://orangepi.vn) --- ## License MIT License — xem file `LICENSE` để biết thêm chi tiết.