ESP32 Bluetooth Chat Example Hướng Dẫn Giao Diện Nhắn Tin Hai Chiều
Ví dụ Bluetooth Chat cho phép nhắn tin văn bản hai chiều giữa ESP32 và smartphone thông qua ứng dụng DIYables Bluetooth STEM. Được thiết kế cho các board ESP32 với hỗ trợ cả BLE (Bluetooth Low Energy) và Classic Bluetooth. Gửi và nhận tin nhắn văn bản thời gian thực, triển khai xử lý lệnh tùy chỉnh, và tạo cầu nối Serial Monitor với Bluetooth — hoàn hảo cho việc debug, điều khiển từ xa và các dự án tương tác.
Ví dụ này hỗ trợ hai chế độ Bluetooth:
Nhắn Tin Hai Chiều: Gửi và nhận tin nhắn văn bản thời gian thực
Chế Độ Echo: Tự động phản hồi lại tin nhắn nhận được từ app
Xử Lý Lệnh: Xử lý các lệnh văn bản tùy chỉnh (ping, status, time, heap)
Tin Nhắn Định Kỳ: Gửi cập nhật trạng thái tự động theo khoảng thời gian có thể cấu hình
Cầu Nối Serial-to-Bluetooth: Nhập tin nhắn trong Serial Monitor và gửi đến app
BLE & Classic Bluetooth: Chọn chế độ Bluetooth phù hợp với dự án của bạn
Đa Nền Tảng: Chế độ BLE hoạt động trên cả Android và iOS; Classic Bluetooth hoạt động trên Android
Tùy Chọn Tiết Kiệm Pin: Chế độ BLE tiêu thụ ít pin hơn Classic Bluetooth
| 1 | × | mô-đun phát triển ESP-WROOM-32 | | |
| 1 | × | Alternatively, ESP32 Uno-form board | | |
| 1 | × | Alternatively, ESP32 S3 Uno-form board | | |
| 1 | × | USB Cable Type-A to Type-C (for USB-A PC) | | |
| 1 | × | USB Cable Type-C to Type-C (for USB-C PC) | | |
| 1 | × | breadboard | | |
| 1 | × | dây jumper | | |
| 1 | × | (Khuyến nghị) Screw Terminal Expansion Board for ESP32 | | |
| 1 | × | (Khuyến nghị) Breakout Expansion Board for ESP32 | | |
| 1 | × | (Khuyến nghị) Power Splitter for ESP32 | | |
Or you can buy the following kits:
| 1 | × | DIYables ESP32 Starter Kit (ESP32 included) | | |
| 1 | × | DIYables Sensor Kit (30 sensors/displays) | | |
| 1 | × | DIYables Sensor Kit (18 sensors/displays) | | |
Thực hiện theo các hướng dẫn từng bước:
Kết nối board ESP32 với máy tính của bạn bằng cáble USB.
Khởi chạy Arduino IDE trên máy tính của bạn.
Chọn board ESP32 và COM port phù hợp.
Điều hướng đến biểu tượng Libraries ở thanh bên trái của Arduino IDE.
Tìm kiếm "DIYables Bluetooth", sau đó tìm thư viện DIYables Bluetooth của DIYables
Nhấp nút Install để cài đặt thư viện.
Chọn một trong hai chế độ Bluetooth dưới đây tùy thuộc vào nhu cầu của bạn:
Lưu ý: Classic Bluetooth KHÔNG được hỗ trợ trên iOS. Nếu bạn cần hỗ trợ iOS, hãy sử dụng code BLE bên dưới.
#include <DIYables_BluetoothServer.h>
#include <DIYables_BluetoothChat.h>
#include <platforms/DIYables_Esp32Bluetooth.h>
DIYables_Esp32Bluetooth bluetooth("ESP32_Chat");
DIYables_BluetoothServer bluetoothServer(bluetooth);
DIYables_BluetoothChat bluetoothChat;
unsigned long lastMessageTime = 0;
const unsigned long MESSAGE_INTERVAL = 10000;
int messageCount = 0;
void setup() {
Serial.begin(115200);
delay(1000);
Serial.println("DIYables Bluetooth - ESP32 Chat Example");
bluetoothServer.begin();
bluetoothServer.addApp(&bluetoothChat);
bluetoothServer.setOnConnected([]() {
Serial.println("Bluetooth connected!");
bluetoothChat.send("Hello! ESP32 is ready to chat.");
});
bluetoothServer.setOnDisconnected([]() {
Serial.println("Bluetooth disconnected!");
messageCount = 0;
});
bluetoothChat.onChatMessage([](const String& message) {
Serial.print("Received: ");
Serial.println(message);
String response = "Echo: ";
response += message;
bluetoothChat.send(response);
if (message.equalsIgnoreCase("ping")) {
bluetoothChat.send("pong!");
} else if (message.equalsIgnoreCase("status")) {
bluetoothChat.send("ESP32 is running normally");
} else if (message.equalsIgnoreCase("time")) {
String timeMsg = "Uptime: ";
timeMsg += String(millis() / 1000);
timeMsg += " seconds";
bluetoothChat.send(timeMsg);
} else if (message.equalsIgnoreCase("heap")) {
String heapMsg = "Free heap: ";
heapMsg += String(ESP.getFreeHeap());
heapMsg += " bytes";
bluetoothChat.send(heapMsg);
}
});
Serial.println("Waiting for Bluetooth connection...");
Serial.println("Type 'ping', 'status', 'time', or 'heap' in the app to test commands");
}
void loop() {
bluetoothServer.loop();
if (bluetooth.isConnected() && millis() - lastMessageTime >= MESSAGE_INTERVAL) {
lastMessageTime = millis();
messageCount++;
String statusMsg = "Status update #";
statusMsg += String(messageCount);
statusMsg += " - All systems operational";
bluetoothChat.send(statusMsg);
Serial.print("Sent: ");
Serial.println(statusMsg);
}
if (Serial.available()) {
String serialMsg = Serial.readStringUntil('\n');
serialMsg.trim();
if (serialMsg.length() > 0 && bluetooth.isConnected()) {
bluetoothChat.send(serialMsg);
Serial.print("Sent from Serial: ");
Serial.println(serialMsg);
}
}
delay(10);
}
DIYables Bluetooth - ESP32 Chat Example
Waiting for Bluetooth connection...
Type 'ping', 'status', 'time', or 'heap' in the app to test commands
#include <DIYables_BluetoothServer.h>
#include <DIYables_BluetoothChat.h>
#include <platforms/DIYables_Esp32BLE.h>
const char* DEVICE_NAME = "ESP32BLE_Chat";
const char* SERVICE_UUID = "19B10000-E8F2-537E-4F6C-D104768A1214";
const char* TX_UUID = "19B10001-E8F2-537E-4F6C-D104768A1214";
const char* RX_UUID = "19B10002-E8F2-537E-4F6C-D104768A1214";
DIYables_Esp32BLE bluetooth(DEVICE_NAME, SERVICE_UUID, TX_UUID, RX_UUID);
DIYables_BluetoothServer bluetoothServer(bluetooth);
DIYables_BluetoothChat bluetoothChat;
unsigned long lastMessageTime = 0;
const unsigned long MESSAGE_INTERVAL = 10000;
int messageCount = 0;
void setup() {
Serial.begin(115200);
delay(1000);
Serial.println("DIYables Bluetooth - ESP32 BLE Chat Example");
bluetoothServer.begin();
bluetoothServer.addApp(&bluetoothChat);
bluetoothServer.setOnConnected([]() {
Serial.println("Bluetooth connected!");
bluetoothChat.send("Hello! ESP32 BLE is ready to chat.");
});
bluetoothServer.setOnDisconnected([]() {
Serial.println("Bluetooth disconnected!");
messageCount = 0;
});
bluetoothChat.onChatMessage([](const String& message) {
Serial.print("Received: ");
Serial.println(message);
String response = "Echo: ";
response += message;
bluetoothChat.send(response);
if (message.equalsIgnoreCase("ping")) {
bluetoothChat.send("pong!");
} else if (message.equalsIgnoreCase("status")) {
bluetoothChat.send("ESP32 BLE is running normally");
} else if (message.equalsIgnoreCase("time")) {
String timeMsg = "Uptime: ";
timeMsg += String(millis() / 1000);
timeMsg += " seconds";
bluetoothChat.send(timeMsg);
} else if (message.equalsIgnoreCase("heap")) {
String heapMsg = "Free heap: ";
heapMsg += String(ESP.getFreeHeap());
heapMsg += " bytes";
bluetoothChat.send(heapMsg);
}
});
Serial.println("Waiting for Bluetooth connection...");
Serial.println("Type 'ping', 'status', 'time', or 'heap' in the app to test commands");
}
void loop() {
bluetoothServer.loop();
if (bluetooth.isConnected() && millis() - lastMessageTime >= MESSAGE_INTERVAL) {
lastMessageTime = millis();
messageCount++;
String statusMsg = "Status update #";
statusMsg += String(messageCount);
statusMsg += " - All systems operational";
bluetoothChat.send(statusMsg);
Serial.print("Sent: ");
Serial.println(statusMsg);
}
if (Serial.available()) {
String serialMsg = Serial.readStringUntil('\n');
serialMsg.trim();
if (serialMsg.length() > 0 && bluetooth.isConnected()) {
bluetoothChat.send(serialMsg);
Serial.print("Sent from Serial: ");
Serial.println(serialMsg);
}
}
delay(10);
}
DIYables Bluetooth - ESP32 BLE Chat Example
Waiting for Bluetooth connection...
Type 'ping', 'status', 'time', or 'heap' in the app to test commands
Cài đặt DIYables Bluetooth App trên smartphone của bạn:
Android |
iOS
Nếu bạn đang sử dụng code ESP32 Classic Bluetooth, bạn cần ghép nối ESP32 với điện thoại Android trước khi mở app:
Vào Settings > Bluetooth của điện thoại
Đảm bảo Bluetooth được bật
Điện thoại sẽ quét các thiết bị có sẵn
Tìm và nhấn "ESP32_Chat" trong danh sách thiết bị có sẵn
Xác nhận yêu cầu ghép nối (không cần PIN)
Đợi cho đến khi hiển thị "Paired" dưới tên thiết bị
Nếu bạn đang sử dụng code ESP32 BLE, không cần ghép nối. Chỉ cần chuyển sang bước tiếp theo.
Mở DIYables Bluetooth App
Khi mở app lần đầu tiên, nó sẽ yêu cầu quyền. Vui lòng cấp các quyền sau:
Quyền Nearby Devices (Android 12+) / quyền Bluetooth (iOS) - cần thiết để quét và kết nối với các thiết bị Bluetooth
Quyền Location (chỉ Android 11 và thấp hơn) - được yêu cầu bởi các phiên bản Android cũ để quét thiết bị BLE
Đảm bảo Bluetooth được bật trên điện thoại
Trên màn hình chính, nhấn nút Connect. App sẽ quét cả thiết bị BLE và Classic Bluetooth.

