ESP32 - DIYables Bluetooth App Chat
The Bluetooth Chat example enables two-way text messaging between your ESP32 and a smartphone through the DIYables Bluetooth STEM app. Designed for ESP32 boards with support for both BLE (Bluetooth Low Energy) and Classic Bluetooth connections. Send and receive text messages in real time, implement custom command handling, and bridge Serial Monitor to Bluetooth — perfect for debugging, remote control, and interactive projects.
This example supports two Bluetooth modes:
Two-Way Messaging: Send and receive text messages in real time
Echo Mode: Automatically echoes received messages back to the app
Command Handling: Process custom text commands (ping, status, time, heap)
Periodic Messages: Send automatic status updates at configurable intervals
Serial-to-Bluetooth Bridge: Type messages in Serial Monitor and send them to the app
BLE & Classic Bluetooth: Choose the Bluetooth mode that suits your project
Cross-Platform: BLE mode works on both Android and iOS; Classic Bluetooth works on Android
Low Power Option: BLE mode consumes less power than Classic Bluetooth
Or you can buy the following kits:
Disclosure: Some of the links in this section are Amazon affiliate links, meaning we may earn a commission at no additional cost to you if you make a purchase through them. Additionally, some links direct you to products from our own brand,
DIYables .
Follow these instructions step by step:
Connect the ESP32 board to your computer using a USB cable.
Launch the Arduino IDE on your computer.
Select the appropriate ESP32 board and COM port.
Navigate to the Libraries icon on the left bar of the Arduino IDE.
Search "DIYables Bluetooth", then find the DIYables Bluetooth library by DIYables
Click Install button to install the library.
Choose one of the two Bluetooth modes below depending on your needs:
Note: Classic Bluetooth is NOT supported on iOS. If you need iOS support, use the BLE code below.
#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
Install the DIYables Bluetooth App on your smartphone:
Android |
iOS
If you are using the ESP32 Classic Bluetooth code, you need to pair the ESP32 with your Android phone before opening the app:
Go to your phone's Settings > Bluetooth
Make sure Bluetooth is turned on
Your phone will scan for available devices
Find and tap "ESP32_Chat" in the list of available devices
Confirm the pairing request (no PIN required)
Wait until it shows "Paired" under the device name
If you are using the ESP32 BLE code, no pairing is needed. Just proceed to the next step.
Open the DIYables Bluetooth App
When opening the app for the first time, it will ask for permissions. Please grant the following:
Nearby Devices permission (Android 12+) / Bluetooth permission (iOS) - required to scan and connect to Bluetooth devices
Location permission (Android 11 and below only) - required by older Android versions to scan for BLE devices
Make sure Bluetooth is turned on on your phone
On the home screen, tap the Connect button. The app will scan for both BLE and Classic Bluetooth devices.

