Điều khiển relay bằng điện thoại với Blynk IoT và ESP32: template, datastream, đồng bộ trạng thái
- 1. Blynk IoT hoạt động thế nào
- 2. Chuẩn bị
- 3. Sơ đồ nối dây
- 4. Tạo template, datastream và thiết bị trên Blynk Console
- 5. Cài thư viện Blynk
- 6. Chương trình
- 7. Giải thích các phần chính
- Ba dòng cấu hình phải đứng trước #include
- Kết nối có mã hóa và không bị treo khi mất mạng
- BLYNK_WRITE: nhận lệnh từ ứng dụng
- BLYNK_CONNECTED: ai giữ trạng thái đúng sau khi mất điện?
- BlynkTimer thay cho delay()
- Khởi tạo chân relay không làm relay đóng thoáng qua
- 8. Kết quả
- 9. Mở rộng
- 10. Xử lý lỗi thường gặp
- 11. Xem thêm
Hướng dẫn Blynk IoT với ESP32 từ đầu: tạo template, datastream V0–V5, thiết bị và token, nối relay 4 kênh, chương trình bật tắt bằng điện thoại kèm nút nhấn tại chỗ.
Blynk IoT là dịch vụ cho phép dựng nhanh một ứng dụng điện thoại để điều khiển ESP32 qua Internet: kéo thả công tắc, ô hiển thị số, rồi gắn mỗi thành phần với một "chân ảo" trong chương trình. Điện thoại và ESP32 không cần ở chung mạng Wi-Fi, không cần mở cổng modem hay thuê IP tĩnh. Bài này hướng dẫn từ đầu: tạo template và datastream trên Blynk Console, nối ESP32 với module relay 4 kênh, nạp chương trình bật tắt 4 thiết bị bằng điện thoại, kèm nút nhấn tại chỗ vẫn dùng được khi mất mạng và cách giữ trạng thái công tắc trong ứng dụng khớp với relay thật.
1. Blynk IoT hoạt động thế nào

Có bốn khái niệm cần nắm trước khi bắt tay vào làm:
- Template: "khuôn" của một loại thiết bị, gồm danh sách datastream và giao diện. Mười mạch giống nhau dùng chung một template.
- Datastream: một kênh dữ liệu, ở bài này là các chân ảo (Virtual Pin) V0–V5. Chân ảo không phải chân GPIO thật; nó chỉ là số hiệu để ứng dụng và chương trình hiểu nhau. Bấm công tắc gắn V0 thì ESP32 nhận giá trị 1 hoặc 0 ở V0.
- Device: một mạch cụ thể tạo từ template, có mã Auth Token riêng. Token là "mật khẩu" của mạch: ai có token đều điều khiển được mạch đó.
- Máy chủ blynk.cloud: ESP32 giữ một kết nối mã hóa (cổng 443) tới máy chủ. Ứng dụng trên điện thoại cũng kết nối tới máy chủ. Mọi lệnh đi qua máy chủ, nên điện thoại dùng 4G hay Wi-Fi nhà nào cũng được.
Blynk có gói dùng miễn phí cho cá nhân, giới hạn số thiết bị và số datastream. Giới hạn cụ thể thay đổi theo thời gian; xem trang giá của Blynk trước khi làm nhiều thiết bị.
2. Chuẩn bị
- ESP32 DevKit V1 30 chân, cổng Type-C (hoặc bản 38 chân, nối theo tên chân in trên mạch).
- Module relay 4 kênh 5 V, loại kích mức thấp.
- Một nút nhấn 2 chân (tùy chọn).
- Breadboard, dây cắm và dây USB nạp code.
- Khi lắp cố định: nguồn 5 V đủ dòng, ví dụ adapter 12 V qua mạch hạ áp LM2596 chỉnh 5 V.
- Tài khoản Blynk (đăng ký miễn phí tại blynk.cloud) và ứng dụng Blynk IoT trên điện thoại.
Nếu máy tính chưa có gói ESP32, làm theo bài cài ESP32 vào Arduino IDE 2 trước.
3. Sơ đồ nối dây

