Nothing on this page will run on a bare S3. The page below is unchanged — read it if you are planning ahead or using a different board.
Classic Bluetooth serial
Four lines of code and a phone terminal app, which is why it is still in every tutorial. Also - only the original ESP32 has it, no iPhone can see it, and it eats a megabyte of flash. Know that before you design around it.
Three ways to get text to a phone
When it is still the right answer
A bench tool you use yourself, on an Android phone, on a board you already own. It takes four lines and it works. There is no shame in that.
When it is not
Anything you hand to somebody else. The iPhone gap alone rules it out, and the chip situation is worse: every ESP32 released since the original has BLE only, so a design built on classic Bluetooth cannot follow the parts you can actually buy. Reach for the Nordic UART service over BLE instead — it is a little more code and it has no ceiling.
The code
This really is the whole thing. It behaves exactly like Serial, which is the appeal and also the reason people build products on it before finding out what it costs.
#include <BluetoothSerial.h>
BluetoothSerial SerialBT;
void setup() {
Serial.begin(115200);
SerialBT.begin("Robot"); // the name that shows up when pairing
}
void loop() {
if (SerialBT.available()) {
char c = SerialBT.read();
Serial.write(c); // forward to the USB console
SerialBT.printf("got %c\n", c);
}
}Compiles only on the original ESP32. On an S3, C3, C6 or P4 the header does not exist, because the hardware does not.
MicroPython has no classic Bluetooth at all, so the replacement is the Nordic UART service over BLE. aioble makes it about as short as the Arduino version above, and it works on iPhones.
import aioble, bluetooth, asyncio
UART = bluetooth.UUID('6E400001-B5A3-F393-E0A9-E50E24DCCA9E')
RX = bluetooth.UUID('6E400002-B5A3-F393-E0A9-E50E24DCCA9E')
TX = bluetooth.UUID('6E400003-B5A3-F393-E0A9-E50E24DCCA9E')
svc = aioble.Service(UART)
rx = aioble.Characteristic(svc, RX, write=True, capture=True)
tx = aioble.Characteristic(svc, TX, notify=True)
aioble.register_services(svc)
async def main():
while True:
conn = await aioble.advertise(250_000, name='Robot',
services=[UART])
while conn.is_connected():
_, data = await rx.written()
tx.notify(conn, b'got ' + data)
asyncio.run(main())This UUID is a convention rather than a standard, and every BLE terminal app on both phone platforms already knows it. Use it rather than inventing one.
When it does not work
Your chip has no classic Bluetooth. Only the original ESP32 does. Use BLE with the Nordic UART service instead - it works everywhere and on both phone platforms.
Apple does not expose classic serial profiles to apps. There is no workaround. Any project that must reach an iPhone needs BLE.
The classic stack is over a megabyte before your code exists. Enabling both classic and BLE at once rarely fits a 4 MB board with OTA.
The phone remembers the link key and the board forgot it after a reflash. Remove the device from the phone's Bluetooth settings and pair again.
Both Bluetooth radios covered. Next: the Wi-Fi radio used without a network at all, two milliseconds a message.
ESP-NOW →Edit this page — content/esp32/classic-bluetooth-serial.mdx
Discuss this article
Ask about this page. The answer stays here, on the page it belongs to, for whoever hits the same wall next.