ESP32 UNO - DIYables Bluetooth App Multiple Apps

Overview

Want a full phone control panel for your ESP32 Uno Bluetooth multiple apps project, with no screen or buttons on the desk? Flash one sketch to the ESP32 Uno form board, and the DIYables Bluetooth STEM app gives you nine screens at once: Monitor, Chat, Slider, Joystick, Temperature, Plotter, Table, Analog Gauge and Rotator. It is an all-in-one Bluetooth dashboard that runs as a BLE multi-app on Android and iPhone, or over Classic Bluetooth on Android.

By the end, you can:

  1. Install the DIYables Bluetooth library and flash the combined sketch
  2. Pick a partition scheme that fits a large Bluetooth program
  3. Link the phone over BLE or Classic Bluetooth and jump between screens
  4. Make one screen drive another, like a slider that moves the gauge
  5. Cut the example down to the few apps your build needs
ESP32 Uno Bluetooth multiple apps

※ NOTE THAT:

The ESP32 Uno chip carries both radios: Bluetooth Low Energy and Classic Bluetooth. That is why this page offers two sketches. BLE suits iPhones and needs no pairing. Classic suits Android-only builds that push a lot of data, such as a fast plotter.

What You Get in One Sketch

After this part, you know which screen does what and how often it refreshes.

App Screen Direction Refresh in the Example
Monitor Board to phone and phone to board Heartbeat line every 5 s, commands on demand
Chat Both ways When a message arrives
Slider Phone to board When you drag a slider (two sliders, 0 to 255)
Joystick Phone to board When you move the pad
Temperature Board to phone New fake reading every 2 s (-10 to 50 °C scale)
Plotter Board to phone Three lines (sine, cosine, noise) every 100 ms
Table Board to phone Ten rows, uptime row every 5 s
Analog Gauge Board to phone Slow sine value every 3 s (0 to 100 %)
Rotator Phone to board When you turn the knob (continuous mode)

Good to know: all nine screens share ONE radio link. The library sends each message to the right app, so your callbacks never get mixed up.

Components You Need

1×ESP32 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×Jumper Wires
1×Optionally, LED
1×Alternatively, Button and LED Kit
1×Optionally, 220 ohm Resistor

Or you can buy the following kits:

1×DIYables Sensor Kit (18 sensors/displays)
Disclosure: Some of the links provided in this section are Amazon affiliate links. We may receive a commission for any purchases made through these links at no additional cost to you.
Additionally, some of these links are for products from our own brand, DIYables .

ESP32 Uno Pinout

The printed header names (D13, A0 and so on) are not the numbers you type in a sketch. Find the label on this map, read its GPIO number, and use that number in code.

ESP32 Uno pinout diagram

How to Connect the Status LED

At the end of this part, you have a light that shows when the phone is linked. The radio is inside the chip, so the dashboard itself works with no wires at all.

The sketch switches a status LED ON when a phone connects and OFF when it leaves. You can also flip it with the LED_ON and LED_OFF commands in the Monitor screen. No LED is soldered to the Uno header pins of this board, so the code drives D13 (GPIO18). Add an external LED there if you want to see it.

LED Pin ESP32 Uno Pin
Long leg (anode), via the 220 ohm resistor D13 (GPIO18)
Cathode (short leg) GND

Good to know: D13 (GPIO18) is a plain I/O pin with no boot role, so an LED here never blocks an upload. ESP32 Uno pins give 3.3V, which is enough for a normal LED with a 220 ohm resistor.

Upload and Test the ESP32 Uno Code

When this part is done, the board is on the air and waits for your phone. Both sketches below create the same nine screens; only the radio type is different.

Step 1 — Prepare Arduino IDE

  1. First time with this board? Set it up with ESP32 Uno - Getting Started before you continue.
  2. Build the circuit by following the wiring table above. Skip it if you do not need the status LED.
  3. Plug in the USB Type-C cable between the ESP32 Uno and your computer.
  4. Select the board in Arduino IDE: pick ESP32 Dev Module and the COM port of the board (CH340).
  5. Open Library Manager with the Libraries icon in the left bar.
  6. Search "DIYables Bluetooth" and locate the library made by DIYables.
  7. Press Install next to it.
  • Search for DIYables Bluetooth created by DIYables and click the Install button.