| Module relay, nút | ESP32 DevKit V1 |
|---|---|
| IN1, IN2, IN3, IN4 | GPIO32, GPIO33, GPIO26, GPIO27 |
| VCC | VIN (5 V khi cấp qua cổng USB) |
| GND | GND |
| Nút nhấn | Một chân vào GPIO4, chân kia vào GND |
Bốn chân 32, 33, 26, 27 không phải chân khởi động của ESP32, nên module relay không làm mạch khởi động sai. Chân D21, D22 để dành cho màn hình OLED ở các bài sau. Cách hoạt động của module, jumper JD-VCC và chuyện kích mức thấp xem bài module relay 1, 2, 4 kênh.
An toàn điện: phía tiếp điểm relay đóng cắt điện 220 V. Thử chương trình với relay chưa nối tải; khi đấu tải thật, ngắt điện, lắp trong hộp kín, dây tải và dây tín hiệu đi cách xa nhau.
4. Tạo template, datastream và thiết bị trên Blynk Console

- Đăng nhập blynk.cloud, vào Developer Zone > My Templates > New Template. Đặt tên (ví dụ "Relay 4 kenh"), phần cứng ESP32, kiểu kết nối WiFi.
- Trong template, mở thẻ Datastreams > New Datastream > Virtual Pin. Tạo V0, V1, V2, V3 kiểu Integer, Min 0, Max 1, giá trị mặc định 0. Tạo V4 (cường độ Wi-Fi, đơn vị dBm) và V5 (thời gian chạy, giây), kiểu Integer.
- Thẻ Web Dashboard: kéo 4 công tắc (Switch) gắn lần lượt V0–V3 và 2 ô hiển thị số gắn V4, V5. Bấm Save.
- Vào Devices > New Device > From template, chọn template vừa tạo, đặt tên thiết bị.
- Mở thiết bị, mục Device Info có ba dòng
BLYNK_TEMPLATE_ID,BLYNK_TEMPLATE_NAME,BLYNK_AUTH_TOKEN. Chép nguyên ba dòng này dán đè lên ba dòng tương ứng ở đầu chương trình.
Trên điện thoại, đăng nhập ứng dụng Blynk IoT bằng cùng tài khoản: thiết bị vừa tạo hiện trong danh sách. Bật chế độ chỉnh sửa giao diện, thêm 4 nút bấm (Button) ở chế độ Switch gắn V0–V3 và 2 ô hiển thị giá trị gắn V4, V5. Tên menu có thể khác một chút giữa các phiên bản ứng dụng.
5. Cài thư viện Blynk
Trong Arduino IDE 2, mở Sketch > Include Library > Manage Libraries…, gõ "Blynk", chọn thư viện Blynk (tác giả Blynk) và bấm Install. IDE có thể hỏi cài thêm thư viện phụ BlynkNcpDriver: chọn cài cùng cũng được, chương trình trong bài không dùng tới thư viện này. Bài dùng gói esp32 phiên bản 3.3.10 và thư viện Blynk 1.4.0.
6. Chương trình
Thay TEN_WIFI, MAT_KHAU_WIFI bằng tên và mật khẩu Wi-Fi 2,4 GHz, dán ba dòng Device Info lên đầu file, chọn bo ESP32 Dev Module rồi nạp.
// PYWORLD.VN - Điều khiển relay 4 kênh bằng ứng dụng Blynk IoT trên điện thoại
// Bo mạch: ESP32 DevKit V1, module relay 4 kênh kích mức thấp, nút nhấn ở GPIO4
// Thư viện: Blynk 1.4.0, kết nối có mã hóa (SSL) tới máy chủ blynk.cloud
// Ba dòng này chép từ mục Device Info của thiết bị trên Blynk Console và phải đặt TRƯỚC các lệnh #include
#define BLYNK_TEMPLATE_ID "TMPLxxxxxxxxx"
#define BLYNK_TEMPLATE_NAME "Relay 4 kenh"
#define BLYNK_AUTH_TOKEN "MA_TOKEN_THIET_BI"
#define BLYNK_PRINT Serial // In nhật ký kết nối của Blynk ra Serial Monitor
#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <BlynkSimpleEsp32_SSL.h>
const char *TEN_WIFI = "TEN_WIFI";
const char *MAT_KHAU_WIFI = "MAT_KHAU_WIFI";
const uint8_t RELAY_PINS[4] = {32, 33, 26, 27}; // IN1, IN2, IN3, IN4
const bool RELAY_ACTIVE_LOW = true; // Module kích mức thấp: IN = LOW thì relay đóng
const uint8_t NUT = 4; // Nút nhấn nối GPIO4 xuống GND
// false: sau khi mất điện, mọi relay tắt và ứng dụng được cập nhật theo ESP32.
// true: ESP32 lấy lại trạng thái cuối cùng đang lưu trên máy chủ Blynk.
const bool LAY_LAI_TRANG_THAI = false;
bool relayBat[4] = {false, false, false, false};
BlynkTimer timer;
unsigned long lanThuKetNoi = 0;
bool chuaThuKetNoi = true;
void datRelay(uint8_t kenh, bool bat) {
relayBat[kenh] = bat;
bool muc = RELAY_ACTIVE_LOW ? !bat : bat;
digitalWrite(RELAY_PINS[kenh], muc ? HIGH : LOW);
Serial.printf("Relay K%u: %s\n", (unsigned)(kenh + 1), bat ? "BAT" : "TAT");
}
// Mỗi datastream V0..V3 ứng với một kênh relay: giá trị 1 là bật, 0 là tắt
BLYNK_WRITE(V0) { datRelay(0, param.asInt() == 1); }
BLYNK_WRITE(V1) { datRelay(1, param.asInt() == 1); }
BLYNK_WRITE(V2) { datRelay(2, param.asInt() == 1); }
BLYNK_WRITE(V3) { datRelay(3, param.asInt() == 1); }
// Chạy mỗi lần kết nối (hoặc kết nối lại) tới máy chủ Blynk
BLYNK_CONNECTED() {
Serial.println("Da ket noi Blynk");
if (LAY_LAI_TRANG_THAI) {
Blynk.syncVirtual(V0, V1, V2, V3); // Máy chủ gửi lại giá trị cuối, các hàm BLYNK_WRITE ở trên sẽ chạy
} else {
for (uint8_t i = 0; i < 4; i++) {
Blynk.virtualWrite(V0 + i, relayBat[i] ? 1 : 0); // Công tắc trong ứng dụng khớp với relay thật
}
}
}
// Gửi cường độ Wi-Fi và thời gian chạy lên V4, V5 mỗi 5 giây
void guiTrangThai() {
if (!Blynk.connected()) return;
Blynk.virtualWrite(V4, WiFi.RSSI());
Blynk.virtualWrite(V5, millis() / 1000);
}
// Nút nhấn tại chỗ đảo trạng thái K1, vẫn dùng được khi mất Internet
void kiemTraNut() {
static bool docTruoc = HIGH, onDinh = HIGH;
static unsigned long lucDoi = 0;
bool doc = digitalRead(NUT);
if (doc != docTruoc) {
docTruoc = doc;
lucDoi = millis();
}
if (millis() - lucDoi > 30 && doc != onDinh) { // Chống dội 30 ms
onDinh = doc;
if (onDinh == LOW) {
datRelay(0, !relayBat[0]);
if (Blynk.connected()) Blynk.virtualWrite(V0, relayBat[0] ? 1 : 0);
}
}
}
void setup() {
Serial.begin(115200);
for (uint8_t i = 0; i < 4; i++) {
pinMode(RELAY_PINS[i], INPUT); // Chân chưa xuất tín hiệu
digitalWrite(RELAY_PINS[i], RELAY_ACTIVE_LOW ? HIGH : LOW); // Ghi sẵn mức tắt
pinMode(RELAY_PINS[i], OUTPUT); // Bật ngõ ra, relay vẫn tắt
}
pinMode(NUT, INPUT_PULLUP);
WiFi.mode(WIFI_STA);
WiFi.begin(TEN_WIFI, MAT_KHAU_WIFI);
Blynk.config(BLYNK_AUTH_TOKEN); // Chỉ ghi cấu hình, chưa kết nối; vòng lặp tự kết nối khi có Wi-Fi
timer.setInterval(5000L, guiTrangThai);
Serial.println("Dang ket noi Wi-Fi...");
}
void loop() {
kiemTraNut();
if (WiFi.status() == WL_CONNECTED) {
if (Blynk.connected()) {
Blynk.run();
} else if (chuaThuKetNoi || millis() - lanThuKetNoi > 10000) {
chuaThuKetNoi = false;
lanThuKetNoi = millis();
Serial.printf("Wi-Fi OK, IP %s. Dang ket noi Blynk...\n", WiFi.localIP().toString().c_str());
Blynk.connect(5000); // Chờ tối đa 5 giây; không được thì 10 giây sau thử lại
}
}
timer.run();
}
7. Giải thích các phần chính
Ba dòng cấu hình phải đứng trước #include
Thư viện đọc BLYNK_TEMPLATE_ID và BLYNK_TEMPLATE_NAME ngay khi được nạp. Đặt ba dòng này sau #include thì Arduino IDE báo lỗi "Please specify your BLYNK_TEMPLATE_ID and BLYNK_TEMPLATE_NAME".
Kết nối có mã hóa và không bị treo khi mất mạng
Chương trình dùng BlynkSimpleEsp32_SSL.h: dữ liệu và token đi qua kết nối mã hóa tới cổng 443, thư viện tự kèm chứng chỉ để kiểm tra máy chủ. Hàm Blynk.begin() trong ví dụ mẫu của thư viện chờ Wi-Fi mãi không thôi, nên nếu mất mạng lúc khởi động thì nút nhấn tại chỗ cũng không chạy. Bài này tách ra: Blynk.config() chỉ ghi cấu hình, vòng lặp tự gọi Blynk.connect(5000) mỗi 10 giây khi đã có Wi-Fi. Trong lúc chờ, kiemTraNut() vẫn chạy bình thường.
BLYNK_WRITE: nhận lệnh từ ứng dụng
Mỗi khi công tắc gắn V0 đổi giá trị, thư viện gọi hàm BLYNK_WRITE(V0); param.asInt() trả về 1 hoặc 0. Hàm datRelay() đổi ra mức điện phù hợp với module kích mức thấp.
BLYNK_CONNECTED: ai giữ trạng thái đúng sau khi mất điện?
Khi ESP32 khởi động lại sau mất điện, các relay đều tắt, trong khi công tắc trong ứng dụng có thể vẫn đang bật. Có hai cách cho khớp lại, chọn bằng hằng LAY_LAI_TRANG_THAI:
false(mặc định): ESP32 gửi trạng thái thật của mình lên (Blynk.virtualWrite), công tắc trong ứng dụng chuyển về tắt. An toàn hơn với máy bơm, lò sưởi, thiết bị không nên tự chạy lại.true: ESP32 xin lại giá trị cuối cùng lưu trên máy chủ (Blynk.syncVirtual), relay trở lại như trước khi mất điện. Hợp với đèn, quạt.
BlynkTimer thay cho delay()
timer.setInterval(5000L, guiTrangThai) gọi hàm gửi V4, V5 mỗi 5 giây mà không chặn vòng lặp. Không dùng delay() dài trong chương trình Blynk: thư viện cần Blynk.run() được gọi thường xuyên để giữ kết nối. Cũng không nên gửi dữ liệu lên máy chủ nhiều lần mỗi giây.
Khởi tạo chân relay không làm relay đóng thoáng qua
Chân được ghi sẵn mức tắt trước khi chuyển sang ngõ ra, giống các bài relay khác trên pyworld.vn. Lý do xem bài relay làm ESP32 bị reset.
8. Kết quả


