Note: This tutorial uses the Arduino UNO R4 WiFi board, whose onboard Bluetooth module supports BLE (Bluetooth Low Energy). Communication with the board is done directly through the Web Bluetooth API of browsers (Chrome/Edge) — no phone APP installation required, and no WiFi router is needed. Any Bluetooth-capable computer or phone can control the board.
BLE (Bluetooth Low Energy) is a communication protocol introduced in Bluetooth 4.0:
Simple analogy: Bluetooth is like an invisible "data cable" between two devices; BLE is a "power-saving data cable" that exchanges small amounts of information reliably once connected.
GATT (Generic Attribute Profile) is the data model for BLE communication, defining how a device "exposes" its features:
Read, Write, Notify (subscribe to notifications)GATT structure of this project:
Service: 19B10000-E8F2-537E-4F6C-D104768A1214 (Control Service)
├─ Characteristic: 19B10001... (LED control) readable/writable
└─ Characteristic: 19B10002... (command) readable/writable/notifiable
Simple analogy: GATT is like a restaurant menu — Services are "main course categories", Characteristics are "specific dishes", and UUIDs are "dish numbers". The web side "orders" (writes) or "asks the price" (reads) by locating data through UUIDs.
UUID (Universally Unique Identifier) is a 128-bit string used to identify Services and Characteristics in BLE:
8-4-4-4-12, totaling 36 characters (including hyphens)19B1000 + a sequence number for easy memorizationThe Web Bluetooth API is a set of JavaScript interfaces provided by browsers that allows web pages to communicate directly with BLE devices:
Simple analogy: The Web Bluetooth API is like a "Bluetooth remote control" built into the browser, turning the web page into an "APP" that can remotely control hardware.
This project makes extensive use of the event callback mechanism:
loop(), resulting in clean code structureSimple analogy: Event-driven programming is like "the delivery person automatically calls you when the food arrives" — you don't have to keep watching the door.
| Library | Board | Source | Installation |
|---|---|---|---|
ArduinoBLE |
UNO R4 WiFi | Arduino IDE Library Manager | See steps below |
Ctrl+Shift+I)ArduinoBLE in the search boxINSTALLED tag will appearNote: You must first install the Arduino UNO R4 Boards core package (search for
UNO R4in Tools → Board → Boards Manager).
| Browser | Supports Web Bluetooth | Notes |
|---|---|---|
| Chrome (recommended) | ✅ Fully supported | Windows/Mac/Linux/Android |
| Edge | ✅ Fully supported | Default Windows browser |
| Firefox | ❌ Not supported | Use Chrome/Edge instead |
| Safari (Mac) | ❌ Not supported | Use Chrome instead |
| iOS Safari | ❌ Not supported | Use a BLE browser like Bluefy |
| Function/Variable | Description | Role in this project |
|---|---|---|
BLE.begin() |
Start the BLE module | Initialize Bluetooth hardware; attempt reset on failure |
BLE.setLocalName(name) |
Set broadcast name | Displays "UNO-R4-BLE" when scanned |
BLE.setDeviceName(name) |
Device name | Device name in GATT |
BLE.setAdvertisedService(svc) |
Set advertised service | Broadcast the Service contained in the device |
BLE.addService(svc) |
Add a service | Register controlService |
BLE.advertise() |
Start advertising | Make the device discoverable |
BLE.central() |
Get central device | Return the connected phone/computer |
BLE.setEventHandler(event, cb) |
Register event callback | Listen for connect/disconnect |
char.setEventHandler(BLEWritten, cb) |
Register write callback | Listen for characteristic writes |
char.writeValue(value) |
Write data | Send data to the web page |
char.readValue(buf, len) |
Read data | Receive commands from the web page |
BLEService |
Define a service | Create a GATT service |
BLEByteCharacteristic |
Byte characteristic | LED switch (0/1) |
BLECharacteristic |
Generic characteristic | Command text (max 20 bytes) |
| Constant | Meaning | Typical Use |
|---|---|---|
BLERead |
Readable | Web page can actively read the current value |
BLEWrite |
Writable | Web page can write data |
BLENotify |
Notifiable | Device can actively push data |
| Function/Object | Description | Role in this project |
|---|---|---|
navigator.bluetooth.requestDevice(options) |
Pop-up device picker | User selects "UNO-R4-BLE" |
device.gatt.connect() |
Establish GATT connection | Establish Bluetooth connection between web page and device |
server.getPrimaryService(uuid) |
Get service | Find controlService by UUID |
service.getCharacteristic(uuid) |
Get characteristic | Find LED/cmd characteristic by UUID |
char.writeValue(data) |
Write data | Send LED control or text commands |
char.startNotifications() |
Start notifications | Subscribe to the command channel |
char.stopNotifications() |
Stop notifications | Unsubscribe |
char.addEventListener('characteristicvaluechanged', cb) |
Register notification callback | Triggered when device pushes data |
| Function | Description | Use Case |
|---|---|---|
new TextEncoder() |
Text → bytes | Web page sends command to device |
new TextDecoder('utf-8') |
Bytes → text | Web page decodes text from device |
Uint8Array.of(0x01) |
Build byte array | Send LED ON command |
| Channel | Direction | Data Type | Content | Trigger |
|---|---|---|---|---|
| LED characteristic | Web → Device | Byte (0/1) | 0=off, 1=on | Button click |
| Command characteristic | Web → Device | String | Custom command (≤20 bytes) | Enter key in input box |
| Command characteristic (Notify) | Device → Web | String | Ack: xxx echo |
After command received |
| Command characteristic (Notify) | Device → Web | String | COM> xxx |
Serial input content |
The Arduino side is a BLE Peripheral device, responsible for advertising services, accepting web connections, responding to reads/writes, and pushing serial data.
#include <ArduinoBLE.h>
BLEService controlService("19B10000-E8F2-537E-4F6C-D104768A1214");
BLEByteCharacteristic ledChar("19B10001-E8F2-537E-4F6C-D104768A1214", BLERead | BLEWrite);
BLECharacteristic cmdChar("19B10002-E8F2-537E-4F6C-D104768A1214", BLERead | BLEWrite | BLENotify, 20);
const int LED_PIN = LED_BUILTIN;
Code Explanation:
ArduinoBLE.h: UNO R4 WiFi onboard Bluetooth library, encapsulating the full BLE protocol stackcontrolService: The main service of this project, UUID is 19B10000-...ledChar: BLEByteCharacteristic means the data type is a single byte (0/1 to control the LED)cmdChar: Parameter 20 means max 20 bytes; BLENotify allows the device to actively push dataLED_BUILTIN: UNO R4 WiFi onboard LED (D13)UUID (Universally Unique Identifier) is the "ID card" that identifies Services and Characteristics in BLE. The UUIDs used in this project are not randomly generated but follow a custom naming convention for easy memorization and extension:
Naming Convention:
Prefix + Type Number + Fixed Suffix
↓ ↓ ↓
19B1000 0/1/2/3... -E8F2-537E-4F6C-D104768A1214
| Component | Meaning | Value in this project | Notes |
|---|---|---|---|
| Prefix | Custom brand/project identifier | 19B1000 |
You can change it to any 8-digit hex number, as long as it's consistent |
| Type Number | Distinguishes Service / Characteristics | 0=Service, 1=LED, 2=command |
Incrementing numbers for identification |
| Fixed Suffix | Prevents conflicts with other projects | -E8F2-537E-4F6C-D104768A1214 |
Can be anything; once chosen, keep it fixed |
UUID Overview of This Project:
| Name | Full UUID | Number | Type |
|---|---|---|---|
| Service | 19B10000-E8F2-537E-4F6C-D104768A1214 |
0 | Main service |
| LED Characteristic | 19B10001-E8F2-537E-4F6C-D104768A1214 |
1 | LED control (single byte) |
| Command Characteristic | 19B10002-E8F2-537E-4F6C-D104768A1214 |
2 | Command send/receive (≤20 bytes) |
Why the 19B1000 prefix?
19: Commemorates the year 2019 when learning BLE beganB1000: A "phonetic" play on "Bluetooth" for easy memorizationA000000, FFFF000, etc., as long as it's consistent on both device and web sidesUUID Generation Methods:
If you want to generate a brand-new UUID, you can use these methods:
uuid.uuid4(), JS crypto.randomUUID()BLEByteCharacteristic ledChar("...", BLERead | BLEWrite);
BLECharacteristic cmdChar("...", BLERead | BLEWrite | BLENotify, 20);
Differences Between Characteristic Classes:
| Class | Data Type | Suitable For | Use in This Project |
|---|---|---|---|
BLEByteCharacteristic |
Single byte (0~255) | Switches, status codes | LED control (0 off / 1 on) |
BLECharacteristic |
Arbitrary byte sequence | Text, binary data | Command string (≤20 bytes) |
BLEFloatCharacteristic |
4-byte float | Sensor values | (Removed; can be added as needed) |
BLEIntCharacteristic |
4-byte integer | Counters, IDs | (Not used in this project) |
Constructor Parameters:
BLEByteCharacteristic(uuid, properties):
|
BLERead: Web page can actively readBLEWrite: Web page can writeBLENotify: Device can actively push (requires web subscription)BLECharacteristic(uuid, properties, maxLength):
Why is cmdChar set to 20 bytes?
The BLE 4.0/4.1 protocol specifies that the maximum ATT transmission unit (MTU) is 23 bytes, of which 3 bytes are protocol overhead (opcode + handle), leaving 20 bytes as the payload. Data exceeding 20 bytes requires packet fragmentation, which greatly increases complexity, so this project uses 20 bytes as the safe upper limit.
setup() Initialization Functionvoid setup() {
Serial.begin(115200);
pinMode(LED_PIN, OUTPUT);
digitalWrite(LED_PIN, LOW);
if (!BLE.begin()) {
Serial.println("BLE initialization failed!");
Serial2.begin(115200);
Serial2.write("AT+RESET\n");
delay(2000);
NVIC_SystemReset();
}
BLE.setLocalName("UNO-R4-BLE");
BLE.setDeviceName("UNO-R4-Controller");
BLE.setAdvertisedService(controlService);
controlService.addCharacteristic(ledChar);
controlService.addCharacteristic(cmdChar);
BLE.addService(controlService);
ledChar.writeValue(0);
BLE.setEventHandler(BLEConnected, onConnect);
BLE.setEventHandler(BLEDisconnected, onDisconnect);
ledChar.setEventHandler(BLEWritten, onLedWritten);
cmdChar.setEventHandler(BLEWritten, onCmdWritten);
BLE.advertise();
Serial.println("BLE started, waiting for connection...");
}
Step-by-Step Breakdown (8 Steps):
Step 1: Serial and Pin Initialization
Serial.begin(115200);
pinMode(LED_PIN, OUTPUT);
digitalWrite(LED_PIN, LOW);
Serial.begin(115200): Initialize USB serial port at 115200 baud for debug outputpinMode(LED_PIN, OUTPUT): Set the onboard LED pin as output modedigitalWrite(LED_PIN, LOW): Ensure the LED is off after power-up, avoiding brief flashes on startupStep 2: BLE Startup and Fault Recovery
if (!BLE.begin()) {
Serial.println("BLE initialization failed!");
Serial2.begin(115200);
Serial2.write("AT+RESET\n");
delay(2000);
NVIC_SystemReset();
}
BLE.begin(): Initialize the nRF52840 Bluetooth module on the UNO R4 WiFifalse (initialization failed), execute a three-level recovery:
AT+RESET command to the Bluetooth module via Serial2 (UART2, pins D14/D15)NVIC_SystemReset(): Soft-reboot the entire board and re-run setup()Step 3: Set Device Name and Advertising Info
BLE.setLocalName("UNO-R4-BLE");
BLE.setDeviceName("UNO-R4-Controller");
BLE.setAdvertisedService(controlService);
setLocalName(): Short name in the advertising packet, displayed when the web page scans for devices
setDeviceName(): Full device name in GATT, read by the web page via GAP after connection
setAdvertisedService(): Set the Service UUID included in advertising
Step 4: Assemble the GATT Data Structure
controlService.addCharacteristic(ledChar);
controlService.addCharacteristic(cmdChar);
BLE.addService(controlService);
service.addCharacteristic(): Register a characteristic to a service
BLE.addService(): Register the service to the BLE stack
getPrimaryService()Step 5: Initialize Characteristic Data
ledChar.writeValue(0);
BLEWritten event (since it's not an external write)Step 6: Register Global Event Callbacks
BLE.setEventHandler(BLEConnected, onConnect);
BLE.setEventHandler(BLEDisconnected, onDisconnect);
BLE.setEventHandler(): Register global BLE events
BLEConnected: Triggered when any device connectsBLEDisconnected: Triggered when any device disconnectsvoid callback(BLEDevice central), parameter is the connected device objectStep 7: Register Characteristic Event Callbacks
ledChar.setEventHandler(BLEWritten, onLedWritten);
cmdChar.setEventHandler(BLEWritten, onCmdWritten);
char.setEventHandler(): Register characteristic-level events
BLEWritten: Triggered when the web page writes data to this characteristicvoid callback(BLEDevice central, BLECharacteristic characteristic)
central: The device that wrote the datacharacteristic: The characteristic that was written tocharacteristic.value() or characteristic.readValue() to get the written contentStep 8: Start Advertising
BLE.advertise();
Serial.println("BLE started, waiting for connection...");
BLE.advertise(): Start device advertising; the web page can now scan for "UNO-R4-BLE"BLE.stopAdvertise()loop() Main Loop Functionvoid loop() {
BLEDevice central = BLE.central();
if (central) {
Serial.print("Connected: ");
Serial.println(central.address());
while (central.connected()) {
// Serial input -> web page echo
if (Serial.available()) {
String input = Serial.readStringUntil('\n');
input.trim();
if (input.length() > 0 && input.length() <= 15) {
String msg = "COM> " + input;
cmdChar.writeValue((const byte*)msg.c_str(), msg.length());
}
}
delay(10);
}
Serial.print("Disconnected: ");
Serial.println(central.address());
digitalWrite(LED_PIN, LOW);
}
}
Core Functionality Analysis:
Core 1: Wait and Maintain Connection
BLEDevice central = BLE.central();
if (central) {
while (central.connected()) {
...
}
}
BLE.central(): Get the currently connected central device (web side)
BLEDevice; returns an empty object if not connectedloop()if (central): Check if any device is connectedwhile (central.connected()): Blocking loop to maintain the connection session
loop() only executes this while bodywhile, turns off the LED, and waits for the next connectionCore 2: Serial Input Reverse Echo
if (Serial.available()) {
String input = Serial.readStringUntil('\n');
input.trim();
if (input.length() > 0 && input.length() <= 15) {
String msg = "COM> " + input;
cmdChar.writeValue((const byte*)msg.c_str(), msg.length());
}
}
Serial.available(): Check if there's data to read in the serial bufferreadStringUntil('\n'): Read until a newline \n is encountered (corresponds to the Serial Monitor "Send" button or Enter key)input.trim(): Remove leading/trailing whitespace (spaces, \r, etc.)0 < len ≤ 15:
"COM> " prefix, total length must not exceed 20 bytes (BLE single-packet limit)"COM> " prefix takes 5 bytes, so original input is at most 15 bytes"COM> " prefix:
COM> prefix are displayed in the logAck: prefix, forming bidirectional communicationcmdChar.writeValue(): Triggers BLENotify; the web page receives the pushvoid onConnect(BLEDevice central) {
Serial.println("[Event] Device connected");
}
void onDisconnect(BLEDevice central) {
Serial.println("[Event] Device disconnected");
}
void onLedWritten(BLEDevice central, BLECharacteristic characteristic) {
byte value = ledChar.value();
if (value == 0x01) {
digitalWrite(LED_PIN, HIGH);
Serial.println("[BLE] LED ON");
} else {
digitalWrite(LED_PIN, LOW);
Serial.println("[BLE] LED OFF");
}
}
void onCmdWritten(BLEDevice central, BLECharacteristic characteristic) {
char buffer[21];
int len = cmdChar.readValue(buffer, 20);
buffer[len] = '\0';
String command = String(buffer);
command.trim();
if (command.startsWith("COM>")) return;
Serial.print("[Web->Serial] Received: ");
Serial.println(command);
String response = "Ack: " + command;
if (response.length() <= 20) {
cmdChar.writeValue((const byte*)response.c_str(), response.length());
}
}
Step-by-Step Code Analysis:
onConnect / onDisconnect:
central is the connected device object; use central.address() to get the Bluetooth addressonLedWritten:
ledChar.value(): Get the most recently written value (byte type)0x01 means turn on the LED; any other value (including 0x00) means turn offdigitalWrite(LED_PIN, HIGH/LOW): Actually controls the LED pin levelonCmdWritten: (The Most Critical Callback)
char buffer[21];
int len = cmdChar.readValue(buffer, 20);
\0 terminator)readValue(buffer, 20): Read up to 20 bytes from the characteristic; returns the actual length readbuffer[len] = '\0';
String command = String(buffer);
command.trim();
String() constructor can parse correctlytrim() removes leading/trailing whitespace (the web writeValue may include extra bytes)if (command.startsWith("COM>")) return;
COM>, it's the looped-back content that Arduino pushed itself from the serial port
COM> prefix → writeValue push → own callback receives it againreturn exits directly without further processingSerial.print("[Web->Serial] Received: ");
Serial.println(command);
String response = "Ack: " + command;
if (response.length() <= 20) {
cmdChar.writeValue((const byte*)response.c_str(), response.length());
}
"Ack: " + original command"Ack: " takes 5 bytes, original command at most 15 byteswriteValue() pushes to the web page via BLENotifyThe web side is a BLE Central device, responsible for scanning devices, establishing connections, sending control commands, and receiving notifications.
<div class="card">
<h1>🎮 UNO R4 WiFi BLE Console</h1>
<div class="status">Status: <span id="status" class="disconnected">Disconnected</span></div>
<button id="connectBtn" onclick="connect()">🔌 Connect Device</button>
</div>
<div class="card">
<div class="section-title">LED Control</div>
<button id="ledOnBtn" onclick="ledOn()">💡 LED ON</button>
<button id="ledOffBtn" onclick="ledOff()">💡 LED OFF</button>
</div>
<div class="card">
<div class="section-title">Command Communication</div>
<div class="input-group">
<input type="text" id="cmdInput" placeholder="Type a command and press Enter to send to UNO R4..."
onkeydown="if(event.key==='Enter')sendCmd()">
<button onclick="sendCmd()" style="background:#FF9800;">📨 Send</button>
</div>
</div>
<div class="card">
<div class="section-title">Communication Log</div>
<div id="log"></div>
</div>
Structure Explanation:
onclick event that calls the corresponding JS function when clickedEnter key to send directly (like a chat box).log-to-uno { color: #64b5f6; } /* Blue: sent from me to UNO R4 */
.log-from-uno { color: #81c784; } /* Green: actively sent from UNO R4 serial */
.log-ack { color: #ffd54f; } /* Yellow: UNO R4 echo confirmation */
.log-info { color: #b0bec5; } /* Gray: system info */
.log-error { color: #ef5350; } /* Red: error */
Significance of Color Categorization:
const SERVICE_UUID = "19b10000-e8f2-537e-4f6c-d104768a1214";
const LED_CHAR_UUID = "19b10001-e8f2-537e-4f6c-d104768a1214";
const CMD_CHAR_UUID = "19b10002-e8f2-537e-4f6c-d104768a1214";
Note:
connect() Device Connection Functionasync function connect() {
try {
log('log-info', '🔍 Scanning BLE devices...');
device = await navigator.bluetooth.requestDevice({
filters: [{ name: "UNO-R4-BLE" }],
optionalServices: [SERVICE_UUID]
});
log('log-info', `📡 Found device: ${device.name}`);
device.addEventListener('gattserverdisconnected', () => {
document.getElementById('status').textContent = "Disconnected";
document.getElementById('status').className = "disconnected";
log('log-error', '⚠️ Bluetooth connection lost');
});
log('log-info', '🔗 Establishing GATT connection...');
server = await device.gatt.connect();
log('log-info', '🔧 Getting Service...');
service = await server.getPrimaryService(SERVICE_UUID);
cmdChar = await service.getCharacteristic(CMD_CHAR_UUID);
await cmdChar.startNotifications();
cmdChar.addEventListener('characteristicvaluechanged', (event) => {
const decoder = new TextDecoder('utf-8');
const text = decoder.decode(event.target.value).replace(/\0/g, '').trim();
if (!text) return;
if (text.startsWith('Ack:')) {
const content = text.substring(4).trim();
log('log-ack', `✓ UNO R4 received: ${content}`);
}
else if (text.startsWith('COM>')) {
const content = text.substring(4).trim();
log('log-from-uno', `← UNO R4 Serial: ${content}`);
}
else {
log('log-from-uno', `← UNO R4: ${text}`);
}
});
document.getElementById('status').textContent = "Connected";
document.getElementById('status').className = "connected";
log('log-info', '✅ Connected! Bidirectional channel subscribed');
} catch (e) {
log('log-error', '❌ Connection failed: ' + e.message);
console.error(e);
}
}
Step-by-Step Breakdown:
navigator.bluetooth.requestDevice shows the browser's native picker
filters: [{ name: "UNO-R4-BLE" }]: Only show devices named "UNO-R4-BLE"optionalServices: Must declare the service UUID to access itdevice.gatt.connect() completes the Bluetooth handshakestartNotifications() enables Notify subscriptioncharacteristicvaluechanged eventTextDecoder('utf-8') decodes bytes to text.replace(/\0/g, '') removes null charactersAck: → yellow (acknowledgment)COM> → green (actively sent from serial)ledOn() / ledOff() LED Control Functionsasync function ledOn() {
if (!service) { log('log-error', '⚠️ Please connect first'); return; }
try {
const char = await service.getCharacteristic(LED_CHAR_UUID);
await char.writeValue(Uint8Array.of(0x01));
log('log-to-uno', 'Me -> UNO R4: [LED ON]');
} catch (e) { log('log-error', '❌ LED ON failed: ' + e.message); }
}
async function ledOff() {
if (!service) { log('log-error', '⚠️ Please connect first'); return; }
try {
const char = await service.getCharacteristic(LED_CHAR_UUID);
await char.writeValue(Uint8Array.of(0x00));
log('log-to-uno', 'Me -> UNO R4: [LED OFF]');
} catch (e) { log('log-error', '❌ LED OFF failed: ' + e.message); }
}
Code Logic Analysis:
service exists to avoid errors when not connectedUint8Array.of(0x01) builds a 1-byte array with value 1writeValue sends it to the deviceledOff() function sends 0x00sendCmd() Command Sending Functionasync function sendCmd() {
if (!service) { log('log-error', '⚠️ Please connect first'); return; }
const input = document.getElementById('cmdInput');
const text = input.value.trim();
if (!text) { log('log-error', '⚠️ Please enter a command'); return; }
try {
const char = await service.getCharacteristic(CMD_CHAR_UUID);
const encoder = new TextEncoder();
await char.writeValue(encoder.encode(text));
log('log-to-uno', `Me -> UNO R4: ${text}`);
input.value = '';
input.focus();
} catch (e) {
log('log-error', '❌ Send failed: ' + e.message);
}
}
Code Logic Analysis:
TextEncoder encodes a string to UTF-8 bytesencoder.encode(text) outputs a Uint8Array| Component | UNO R4 Pin | Notes |
|---|---|---|
| Onboard LED | D13 | Integrated on the board, no wiring needed |
bluetooth.ino and click UploadBLE started, waiting for connection...Bluetooth.html with Chrome or Edge
Ctrl+O in the browser and select the file| Action | Web Page Effect | Arduino Serial |
|---|---|---|
| Click LED ON | Onboard LED lights up + log "Me -> UNO R4: [LED ON]" | [BLE] LED ON |
| Click LED OFF | Onboard LED turns off + log "Me -> UNO R4: [LED OFF]" | [BLE] LED OFF |
Type hello + Enter |
Log "Me -> UNO R4: hello" + yellow "✓ UNO R4 received: hello" | [Web->Serial] Received: hello |
Serial input test + Enter |
Green "← UNO R4 Serial: test" | (No output) |
BLE started, waiting for connection...
Connected: aa:bb:cc:dd:ee:ff
[Event] Device connected
[BLE] LED ON
[BLE] LED OFF
[Web->Serial] Received: hello
[Web->Serial] Received: test
Type test in the Arduino IDE Serial Monitor (select "NL" line ending):
← UNO R4 Serial: test[Event] Device disconnected
Disconnected: aa:bb:cc:dd:ee:ff
Q1: The browser "Connect" button does nothing?
A: Please check:
Q2: Can't see UNO-R4-BLE in the device picker?
A: Please check:
BLE started, waiting for connection...)BLE initialization failed!)Q3: Connection drops immediately?
A: Possible causes:
BLE.addService(controlService))Q4: Serial Monitor input doesn't show on the web page?
A: Please check:
Q5: Upload fails with ArduinoBLE.h: No such file or directory?
A: Library not installed correctly:
ArduinoBLE in the Library Manager (not BLEPeripheral or similar)Q6: Compile error: BLECharacteristic does not take 3 arguments?
A: Library version issue:
| Problem | Possible Cause | Solution |
|---|---|---|
| Browser doesn't show picker | Web Bluetooth not supported | Use Chrome/Edge |
| Can't see device | Not flashed / not powered / too far | Check serial output, move closer |
| Connection drops immediately | UUID mismatch | Verify UUIDs match exactly on both sides |
| LED buttons don't work | Device not connected | Click "Connect Device" first |
| No command echo | Chinese text too long | Reduce characters or use English |
| Serial reverse send fails | Wrong line ending | Set Serial Monitor to NL |
| Compilation fails | Library not installed / too old | Install ArduinoBLE ≥1.3.0 |
In blueteeth.ino:
BLE.setLocalName("MyCustom-BLE");
Sync the web-side filter:
device = await navigator.bluetooth.requestDevice({
filters: [{ name: "MyCustom-BLE" }],
optionalServices: [SERVICE_UUID]
});
Change the byte count for cmdChar (note: BLE single packet max is 20 bytes; exceeding requires fragmentation):
BLECharacteristic cmdChar("19B10002-E8F2-537E-4F6C-D104768A1214",
BLERead | BLEWrite | BLENotify, 20); // Larger values require a fragmentation protocol
Note: The actual payload is 20 bytes; excess will be truncated.
Add a new characteristic in blueteeth.ino (e.g., buzzer control):
BLEByteCharacteristic buzzerChar("19B10004-E8F2-537E-4F6C-D104768A1214", BLERead | BLEWrite);
// In setup()
controlService.addCharacteristic(buzzerChar);
buzzerChar.setEventHandler(BLEWritten, onBuzzerWritten);
// Add the callback
void onBuzzerWritten(BLEDevice central, BLECharacteristic characteristic) {
byte value = buzzerChar.value();
// Control the buzzer...
}
Add corresponding buttons and UUID constants on the web side.
If you want to use an external LED (not the onboard one):
const int LED_PIN = 9; // Change to D9; requires a 220Ω current-limiting resistor in series
The 3 UUIDs (1 Service + 2 Characteristics) in blueteeth.ino and the HTML file must be exactly the same. When modifying, update both sides simultaneously.
BLE 4.0/4.1 has a maximum payload of 20 bytes per transmission. This project sets cmdChar to 20 bytes; excess will be truncated. To transmit longer text, you must implement a fragmentation protocol.
navigator.bluetooth.requestDevice() cannot be called automatically on page load; it must be triggered by a user action like a button click. This is a browser security restriction.
The line if (command.startsWith("COM>")) return; in onCmdWritten is critical:
In the Arduino IDE Serial Monitor's bottom-right corner, set the line ending to NL (newline only) or NL & CR, otherwise readStringUntil('\n') won't read complete data.
LED_BUILTIN on the UNO R4 WiFi is pin D13. If you have external components on D13, use a different pin to avoid conflicts.
The "AT+RESET + NVIC_SystemReset" logic in setup() is a self-healing mechanism for occasional Bluetooth module init failures:
NVIC_SystemReset() to soft-reboot the boardBLE.begin(), which usually recovers