The Multiple Bluetooth Apps example demonstrates how to combine multiple Bluetooth app interfaces into a single ESP32 project. Run Monitor, Chat, Slider, Joystick, Temperature, Plotter, Table, Analog Gauge, and Rotator all at once — accessible through the DIYables Bluetooth STEM app. Designed for ESP32 boards with support for both BLE (Bluetooth Low Energy) and Classic Bluetooth connections. This is ideal for complex projects that need multiple control and display interfaces simultaneously.
This example supports two Bluetooth modes:
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.
> Important: Since this example includes many Bluetooth app libraries, the compiled sketch is larger than usual. You must select the correct partition scheme (see below).
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 <DIYables_BluetoothChat.h>
#include <DIYables_BluetoothSlider.h>
#include <DIYables_BluetoothJoystick.h>
#include <DIYables_BluetoothTemperature.h>
#include <DIYables_BluetoothPlotter.h>
#include <DIYables_BluetoothTable.h>
#include <DIYables_BluetoothAnalogGauge.h>
#include <DIYables_BluetoothRotator.h>
#include <platforms/DIYables_Esp32Bluetooth.h>
DIYables_Esp32Bluetooth bluetooth("ESP32 Multi-App");
DIYables_BluetoothServer bluetoothServer(bluetooth);
DIYables_BluetoothMonitor bluetoothMonitor;
DIYables_BluetoothChat bluetoothChat;
DIYables_BluetoothSlider bluetoothSlider(0, 255, 1);
DIYables_BluetoothJoystick bluetoothJoystick(false, 5);
DIYables_BluetoothTemperature bluetoothTemperature(-10.0, 50.0, "°C");
DIYables_BluetoothPlotter bluetoothPlotter;
DIYables_BluetoothTable bluetoothTable;
DIYables_BluetoothAnalogGauge bluetoothGauge(0.0, 100.0, "%");
DIYables_BluetoothRotator bluetoothRotator(ROTATOR_MODE_CONTINUOUS);
const int LED_PIN = 2;
int currentSlider1 = 128;
int currentSlider2 = 64;
int currentJoystickX = 0;
int currentJoystickY = 0;
float currentTemperature = 25.0;
float currentGaugeValue = 50.0;
float currentRotatorAngle = 0.0;
int messageCount = 0;
unsigned long lastMonitorUpdate = 0;
unsigned long lastTempUpdate = 0;
unsigned long lastPlotUpdate = 0;
unsigned long lastTableUpdate = 0;
unsigned long lastGaugeUpdate = 0;
float plotPhase = 0;
void setup() {
Serial.begin(115200);
delay(1000);
Serial.println("DIYables Bluetooth - ESP32 Multiple Apps Example");
pinMode(LED_PIN, OUTPUT);
digitalWrite(LED_PIN, LOW);
bluetoothServer.begin();
bluetoothServer.addApp(&bluetoothMonitor);
bluetoothServer.addApp(&bluetoothChat);
bluetoothServer.addApp(&bluetoothSlider);
bluetoothServer.addApp(&bluetoothJoystick);
bluetoothServer.addApp(&bluetoothTemperature);
bluetoothServer.addApp(&bluetoothPlotter);
bluetoothServer.addApp(&bluetoothTable);
bluetoothServer.addApp(&bluetoothGauge);
bluetoothServer.addApp(&bluetoothRotator);
Serial.print("Registered apps: ");
Serial.println(bluetoothServer.getAppCount());
bluetoothPlotter.setPlotTitle("Sensor Data");
bluetoothPlotter.setAxisLabels("Time", "Value");
bluetoothPlotter.setYAxisRange(-1.5, 1.5);
bluetoothPlotter.setMaxSamples(100);
bluetoothPlotter.setLegendLabels("Sine", "Cosine", "Random");
bluetoothTable.addRow("Status");
bluetoothTable.addRow("Uptime");
bluetoothTable.addRow("Slider 1");
bluetoothTable.addRow("Slider 2");
bluetoothTable.addRow("Joystick X");
bluetoothTable.addRow("Joystick Y");
bluetoothTable.addRow("Temperature");
bluetoothTable.addRow("Gauge Value");
bluetoothTable.addRow("Rotator Angle");
bluetoothTable.addRow("Free Heap");
setupCallbacks();
Serial.println("Waiting for Bluetooth connection...");
}
void setupCallbacks() {
bluetoothServer.setOnConnected([]() {
Serial.println("Bluetooth connected!");
digitalWrite(LED_PIN, HIGH);
bluetoothMonitor.send("=== ESP32 Multi-App Connected ===");
bluetoothMonitor.send("All apps are ready!");
bluetoothChat.send("Hello! ESP32 Multi-App is connected.");
});
bluetoothServer.setOnDisconnected([]() {
Serial.println("Bluetooth disconnected!");
digitalWrite(LED_PIN, LOW);
});
bluetoothMonitor.onMonitorMessage([](const String& message) {
Serial.println("Monitor cmd: " + message);
if (message == "HELP") {
bluetoothMonitor.send("Commands: STATUS, HELP, LED_ON, LED_OFF, HEAP");
} else if (message == "STATUS") {
bluetoothMonitor.send("Slider1=" + String(currentSlider1) + " Slider2=" + String(currentSlider2));
bluetoothMonitor.send("Joystick X=" + String(currentJoystickX) + " Y=" + String(currentJoystickY));
bluetoothMonitor.send("Temp=" + String(currentTemperature, 1) + "°C");
bluetoothMonitor.send("Gauge=" + String(currentGaugeValue, 1) + "%");
bluetoothMonitor.send("Rotator=" + String(currentRotatorAngle, 0) + "°");
} else if (message == "LED_ON") {
digitalWrite(LED_PIN, HIGH);
bluetoothMonitor.send("LED turned ON");
} else if (message == "LED_OFF") {
digitalWrite(LED_PIN, LOW);
bluetoothMonitor.send("LED turned OFF");
} else if (message == "HEAP") {
bluetoothMonitor.send("Free Heap: " + String(ESP.getFreeHeap()) + " bytes");
} else {
bluetoothMonitor.send("Unknown: " + message + " (type HELP)");
}
});
bluetoothChat.onChatMessage([](const String& message) {
Serial.println("Chat: " + message);
bluetoothChat.send("Echo: " + message);
if (message.equalsIgnoreCase("ping")) {
bluetoothChat.send("pong!");
} else if (message.equalsIgnoreCase("status")) {
bluetoothChat.send("Uptime: " + String(millis() / 1000) + "s, Apps: " + String(bluetoothServer.getAppCount()));
} else if (message.equalsIgnoreCase("heap")) {
bluetoothChat.send("Free heap: " + String(ESP.getFreeHeap()) + " bytes");
}
});
bluetoothSlider.onSliderValue([](int slider1, int slider2) {
currentSlider1 = slider1;
currentSlider2 = slider2;
Serial.print("Slider 1: "); Serial.print(slider1);
Serial.print(", Slider 2: "); Serial.println(slider2);
currentGaugeValue = map(slider1, 0, 255, 0, 100);
bluetoothGauge.send(currentGaugeValue);
bluetoothTable.sendValueUpdate("Slider 1", String(slider1));
bluetoothTable.sendValueUpdate("Slider 2", String(slider2));
bluetoothTable.sendValueUpdate("Gauge Value", String(currentGaugeValue, 1) + "%");
});
bluetoothSlider.onGetConfig([]() {
bluetoothSlider.send(currentSlider1, currentSlider2);
});
bluetoothJoystick.onJoystickValue([](int x, int y) {
currentJoystickX = x;
currentJoystickY = y;
Serial.print("Joystick X: "); Serial.print(x);
Serial.print(", Y: "); Serial.println(y);
bluetoothTable.sendValueUpdate("Joystick X", String(x));
bluetoothTable.sendValueUpdate("Joystick Y", String(y));
});
bluetoothJoystick.onGetConfig([]() {
bluetoothJoystick.send(currentJoystickX, currentJoystickY);
});
bluetoothTemperature.onTemperatureRequest([]() {
bluetoothTemperature.send(currentTemperature);
});
bluetoothPlotter.onDataRequest([]() {
Serial.println("Plotter data requested");
});
bluetoothTable.onDataRequest([]() {
Serial.println("Table data requested");
bluetoothTable.sendTableStructure();
updateAllTableValues();
});
bluetoothGauge.onValueRequest([]() {
bluetoothGauge.send(currentGaugeValue);
});
bluetoothRotator.onRotatorAngle([](float angle) {
currentRotatorAngle = angle;
Serial.print("Rotator: "); Serial.print(angle); Serial.println("°");
bluetoothTable.sendValueUpdate("Rotator Angle", String(angle, 0) + "°");
});
}
void updateAllTableValues() {
bluetoothTable.sendValueUpdate("Status", "Running");
unsigned long uptime = millis() / 1000;
String uptimeStr;
if (uptime >= 60) {
uptimeStr = String(uptime / 60) + "m " + String(uptime % 60) + "s";
} else {
uptimeStr = String(uptime) + "s";
}
bluetoothTable.sendValueUpdate("Uptime", uptimeStr);
bluetoothTable.sendValueUpdate("Slider 1", String(currentSlider1));
bluetoothTable.sendValueUpdate("Slider 2", String(currentSlider2));
bluetoothTable.sendValueUpdate("Joystick X", String(currentJoystickX));
bluetoothTable.sendValueUpdate("Joystick Y", String(currentJoystickY));
bluetoothTable.sendValueUpdate("Temperature", String(currentTemperature, 1) + " °C");
bluetoothTable.sendValueUpdate("Gauge Value", String(currentGaugeValue, 1) + "%");
bluetoothTable.sendValueUpdate("Rotator Angle", String(currentRotatorAngle, 0) + "°");
bluetoothTable.sendValueUpdate("Free Heap", String(ESP.getFreeHeap()) + " bytes");
}
void loop() {
bluetoothServer.loop();
if (!bluetooth.isConnected()) {
delay(10);
return;
}
if (millis() - lastMonitorUpdate >= 5000) {
lastMonitorUpdate = millis();
messageCount++;
bluetoothMonitor.send("[INFO] Heartbeat #" + String(messageCount) + " - Uptime: " + String(millis() / 1000) + "s");
}
if (millis() - lastTempUpdate >= 2000) {
lastTempUpdate = millis();
static float tempOffset = 0;
tempOffset += random(-10, 11) / 10.0;
if (tempOffset > 5.0) tempOffset = 5.0;
if (tempOffset < -5.0) tempOffset = -5.0;
currentTemperature = 25.0 + tempOffset;
bluetoothTemperature.send(currentTemperature);
bluetoothTable.sendValueUpdate("Temperature", String(currentTemperature, 1) + " °C");
}
if (millis() - lastPlotUpdate >= 100) {
lastPlotUpdate = millis();
float sine = sin(plotPhase);
float cosine = cos(plotPhase);
float noise = random(-50, 51) / 100.0;
bluetoothPlotter.send(sine, cosine, noise);
plotPhase += 0.1;
if (plotPhase > 2 * PI) plotPhase = 0;
}
if (millis() - lastTableUpdate >= 5000) {
lastTableUpdate = millis();
unsigned long uptime = millis() / 1000;
String uptimeStr;
if (uptime >= 60) {
uptimeStr = String(uptime / 60) + "m " + String(uptime % 60) + "s";
} else {
uptimeStr = String(uptime) + "s";
}
bluetoothTable.sendValueUpdate("Uptime", uptimeStr);
bluetoothTable.sendValueUpdate("Free Heap", String(ESP.getFreeHeap()) + " bytes");
}
if (millis() - lastGaugeUpdate >= 3000) {
lastGaugeUpdate = millis();
float sensorValue = 50.0 + 30.0 * sin(millis() / 10000.0);
currentGaugeValue = sensorValue;
bluetoothGauge.send(currentGaugeValue);
bluetoothTable.sendValueUpdate("Gauge Value", String(currentGaugeValue, 1) + "%");
}
delay(10);
}
Important: Go to Tools > Partition Scheme and select "Huge APP (3MB No OTA/1MB SPIFFS)". This is required because the multiple apps example uses significantly more flash space.
Click Upload button on Arduino IDE to upload code to ESP32
Open the Serial Monitor
Check out the result on Serial Monitor. It looks like the below:
DIYables Bluetooth - ESP32 Multiple Apps Example
Waiting for Bluetooth connection...
#include <DIYables_BluetoothServer.h>
#include <DIYables_BluetoothMonitor.h>
#include <DIYables_BluetoothChat.h>
#include <DIYables_BluetoothSlider.h>
#include <DIYables_BluetoothJoystick.h>
#include <DIYables_BluetoothTemperature.h>
#include <DIYables_BluetoothPlotter.h>
#include <DIYables_BluetoothTable.h>
#include <DIYables_BluetoothAnalogGauge.h>
#include <DIYables_BluetoothRotator.h>
#include <platforms/DIYables_Esp32BLE.h>
const char* DEVICE_NAME = "ESP32BLE Multi-App";
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;
DIYables_BluetoothChat bluetoothChat;
DIYables_BluetoothSlider bluetoothSlider(0, 255, 1);
DIYables_BluetoothJoystick bluetoothJoystick(false, 5);
DIYables_BluetoothTemperature bluetoothTemperature(-10.0, 50.0, "°C");
DIYables_BluetoothPlotter bluetoothPlotter;
DIYables_BluetoothTable bluetoothTable;
DIYables_BluetoothAnalogGauge bluetoothGauge(0.0, 100.0, "%");
DIYables_BluetoothRotator bluetoothRotator(ROTATOR_MODE_CONTINUOUS);
int currentSlider1 = 128;
int currentSlider2 = 64;
int currentJoystickX = 0;
int currentJoystickY = 0;
float currentTemperature = 25.0;
float currentGaugeValue = 50.0;
float currentRotatorAngle = 0.0;
int messageCount = 0;
unsigned long lastMonitorUpdate = 0;
unsigned long lastTempUpdate = 0;
unsigned long lastPlotUpdate = 0;
unsigned long lastTableUpdate = 0;
unsigned long lastGaugeUpdate = 0;
float plotPhase = 0;
void setup() {
Serial.begin(115200);
delay(1000);
Serial.println("DIYables Bluetooth - ESP32 BLE Multiple Apps Example");
pinMode(2, OUTPUT);
digitalWrite(2, LOW);
bluetoothServer.begin();
bluetoothServer.addApp(&bluetoothMonitor);
bluetoothServer.addApp(&bluetoothChat);
bluetoothServer.addApp(&bluetoothSlider);
bluetoothServer.addApp(&bluetoothJoystick);
bluetoothServer.addApp(&bluetoothTemperature);
bluetoothServer.addApp(&bluetoothPlotter);
bluetoothServer.addApp(&bluetoothTable);
bluetoothServer.addApp(&bluetoothGauge);
bluetoothServer.addApp(&bluetoothRotator);
Serial.print("Registered apps: ");
Serial.println(bluetoothServer.getAppCount());
bluetoothPlotter.setPlotTitle("Sensor Data");
bluetoothPlotter.setAxisLabels("Time", "Value");
bluetoothPlotter.setYAxisRange(-1.5, 1.5);
bluetoothPlotter.setMaxSamples(100);
bluetoothPlotter.setLegendLabels("Sine", "Cosine", "Random");
bluetoothTable.addRow("Status");
bluetoothTable.addRow("Uptime");
bluetoothTable.addRow("Slider 1");
bluetoothTable.addRow("Slider 2");
bluetoothTable.addRow("Joystick X");
bluetoothTable.addRow("Joystick Y");
bluetoothTable.addRow("Temperature");
bluetoothTable.addRow("Gauge Value");
bluetoothTable.addRow("Rotator Angle");
bluetoothTable.addRow("Messages");
setupCallbacks();
Serial.println("Waiting for Bluetooth connection...");
}
void setupCallbacks() {
bluetoothServer.setOnConnected([]() {
Serial.println("Bluetooth connected!");
digitalWrite(2, HIGH);
bluetoothMonitor.send("=== ESP32 BLE Multi-App Connected ===");
bluetoothMonitor.send("All apps are ready!");
bluetoothChat.send("Hello! ESP32 BLE Multi-App is connected.");
});
bluetoothServer.setOnDisconnected([]() {
Serial.println("Bluetooth disconnected!");
digitalWrite(2, LOW);
});
bluetoothMonitor.onMonitorMessage([](const String& message) {
Serial.println("Monitor cmd: " + message);
if (message == "HELP") {
bluetoothMonitor.send("Commands: STATUS, HELP, LED_ON, LED_OFF, HEAP");
} else if (message == "STATUS") {
bluetoothMonitor.send("Slider1=" + String(currentSlider1) + " Slider2=" + String(currentSlider2));
bluetoothMonitor.send("Joystick X=" + String(currentJoystickX) + " Y=" + String(currentJoystickY));
bluetoothMonitor.send("Temp=" + String(currentTemperature, 1) + "°C");
bluetoothMonitor.send("Gauge=" + String(currentGaugeValue, 1) + "%");
bluetoothMonitor.send("Rotator=" + String(currentRotatorAngle, 0) + "°");
} else if (message == "LED_ON") {
digitalWrite(2, HIGH);
bluetoothMonitor.send("LED turned ON");
} else if (message == "LED_OFF") {
digitalWrite(2, LOW);
bluetoothMonitor.send("LED turned OFF");
} else if (message == "HEAP") {
bluetoothMonitor.send("Free heap: " + String(ESP.getFreeHeap()) + " bytes");
} else {
bluetoothMonitor.send("Unknown: " + message + " (type HELP)");
}
});
bluetoothChat.onChatMessage([](const String& message) {
Serial.println("Chat: " + message);
bluetoothChat.send("Echo: " + message);
if (message.equalsIgnoreCase("ping")) {
bluetoothChat.send("pong!");
} else if (message.equalsIgnoreCase("status")) {
bluetoothChat.send("Uptime: " + String(millis() / 1000) + "s, Apps: " + String(bluetoothServer.getAppCount()));
}
});
bluetoothSlider.onSliderValue([](int slider1, int slider2) {
currentSlider1 = slider1;
currentSlider2 = slider2;
Serial.print("Slider 1: "); Serial.print(slider1);
Serial.print(", Slider 2: "); Serial.println(slider2);
currentGaugeValue = map(slider1, 0, 255, 0, 100);
bluetoothGauge.send(currentGaugeValue);
bluetoothTable.sendValueUpdate("Slider 1", String(slider1));
bluetoothTable.sendValueUpdate("Slider 2", String(slider2));
bluetoothTable.sendValueUpdate("Gauge Value", String(currentGaugeValue, 1) + "%");
});
bluetoothSlider.onGetConfig([]() {
bluetoothSlider.send(currentSlider1, currentSlider2);
});
bluetoothJoystick.onJoystickValue([](int x, int y) {
currentJoystickX = x;
currentJoystickY = y;
Serial.print("Joystick X: "); Serial.print(x);
Serial.print(", Y: "); Serial.println(y);
bluetoothTable.sendValueUpdate("Joystick X", String(x));
bluetoothTable.sendValueUpdate("Joystick Y", String(y));
});
bluetoothJoystick.onGetConfig([]() {
bluetoothJoystick.send(currentJoystickX, currentJoystickY);
});
bluetoothTemperature.onTemperatureRequest([]() {
bluetoothTemperature.send(currentTemperature);
});
bluetoothPlotter.onDataRequest([]() {
Serial.println("Plotter data requested");
});
bluetoothTable.onDataRequest([]() {
Serial.println("Table data requested");
bluetoothTable.sendTableStructure();
updateAllTableValues();
});
bluetoothGauge.onValueRequest([]() {
bluetoothGauge.send(currentGaugeValue);
});
bluetoothRotator.onRotatorAngle([](float angle) {
currentRotatorAngle = angle;
Serial.print("Rotator: "); Serial.print(angle); Serial.println("°");
bluetoothTable.sendValueUpdate("Rotator Angle", String(angle, 0) + "°");
});
}
void updateAllTableValues() {
bluetoothTable.sendValueUpdate("Status", "Running");
unsigned long uptime = millis() / 1000;
String uptimeStr;
if (uptime >= 60) {
uptimeStr = String(uptime / 60) + "m " + String(uptime % 60) + "s";
} else {
uptimeStr = String(uptime) + "s";
}
bluetoothTable.sendValueUpdate("Uptime", uptimeStr);
bluetoothTable.sendValueUpdate("Slider 1", String(currentSlider1));
bluetoothTable.sendValueUpdate("Slider 2", String(currentSlider2));
bluetoothTable.sendValueUpdate("Joystick X", String(currentJoystickX));
bluetoothTable.sendValueUpdate("Joystick Y", String(currentJoystickY));
bluetoothTable.sendValueUpdate("Temperature", String(currentTemperature, 1) + " °C");
bluetoothTable.sendValueUpdate("Gauge Value", String(currentGaugeValue, 1) + "%");
bluetoothTable.sendValueUpdate("Rotator Angle", String(currentRotatorAngle, 0) + "°");
bluetoothTable.sendValueUpdate("Messages", String(messageCount));
}
void loop() {
bluetoothServer.loop();
if (!bluetooth.isConnected()) {
delay(10);
return;
}
if (millis() - lastMonitorUpdate >= 5000) {
lastMonitorUpdate = millis();
messageCount++;
bluetoothMonitor.send("[INFO] Heartbeat #" + String(messageCount) + " - Uptime: " + String(millis() / 1000) + "s");
}
if (millis() - lastTempUpdate >= 2000) {
lastTempUpdate = millis();
static float tempOffset = 0;
tempOffset += random(-10, 11) / 10.0;
if (tempOffset > 5.0) tempOffset = 5.0;
if (tempOffset < -5.0) tempOffset = -5.0;
currentTemperature = 25.0 + tempOffset;
bluetoothTemperature.send(currentTemperature);
bluetoothTable.sendValueUpdate("Temperature", String(currentTemperature, 1) + " °C");
}
if (millis() - lastPlotUpdate >= 100) {
lastPlotUpdate = millis();
float sine = sin(plotPhase);
float cosine = cos(plotPhase);
float noise = random(-50, 51) / 100.0;
bluetoothPlotter.send(sine, cosine, noise);
plotPhase += 0.1;
if (plotPhase > 2 * PI) plotPhase = 0;
}
if (millis() - lastTableUpdate >= 5000) {
lastTableUpdate = millis();
unsigned long uptime = millis() / 1000;
String uptimeStr;
if (uptime >= 60) {
uptimeStr = String(uptime / 60) + "m " + String(uptime % 60) + "s";
} else {
uptimeStr = String(uptime) + "s";
}
bluetoothTable.sendValueUpdate("Uptime", uptimeStr);
bluetoothTable.sendValueUpdate("Messages", String(messageCount));
}
if (millis() - lastGaugeUpdate >= 3000) {
lastGaugeUpdate = millis();
float sensorValue = 50.0 + 30.0 * sin(millis() / 10000.0);
currentGaugeValue = sensorValue;
bluetoothGauge.send(currentGaugeValue);
bluetoothTable.sendValueUpdate("Gauge Value", String(currentGaugeValue, 1) + "%");
}
delay(10);
}
Important: Go to Tools > Partition Scheme and select "Huge APP (3MB No OTA/1MB SPIFFS)". This is required because the multiple apps example uses significantly more flash space.
Click Upload button on Arduino IDE to upload code to ESP32
Open the Serial Monitor
Check out the result on Serial Monitor. It looks like the below:
DIYables Bluetooth - ESP32 BLE Multiple Apps 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 Multi-App" 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. The home screen shows all available apps. The 9 apps initialized in the Arduino code will respond and work — other apps on the home screen will appear but will not function with this sketch.
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.
Tap some of the following apps to open and interact with the ESP32: Monitor, Chat, Slider, Joystick, Temperature, Plotter, Table, Analog Gauge, Rotator
Switch between apps freely — they all share the same Bluetooth connection
Now look back at the Serial Monitor on Arduino IDE. You will see:
Bluetooth connected!
Monitor: Uptime: 5s | Free heap: 245780 bytes
Temperature: 25.3°C
Plotter values sent
Table values updated
Gauge value: 50.0
Each app is created as a separate object and registered with the same Bluetooth server. They share the single Bluetooth connection but operate independently:
#include <DIYables_BluetoothMonitor.h>
#include <DIYables_BluetoothChat.h>
#include <DIYables_BluetoothSlider.h>
#include <DIYables_BluetoothJoystick.h>
#include <DIYables_BluetoothTemperature.h>
#include <DIYables_BluetoothPlotter.h>
#include <DIYables_BluetoothTable.h>
#include <DIYables_BluetoothAnalogGauge.h>
#include <DIYables_BluetoothRotator.h>
DIYables_BluetoothMonitor bluetoothMonitor;
DIYables_BluetoothChat bluetoothChat;
DIYables_BluetoothSlider bluetoothSlider(0, 100, 1);
DIYables_BluetoothJoystick bluetoothJoystick;
DIYables_BluetoothTemperature bluetoothTemperature(-10.0, 50.0, "°C");
DIYables_BluetoothPlotter bluetoothPlotter;
DIYables_BluetoothTable bluetoothTable;
DIYables_BluetoothAnalogGauge bluetoothGauge(0.0, 100.0, "km/h");
DIYables_BluetoothRotator bluetoothRotator(ROTATOR_MODE_LIMITED, 0, 180);
bluetoothServer.addApp(bluetoothMonitor);
bluetoothServer.addApp(bluetoothChat);
bluetoothServer.addApp(bluetoothSlider);
bluetoothServer.addApp(bluetoothJoystick);
bluetoothServer.addApp(bluetoothTemperature);
bluetoothServer.addApp(bluetoothPlotter);
bluetoothServer.addApp(bluetoothTable);
bluetoothServer.addApp(bluetoothGauge);
bluetoothServer.addApp(bluetoothRotator);
Each app has its own event handlers:
void setup() {
bluetoothMonitor.onMonitorMessage([](String message) {
Serial.println("Monitor received: " + message);
});
bluetoothChat.onChatMessage([](String message) {
Serial.println("Chat: " + message);
bluetoothChat.send("Echo: " + message);
});
bluetoothSlider.onSliderValue([](int slider1, int slider2) {
Serial.println("Slider: " + String(slider1));
});
bluetoothJoystick.onJoystickValue([](int x, int y) {
Serial.println("Joystick: X=" + String(x) + " Y=" + String(y));
});
bluetoothRotator.onRotatorAngle([](float angle) {
Serial.println("Rotator: " + String(angle) + "°");
});
}
Each app can have its own update timing:
void loop() {
bluetoothServer.loop();
unsigned long now = millis();
static unsigned long lastMonitor = 0;
if (now - lastMonitor >= 5000) {
lastMonitor = now;
bluetoothMonitor.send("Uptime: " + String(now / 1000) + "s");
}
static unsigned long lastTemp = 0;
if (now - lastTemp >= 2000) {
lastTemp = now;
bluetoothTemperature.send(readTemperature());
}
static unsigned long lastPlotter = 0;
if (now - lastPlotter >= 100) {
lastPlotter = now;
bluetoothPlotter.send(readSensorValue());
}
static unsigned long lastTable = 0;
if (now - lastTable >= 5000) {
lastTable = now;
updateTableValues();
}
static unsigned long lastGauge = 0;
if (now - lastGauge >= 3000) {
lastGauge = now;
bluetoothGauge.send(readGaugeValue());
}
delay(10);
}
You don't have to include all 9 apps. Choose only the ones you need:
#include <DIYables_BluetoothMonitor.h>
#include <DIYables_BluetoothSlider.h>
#include <DIYables_BluetoothTemperature.h>
DIYables_BluetoothMonitor bluetoothMonitor;
DIYables_BluetoothSlider bluetoothSlider(0, 100, 1);
DIYables_BluetoothTemperature bluetoothTemperature(-10.0, 50.0, "°C");
void setup() {
bluetoothServer.addApp(bluetoothMonitor);
bluetoothServer.addApp(bluetoothSlider);
bluetoothServer.addApp(bluetoothTemperature);
}
bluetoothServer.setOnConnected([]() {
Serial.println("Bluetooth connected!");
bluetoothTemperature.send(currentTemperature);
bluetoothGauge.send(currentGaugeValue);
bluetoothRotator.send(currentAngle);
});
bluetoothServer.setOnDisconnected([]() {
Serial.println("Bluetooth disconnected!");
});
In the DIYables Bluetooth App:
The home screen shows all registered apps as buttons
Tap any app to open it
Use the back button or home button to return and switch to another app
All apps continue running on the ESP32 regardless of which app is shown
Input Apps (Slider, Joystick, Rotator, Chat): Send data from phone to ESP32
Output Apps (Monitor, Temperature, Plotter, Table, Gauge): Send data from ESP32 to phone
Bidirectional Apps (Chat, Monitor): Can send and receive data
DIYables_BluetoothTemperature bluetoothTemperature(-10.0, 50.0, "°C");
DIYables_BluetoothTable bluetoothTable;
DIYables_BluetoothPlotter bluetoothPlotter;
DIYables_BluetoothMonitor bluetoothMonitor;
void setup() {
bluetoothTable.addRow("Temperature");
bluetoothTable.addRow("Humidity");
bluetoothTable.addRow("Pressure");
bluetoothTable.addRow("Wind Speed");
bluetoothTable.addRow("Rain");
bluetoothPlotter.setPlotTitle("Weather Trends");
bluetoothPlotter.setLegendLabels("Temp", "Humidity", "Pressure");
}
void loop() {
bluetoothServer.loop();
static unsigned long lastUpdate = 0;
if (millis() - lastUpdate >= 2000) {
lastUpdate = millis();
float temp = readTemperature();
float hum = readHumidity();
float press = readPressure();
bluetoothTemperature.send(temp);
bluetoothTable.sendValueUpdate("Temperature", String(temp, 1) + " °C");
bluetoothTable.sendValueUpdate("Humidity", String(hum, 0) + "%");
bluetoothTable.sendValueUpdate("Pressure", String(press, 0) + " hPa");
bluetoothPlotter.send(temp, hum, press / 10.0);
bluetoothMonitor.send("Weather update: " + String(temp, 1) + "°C, " + String(hum, 0) + "%, " + String(press, 0) + "hPa");
}
delay(10);
}
DIYables_BluetoothJoystick bluetoothJoystick;
DIYables_BluetoothSlider bluetoothSlider(0, 100, 5);
DIYables_BluetoothRotator bluetoothRotator(ROTATOR_MODE_LIMITED, 0, 180);
DIYables_BluetoothMonitor bluetoothMonitor;
DIYables_BluetoothAnalogGauge bluetoothGauge(0.0, 100.0, "%");
int maxSpeed = 50;
void setup() {
bluetoothSlider.onSliderValue([](int slider1, int slider2) {
maxSpeed = slider1;
bluetoothMonitor.send("Max speed set to: " + String(maxSpeed) + "%");
});
bluetoothJoystick.onJoystickValue([](int x, int y) {
int leftMotor = constrain(y + x, -maxSpeed, maxSpeed);
int rightMotor = constrain(y - x, -maxSpeed, maxSpeed);
setMotors(leftMotor, rightMotor);
bluetoothMonitor.send("Motors: L=" + String(leftMotor) + " R=" + String(rightMotor));
});
bluetoothRotator.onRotatorAngle([](float angle) {
setArmAngle((int)angle);
bluetoothMonitor.send("Arm angle: " + String((int)angle) + "°");
});
}
void loop() {
bluetoothServer.loop();
static unsigned long lastBattery = 0;
if (millis() - lastBattery >= 10000) {
lastBattery = millis();
float battery = readBatteryLevel();
bluetoothGauge.send(battery);
}
delay(10);
}
DIYables_BluetoothChat bluetoothChat;
DIYables_BluetoothSlider bluetoothSlider(0, 100, 1);
DIYables_BluetoothTable bluetoothTable;
DIYables_BluetoothTemperature bluetoothTemperature(10.0, 40.0, "°C");
DIYables_BluetoothMonitor bluetoothMonitor;
void setup() {
bluetoothTable.addRow("Living Room Light");
bluetoothTable.addRow("Bedroom Light");
bluetoothTable.addRow("AC Status");
bluetoothTable.addRow("Door Lock");
bluetoothTable.addRow("Security");
bluetoothChat.onChatMessage([](String message) {
message.toLowerCase();
if (message == "lights on") {
setLights(true);
bluetoothChat.send("Lights turned ON");
bluetoothTable.sendValueUpdate("Living Room Light", "ON ?");
} else if (message == "lights off") {
setLights(false);
bluetoothChat.send("Lights turned OFF");
bluetoothTable.sendValueUpdate("Living Room Light", "OFF ?");
} else if (message == "status") {
bluetoothChat.send("Temp: " + String(readTemperature(), 1) + "°C");
bluetoothChat.send("All systems normal");
}
bluetoothMonitor.send("[CMD] " + message);
});
bluetoothSlider.onSliderValue([](int slider1, int slider2) {
setDimmer(slider1);
bluetoothMonitor.send("[DIMMER] Set to " + String(slider1) + "%");
});
}
Use data from one app to update another:
float posX = 0, posY = 0;
bluetoothJoystick.onJoystickValue([](int x, int y) {
posX += x * 0.1;
posY += y * 0.1;
bluetoothPlotter.send(posX, posY);
bluetoothTable.sendValueUpdate("Position X", String(posX, 1));
bluetoothTable.sendValueUpdate("Position Y", String(posY, 1));
bluetoothMonitor.send("Pos: (" + String(posX, 1) + ", " + String(posY, 1) + ")");
});
When memory is tight, share variables between apps:
struct SensorData {
float temperature;
float humidity;
float pressure;
unsigned long lastUpdate;
} sensorData;
void readAllSensors() {
sensorData.temperature = readTemperature();
sensorData.humidity = readHumidity();
sensorData.pressure = readPressure();
sensorData.lastUpdate = millis();
}
void updateAllApps() {
readAllSensors();
bluetoothTemperature.send(sensorData.temperature);
bluetoothTable.sendValueUpdate("Temperature", String(sensorData.temperature, 1) + " °C");
bluetoothTable.sendValueUpdate("Humidity", String(sensorData.humidity, 0) + "%");
bluetoothPlotter.send(sensorData.temperature, sensorData.humidity);
bluetoothGauge.send(sensorData.pressure);
bluetoothMonitor.send("Sensors updated at " + String(sensorData.lastUpdate / 1000) + "s");
}
| Feature | BLE (Esp32BLE_MultipleApps) | Classic Bluetooth (Esp32Bluetooth_MultipleApps) |
| 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 |
> Note for Multiple Apps: Since multiple apps send data simultaneously, Classic Bluetooth's higher data rate can be beneficial if you have many fast-updating apps (e.g., plotter at 100ms). For battery-powered projects, BLE is still recommended.
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) — this is critical for multi-app examples
2. 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 the multi-app example
This setting gives ~3MB by sacrificing the OTA (over-the-air update) partition
If still too large, remove apps you don't need
3. Some apps not appearing in the mobile app
Ensure each app is registered with bluetoothServer.addApp()
Check that the include headers match the app objects created
Verify all apps are registered before bluetoothServer.begin()
4. Data updates are slow or delayed
Reduce the number of apps sending data simultaneously
Increase update intervals for less critical apps
BLE has limited bandwidth — stagger updates across time
5. ESP32 crashes or resets
Check available heap memory with ESP.getFreeHeap()
Reduce the number of table rows or plotter samples
Ensure power supply is adequate (USB 3.0 or external supply)
6. Connection drops frequently
Move closer to the ESP32 (reduce distance)
Reduce the total amount of data being sent
Stagger update intervals so apps don't all send at the same time
Monitor memory and performance:
void debugMultiApp() {
Serial.println("=== Multi-App Debug ===");
Serial.println("Free Heap: " + String(ESP.getFreeHeap()) + " bytes");
Serial.println("Min Free Heap: " + String(ESP.getMinFreeHeap()) + " bytes");
Serial.println("Uptime: " + String(millis() / 1000) + "s");
Serial.println("=======================");
}
static unsigned long lastDebug = 0;
if (millis() - lastDebug >= 30000) {
lastDebug = millis();
debugMultiApp();
}
Now that you've mastered combining multiple Bluetooth apps, you have all the tools to build complex ESP32 projects with rich mobile interfaces. Explore each individual app tutorial for deeper API knowledge:
Bluetooth Monitor - Text-based serial monitor
Bluetooth Chat - Two-way messaging
Bluetooth Slider - Value control with sliders
Bluetooth Joystick - 2D directional input
Bluetooth Temperature - Temperature gauge display
Bluetooth Plotter - Real-time data plotting
Bluetooth Table - Structured data display
Bluetooth Analog Gauge - Analog meter display
Bluetooth Rotator - Rotatable knob control