∞
Newbiely | Arduino IDE 2.3.8
──
☐
✕
File
Edit
Sketch
Tools
Help
ESP32 Dev Module
Library Manager
Type:
All
Topic:
All
DIYables Bluetooth by DIYables
A comprehensive Bluetooth library that connects Arduino to the DIYables Bluetooth STEM mobile app (Android & iOS) via BLE and Classic Bluetooth. Features 11 built-in apps: Monitor, Chat, Digital Pin Control, Joystick, Sliders, Plotter, Analog Gauge, Rotator, Temperature, RTC, and Data Table. Clean architecture with platform abstraction. Works with Arduino Uno R4 WiFi, Arduino Nano 33 BLE, ESP32, and other BLE-capable boards. No mobile coding required. More info
1.0.1
INSTALL
Newbiely.ino
···
1 void setup() {
Output
Serial Monitor
Ln 1, Col 1
ESP32 Dev Module on COM15
1
  1. Accept the extras: the IDE lists more libraries it needs. Press Install All to add them too.

WARNING

Nine app libraries plus a Bluetooth stack make a big binary. Set this before the first upload: Tools > Partition Scheme and choose "Huge APP (3MB No OTA/1MB SPIFFS)". The default layout gives the program only about 1.2 MB, and the build stops with a "Sketch too big" error.

Step 2 — Option A: ESP32 Uno Classic Bluetooth Sketch (Android only)

Good to know: iPhones cannot talk Classic Bluetooth. If anyone will use iOS, jump to Option B.

  1. Load the sketch: open File Examples DIYables Bluetooth Esp32Bluetooth_MultipleApps, or paste the code below into a new sketch.
/* * This ESP32 Uno code was developed by newbiely.com * * This ESP32 Uno code is made available for public use without any restriction * * For comprehensive instructions and wiring diagrams, please visit: * https://newbiely.com/tutorials/esp32-uno/esp32-uno-diyables-bluetooth-app-multiple-apps */ /* * DIYables Bluetooth Library - ESP32 Uno Classic Bluetooth Multiple Apps Example * Pairs with the DIYables Bluetooth STEM app on Android * Note: iPhones cannot use Classic Bluetooth. Take the BLE sketch for iOS. * * One sketch, nine app screens on a single Classic Bluetooth link: * - Monitor: text log from the board, commands from the phone * - Chat: text messages in both directions * - Slider: two sliders, range 0-255 * - Joystick: X and Y position from a touch pad * - Temperature: thermometer screen * - Plotter: live graph with 3 lines * - Table: name/value rows that refresh live * - Analog Gauge: round dial with a needle * - Rotator: turning knob for an angle * - Pin Control: switch and read digital pins * * What the library does for you: * - Every app shares the one Bluetooth connection * - Each incoming message goes to the right app handler * - You only write short callbacks, no protocol parsing * - The link details stay inside the library * - Made for the DIYables Bluetooth mobile app * * Board: ESP32 Uno form board (Classic Bluetooth radio) * * Note: the sketch is big, so pick Tools > Partition Scheme > * "Huge APP (3MB No OTA/1MB SPIFFS)" in Arduino IDE * * How to use: * 1. Flash this sketch to the ESP32 Uno * 2. Open the Serial Monitor at 115200 baud * 3. Pair the phone, then start the DIYables Bluetooth App * 4. Pick "ESP32 Multi-App" in the device list * 5. Open each app screen and try it * * Tutorial: https://diyables.io/bluetooth-app * Author: DIYables */ #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> // radio object plus the server that hosts every app DIYables_Esp32Bluetooth bluetooth("ESP32 Multi-App"); DIYables_BluetoothServer bluetoothServer(bluetooth); // the nine app objects and their settings 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); // D13 (GPIO18) drives an external status LED (no built-in LED on the Uno headers) const int LED_PIN = 18; // D13 (GPIO18) // latest values shared between the apps 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; // millis() time stamps for each periodic send 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"); // status LED on D13 (GPIO18) starts OFF pinMode(LED_PIN, OUTPUT); digitalWrite(LED_PIN, LOW); // start the radio and the app server bluetoothServer.begin(); // add each app so the phone can see it 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()); // plot title, axes, range and line names bluetoothPlotter.setPlotTitle("Sensor Data"); bluetoothPlotter.setAxisLabels("Time", "Value"); bluetoothPlotter.setYAxisRange(-1.5, 1.5); bluetoothPlotter.setMaxSamples(100); bluetoothPlotter.setLegendLabels("Sine", "Cosine", "Random"); // the row names shown in the Table app 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"); // hook up the handlers for every app setupCallbacks(); Serial.println("Waiting for Bluetooth connection..."); } void setupCallbacks() { // ---- phone links or drops ---- 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); }); // ---- text commands from the Monitor screen ---- 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)"); } }); // ---- Chat screen messages ---- 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"); } }); // ---- Slider moves ---- bluetoothSlider.onSliderValue([](int slider1, int slider2) { currentSlider1 = slider1; currentSlider2 = slider2; Serial.print("Slider 1: "); Serial.print(slider1); Serial.print(", Slider 2: "); Serial.println(slider2); // slider 1 also sets the needle of the gauge currentGaugeValue = map(slider1, 0, 255, 0, 100); bluetoothGauge.send(currentGaugeValue); // copy the new values into the Table rows 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); }); // ---- Joystick moves ---- bluetoothJoystick.onJoystickValue([](int x, int y) { currentJoystickX = x; currentJoystickY = y; Serial.print("Joystick X: "); Serial.print(x); Serial.print(", Y: "); Serial.println(y); // copy the new values into the Table rows bluetoothTable.sendValueUpdate("Joystick X", String(x)); bluetoothTable.sendValueUpdate("Joystick Y", String(y)); }); bluetoothJoystick.onGetConfig([]() { bluetoothJoystick.send(currentJoystickX, currentJoystickY); }); // ---- Temperature screen asks for a value ---- bluetoothTemperature.onTemperatureRequest([]() { bluetoothTemperature.send(currentTemperature); }); // ---- Plotter screen opens ---- bluetoothPlotter.onDataRequest([]() { Serial.println("Plotter data requested"); }); // ---- Table screen asks for its rows ---- bluetoothTable.onDataRequest([]() { Serial.println("Table data requested"); bluetoothTable.sendTableStructure(); updateAllTableValues(); }); // ---- Gauge screen asks for a value ---- bluetoothGauge.onValueRequest([]() { bluetoothGauge.send(currentGaugeValue); }); // ---- Rotator knob turns ---- 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; } // ---- heartbeat line to the Monitor, 5 s period ---- if (millis() - lastMonitorUpdate >= 5000) { lastMonitorUpdate = millis(); messageCount++; bluetoothMonitor.send("[INFO] Heartbeat #" + String(messageCount) + " - Uptime: " + String(millis() / 1000) + "s"); } // ---- new fake temperature, 2 s period ---- if (millis() - lastTempUpdate >= 2000) { lastTempUpdate = millis(); // random walk of +/-5 degrees around 25 °C 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"); } // ---- three plot points, 100 ms period ---- 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; } // ---- refresh uptime row, 5 s period ---- 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"); } // ---- slow sine value for the gauge, 3 s period ---- 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); }
  1. Check the partition: Tools > Partition Scheme must show "Huge APP (3MB No OTA/1MB SPIFFS)".
  2. Flash it: press the right-arrow Upload button and wait for "Done uploading".
  3. Open the Serial Monitor at 115200 baud. After a reset you get:
∞
Newbiely | Arduino IDE 2.3.8
──
☐
✕
File
Edit
Sketch
Tools
Help
ESP32 Dev Module
Newbiely.ino
···
8 Serial.println("Hello World!");
Output
Serial Monitor
Message (Enter to send message to 'ESP32 Dev Module' on 'COM15')
New Line
9600 baud
DIYables Bluetooth - ESP32 Multiple Apps Example Registered apps: 9 Waiting for Bluetooth connection...
Ln 11, Col 1
ESP32 Dev Module on COM15
2

Step 2 — Option B: ESP32 Uno BLE Sketch (Android and iOS)

  1. Load the sketch: open File Examples DIYables Bluetooth Esp32BLE_MultipleApps, or paste the code below.
/* * This ESP32 Uno code was developed by newbiely.com * * This ESP32 Uno code is made available for public use without any restriction * * For comprehensive instructions and wiring diagrams, please visit: * https://newbiely.com/tutorials/esp32-uno/esp32-uno-diyables-bluetooth-app-multiple-apps */ /* * DIYables Bluetooth Library - ESP32 Uno BLE Multiple Apps Example * Pairs with the DIYables Bluetooth STEM app on Android and iOS * * One sketch, nine app screens on a single BLE link: * - Monitor, Chat, Slider, Joystick, Temperature, Plotter, * Table, Analog Gauge, Rotator * * Board: ESP32 Uno form board (BLE radio) * * Note: the sketch is big, so pick Tools > Partition Scheme > * "Huge APP (3MB No OTA/1MB SPIFFS)" in Arduino IDE * * Optional: external LED + 220 ohm resistor on D13 (GPIO18), * the board has no built-in LED on the Uno headers * * How to use: * 1. Flash this sketch to the ESP32 Uno * 2. Open the Serial Monitor at 115200 baud * 3. Start the DIYables Bluetooth App on the phone * 4. Pick "ESP32BLE Multi-App" in the scan list * 5. Open each app screen and try it * * Tutorial: https://diyables.io/bluetooth-app * Author: DIYables */ #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> // name and UUIDs the phone app scans for 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"; // radio object plus the server that hosts every app DIYables_Esp32BLE bluetooth(DEVICE_NAME, SERVICE_UUID, TX_UUID, RX_UUID); DIYables_BluetoothServer bluetoothServer(bluetooth); // the nine app objects and their settings 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); // latest values shared between the apps 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; // millis() time stamps for each periodic send 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"); // status LED on D13 (GPIO18) starts OFF pinMode(18, OUTPUT); digitalWrite(18, 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(18, 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(18, 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(18, HIGH); bluetoothMonitor.send("LED turned ON"); } else if (message == "LED_OFF") { digitalWrite(18, 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); }
  1. Same partition rule: confirm it still says "Huge APP (3MB No OTA/1MB SPIFFS)".
  2. Flash it to the ESP32 Uno with the Upload arrow.
  3. Watch the Serial Monitor at 115200 baud:
∞
Newbiely | Arduino IDE 2.3.8
──
☐
✕
File
Edit
Sketch
Tools
Help
ESP32 Dev Module
Newbiely.ino
···
8 Serial.println("Hello World!");
Output
Serial Monitor
Message (Enter to send message to 'ESP32 Dev Module' on 'COM15')
New Line
9600 baud
DIYables Bluetooth - ESP32 BLE Multiple Apps Example Registered apps: 9 Waiting for Bluetooth connection...
Ln 11, Col 1
ESP32 Dev Module on COM15
2
  1. Pro tip: the first build of this sketch can take a few minutes, because the IDE compiles nine app libraries and the BLE stack. Later builds are much faster, so do not stop the first one halfway.

Step 3 — Connect the Phone App

At the end of this step, all nine screens on your phone talk to the board.

  1. Get the app: DIYables Bluetooth App for Android or iOS.
  2. Classic sketch only — pair first: on the Android phone open Settings > Bluetooth, switch Bluetooth on, tap "ESP32 Multi-App", confirm (no PIN), and wait for "Paired". With the BLE sketch, skip this.
  3. Launch the app and allow the requested access on first start:
    • Nearby Devices (Android 12 or newer) or Bluetooth (iOS) — used to find and link devices
    • Location (Android 11 or older) — old Android versions need it to scan for BLE
  • Check that phone Bluetooth is on.
  • Scan: tap Connect on the home screen. The list shows both BLE and Classic devices.
  • DIYables Bluetooth App - Home Screen with Scan Button
    1. Pick your board: "ESP32 Multi-App" for Classic, or "ESP32BLE Multi-App" for BLE. Once linked, the app jumps back to the home screen and the LED on D13 (GPIO18) lights up.
    2. Look at the buttons: the nine screens set up in the sketch respond. Other buttons stay visible but do nothing with this sketch.
    DIYables Bluetooth App - Home Screen with Multiple Apps
    1. Try each screen: tap through Monitor, Chat, Slider, Joystick, Temperature, Plotter, Table, Analog Gauge and Rotator in any order. They all ride on the same link.
    2. Pro tip: in the Monitor screen, type HEAP. The board answers with its free memory, which is a quick health check before you add your own code. The gear icon on the home screen hides unused buttons; the DIYables Bluetooth App User Manual explains every screen.

    Back in the Serial Monitor, each action on the phone shows up as a line:

    ∞
    Newbiely | Arduino IDE 2.3.8
    ──
    ☐
    ✕
    File
    Edit
    Sketch
    Tools
    Help
    ESP32 Dev Module
    Newbiely.ino
    ···
    8 Serial.println("Hello World!");
    Output
    Serial Monitor
    Message (Enter to send message to 'ESP32 Dev Module' on 'COM15')
    New Line
    9600 baud
    Bluetooth connected! Monitor cmd: HELP Chat: hi there Slider 1: 173, Slider 2: 40 Joystick X: -22, Y: 81 Rotator: 245.50° Table data requested
    Ln 11, Col 1
    ESP32 Dev Module on COM15
    2

    The Monitor screen knows five commands: HELP, STATUS (all current values), LED_ON, LED_OFF (the LED on D13) and HEAP (free memory).

    How the Sketch Works

    Here is what each block of the ESP32 Uno sketch does.

    Code Part What It Does
    DIYables_Esp32BLE or DIYables_Esp32Bluetooth Starts the radio with the name the phone sees
    DIYables_BluetoothServer Hosts all apps on the one link and routes each message
    addApp() x 9 Registers each screen so the phone can show it
    setupCallbacks() Joins the screens, for example slider 1 sets the gauge and fills two table rows
    loop() timers Send the heartbeat, temperature, plot points, table and gauge values at their own pace
    pinMode(18) and digitalWrite(18) Drive the status LED on D13 (GPIO18)

    The result is a dashboard where input screens (Slider, Joystick, Rotator) feed output screens (Gauge, Table) with no extra code from you. In loop(), nothing is sent while no phone is linked, so the board does not waste time.

    ESP32 Uno vs UNO R4 for This Project

    This table lists what changes when you move the multi-app dashboard from the UNO R4 WiFi to the ESP32 Uno.

    Item Arduino UNO R4 WiFi ESP32 Uno
    Logic voltage 5V 3.3V
    Bluetooth radio BLE only BLE and Classic Bluetooth
    Phone support Android and iOS BLE on Android and iOS, Classic on Android
    Library platform file DIYables_ArduinoBLE DIYables_Esp32BLE or DIYables_Esp32Bluetooth
    Status LED LED_BUILTIN (on board) External LED on D13 (GPIO18)
    Extra IDE setting None Partition Scheme set to Huge APP
    Arduino IDE board Arduino UNO R4 WiFi ESP32 Dev Module

    The phone side is the same, and the app callbacks look almost the same. The main code change is the platform include and the LED pin. Start from the Esp32 examples on this page instead of editing the UNO R4 sketch.

    Shape the Dashboard for Your Project

    The example sends fake data. In a real build, you keep its layout and change what each screen sends or does. The blocks below are the parts you reuse.

    Create and Register the Apps

    Each screen is one object. You build it, then hand it to the server with addApp().

    // headers for every screen you want #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> // one object per screen, with its scale or mode 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); // inside setup(): hand every screen to the server 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);

    Give Each Screen a Handler

    A handler runs only when its own screen sends something from the phone.

    void setup() { // text typed in the Monitor screen bluetoothMonitor.onMonitorMessage([](const String& message) { Serial.println("Monitor received: " + message); }); // chat line in, echo line out bluetoothChat.onChatMessage([](const String& message) { Serial.println("Chat: " + message); bluetoothChat.send("Echo: " + message); }); // new slider positions bluetoothSlider.onSliderValue([](int slider1, int slider2) { Serial.println("Slider: " + String(slider1)); }); // joystick pad position bluetoothJoystick.onJoystickValue([](int x, int y) { Serial.println("Joystick: X=" + String(x) + " Y=" + String(y)); }); // knob angle in degrees bluetoothRotator.onRotatorAngle([](float angle) { Serial.println("Rotator: " + String(angle) + "°"); }); }

    One Timer per Output Screen

    Use millis() timers, not long delay() calls, so bluetoothServer.loop() keeps running between sends.

    void loop() { bluetoothServer.loop(); unsigned long now = millis(); // Monitor line, 5 s period static unsigned long lastMonitor = 0; if (now - lastMonitor >= 5000) { lastMonitor = now; bluetoothMonitor.send("Uptime: " + String(now / 1000) + "s"); } // Temperature value, 2 s period static unsigned long lastTemp = 0; if (now - lastTemp >= 2000) { lastTemp = now; bluetoothTemperature.send(readTemperature()); } // Plotter point, 100 ms period for a smooth line static unsigned long lastPlotter = 0; if (now - lastPlotter >= 100) { lastPlotter = now; bluetoothPlotter.send(readSensorValue()); } // Table rows, 5 s period static unsigned long lastTable = 0; if (now - lastTable >= 5000) { lastTable = now; updateTableValues(); } // Gauge needle, 3 s period static unsigned long lastGauge = 0; if (now - lastGauge >= 3000) { lastGauge = now; bluetoothGauge.send(readGaugeValue()); } delay(10); }

    Keep Only the Screens You Use

    Fewer screens mean a smaller binary, less memory and less radio traffic. This cut-down version shows three screens only.

    // three screens: Monitor, Slider and Temperature #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() { // ... radio and server start here ... bluetoothServer.addApp(&bluetoothMonitor); bluetoothServer.addApp(&bluetoothSlider); bluetoothServer.addApp(&bluetoothTemperature); // the phone now lists just these three }

    Fill the Screens When the Phone Links

    Send first values in the connect handler, so no screen starts blank.

    bluetoothServer.setOnConnected([]() { Serial.println("Bluetooth connected!"); // push the latest numbers right away bluetoothTemperature.send(currentTemperature); bluetoothGauge.send(currentGaugeValue); bluetoothRotator.send(currentAngle); }); bluetoothServer.setOnDisconnected([]() { Serial.println("Bluetooth disconnected!"); });

    Using Many Screens on the Phone

    After this part, you can plan which screens send and which ones receive.

    Topic How It Works
    Home screen One button for each registered app
    Switching Tap a button to open it, use back or home to pick another one
    Background The ESP32 Uno keeps all apps running, whatever screen is open
    Input screens Slider, Joystick, Rotator and Chat send from the phone to the board
    Output screens Monitor, Temperature, Plotter, Table and Gauge send from the board to the phone
    Two-way screens Chat and Monitor both send and receive text

    Ready-Made Dashboard Recipes

    Each recipe mixes a few screens for one job. Functions like readTemperature(), setMotors() or setLights() are placeholders for your own sensor and driver code.

    Recipe 1 — Weather Station

    // big reading on the thermometer screen DIYables_BluetoothTemperature bluetoothTemperature(-10.0, 50.0, "°C"); // every value as a row DIYables_BluetoothTable bluetoothTable; // trend lines over time DIYables_BluetoothPlotter bluetoothPlotter; // text log of each update DIYables_BluetoothMonitor bluetoothMonitor; void setup() { // ... radio and server start here ... 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(); // thermometer bluetoothTemperature.send(temp); // table rows bluetoothTable.sendValueUpdate("Temperature", String(temp, 1) + " °C"); bluetoothTable.sendValueUpdate("Humidity", String(hum, 0) + "%"); bluetoothTable.sendValueUpdate("Pressure", String(press, 0) + " hPa"); // graph, pressure scaled down to fit bluetoothPlotter.send(temp, hum, press / 10.0); // one log line bluetoothMonitor.send("Weather update: " + String(temp, 1) + "°C, " + String(hum, 0) + "%, " + String(press, 0) + "hPa"); } delay(10); }

    Recipe 2 — Robot Remote

    // drive direction DIYables_BluetoothJoystick bluetoothJoystick; // top speed limit DIYables_BluetoothSlider bluetoothSlider(0, 100, 5); // arm or turret angle DIYables_BluetoothRotator bluetoothRotator(ROTATOR_MODE_LIMITED, 0, 180); // status text DIYables_BluetoothMonitor bluetoothMonitor; // battery dial DIYables_BluetoothAnalogGauge bluetoothGauge(0.0, 100.0, "%"); int maxSpeed = 50; void setup() { // ... radio and server start here ... 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(); // battery level, 10 s period static unsigned long lastBattery = 0; if (millis() - lastBattery >= 10000) { lastBattery = millis(); float battery = readBatteryLevel(); bluetoothGauge.send(battery); } delay(10); }

    Recipe 3 — Home Control Hub

    // typed commands DIYables_BluetoothChat bluetoothChat; // dimmer level DIYables_BluetoothSlider bluetoothSlider(0, 100, 1); // state of each device DIYables_BluetoothTable bluetoothTable; // room temperature, 10 to 40 °C scale DIYables_BluetoothTemperature bluetoothTemperature(10.0, 40.0, "°C"); // activity log DIYables_BluetoothMonitor bluetoothMonitor; void setup() { // ... radio and server start here ... bluetoothTable.addRow("Living Room Light"); bluetoothTable.addRow("Bedroom Light"); bluetoothTable.addRow("AC Status"); bluetoothTable.addRow("Door Lock"); bluetoothTable.addRow("Security"); bluetoothChat.onChatMessage([](const String& msg) { String message = msg; 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) + "%"); }); }

    Linking Screens and Saving Memory

    When several screens show the same data, connect them and store the data in one place.

    One Input, Many Outputs

    In this snippet, each joystick push shifts a dot. The plotter draws its path, the table shows the numbers and the monitor logs it.

    // point that the joystick moves around float posX = 0, posY = 0; bluetoothJoystick.onJoystickValue([](int x, int y) { posX += x * 0.1; posY += y * 0.1; // path on the graph bluetoothPlotter.send(posX, posY); // numbers in the table bluetoothTable.sendValueUpdate("Position X", String(posX, 1)); bluetoothTable.sendValueUpdate("Position Y", String(posY, 1)); // log line bluetoothMonitor.send("Pos: (" + String(posX, 1) + ", " + String(posY, 1) + ")"); });

    Read Once, Send Everywhere

    Read all sensors one time into a struct, then send the same values to each screen. You do fewer sensor reads and keep less data in RAM.

    // one place for all sensor readings 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(); // thermometer bluetoothTemperature.send(sensorData.temperature); // table rows bluetoothTable.sendValueUpdate("Temperature", String(sensorData.temperature, 1) + " °C"); bluetoothTable.sendValueUpdate("Humidity", String(sensorData.humidity, 0) + "%"); // graph bluetoothPlotter.send(sensorData.temperature, sensorData.humidity); // dial shows pressure bluetoothGauge.send(sensorData.pressure); // log line bluetoothMonitor.send("Sensors updated at " + String(sensorData.lastUpdate / 1000) + "s"); }

    Choosing BLE or Classic on the ESP32 Uno

    Use this table to pick the right sketch for your phone and your data rate.

    Point BLE sketch (Esp32BLE_MultipleApps) Classic sketch (Esp32Bluetooth_MultipleApps)
    iPhone Yes No
    Android Yes Yes
    Power draw Low Higher
    Data rate Lower Higher
    Pairing step No, scan and connect Yes, in phone settings
    Name in the scan list ESP32BLE Multi-App ESP32 Multi-App
    Best for Battery builds and mixed phones Fast plotters on Android

    Good to know: with nine screens sending, radio bandwidth is the first limit you hit. Classic handles a 100 ms plotter plus other screens more easily. For battery power or iPhones, stay with BLE and spread the timers apart.

    Memory Check Helper

    Print the free memory now and then. If the number keeps going down, some part of your code leaks memory.

    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("======================="); } // inside loop(): run the check every 30 s static unsigned long lastDebug = 0; if (millis() - lastDebug >= 30000) { lastDebug = millis(); debugMultiApp(); }

    Build Ideas

    Pick the screens that fit the job.

    Project Screens to Use
    Home weather box Temperature, Plotter, Table, Monitor
    Car or bike dashboard Gauge, Rotator, Plotter, Monitor
    Robot remote Joystick, Slider, Rotator, Monitor, Gauge
    Camera pan and tilt Joystick, Rotator, Monitor
    Small CNC panel Slider, Rotator, Monitor, Table
    Classroom science kit Plotter, Temperature, Gauge, Table, Monitor
    Plant grow tent Temperature, Slider, Plotter, Table, Monitor
    Fish tank Temperature, Slider, Table, Monitor
    Sun-following panel Rotator, Gauge, Table, Plotter
    Power meter Gauge, Plotter, Table, Monitor

    Try These Next

    Each single-screen guide goes deeper into one app API on the ESP32 Uno:

    1. ESP32 Uno - Bluetooth Monitor - text log and commands
    2. ESP32 Uno - Bluetooth Chat - two-way messages
    3. ESP32 Uno - Bluetooth Slider - set values with sliders
    4. ESP32 Uno - Bluetooth Table - name and value rows

    Challenge: add a 220 ohm LED on D13 (GPIO18) and make Chat switch it with the words "on" and "off", then show its state in a new Table row.

    Where to Get Help

    • Read the API reference that ships with the DIYables Bluetooth library
    • Post your question in the Arduino community forum

    Troubleshooting

    Problem Possible Cause Solution
    No COM port shows up CH340 driver missing or a charge-only USB cable Install the CH340 driver and use a USB cable that carries data
    Upload stops at Failed to connect or Waiting for download Board did not enter download mode Hold the BOOT button while the upload starts, then let go
    Sketch too big error Default partition gives only about 1.2 MB Set Tools > Partition Scheme to Huge APP (3MB No OTA/1MB SPIFFS), or remove screens
    ESP32 Multi-App missing in the app list Classic sketch used but phone not paired Pair the board in Android Bluetooth settings first, then scan in the app
    ESP32BLE Multi-App missing in the scan Bluetooth or Location off on the phone Turn on Bluetooth, and on Android 11 or older also Location, then press RESET and scan again
    A screen button does nothing That app was never passed to addApp() Add the object with bluetoothServer.addApp(&name) in setup()
    Plotter lags or the link drops Too many sends at the same moment Make the timers longer and give them different periods
    Board reboots after a while Low free heap or weak USB power Remove unused screens, check HEAP in the Monitor screen, and use a good USB port
    Status LED stays dark LED not on D13 or wired backwards Long leg through 220 ohm to D13 (GPIO18), short leg to GND
    Unreadable text in the Serial Monitor Baud rate does not match Set the Serial Monitor to 115200

    FAQ

    Which sketch should I flash, BLE or Classic?

    Flash the BLE sketch if any iPhone will connect, or if you do not want a pairing step. Flash the Classic sketch if only Android phones are used and you need more data speed. Both sketches create the same nine screens on the ESP32 Uno.

    Do I need any parts to try the ESP32 Uno multi-app dashboard?

    No. The radio is inside the board, and all values in the example are made by the code. You only need the board and a USB Type-C cable. The LED on D13 is optional.

    Why is the Huge APP partition needed for this example?

    Nine app libraries and a Bluetooth stack produce a large program. The default partition gives about 1.2 MB for code, which is too little. Huge APP raises that to about 3 MB, but you lose over-the-air (OTA) updates.

    Can I run all nine apps and still add my own sensors?

    Yes, but watch memory and radio traffic. Type HEAP in the Monitor screen to see free memory. If the board resets or the plotter lags, drop screens you do not need and make the send periods longer.

    Why does the code use GPIO18 for the LED?

    The ESP32 Uno has no built-in LED on the Uno headers. The UNO R4 sketch uses LED_BUILTIN, so the ESP32 Uno version moves it to an external LED on D13, which is GPIO18. D13 is a safe pin with no boot role.

    Can I reuse my UNO R4 multi-app code on the ESP32 Uno?

    Only partly. The app objects and callbacks are almost the same, but the UNO R4 sketch uses DIYables_ArduinoBLE and LED_BUILTIN. On the ESP32 Uno you use DIYables_Esp32BLE or DIYables_Esp32Bluetooth and pin 18. Starting from the Esp32 example is faster.

    Can a sensor on A0 or A1 send values to the gauge while Bluetooth runs?

    Yes. The ADC2 limit on A0 (GPIO2) and A1 (GPIO4) applies to WiFi, not to Bluetooth. Add analogSetAttenuation(ADC_11db); in setup() and expect values from 0 to 4095. Keep the sensor signal at 3.3V or less.

    Do UNO shields fit the ESP32 Uno for this dashboard?

    Most 3.3V-safe shields fit, because the headers have the Uno shape. ESP32 Uno pins are not 5V tolerant, so a shield that sends 5V signals into the board needs a level shifter. Also keep in mind that A4 and A5 are not I2C on this board; use the SDA and SCL header pins.

    Can I use WiFi and Bluetooth at the same time on the ESP32 Uno?

    The chip can run both, but they share one antenna and the memory. With nine Bluetooth screens, adding WiFi often leaves too little heap. For a web dashboard, look at the ESP32 Uno - DIYables Web Apps guide and pick one radio per project.

    What if my dashboard project needs more pins than the Uno headers give?

    The ESP32 Uno has a few extra holes near the power header. GPIO32 and GPIO33 work as I/O, PWM and analog on ADC1, so they still read analog values while WiFi is on. GPIO15 works as I/O, PWM and analog on ADC2. Solder pin headers and write the GPIO number in code. For a dashboard, GPIO32/33 are a good fit for analog sensors that feed the Gauge or Plotter. Two warnings: GPIO15 and GPIO0 (the IO0 pin at the unused Uno NC position) are boot strapping pins, and a wrong connection can stop the board from booting or uploading, so use GPIO32/33 first. Never use the six holes under the digital header (SD0 to SD3, CMD, CLK = GPIO6 to GPIO11); they are wired to the flash chip, and using them crashes the board.

    ※ OUR MESSAGES

    • As freelancers, We are AVAILABLE for HIRE. See how to outsource your project to us
    • Please feel free to share the link of this tutorial. However, Please do not use our content on any other websites. We invested a lot of effort and time to create the content, please respect our work!