Bấm công tắc trong ứng dụng, relay tương ứng đóng sau khoảng nửa giây, đèn báo trên module sáng. Bấm nút nhấn trên mạch, K1 đổi trạng thái và công tắc K1 trong ứng dụng cũng đổi theo. Rút dây Wi-Fi của modem: nút nhấn vẫn dùng được; khi có mạng lại, ESP32 tự kết nối và cập nhật ứng dụng.
9. Mở rộng
- Thông báo về điện thoại: Blynk có mục Events trong template; chương trình gọi sự kiện khi có chuyện cần báo (ví dụ mất nguồn lưới, cửa mở). Cách gửi cảnh báo không cần dịch vụ trả phí xem bài gửi cảnh báo qua Telegram trong cùng loạt.
- Hẹn giờ, tự động hóa: thực hiện được trên Blynk Console tùy gói, hoặc tự viết trong chương trình bằng giờ Internet như bài đồng hồ Internet NTP.
- Không muốn phụ thuộc dịch vụ bên ngoài: điều khiển trong mạng nội bộ bằng web server trên ESP32, hoặc qua MQTT.
- Đóng hộp: hộp mica cho ESP32 DevKit 30 chân và nguồn 5 V riêng cho cuộn relay.
Giữ kín token: không đăng chương trình có token thật lên mạng. Lỡ để lộ, vào thiết bị trên Blynk Console tạo token mới rồi nạp lại chương trình.
10. Xử lý lỗi thường gặp
| Hiện tượng | Nguyên nhân thường gặp | Cách xử lý |
|---|---|---|
| Biên dịch báo "Please specify your BLYNK_TEMPLATE_ID and BLYNK_TEMPLATE_NAME" | Ba dòng cấu hình nằm sau #include hoặc bị xóa | Đưa ba dòng Device Info lên đầu file |
| Serial in "Invalid auth token" | Chép thiếu ký tự token, hoặc token đã bị tạo lại | Chép lại cả ba dòng từ Device Info |
| Đứng mãi ở "Connecting to blynk.cloud:443", hoặc "Secure connection failed" | Mạng chặn cổng 443 ra ngoài, Wi-Fi yếu, nguồn sụt khi phát Wi-Fi | Thử Wi-Fi điện thoại phát; xem cường độ ở V4; cấp nguồn khỏe hơn (bài cấp nguồn cho ESP32) |
| Không vào được Wi-Fi | Sai tên, mật khẩu, hoặc modem chỉ phát 5 GHz | ESP32 chỉ dùng Wi-Fi 2,4 GHz; kiểm tra theo bài kết nối Wi-Fi trên ESP32 |
| Bấm công tắc, relay không đổi nhưng Serial có in "Relay K1: BAT" | Module kích mức cao, hoặc dây IN cắm nhầm chân | Đổi RELAY_ACTIVE_LOW thành false, dò lại dây theo bảng |
| Công tắc trong ứng dụng không có tác dụng, Serial không in gì | Công tắc gắn nhầm datastream, hoặc datastream kiểu khác Integer 0–1 | Mở cài đặt công tắc, chọn đúng V0–V3 |
| ESP32 khởi động lại khi relay đóng | Sụt áp hoặc nhiễu từ tải | Xem bài relay làm ESP32 bị reset |
11. Xem thêm
- ESP32 điều khiển relay an toàn
- Điều khiển relay bằng điện thoại qua Bluetooth, không cần Internet
- Nạp chương trình cho ESP32 qua Wi-Fi (OTA) khi mạch đã lắp trong hộp
- Các bài cùng loạt ESP32 với điện thoại và dịch vụ đám mây: gửi cảnh báo qua Telegram, ghi dữ liệu lên Google Sheets, đưa ESP32 vào Home Assistant, cấu hình Wi-Fi bằng điện thoại.
Xem thêm