Bluetooth, simulated
Pair a phone with your ESP32 or HC-05 and talk to it. The phone is the Bluetooth pane in Mokxi, not a real radio.
Simulated Bluetooth: a phone in Mokxi, not a real radio. Nothing in Mokxi transmits anything, and no real phone can see a board here. What is faithful is the Arduino code you write: the ESP32 core's own Bluetooth examples run unchanged, and the pane does what the phone apps those tutorials use do.
Where the phone is
Press Run, then open the Bluetooth tab beside Serial (on a phone it is under More). The pane lists every device the phone can see:
- an ESP32 running
BluetoothSerial(Bluetooth Classic, the classic ESP32 only), - an ESP32 advertising over Bluetooth LE (the ESP32, ESP32-S3, C3 and C6 with an Arduino sketch, and the ESP32-C3 and C6 with MicroPython),
- every HC-05 and HC-06 module in the circuit, on any board.
Pick one, pair, connect, and the right side of the pane becomes the app.
Bluetooth serial: the ESP32's SerialBT
The tutorial sketch runs as written:
#include "BluetoothSerial.h"
BluetoothSerial SerialBT;
void setup() { Serial.begin(115200); SerialBT.begin("ESP32test"); }
void loop() {
if (Serial.available()) SerialBT.write(Serial.read());
if (SerialBT.available()) Serial.write(SerialBT.read());
}
ESP32test shows up in the pane. Press Pair, then Connect, and the
pane is a terminal like the "Serial Bluetooth Terminal" app: type a line and
send it (with CR+LF, LF, CR or nothing on the end), and whatever the sketch
writes to SerialBT appears under it.
What the sketch sees is what it sees on a real board:
register_callback()getsESP_SPP_INIT_EVTandESP_SPP_START_EVTfrombegin(),ESP_SPP_SRV_OPEN_EVTwhen the phone connects (with the phone's address),ESP_SPP_DATA_IND_EVTfor each line it sends, andESP_SPP_CLOSE_EVTwhen it disconnects;hasClient()andconnected()follow the link, andwrite()returns 0 with nobody connected;available(),read(),peek(),readString()and the rest ofStreamread what the phone sent, andonData()takes it instead when it is set.
Pairing the way your sketch asks
- Just works is the default: Pair succeeds at once.
- A PIN: after
SerialBT.disableSSP()andSerialBT.setPin("1234"), the pane asks for the PIN and a wrong one fails, callingonAuthComplete(false). - Numeric comparison: with
SerialBT.enableSSP()andonConfirmRequest(), the pane shows a six-digit code and the sketch's callback gets the same number. Both sides have to say yes: the pane's Pair button, and the sketch'sconfirmReply(true). ThenonAuthComplete(true)runs. - With
enableSSP(true, false)the board types the code the pane shows (onKeyRequest(), thenrespondPasskey()); withenableSSP(false, true)the board shows it and you type it into the pane.
Forget unpairs, as a phone's settings do.
Bluetooth LE: the ESP32's BLE server
The ESP32 core's BLE_server, BLE_notify and BLE_uart examples run
unchanged. BLEDevice::startAdvertising() makes the board visible; the pane is
then a scanner like nRF Connect:
- Connect, and the services and characteristics the sketch made are listed with their UUIDs, their names where a phone knows them (the Nordic UART Service, Battery Level and so on) and their properties;
- Read runs the characteristic's
onRead()first and shows the value as text and as hex; - Write sends text or hex, and the sketch's
onWrite()runs with it; - Notify subscribes, and every
notify()from then on shows up with its value. Before you subscribe,notify()reaches nobody, as on a real phone.
onConnect() and onDisconnect() run when the pane connects and disconnects.
The board stops advertising once connected, so it disappears from the list
after a disconnect until the sketch calls startAdvertising() again. That is
why the examples do it in loop().
Bluetooth LE in MicroPython: the ESP32-C3 and C6
On an ESP32-C3 or ESP32-C6 running MicroPython, the bluetooth module works as
on the board, because the board's own Bluetooth software runs: only the radio
chip's part is stood in for. A GATT server like this one shows up in the pane:
import bluetooth
b = bluetooth.BLE()
b.active(True)
F = bluetooth.FLAG_READ | bluetooth.FLAG_WRITE | bluetooth.FLAG_NOTIFY
((h,),) = b.gatts_register_services(((bluetooth.UUID(0x180F), ((bluetooth.UUID(0x2A19), F),)),))
b.gatts_write(h, b'42')
b.gap_advertise(100000, bytes([2, 1, 6, 8, 9]) + b'mpy-ble')
- The pane lists the board by the name in its advertising data.
- Connect, and your
irqhandler gets_IRQ_CENTRAL_CONNECT(1). The pane then finds the services the way a phone app does, so it lists the board's Generic Access and Generic Attribute services before yours. - Read shows what
gatts_write()stored, Write fires_IRQ_GATTS_WRITE(3), and after Notify, everygatts_notify()shows up. aiobleworks too: add micropython-lib'saioblefolder to your project, as you would copy it onto a real board.gap_scan()ends without finding anything, because the phone does not advertise, and the board cannot connect to the phone as a central.
HC-05 and HC-06 on any board
The modules are serial bridges: whatever the phone sends comes out of TXD, at
the module's baud rate, bit by bit, and what the board sends into RXD goes to
the phone. In the pane each one is listed by its name. Pair with its PIN
(1234 until AT+PSWD or AT+PIN changes it), connect, and use the same
terminal. Clicking the module on the canvas connects or hangs up too.
An HC-05 starts connected, so the serial monitor's box for it works at once. An HC-06 starts with nothing connected, as one out of the bag does, and while nothing is connected it answers AT commands. The wireless serial guide has both modules' AT commands.
What is real and what is not
Real, in the sense that it behaves the way your board will: the calls and their signatures, the order the callbacks come in, pairing that has to succeed before anything connects, a PIN or a code that has to match, the bytes each way, the GATT table, and a notification only after the phone subscribes.
Not real, and never pretended to be:
- no radio. No 2.4 GHz, no RFCOMM, no advertising channels, no range, no link keys, no encryption. A PIN is compared as a string.
- no delay of its own. A line arrives whole, at once; a real link takes tens
of milliseconds and jitters. The callbacks run every 10 ms of simulated time
while the sketch is in
delay(), which is about where a real board's Bluetooth task gets in. - no other devices. The phone is the only one. A board that scans
(
BLEScan) or connects as a client finds nothing, andSerialBT.connect()finds nothing to connect to. - no MTU exchange for an Arduino sketch: a value up to 512 bytes arrives whole. With MicroPython the phone exchanges the MTU with the board's own Bluetooth software, as a real phone does.
- no pairing over LE with MicroPython: the phone says pairing is not supported.
- NimBLE-Arduino and the Pico W's Bluetooth are not simulated.