Web Bluetooth API
This feature is not (yet) supported on your device
The Web Bluetooth API enables apps to connect to Bluetooth Low Energy (BLE) devices and read values from or write
values to it.
Demo
Connect to a nearby Bluetooth Low Energy device to explore its battery, device information, heart rate,
temperature and humidity. Available readings depend on the device. Sensor values update live when the device
supports notifications.
Turn on your device and make it available for a BLE connection, then tap “Scan”. Appearing in the picker does not
guarantee readable data. Bluetooth Classic devices and proprietary services are not supported by this demo.
Scan
Disconnect
Scan to choose a device.
No device connected
battery_full Battery
—
Not connected
favorite Heart rate
—
Not connected
thermostat Temperature
—
Not connected
water_drop Humidity
—
Not connected
info Manufacturer
—
Not connected
devices Model
—
Not connected
settings Firmware
—
Not connected
if('bluetooth' in navigator) {
const scan = document.querySelector('#scan');
const disconnect = document.querySelector('#disconnect');
const deviceName = document.querySelector('#bluetooth-device');
const status = document.querySelector('#bluetooth-status');
const output = document.querySelector('[data-bluetooth-value="battery"]');
let device;
let characteristic;
let listener;
const onDisconnected = () => {
characteristic?.removeEventListener('characteristicvaluechanged', listener);
device?.removeEventListener('gattserverdisconnected', onDisconnected);
device = undefined;
characteristic = undefined;
listener = undefined;
deviceName.textContent = 'No device connected';
output.textContent = '—';
disconnect.disabled = true;
status.textContent = 'Disconnected. Scan to connect again.';
};
const withTimeout = async (operation, description) => {
let timer;
try {
return await Promise.race([
operation,
new Promise((resolve, reject) => {
timer = setTimeout(() => {
reject(new DOMException(description + ' timed out after 30 seconds', 'TimeoutError'));
}, 30000);
})
]);
}
finally {
clearTimeout(timer);
}
};
disconnect.disabled = true;
disconnect.addEventListener('click', () => device?.gatt.disconnect());
scan.addEventListener('click', async () => {
scan.disabled = true;
disconnect.disabled = true;
let selected;
try {
status.textContent = 'Choose a Bluetooth Low Energy device.';
selected = await navigator.bluetooth.requestDevice({
acceptAllDevices: true,
optionalServices: ['battery_service']
});
if(device) {
device.gatt.disconnect();
onDisconnected();
}
device = selected;
device.addEventListener('gattserverdisconnected', onDisconnected);
deviceName.textContent = selected.name || 'Unnamed Bluetooth device';
output.textContent = '—';
status.textContent = 'Connecting…';
const server = await withTimeout(selected.gatt.connect().then(server => {
// Release a late connection unless a new attempt is using the same device.
if(device !== selected) server.disconnect();
return server;
}), 'Connecting');
if(device !== selected || !server.connected) return;
status.textContent = 'Connected. Checking battery service…';
const service = await withTimeout(server.getPrimaryService('battery_service'), 'Finding battery service');
if(device !== selected || !server.connected) return;
const battery = await withTimeout(service.getCharacteristic('battery_level'), 'Finding battery level');
if(device !== selected || !server.connected) return;
characteristic = battery;
const update = value => {
if(device !== selected || !server.connected) return;
try {
const level = value.getUint8(0);
if(level > 100) throw new Error('Invalid battery level');
output.textContent = level + '%';
}
catch(err) {
console.error('Battery value:', err);
output.textContent = 'Unavailable';
}
};
const messages = [];
if(battery.properties.read) {
try {
update(await withTimeout(battery.readValue(), 'Reading battery level'));
messages.push('Battery read');
}
catch(err) {
if(err.name === 'TimeoutError') throw err;
console.error('Battery read failed:', err);
messages.push('Battery read failed');
}
}
if(device !== selected || !server.connected) return;
if(battery.properties.notify || battery.properties.indicate) {
listener = event => update(event.target.value);
battery.addEventListener('characteristicvaluechanged', listener);
try {
await withTimeout(battery.startNotifications(), 'Starting battery updates');
messages.push('Live updates enabled');
}
catch(err) {
if(err.name === 'TimeoutError') throw err;
console.error('Battery notifications unavailable:', err);
messages.push('Live updates unavailable');
}
}
if(device === selected && server.connected) {
status.textContent = messages.join(' · ') || 'Battery reading and live updates are not supported.';
}
}
catch(err) {
if(selected && device === selected && selected.gatt.connected && err.name === 'NotFoundError') {
status.textContent = 'Connected. Battery service or battery level is not provided by this device.';
}
else {
console.error('Bluetooth:', err);
if(selected && device === selected) {
selected.gatt.disconnect();
onDisconnected();
}
status.textContent = err.name === 'NetworkError' && err.message === 'Unsupported device.' ?
'This device entry cannot establish a BLE connection. If another entry has the same name, try that one.' :
!selected && err.name === 'NotFoundError' ?
'No device selected.' : err.message;
}
}
finally {
scan.disabled = false;
disconnect.disabled = !device?.gatt.connected;
}
});
}