Find and tap your device in the scan results to connect:
Once connected, the app automatically goes back to the home screen. Select the Chat app from the app menu.
Note: You can tap the settings icon on the home screen to hide/show apps on the home screen. For more details, see the DIYables Bluetooth App User Manual.
Now look back at the Serial Monitor on Arduino IDE. You will see:
Bluetooth connected!
Received: Hello
Received: ping
Received: status
Sent: Status update #1 - All systems operational
Type messages in the app and watch the real-time feedback in the Serial Monitor
Try built-in commands: ping, status, time, heap
You can also type messages in the Serial Monitor and they will be sent to the app
Use the onChatMessage() callback to handle messages received from the app. You can define any custom command words that make sense for your project — the ESP32 will react accordingly:
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);
}
});
You can add as many custom commands as you need by adding more else if blocks. For example, add RELAY_ON / RELAY_OFF to control a relay, or READ to trigger a sensor reading — any word you type in the app becomes a command.
You can send text messages from ESP32 to the app at any time:
bluetoothChat.send("Hello from ESP32!");
float temperature = 25.3;
bluetoothChat.send("Temperature: " + String(temperature) + "°C");
bluetoothChat.send("System ready");
You can detect when the app connects or disconnects from the 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!");
}
Forward messages typed in Arduino IDE Serial Monitor to the Bluetooth app:
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);
}
The chat interface in the DIYables Bluetooth App provides:
Message List: Scrollable list showing sent and received messages
Text Input: Type your message at the bottom
Send Button: Tap to send the typed message
Auto-scroll: Automatically scrolls to the latest message
The example code includes these built-in commands:
ping ? Responds with "pong!"
status ? Reports ESP32 running status
time ? Shows uptime in seconds
heap ? Shows free heap memory in bytes
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));
}
});
}
| Feature | BLE (Esp32BLE_Chat) | Classic Bluetooth (Esp32Bluetooth_Chat) |
| iOS Support | ? Yes | ? No |
| Android Support | ? Yes | ? Yes |
| Power Consumption | Low | Higher |
| Range | ~30-100m | ~10-100m |
| Data Rate | Lower | Higher |
| Pairing Required | No (auto-connect) | Yes (manual pairing) |
| Best For | Battery-powered, cross-platform | High throughput, Android-only |
1. Cannot find the device in the app
Make sure the ESP32 is powered on and the sketch is uploaded
For BLE: Ensure your phone's Bluetooth and Location are enabled
For Classic Bluetooth: Pair the device first in phone's Bluetooth settings
Check that the correct partition scheme is selected (Huge APP)
2. Messages not appearing in the app
Check Bluetooth connection status in the app
Verify connection in Serial Monitor
Ensure messages are being sent with bluetoothChat.send()
Try disconnecting and reconnecting
3. Echo messages not working
Verify the onChatMessage() callback is set up correctly
Check Serial Monitor for received messages
Ensure the chat app screen is selected in the mobile app
4. Connection drops frequently
Move closer to the ESP32 (reduce distance)
For BLE: Check for interference from other BLE devices
For Classic Bluetooth: Ensure stable power supply to ESP32
Check Serial Monitor for disconnect/reconnect messages
5. Serial-to-Bluetooth bridge not working
Ensure Serial Monitor baud rate is 115200
Set line ending to "Newline" in Serial Monitor
Check that bluetooth.isConnected() returns true
Verify the Serial.available() code is in the loop()
6. Sketch too large / not enough space
In Arduino IDE, go to Tools > Partition Scheme and select "Huge APP (3MB No OTA/1MB SPIFFS)" or "No OTA (Large APP)"
The default partition scheme only provides ~1.2MB for app code, which is not enough for Bluetooth libraries
This setting gives ~3MB by sacrificing the OTA (over-the-air update) partition
Add comprehensive debugging:
void debugChatMessage(const String& message) {
Serial.println("=== Chat Debug ===");
Serial.println("Message: " + message);
Serial.println("Length: " + String(message.length()));
Serial.println("Free Heap: " + String(ESP.getFreeHeap()));
Serial.println("==================");
}
Voice-command-style text control for robots
Home automation command center
Wireless relay control with text feedback
Remote servo positioning via chat
Remote sensor data querying
System health check via chat commands
Data logging with Bluetooth feedback
Remote debugging and diagnostics
Quiz or trivia game via Bluetooth chat
Interactive tutorials with step-by-step instructions
Chatbot for ESP32 project information
Remote configuration tool
Learn Bluetooth communication basics
Practice string parsing and command handling
Understand client-server messaging patterns
Build custom communication protocols
Use Chat for commands and Monitor for continuous output:
bluetoothChat.onChatMessage([](const String& message) {
if (message.equalsIgnoreCase("start")) {
monitoring = true;
bluetoothChat.send("Monitoring started");
} else if (message.equalsIgnoreCase("stop")) {
monitoring = false;
bluetoothChat.send("Monitoring stopped");
}
});
if (monitoring) {
bluetoothMonitor.send("Sensor: " + String(analogRead(34)));
}
Use Chat to set slider presets and Slider for fine control:
bluetoothChat.onChatMessage([](const String& message) {
if (message.equalsIgnoreCase("preset1")) {
currentSlider1 = 25;
currentSlider2 = 75;
bluetoothSlider.send(currentSlider1, currentSlider2);
bluetoothChat.send("Applied Preset 1: 25%, 75%");
} else if (message.equalsIgnoreCase("preset2")) {
currentSlider1 = 50;
currentSlider2 = 50;
bluetoothSlider.send(currentSlider1, currentSlider2);
bluetoothChat.send("Applied Preset 2: 50%, 50%");
}
});
After mastering the Bluetooth Chat example, try:
Bluetooth Monitor - For one-way data streaming to the app
Bluetooth Slider - For analog-like value control
Bluetooth Digital Pins - For discrete on/off control
Multiple Bluetooth Apps - Combining chat with other controls