Tìm và nhấn thiết bị của bạn trong kết quả quét để kết nối:
Sau khi kết nối, app tự động quay về màn hình chính. Chọn ứng dụng Chat từ menu app.
Lưu ý: Bạn có thể nhấn biểu tượng cài đặt trên màn hình chính để ẩn/hiện các app trên màn hình chính. Để biết thêm chi tiết, xem DIYables Bluetooth App User Manual.
Bây giờ hãy nhìn lại Serial Monitor trên Arduino IDE. Bạn sẽ thấy:
Bluetooth connected!
Received: Hello
Received: ping
Received: status
Sent: Status update #1 - All systems operational
Nhập tin nhắn trong app và quan sát phản hồi thời gian thực trong Serial Monitor
Thử các lệnh tích hợp: ping, status, time, heap
Bạn cũng có thể nhập tin nhắn trong Serial Monitor và chúng sẽ được gửi đến app
Sử dụng callback onChatMessage() để xử lý tin nhắn nhận được từ app. Bạn có thể định nghĩa bất kỳ từ lệnh tùy chỉnh nào phù hợp với dự án của mình — ESP32 sẽ phản ứng tương ứng:
bluetoothChat.onChatMessage([](const String& message) {
Serial.print("Received: ");
Serial.println(message);
if (message == "LED_ON") {
digitalWrite(2, HIGH);
bluetoothChat.send("LED turned ON");
} else if (message == "LED_OFF") {
digitalWrite(2, LOW);
bluetoothChat.send("LED turned OFF");
} else {
bluetoothChat.send("Unknown command: " + message);
}
});
Bạn có thể thêm nhiều lệnh tùy chỉnh bằng cách thêm nhiều khối else if. Ví dụ, thêm RELAY_ON / RELAY_OFF để điều khiển relay, hoặc READ để kích hoạt đọc cảm biến — bất kỳ từ nào bạn nhập trong app đều trở thành lệnh.
Bạn có thể gửi tin nhắn văn bản từ ESP32 đến app bất kỳ lúc nào:
bluetoothChat.send("Hello from ESP32!");
float temperature = 25.3;
bluetoothChat.send("Temperature: " + String(temperature) + "°C");
bluetoothChat.send("System ready");
Bạn có thể phát hiện khi app kết nối hoặc ngắt kết nối khỏi ESP32:
bluetoothServer.setOnConnected([]() {
Serial.println("Bluetooth connected!");
bluetoothChat.send("Welcome! ESP32 is ready to chat.");
});
bluetoothServer.setOnDisconnected([]() {
Serial.println("Bluetooth disconnected!");
});
if (bluetoothServer.isConnected()) {
bluetoothChat.send("Still connected!");
}
Chuyển tiếp tin nhắn được nhập trong Arduino IDE Serial Monitor đến app Bluetooth:
void loop() {
bluetoothServer.loop();
if (Serial.available()) {
String serialMsg = Serial.readStringUntil('\n');
serialMsg.trim();
if (serialMsg.length() > 0 && bluetooth.isConnected()) {
bluetoothChat.send(serialMsg);
Serial.print("Sent from Serial: ");
Serial.println(serialMsg);
}
}
delay(10);
}
Giao diện chat trong DIYables Bluetooth App cung cấp:
Danh Sách Tin Nhắn: Danh sách có thể cuộn hiển thị tin nhắn đã gửi và nhận
Ô Nhập Văn Bản: Nhập tin nhắn của bạn ở dưới cùng
Nút Send: Nhấn để gửi tin nhắn đã nhập
Auto-scroll: Tự động cuộn đến tin nhắn mới nhất
Code ví dụ bao gồm các lệnh tích sẵn sau:
ping → Phản hồi với "pong!"
status → Báo cáo trạng thái chạy của ESP32
time → Hiển thị thời gian hoạt động bằng giây
heap → Hiển thị bộ nhớ heap còn trống bằng byte
void setup() {
bluetoothChat.onChatMessage([](const String& message) {
Serial.print("Received: ");
Serial.println(message);
bluetoothChat.send("Echo: " + message);
});
}
const int LED_PIN = 2;
const int RED_PIN = 16;
const int GREEN_PIN = 17;
const int BLUE_PIN = 18;
void setup() {
pinMode(LED_PIN, OUTPUT);
pinMode(RED_PIN, OUTPUT);
pinMode(GREEN_PIN, OUTPUT);
pinMode(BLUE_PIN, OUTPUT);
bluetoothChat.onChatMessage([](const String& message) {
String cmd = message;
cmd.toUpperCase();
if (cmd == "LED ON") {
digitalWrite(LED_PIN, HIGH);
bluetoothChat.send("LED is now ON");
} else if (cmd == "LED OFF") {
digitalWrite(LED_PIN, LOW);
bluetoothChat.send("LED is now OFF");
} else if (cmd == "RED") {
digitalWrite(RED_PIN, HIGH);
digitalWrite(GREEN_PIN, LOW);
digitalWrite(BLUE_PIN, LOW);
bluetoothChat.send("Red LED activated");
} else if (cmd == "GREEN") {
digitalWrite(RED_PIN, LOW);
digitalWrite(GREEN_PIN, HIGH);
digitalWrite(BLUE_PIN, LOW);
bluetoothChat.send("Green LED activated");
} else if (cmd == "BLUE") {
digitalWrite(RED_PIN, LOW);
digitalWrite(GREEN_PIN, LOW);
digitalWrite(BLUE_PIN, HIGH);
bluetoothChat.send("Blue LED activated");
} else if (cmd == "OFF") {
digitalWrite(RED_PIN, LOW);
digitalWrite(GREEN_PIN, LOW);
digitalWrite(BLUE_PIN, LOW);
bluetoothChat.send("All LEDs off");
} else if (cmd == "HELP") {
bluetoothChat.send("Commands: LED ON, LED OFF, RED, GREEN, BLUE, OFF, HELP");
} else {
bluetoothChat.send("Unknown command. Type HELP for list.");
}
});
}
#include <DHT.h>
DHT dht(4, DHT22);
void setup() {
dht.begin();
bluetoothChat.onChatMessage([](const String& message) {
String cmd = message;
cmd.toUpperCase();
if (cmd == "TEMP") {
float temp = dht.readTemperature();
if (!isnan(temp)) {
bluetoothChat.send("Temperature: " + String(temp, 1) + " °C");
} else {
bluetoothChat.send("Error reading temperature sensor");
}
} else if (cmd == "HUMIDITY") {
float hum = dht.readHumidity();
if (!isnan(hum)) {
bluetoothChat.send("Humidity: " + String(hum, 1) + " %");
} else {
bluetoothChat.send("Error reading humidity sensor");
}
} else if (cmd == "ALL") {
float temp = dht.readTemperature();
float hum = dht.readHumidity();
bluetoothChat.send("Temp: " + String(temp, 1) + "°C | Humidity: " + String(hum, 1) + "%");
} else if (cmd == "HELP") {
bluetoothChat.send("Commands: TEMP, HUMIDITY, ALL, HELP");
}
});
}
const int RELAY_1 = 16;
const int RELAY_2 = 17;
bool relay1State = false;
bool relay2State = false;
void setup() {
pinMode(RELAY_1, OUTPUT);
pinMode(RELAY_2, OUTPUT);
digitalWrite(RELAY_1, LOW);
digitalWrite(RELAY_2, LOW);
bluetoothChat.onChatMessage([](const String& message) {
String cmd = message;
cmd.toUpperCase();
if (cmd == "R1 ON") {
digitalWrite(RELAY_1, HIGH);
relay1State = true;
bluetoothChat.send("Relay 1: ON");
} else if (cmd == "R1 OFF") {
digitalWrite(RELAY_1, LOW);
relay1State = false;
bluetoothChat.send("Relay 1: OFF");
} else if (cmd == "R2 ON") {
digitalWrite(RELAY_2, HIGH);
relay2State = true;
bluetoothChat.send("Relay 2: ON");
} else if (cmd == "R2 OFF") {
digitalWrite(RELAY_2, LOW);
relay2State = false;
bluetoothChat.send("Relay 2: OFF");
} else if (cmd == "STATUS") {
bluetoothChat.send("Relay 1: " + String(relay1State ? "ON" : "OFF"));
bluetoothChat.send("Relay 2: " + String(relay2State ? "ON" : "OFF"));
} else if (cmd == "HELP") {
bluetoothChat.send("Commands: R1 ON, R1 OFF, R2 ON, R2 OFF, STATUS, HELP");
}
});
}
unsigned long lastMessageTime = 0;
const unsigned long MESSAGE_INTERVAL = 10000;
void loop() {
bluetoothServer.loop();
if (bluetooth.isConnected() && millis() - lastMessageTime >= MESSAGE_INTERVAL) {
lastMessageTime = millis();
messageCount++;
String statusMsg = "Status update #" + String(messageCount);
statusMsg += " - Uptime: " + String(millis() / 1000) + "s";
statusMsg += " - Heap: " + String(ESP.getFreeHeap()) + " bytes";
bluetoothChat.send(statusMsg);
}
delay(10);
}
bluetoothChat.onChatMessage([](const String& message) {
unsigned long timestamp = millis() / 1000;
Serial.print("[");
Serial.print(timestamp);
Serial.print("s] Received: ");
Serial.println(message);
String response = processCommand(message);
bluetoothChat.send(response);
Serial.print("[");
Serial.print(millis() / 1000);
Serial.print("s] Sent: ");
Serial.println(response);
});
String processCommand(const String& cmd) {
if (cmd.equalsIgnoreCase("ping")) return "pong!";
if (cmd.equalsIgnoreCase("status")) return "All systems operational";
if (cmd.equalsIgnoreCase("time")) return "Uptime: " + String(millis() / 1000) + "s";
return "Echo: " + cmd;
}
bluetoothChat.onChatMessage([](const String& message) {
int spaceIndex = message.indexOf(' ');
String command, argument;
if (spaceIndex > 0) {
command = message.substring(0, spaceIndex);
argument = message.substring(spaceIndex + 1);
} else {
command = message;
argument = "";
}
command.toUpperCase();
if (command == "PWM") {
int value = argument.toInt();
value = constrain(value, 0, 255);
analogWrite(16, value);
bluetoothChat.send("PWM set to " + String(value));
} else if (command == "DELAY") {
int ms = argument.toInt();
bluetoothChat.send("Waiting " + String(ms) + "ms...");
delay(ms);
bluetoothChat.send("Done!");
} else if (command == "PIN") {
int secondSpace = argument.indexOf(' ');
if (secondSpace > 0) {
int pin = argument.substring(0, secondSpace).toInt();
String state = argument.substring(secondSpace + 1);
state.toUpperCase();
digitalWrite(pin, state == "HIGH" ? HIGH : LOW);
bluetoothChat.send("Pin " + String(pin) + " set " + state);
}
}
});
#include <ESP32Servo.h>
Servo myServo;
const int SERVO_PIN = 13;
void setup() {
myServo.attach(SERVO_PIN);
bluetoothChat.onChatMessage([](const String& message) {
String cmd = message;
cmd.toUpperCase();
int angle = -1;
if (cmd.startsWith("SERVO ")) {
angle = cmd.substring(6).toInt();
} else {
angle = cmd.toInt();
if (angle == 0 && cmd != "0") angle = -1;
}
if (angle >= 0 && angle <= 180) {
myServo.write(angle);
bluetoothChat.send("Servo moved to " + String(angle) + "°");
} else if (cmd == "SWEEP") {
bluetoothChat.send("Sweeping servo...");
for (int a = 0; a <= 180; a += 5) {
myServo.write(a);
delay(30);
}
for (int a = 180; a >= 0; a -= 5) {
myServo.write(a);
delay(30);
}
bluetoothChat.send("Sweep complete");
}
});
}
const int MOTOR_PWM = 16;
const int MOTOR_DIR1 = 18;
const int MOTOR_DIR2 = 19;
void setup() {
pinMode(MOTOR_PWM, OUTPUT);
pinMode(MOTOR_DIR1, OUTPUT);
pinMode(MOTOR_DIR2, OUTPUT);
bluetoothChat.onChatMessage([](const String& message) {
String cmd = message;
cmd.toUpperCase();
if (cmd == "FORWARD") {
digitalWrite(MOTOR_DIR1, HIGH);
digitalWrite(MOTOR_DIR2, LOW);
analogWrite(MOTOR_PWM, 200);
bluetoothChat.send("Motor running forward");
} else if (cmd == "REVERSE") {
digitalWrite(MOTOR_DIR1, LOW);
digitalWrite(MOTOR_DIR2, HIGH);
analogWrite(MOTOR_PWM, 200);
bluetoothChat.send("Motor running reverse");
} else if (cmd == "STOP") {
analogWrite(MOTOR_PWM, 0);
bluetoothChat.send("Motor stopped");
} else if (cmd.startsWith("SPEED ")) {
int speed = cmd.substring(6).toInt();
speed = constrain(speed, 0, 255);
analogWrite(MOTOR_PWM, speed);
bluetoothChat.send("Motor speed: " + String(speed));
}
});
}
| Tính Năng | BLE (Esp32BLE_Chat) | Classic Bluetooth (Esp32Bluetooth_Chat) |
| Hỗ Trợ iOS | ✓ Có | ✗ Không |
| Hỗ Trợ Android | ✓ Có | ✓ Có |
| Tiêu Thụ Pin | Thấp | Cao hơn |
| Phạm Vi | ~30-100m | ~10-100m |
| Tốc Độ Dữ Liệu | Thấp hơn | Cao hơn |
| Yêu Cầu Ghép Nối | Không (tự động kết nối) | Có (ghép nối thủ công) |
| Tốt Nhất Cho | Tiết kiệm pin, đa nền tảng | Thông lượng cao, chỉ Android |