ESP32 - DIYables Bluetooth App Monitor
The Bluetooth Monitor example provides a wireless serial monitor interface accessible through the DIYables Bluetooth STEM app. Designed for ESP32 boards with support for both BLE (Bluetooth Low Energy) and Classic Bluetooth connections. Stream real-time status messages to the app, receive and process text commands, display system information, and debug your projects wirelessly — perfect for untethered monitoring, remote debugging, and system status displays.
This example supports two Bluetooth modes:
Real-Time Streaming: Send continuous status messages to the app display
Command Processing: Receive and handle text commands from the app
System Monitoring: Display uptime, free heap, CPU info, and more
LED Control: Built-in LED ON/OFF commands for quick testing
Periodic Updates: Automatic heartbeat and status messages at configurable intervals
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_BluetoothMonitor.h>
#include <platforms/DIYables_Esp32Bluetooth.h>
DIYables_Esp32Bluetooth bluetooth("ESP32_Monitor");
DIYables_BluetoothServer bluetoothServer(bluetooth);
DIYables_BluetoothMonitor bluetoothMonitor;
unsigned long lastUpdate = 0;
const unsigned long UPDATE_INTERVAL = 3000;
int messageCount = 0;
bool ledState = false;
const int LED_PIN = 2;
void setup() {
Serial.begin(115200);
delay(1000);
Serial.println("DIYables Bluetooth - ESP32 Monitor Example");
pinMode(LED_PIN, OUTPUT);
digitalWrite(LED_PIN, LOW);
bluetoothServer.begin();
bluetoothServer.addApp(&bluetoothMonitor);
bluetoothServer.setOnConnected([]() {
Serial.println("Bluetooth connected!");
bluetoothMonitor.send("=== ESP32 Monitor Connected ===");
bluetoothMonitor.send("System Ready");
bluetoothMonitor.send("Type HELP for available commands");
bluetoothMonitor.send("");
});
bluetoothServer.setOnDisconnected([]() {
Serial.println("Bluetooth disconnected!");
});
bluetoothMonitor.onMonitorMessage([](const String& message) {
Serial.print("Received command: ");
Serial.println(message);
handleCommand(message);
});
Serial.println("Waiting for Bluetooth connection...");
}
void handleCommand(const String& cmd) {
if (cmd == "HELP") {
bluetoothMonitor.send("Available Commands:");
bluetoothMonitor.send(" LED_ON - Turn LED on");
bluetoothMonitor.send(" LED_OFF - Turn LED off");
bluetoothMonitor.send(" STATUS - Show system status");
bluetoothMonitor.send(" HEAP - Show memory info");
bluetoothMonitor.send(" CLEAR - Clear monitor (if supported)");
bluetoothMonitor.send(" HELP - Show this help");
}
else if (cmd == "LED_ON") {
digitalWrite(LED_PIN, HIGH);
ledState = true;
bluetoothMonitor.send("✓ LED turned ON");
}
else if (cmd == "LED_OFF") {
digitalWrite(LED_PIN, LOW);
ledState = false;
bluetoothMonitor.send("✓ LED turned OFF");
}
else if (cmd == "STATUS") {
showStatus();
}
else if (cmd == "HEAP") {
bluetoothMonitor.send("=== Memory Info ===");
bluetoothMonitor.send("Free Heap: " + String(ESP.getFreeHeap()) + " bytes");
bluetoothMonitor.send("Min Free Heap: " + String(ESP.getMinFreeHeap()) + " bytes");
bluetoothMonitor.send("Heap Size: " + String(ESP.getHeapSize()) + " bytes");
bluetoothMonitor.send("===================");
}
else if (cmd == "CLEAR") {
bluetoothMonitor.send("");
}
else {
bluetoothMonitor.send("✗ Unknown command: " + cmd);
bluetoothMonitor.send("Type HELP for available commands");
}
}
void showStatus() {
bluetoothMonitor.send("=== System Status ===");
bluetoothMonitor.send("LED State: " + String(ledState ? "ON" : "OFF"));
unsigned long uptime = millis() / 1000;
bluetoothMonitor.send("Uptime: " + String(uptime / 3600) + "h " + String((uptime % 3600) / 60) + "m " + String(uptime % 60) + "s");
bluetoothMonitor.send("Free Heap: " + String(ESP.getFreeHeap()) + " bytes");
bluetoothMonitor.send("CPU Freq: " + String(ESP.getCpuFreqMHz()) + " MHz");
bluetoothMonitor.send("Chip Model: " + String(ESP.getChipModel()));
bluetoothMonitor.send("Messages Sent: " + String(messageCount));
bluetoothMonitor.send("====================");
}
void sendPeriodicUpdate() {
messageCount++;
if (messageCount % 3 == 0) {
bluetoothMonitor.send("[INFO] Heartbeat #" + String(messageCount));
}
else if (messageCount % 5 == 0) {
bluetoothMonitor.send("[HEAP] Free: " + String(ESP.getFreeHeap()) + " bytes");
}
else {
bluetoothMonitor.send("[TIME] Uptime: " + String(millis() / 1000) + "s");
}
Serial.print("Sent update #");
Serial.println(messageCount);
}
void loop() {
bluetoothServer.loop();
if (bluetooth.isConnected() && millis() - lastUpdate >= UPDATE_INTERVAL) {
lastUpdate = millis();
sendPeriodicUpdate();
}
delay(10);
}
DIYables Bluetooth - ESP32 Monitor Example
Waiting for Bluetooth connection...
#include <DIYables_BluetoothServer.h>
#include <DIYables_BluetoothMonitor.h>
#include <platforms/DIYables_Esp32BLE.h>
const char* DEVICE_NAME = "ESP32BLE_Monitor";
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_BluetoothMonitor bluetoothMonitor;
unsigned long lastUpdate = 0;
const unsigned long UPDATE_INTERVAL = 3000;
int messageCount = 0;
bool ledState = false;
void setup() {
Serial.begin(115200);
delay(1000);
Serial.println("DIYables Bluetooth - ESP32 BLE Monitor Example");
pinMode(2, OUTPUT);
digitalWrite(2, LOW);
bluetoothServer.begin();
bluetoothServer.addApp(&bluetoothMonitor);
bluetoothServer.setOnConnected([]() {
Serial.println("Bluetooth connected!");
bluetoothMonitor.send("=== ESP32 BLE Monitor Connected ===");
bluetoothMonitor.send("System Ready");
bluetoothMonitor.send("Type HELP for available commands");
bluetoothMonitor.send("");
});
bluetoothServer.setOnDisconnected([]() {
Serial.println("Bluetooth disconnected!");
});
bluetoothMonitor.onMonitorMessage([](const String& message) {
Serial.print("Received command: ");
Serial.println(message);
handleCommand(message);
});
Serial.println("Waiting for Bluetooth connection...");
}
void handleCommand(const String& cmd) {
if (cmd == "HELP") {
bluetoothMonitor.send("Available Commands:");
bluetoothMonitor.send(" LED_ON - Turn LED on");
bluetoothMonitor.send(" LED_OFF - Turn LED off");
bluetoothMonitor.send(" STATUS - Show system status");
bluetoothMonitor.send(" HEAP - Show free heap memory");
bluetoothMonitor.send(" HELP - Show this help");
}
else if (cmd == "LED_ON") {
digitalWrite(2, HIGH);
ledState = true;
bluetoothMonitor.send("LED turned ON");
}
else if (cmd == "LED_OFF") {
digitalWrite(2, LOW);
ledState = false;
bluetoothMonitor.send("LED turned OFF");
}
else if (cmd == "STATUS") {
showStatus();
}
else if (cmd == "HEAP") {
bluetoothMonitor.send("Free heap: " + String(ESP.getFreeHeap()) + " bytes");
}
else {
bluetoothMonitor.send("Unknown command: " + cmd);
bluetoothMonitor.send("Type HELP for available commands");
}
}
void showStatus() {
bluetoothMonitor.send("=== System Status ===");
bluetoothMonitor.send("LED State: " + String(ledState ? "ON" : "OFF"));
unsigned long uptime = millis() / 1000;
bluetoothMonitor.send("Uptime: " + String(uptime / 3600) + "h " + String((uptime % 3600) / 60) + "m " + String(uptime % 60) + "s");
bluetoothMonitor.send("Free Heap: " + String(ESP.getFreeHeap()) + " bytes");
bluetoothMonitor.send("Messages Sent: " + String(messageCount));
bluetoothMonitor.send("====================");
}
void sendPeriodicUpdate() {
messageCount++;
if (messageCount % 3 == 0) {
bluetoothMonitor.send("[INFO] Heartbeat #" + String(messageCount));
}
else if (messageCount % 5 == 0) {
bluetoothMonitor.send("[HEAP] Free: " + String(ESP.getFreeHeap()) + " bytes");
}
else {
bluetoothMonitor.send("[TIME] Uptime: " + String(millis() / 1000) + "s");
}
Serial.print("Sent update #");
Serial.println(messageCount);
}
void loop() {
bluetoothServer.loop();
if (bluetooth.isConnected() && millis() - lastUpdate >= UPDATE_INTERVAL) {
lastUpdate = millis();
sendPeriodicUpdate();
}
delay(10);
}
DIYables Bluetooth - ESP32 BLE Monitor Example
Waiting for Bluetooth connection...
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_Monitor" 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 Monitor 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.
You will see status messages streaming in the monitor display
Type LED_ON in the input field and tap Send — the built-in LED on the ESP32 will turn ON, and the monitor will display a confirmation message
Now look back at the Serial Monitor on Arduino IDE. You will see:
Bluetooth connected!
Sent update #1
Sent update #2
Received command: HELP
Received command: STATUS
Type commands in the app (HELP, STATUS, LED_ON, LED_OFF, HEAP) and watch the responses
Use the send() method to stream text messages to the monitor display:
bluetoothMonitor.send("System initialized");
bluetoothMonitor.send("Sensor reading: 25.3°C");
bluetoothMonitor.send("[INFO] Heartbeat #" + String(count));
bluetoothMonitor.send("[WARN] Temperature high: " + String(temp) + "°C");
bluetoothMonitor.send("[ERROR] Sensor disconnected");
bluetoothMonitor.send("Free Heap: " + String(ESP.getFreeHeap()) + " bytes");
bluetoothMonitor.send("Uptime: " + String(millis() / 1000) + " seconds");
Use the onMonitorMessage() callback to receive commands typed in the Monitor app and react to them:
bluetoothMonitor.onMonitorMessage([](const String& message) {
Serial.print("Received command: ");
Serial.println(message);
if (message == "HELP") {
bluetoothMonitor.send("Available Commands:");
bluetoothMonitor.send(" LED_ON - Turn LED on");
bluetoothMonitor.send(" LED_OFF - Turn LED off");
bluetoothMonitor.send(" STATUS - Show system status");
bluetoothMonitor.send(" HEAP - Show memory info");
} else if (message == "LED_ON") {
digitalWrite(LED_BUILTIN, HIGH);
bluetoothMonitor.send("LED turned ON");
} else if (message == "LED_OFF") {
digitalWrite(LED_BUILTIN, LOW);
bluetoothMonitor.send("LED turned OFF");
} else if (message == "STATUS") {
bluetoothMonitor.send("System is running normally");
bluetoothMonitor.send("Uptime: " + String(millis() / 1000) + "s");
} else if (message == "HEAP") {
bluetoothMonitor.send("Free Heap: " + String(ESP.getFreeHeap()) + " bytes");
} else {
bluetoothMonitor.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 detect when the app connects or disconnects from the ESP32:
bluetoothServer.setOnConnected([]() {
Serial.println("Bluetooth connected!");
bluetoothMonitor.send("=== ESP32 Monitor Connected ===");
bluetoothMonitor.send("System Ready");
bluetoothMonitor.send("Type HELP for available commands");
});
bluetoothServer.setOnDisconnected([]() {
Serial.println("Bluetooth disconnected!");
});
if (bluetoothServer.isConnected()) {
bluetoothMonitor.send("Status update");
}
The monitor interface in the DIYables Bluetooth App provides:
Message Display: Scrollable list of received messages with auto-scroll
Text Input: Type commands at the bottom
Send Button: Tap to send the typed command to ESP32
The example code includes these built-in commands:
HELP ? Shows all available commands
LED_ON ? Turns the built-in LED on
LED_OFF ? Turns the built-in LED off
STATUS ? Shows system status (LED state, uptime, heap, messages sent)
HEAP ? Shows detailed memory information
unsigned long lastUpdate = 0;
const unsigned long UPDATE_INTERVAL = 3000;
int messageCount = 0;
void loop() {
bluetoothServer.loop();
if (bluetooth.isConnected() && millis() - lastUpdate >= UPDATE_INTERVAL) {
lastUpdate = millis();
messageCount++;
if (messageCount % 3 == 0) {
bluetoothMonitor.send("[INFO] Heartbeat #" + String(messageCount));
} else if (messageCount % 5 == 0) {
bluetoothMonitor.send("[HEAP] Free: " + String(ESP.getFreeHeap()) + " bytes");
} else {
bluetoothMonitor.send("[TIME] Uptime: " + String(millis() / 1000) + "s");
}
}
delay(10);
}
#include <DHT.h>
DHT dht(4, DHT22);
unsigned long lastSensorRead = 0;
const unsigned long SENSOR_INTERVAL = 5000;
void setup() {
dht.begin();
bluetoothMonitor.onMonitorMessage([](const String& message) {
if (message == "READ") {
readAndSendSensorData();
} else if (message == "INTERVAL") {
bluetoothMonitor.send("Sensor interval: " + String(SENSOR_INTERVAL / 1000) + "s");
}
});
}
void readAndSendSensorData() {
float temp = dht.readTemperature();
float hum = dht.readHumidity();
if (!isnan(temp) && !isnan(hum)) {
bluetoothMonitor.send("Temp: " + String(temp, 1) + "°C | Humidity: " + String(hum, 1) + "%");
} else {
bluetoothMonitor.send("[ERROR] Failed to read sensor");
}
}
void loop() {
bluetoothServer.loop();
if (bluetooth.isConnected() && millis() - lastSensorRead >= SENSOR_INTERVAL) {
lastSensorRead = millis();
readAndSendSensorData();
}
delay(10);
}
void sendDashboard() {
bluetoothMonitor.send("========== DASHBOARD ==========");
bluetoothMonitor.send("Temp: " + String(readTemperature(), 1) + "°C");
bluetoothMonitor.send("Humidity: " + String(readHumidity(), 0) + "%");
bluetoothMonitor.send("Light: " + String(analogRead(34)));
bluetoothMonitor.send("Battery: " + String(readBatteryVoltage(), 2) + "V");
bluetoothMonitor.send("Heap: " + String(ESP.getFreeHeap()) + " bytes");
unsigned long uptime = millis() / 1000;
bluetoothMonitor.send("Uptime: " + String(uptime / 3600) + "h " +
String((uptime % 3600) / 60) + "m " +
String(uptime % 60) + "s");
bluetoothMonitor.send("===============================");
}
const int MONITOR_PINS[] = {16, 17, 18, 19, 21, 22};
const int NUM_PINS = 6;
int lastPinStates[6] = {0};
void setup() {
for (int i = 0; i < NUM_PINS; i++) {
pinMode(MONITOR_PINS[i], INPUT_PULLUP);
lastPinStates[i] = digitalRead(MONITOR_PINS[i]);
}
bluetoothMonitor.onMonitorMessage([](const String& message) {
if (message == "PINS") {
for (int i = 0; i < NUM_PINS; i++) {
int state = digitalRead(MONITOR_PINS[i]);
bluetoothMonitor.send("Pin " + String(MONITOR_PINS[i]) + ": " +
String(state ? "HIGH" : "LOW"));
}
}
});
}
void loop() {
bluetoothServer.loop();
for (int i = 0; i < NUM_PINS; i++) {
int state = digitalRead(MONITOR_PINS[i]);
if (state != lastPinStates[i]) {
lastPinStates[i] = state;
if (bluetooth.isConnected()) {
bluetoothMonitor.send("[PIN] GPIO " + String(MONITOR_PINS[i]) +
" changed to " + String(state ? "HIGH" : "LOW"));
}
}
}
delay(10);
}
enum LogLevel { LOG_INFO, LOG_WARN, LOG_ERROR, LOG_DEBUG };
void sendLog(LogLevel level, const String& message) {
String prefix;
switch (level) {
case LOG_INFO: prefix = "[INFO] "; break;
case LOG_WARN: prefix = "[WARN] "; break;
case LOG_ERROR: prefix = "[ERROR] "; break;
case LOG_DEBUG: prefix = "[DEBUG] "; break;
}
bluetoothMonitor.send(prefix + message);
}
sendLog(LOG_INFO, "System started");
sendLog(LOG_WARN, "Battery low: " + String(voltage, 2) + "V");
sendLog(LOG_ERROR, "Sensor read failed");
sendLog(LOG_DEBUG, "Loop time: " + String(loopTime) + "ms");
int sensorInterval = 3000;
bool verboseMode = false;
bluetoothMonitor.onMonitorMessage([](const String& message) {
String cmd = message;
cmd.toUpperCase();
if (cmd.startsWith("SET INTERVAL ")) {
sensorInterval = cmd.substring(13).toInt() * 1000;
bluetoothMonitor.send("Interval set to " + String(sensorInterval / 1000) + "s");
} else if (cmd == "VERBOSE ON") {
verboseMode = true;
bluetoothMonitor.send("Verbose mode enabled");
} else if (cmd == "VERBOSE OFF") {
verboseMode = false;
bluetoothMonitor.send("Verbose mode disabled");
} else if (cmd == "CONFIG") {
bluetoothMonitor.send("Interval: " + String(sensorInterval / 1000) + "s");
bluetoothMonitor.send("Verbose: " + String(verboseMode ? "ON" : "OFF"));
}
});
unsigned long lastLoopTime = 0;
unsigned long maxLoopTime = 0;
int warningCount = 0;
void loop() {
unsigned long loopStart = millis();
bluetoothServer.loop();
performTasks();
unsigned long loopTime = millis() - loopStart;
if (loopTime > maxLoopTime) {
maxLoopTime = loopTime;
}
if (loopTime > 100) {
warningCount++;
if (bluetooth.isConnected()) {
bluetoothMonitor.send("[WARN] Slow loop: " + String(loopTime) + "ms");
}
}
delay(10);
}
| Feature | BLE (Esp32BLE_Monitor) | Classic Bluetooth (Esp32Bluetooth_Monitor) |
| 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 monitor
Check Bluetooth connection status in the app
Verify connection in Serial Monitor
Ensure messages are being sent with bluetoothMonitor.send()
Try disconnecting and reconnecting
3. Commands not being received
Verify the onMonitorMessage() callback is set up correctly
Check Serial Monitor for received commands
Ensure the monitor 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
Reduce message frequency if sending too fast
5. Messages appear garbled or truncated
Keep individual messages short (under 200 characters)
Add small delays between rapid sends
Check free heap memory for buffer issues
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 debugMonitorStatus() {
Serial.println("=== Monitor Debug ===");
Serial.println("Connected: " + String(bluetooth.isConnected()));
Serial.println("Messages sent: " + String(messageCount));
Serial.println("Free Heap: " + String(ESP.getFreeHeap()));
Serial.println("=====================");
}
Wireless serial monitor replacement
Remote sensor data viewer
Real-time variable inspection
System performance monitoring
Weather station data display
Home automation system status
Industrial sensor monitoring
IoT device health dashboard
Learn Bluetooth communication
Practice serial-like messaging
Understand embedded system monitoring
Build custom debugging tools
Use Monitor for continuous output and Chat for interactive commands:
void loop() {
if (bluetooth.isConnected() && millis() - lastUpdate >= 3000) {
bluetoothMonitor.send("[SENSOR] " + String(readSensor()));
}
}
bluetoothChat.onChatMessage([](const String& message) {
if (message == "read") {
bluetoothChat.send("Value: " + String(readSensor()));
}
});
Use Monitor to display slider values in real time:
bluetoothSlider.onSliderValue([](int slider1, int slider2) {
bluetoothMonitor.send("Slider1=" + String(slider1) + "% Slider2=" + String(slider2) + "%");
bluetoothMonitor.send("PWM1=" + String(map(slider1, 0, 100, 0, 255)) +
" PWM2=" + String(map(slider2, 0, 100, 0, 255)));
});
After mastering the Bluetooth Monitor example, try:
Bluetooth Chat - For two-way interactive messaging
Bluetooth Table - For structured data display
Bluetooth Plotter - For graphical data visualization
Multiple Bluetooth Apps - Combining monitor with other controls