API del bridge JavaScript
Web Bluetooth
La forma di navigator.bluetooth, gestita in modo nativo così una pagina scritta per Chrome funziona senza modifiche.
wbConnect bridge#
window.WebToApk.wbConnect(requestId: String, deviceId: String)
Trasporto grezzo per WebBluetoothPolyfill. Una pagina non lo chiama mai direttamente: chiama navigator.bluetooth, esattamente come farebbe in un browser.
Descritta dal suo gruppo, Web Bluetooth (navigator.bluetooth), e non singolarmente.
Esempio
Trasporto interno dietro device.gatt.connect(). Le pagine usano navigator.bluetooth; non chiamare questo direttamente.
Restituisce: (tramite gatt.connect()) una Promise del BluetoothRemoteGATTServer, dopo che i servizi del dispositivo sono stati individuati. Richiede: Bluetooth (attivato da una build ZIP o HTML quando il tuo codice nomina navigator.bluetooth).
async function connectAndList(device) {
try {
var server = await device.gatt.connect();
var services = await server.getPrimaryServices();
services.forEach(function (s) { console.log('service', s.uuid); });
return server;
} catch (err) {
showStatus('Could not connect: ' + err.message); // NetworkError, NotFoundError
return null;
}
}Riconnettiti quando il dispositivo cade. Chiamare di nuovo connect() sullo stesso dispositivo funziona:
device.addEventListener('gattserverdisconnected', async function () {
showStatus('Reconnecting...');
try { await connectAndList(device); showStatus('Connected'); } catch (e) {}
});Collegati a due sensori contemporaneamente, per esempio una fascia cardio e un misuratore di potenza:
async function connectBoth(hrDevice, powerDevice) {
var servers = await Promise.all([hrDevice.gatt.connect(), powerDevice.gatt.connect()]);
var hr = await servers[0].getPrimaryService('heart_rate');
var power = await servers[1].getPrimaryService('cycling_power');
return {
heartRate: await hr.getCharacteristic('heart_rate_measurement'),
power: await power.getCharacteristic('cycling_power_measurement')
};
}Note: Firma diretta wbConnect(requestId, deviceId). Connettersi a un dispositivo giĂ connesso si risolve di nuovo subito. getPrimaryService(s) viene rifiutato con NetworkError prima che connect() sia terminato. PiĂč dispositivi possono essere connessi contemporaneamente.
wbDisconnect bridge#
window.WebToApk.wbDisconnect(deviceId: String)
Trasporto grezzo per WebBluetoothPolyfill. Una pagina non lo chiama mai direttamente: chiama navigator.bluetooth, esattamente come farebbe in un browser.
Descritta dal suo gruppo, Web Bluetooth (navigator.bluetooth), e non singolarmente.
Esempio
Trasporto interno dietro device.gatt.disconnect() (e device.forget()). Le pagine usano navigator.bluetooth; non chiamarlo direttamente.
Restituisce: niente (disconnect() è sincrono, come nello standard web). Il dispositivo lancia poi gattserverdisconnected. Richiede: Bluetooth (attivato da una build ZIP o HTML quando il tuo codice menziona navigator.bluetooth).
var device = null; // from navigator.bluetooth.requestDevice(...)
document.getElementById('stop').addEventListener('click', function () {
if (device && device.gatt.connected) device.gatt.disconnect();
});
// Fires for your own disconnect() and when the device goes away by itself.
function watch(dev) {
dev.addEventListener('gattserverdisconnected', function () {
showStatus('Disconnected from ' + (dev.name || dev.id));
});
}Distingui il tuo Stop dal sensore che esce dal raggio d'azione e riconnettiti solo nel secondo caso:
var userStopped = false;
function stopSession(device) {
userStopped = true;
if (device.gatt.connected) device.gatt.disconnect();
}
function watchDrops(device) {
device.addEventListener('gattserverdisconnected', function () {
if (userStopped) { userStopped = false; showStatus('Session ended'); return; }
showStatus('Sensor out of range, reconnecting...');
device.gatt.connect()
.then(resubscribe) // your code: get the characteristics, startNotifications()
.catch(function () { showStatus('Could not reconnect'); });
});
}Un pulsante "Rimuovi sensore": forget() disconnette ed elimina il dispositivo, e non segue alcun gattserverdisconnected:
var device = null; // from navigator.bluetooth.requestDevice(...)
document.getElementById('forget').addEventListener('click', async function () {
if (!device) return;
await device.forget();
device = null;
showStatus('Sensor removed. Tap Connect to pick one again.');
});Note: Firma diretta wbDisconnect(deviceId). Dopo una disconnessione, i vecchi oggetti servizio e caratteristica smettono di ricevere notifiche; dopo il successivo connect(), ottienili di nuovo e chiama di nuovo startNotifications(). L'app chiude ogni connessione quando si chiude.
wbGetCharacteristics bridge#
window.WebToApk.wbGetCharacteristics(requestId: String, deviceId: String, service: String)
Trasporto grezzo per WebBluetoothPolyfill. Una pagina non lo chiama mai direttamente: chiama navigator.bluetooth, esattamente come farebbe in un browser.
Descritta dal suo gruppo, Web Bluetooth (navigator.bluetooth), e non singolarmente.
Esempio
Trasporto interno dietro service.getCharacteristic(uuid) e service.getCharacteristics(). Le pagine usano navigator.bluetooth; non chiamare questo direttamente.
Restituisce: (tramite il servizio) una Promise di una BluetoothRemoteGATTCharacteristic, o di un array di esse, ciascuna con uuid e properties (read, write, writeWithoutResponse, notify, indicate, broadcast, authenticatedSignedWrites). Richiede: Bluetooth (attivato da una build ZIP o HTML quando il tuo codice nomina navigator.bluetooth).
async function describe(server) {
var service = await server.getPrimaryService('heart_rate');
// One characteristic, by standard name, 16-bit number or full UUID:
var measurement = await service.getCharacteristic('heart_rate_measurement'); // same as 0x2a37
// All of them:
var list = await service.getCharacteristics();
list.forEach(function (c) {
var p = c.properties;
console.log(c.uuid, p.read ? 'read' : '', p.write ? 'write' : '', p.notify ? 'notify' : '');
});
return measurement;
}Trova le caratteristiche di un servizio personalizzato in base a ciò che sanno fare, invece di scrivere i loro UUID direttamente nel codice:
async function findSerialPipes(server) {
var svc = await server.getPrimaryService('0000ffe0-0000-1000-8000-00805f9b34fb');
var list = await svc.getCharacteristics();
var incoming = list.find(function (c) { return c.properties.notify || c.properties.indicate; });
var outgoing = list.find(function (c) { return c.properties.write || c.properties.writeWithoutResponse; });
if (!incoming || !outgoing) throw new Error('This device does not look like a serial adapter');
return { incoming: incoming, outgoing: outgoing };
}Leggi una caratteristica facoltativa e tratta NotFoundError come «questo dispositivo non ce l'ha»:
var LOCATIONS = ['Other', 'Chest', 'Wrist', 'Finger', 'Hand', 'Ear lobe', 'Foot'];
async function sensorLocation(server) {
var hr = await server.getPrimaryService('heart_rate');
try {
var chr = await hr.getCharacteristic('body_sensor_location'); // 0x2a38, optional in the spec
var v = await chr.readValue();
return LOCATIONS[v.getUint8(0)] || 'Unknown';
} catch (err) {
if (err.name === 'NotFoundError') return null; // this strap does not report it
throw err;
}
}Note: firma grezza wbGetCharacteristics(requestId, deviceId, service). Un UUID che il servizio non ha viene rifiutato con NotFoundError; un nome sconosciuto genera TypeError (passa un UUID completo per le caratteristiche personalizzate). L'elenco viene recuperato una sola volta per servizio e poi riutilizzato.
wbIsAvailable bridge#
window.WebToApk.wbIsAvailable(): Boolean
getAvailability(): se il telefono HA il Bluetooth (la risposta di Chrome), non se Ăš acceso. requestDevice() chiede all'utente di accenderlo; rispondere false quando era solo spento faceva sĂŹ che le pagine che controllano prima ("if (!await getAvailability())") dicessero "Bluetooth non supportato" senza mai arrivare a quella richiesta (rapporto fitness 2026-10-03).
Esempio
Trasporto interno dietro navigator.bluetooth.getAvailability(). Le pagine usano navigator.bluetooth; non chiamare questo direttamente.
Restituisce: (tramite getAvailability()) una Promise di true quando la build ha il Bluetooth e il Bluetooth del telefono Ăš attivo, altrimenti false. Richiede: Bluetooth (attivato da una build ZIP o HTML quando il tuo codice nomina navigator.bluetooth).
async function updateBluetoothHint() {
var hint = document.getElementById('bt-hint');
if (!navigator.bluetooth) { hint.textContent = 'Bluetooth is not available in this app.'; return; }
var on = await navigator.bluetooth.getAvailability();
hint.textContent = on ? '' : 'Turn on Bluetooth to connect your sensor.';
}
document.addEventListener('visibilitychange', function () {
if (!document.hidden) updateBluetoothHint();
});Abilita il pulsante Connetti solo mentre il Bluetooth Ăš attivo, ricontrollando ogni volta che l'utente torna all'app:
async function refreshConnectButton() {
var btn = document.getElementById('connect');
if (!navigator.bluetooth) { btn.hidden = true; return; }
btn.disabled = !(await navigator.bluetooth.getAvailability());
}
window.addEventListener('load', refreshConnectButton);
document.addEventListener('visibilitychange', function () {
if (!document.hidden) refreshConnectButton();
});Mostra un chiaro messaggio "Il Bluetooth Ăš spento" prima di aprire il selettore, invece di un NotFoundError dopo:
document.getElementById('scan').addEventListener('click', async function () {
if (!navigator.bluetooth) { showStatus('Bluetooth is not available in this app.'); return; }
if (!(await navigator.bluetooth.getAvailability())) {
showStatus('Bluetooth is off. Turn it on, then tap Scan again.');
return;
}
try {
var device = await navigator.bluetooth.requestDevice({ acceptAllDevices: true });
showStatus('Picked ' + (device.name || device.id));
} catch (err) {
showStatus(err.name === 'NotFoundError' ? 'No device chosen.' : err.message);
}
});Note: Firma diretta wbIsAvailable(): Boolean (sincrona). Controlla solo l'adattatore: non chiede alcuna autorizzazione. L'evento availabilitychanged dello standard web non viene lanciato, quindi ricontrolla quando la pagina torna visibile.
wbNotify bridge#
window.WebToApk.wbNotify(
Trasporto grezzo per WebBluetoothPolyfill. Una pagina non lo chiama mai direttamente: chiama navigator.bluetooth, esattamente come farebbe in un browser.
Descritta dal suo gruppo, Web Bluetooth (navigator.bluetooth), e non singolarmente.
Esempio
Trasporto interno dietro characteristic.startNotifications() e stopNotifications(). Le pagine usano navigator.bluetooth; non chiamare questo direttamente.
Restituisce: (tramite la caratteristica) una Promise della caratteristica stessa. Ogni nuovo valore fa scattare characteristicvaluechanged su di essa, con il valore in event.target.value (un DataView). Richiede: Bluetooth (attivato da una build ZIP o HTML quando il tuo codice nomina navigator.bluetooth).
var cadenceChr = null;
async function startCadence(server) {
var svc = await server.getPrimaryService('running_speed_and_cadence');
cadenceChr = await svc.getCharacteristic('rsc_measurement');
cadenceChr.addEventListener('characteristicvaluechanged', onRsc);
await cadenceChr.startNotifications();
}
function onRsc(e) {
var v = e.target.value;
var kmh = v.getUint16(1, true) / 256 * 3.6;
var spm = v.getUint8(3);
show('run', kmh.toFixed(1) + ' km/h, ' + spm + ' steps/min');
}
async function stopCadence() {
if (!cadenceChr) return;
cadenceChr.removeEventListener('characteristicvaluechanged', onRsc);
try { await cadenceChr.stopNotifications(); } catch (e) {}
}Un termometro la cui misurazione supporta solo le indicazioni si sottoscrive allo stesso modo, qui tramite la proprietà oncharacteristicvaluechanged:
async function watchThermometer(server) {
var svc = await server.getPrimaryService('health_thermometer');
var chr = await svc.getCharacteristic('temperature_measurement');
chr.oncharacteristicvaluechanged = function (e) {
var v = e.target.value;
var mantissa = v.getUint32(1, true) & 0x00ffffff; // IEEE-11073 FLOAT
if (mantissa & 0x800000) mantissa -= 0x1000000;
var fahrenheit = v.getUint8(0) & 1;
show('temp', (mantissa * Math.pow(10, v.getInt8(4))).toFixed(1) + (fahrenheit ? ' °F' : ' °C'));
};
await chr.startNotifications();
}Ricevi righe di testo da un dispositivo di tipo seriale, unendo le notifiche che spezzano una riga a metĂ :
async function listenForLines(server, onLine) {
var uart = await server.getPrimaryService('6e400001-b5a3-f393-e0a9-e50e24dcca9e');
var tx = await uart.getCharacteristic('6e400003-b5a3-f393-e0a9-e50e24dcca9e');
if (!tx.properties.notify && !tx.properties.indicate) throw new Error('This device does not stream data');
var decoder = new TextDecoder(), buffer = '';
tx.addEventListener('characteristicvaluechanged', function (e) {
buffer += decoder.decode(e.target.value, { stream: true });
var lines = buffer.split('\n');
buffer = lines.pop();
lines.forEach(onLine);
});
await tx.startNotifications();
return tx;
}Note: Firma diretta wbNotify(requestId, deviceId, service, characteristic, enable). Una caratteristica che supporta solo le indicazioni viene sottoscritta automaticamente con le indicazioni. Una caratteristica che non puĂČ notificare viene rifiutata con NotSupportedError. Funziona anche oncharacteristicvaluechanged = fn. Dopo una disconnessione, sottoscrivi di nuovo.
wbRead bridge#
window.WebToApk.wbRead(requestId: String, deviceId: String, service: String, characteristic: String)
Trasporto grezzo per WebBluetoothPolyfill. Una pagina non lo chiama mai direttamente: chiama navigator.bluetooth, esattamente come farebbe in un browser.
Descritta dal suo gruppo, Web Bluetooth (navigator.bluetooth), e non singolarmente.
Esempio
Trasporto interno dietro characteristic.readValue(). Le pagine usano navigator.bluetooth; non chiamare questo direttamente.
Restituisce: (tramite readValue()) una Promise di un DataView; anche characteristic.value viene aggiornato. Richiede: Bluetooth (attivato da una build ZIP o HTML quando il tuo codice nomina navigator.bluetooth).
async function readInfo(server) {
try {
var battery = await server.getPrimaryService('battery_service');
var level = await (await battery.getCharacteristic('battery_level')).readValue();
show('battery', level.getUint8(0) + '%');
var info = await server.getPrimaryService('device_information');
var maker = await (await info.getCharacteristic('manufacturer_name_string')).readValue();
show('maker', new TextDecoder().decode(maker));
} catch (err) {
// NotFoundError: the device has no such service/characteristic
// NotSupportedError: read rejected by the device NetworkError: not connected / read failed
show('info', err.message);
}
}Interroga periodicamente un sensore che può solo essere letto, qui la caratteristica standard Temperature (0x2a6e, in centesimi di grado):
var pollTimer = null;
async function startTemperaturePolling(server) {
var env = await server.getPrimaryService('environmental_sensing');
var temp = await env.getCharacteristic(0x2a6e);
pollTimer = setInterval(async function () {
try {
var v = await temp.readValue();
show('temp', (v.getInt16(0, true) / 100).toFixed(1) + ' °C');
} catch (err) {
clearInterval(pollTimer); // NetworkError once the sensor is gone
show('temp', err.message);
}
}, 5000);
}Compila un pannello «Informazioni su questo dispositivo» avviando più letture insieme; vengono eseguite una dopo l'altra al posto tuo:
async function readDeviceInfo(server) {
var info = await server.getPrimaryService('device_information');
var names = ['model_number_string', 'serial_number_string', 'firmware_revision_string'];
var decoder = new TextDecoder();
var results = await Promise.allSettled(names.map(async function (name) {
var chr = await info.getCharacteristic(name);
return decoder.decode(await chr.readValue());
}));
results.forEach(function (r, i) {
show(names[i], r.status === 'fulfilled' ? r.value : '-'); // missing ones reject with NotFoundError
});
}Note: firma grezza wbRead(requestId, deviceId, service, characteristic). Le letture, le scritture e le modifiche alle notifiche su un dispositivo vengono eseguite una dopo l'altra nell'ordine in cui le chiami, quindi puoi lanciarne diverse senza aspettare tra l'una e l'altra.
wbRequestDevice bridge#
window.WebToApk.wbRequestDevice(requestId: String, optionsJson: String)
Trasporto grezzo per WebBluetoothPolyfill. Una pagina non lo chiama mai direttamente: chiama navigator.bluetooth, esattamente come farebbe in un browser.
Descritta dal suo gruppo, Web Bluetooth (navigator.bluetooth), e non singolarmente.
Esempio
Trasporto interno dietro navigator.bluetooth.requestDevice(options): mostra il selettore di dispositivi dell'app. Le pagine usano navigator.bluetooth.requestDevice; non chiamare questo direttamente.
Restituisce: (tramite requestDevice) una Promise di un BluetoothDevice {id, name, gatt}. Richiede: Bluetooth (attivato da una build ZIP o HTML quando il tuo codice nomina navigator.bluetooth) e l'autorizzazione Dispositivi nelle vicinanze, richiesta al primo utilizzo.
document.getElementById('pick').addEventListener('click', async function () {
if (!navigator.bluetooth) { showStatus('Bluetooth is not available in this app.'); return; }
try {
var device = await navigator.bluetooth.requestDevice({
filters: [{ services: ['cycling_power'] }, { namePrefix: 'KICKR' }],
optionalServices: ['battery_service']
});
showStatus('Picked ' + (device.name || device.id));
} catch (err) {
if (err.name === 'NotFoundError') showStatus('No device chosen.'); // also: Bluetooth off
else showStatus(err.name + ': ' + err.message); // SecurityError = permission
}
});Per elencare tutti i dispositivi vicini invece di filtrarli: requestDevice({ acceptAllDevices: true }).
Scegli un dispositivo personalizzato in base al nome esatto o al prefisso del nome, elencando il suo servizio a 128 bit così da poterlo usare dopo la connessione:
async function pickLamp() {
try {
return await navigator.bluetooth.requestDevice({
filters: [{ name: 'Desk Lamp' }, { namePrefix: 'LAMP-' }], // either one matches
optionalServices: ['0000ffe0-0000-1000-8000-00805f9b34fb']
});
} catch (err) {
if (err.name === 'SecurityError') showStatus('Allow Nearby devices to find your lamp.');
else if (err.name !== 'NotFoundError') showStatus(err.message); // NotFoundError = cancelled
return null;
}
}Richiedi il dispositivo una sola volta per sessione e riutilizzalo nei tocchi successivi (getDevices() non è disponibile, quindi conserva l'oggetto che hai ottenuto):
var scale = null;
document.getElementById('weigh').addEventListener('click', async function () {
if (!navigator.bluetooth) { showStatus('Bluetooth is not available in this app.'); return; }
try {
if (!scale) {
scale = await navigator.bluetooth.requestDevice({
filters: [{ services: ['weight_scale'], namePrefix: 'MI' }] // both keys must match
});
}
var server = await scale.gatt.connect();
var svc = await server.getPrimaryService('weight_scale');
var chr = await svc.getCharacteristic('weight_measurement');
chr.addEventListener('characteristicvaluechanged', onWeight);
await chr.startNotifications();
} catch (err) {
showStatus(err.name + ': ' + err.message);
}
});Note: Firma diretta wbRequestDevice(requestId, optionsJson); la risposta torna al polyfill, non alla pagina. Chiama requestDevice da un tocco. I filtri usano services (deve essere annunciato), name (esatto) e namePrefix; un filtro corrisponde quando tutte le sue chiavi corrispondono, e il dispositivo corrisponde quando corrisponde un qualsiasi filtro. Se non ci sono nĂ© filters nĂ© acceptAllDevices → TypeError. Annullare il selettore viene rifiutato con NotFoundError. Il selettore cerca per 30 secondi. device.id Ăš l'indirizzo Bluetooth del dispositivo.
wbWrite bridge#
window.WebToApk.wbWrite(
Trasporto grezzo per WebBluetoothPolyfill. Una pagina non lo chiama mai direttamente: chiama navigator.bluetooth, esattamente come farebbe in un browser.
Descritta dal suo gruppo, Web Bluetooth (navigator.bluetooth), e non singolarmente.
Esempio
Trasporto interno dietro characteristic.writeValue(), writeValueWithResponse() e writeValueWithoutResponse(). Le pagine usano navigator.bluetooth; non chiamare questo direttamente.
Restituisce: (tramite la caratteristica) una Promise di undefined. Richiede: Bluetooth (attivato da una build ZIP o HTML quando il tuo codice menziona navigator.bluetooth).
Con risposta (writeValue è lo stesso): si risolve quando il dispositivo conferma la scrittura.
async function setTargetPower(server, watts) {
var ftms = await server.getPrimaryService('fitness_machine');
var cp = await ftms.getCharacteristic('00002ad9-0000-1000-8000-00805f9b34fb'); // FTMS control point
await cp.writeValueWithResponse(new Uint8Array([0x00])); // request control
var cmd = new DataView(new ArrayBuffer(3));
cmd.setUint8(0, 0x05); // set target power
cmd.setInt16(1, watts, true);
await cp.writeValueWithResponse(cmd);
}Senza risposta: piĂč veloce, per flussi in cui la perdita di un pacchetto non Ăš importante.
async function sendText(characteristic, text) {
if (!characteristic.properties.writeWithoutResponse) throw new Error('This characteristic needs writeValue');
await characteristic.writeValueWithoutResponse(new TextEncoder().encode(text));
}Invia un lungo comando di testo in pezzi da 20 byte. L'app non chiede al dispositivo una dimensione di pacchetto maggiore, quindi 20 byte Ăš ciĂČ che entra in una scrittura:
async function sendLine(rx, text) {
var bytes = new TextEncoder().encode(text + '\n');
for (var i = 0; i < bytes.length; i += 20) {
await rx.writeValueWithoutResponse(bytes.subarray(i, i + 20));
}
}
// sendLine(rxCharacteristic, 'SET COLOR 255 120 0 BRIGHTNESS 80');Note: firma grezza wbWrite(requestId, deviceId, service, characteristic, valueBase64, withResponse). Il valore può essere un ArrayBuffer, qualsiasi array tipizzato, un DataView o un normale array di byte; qualsiasi altra cosa genera TypeError. Errori: NotSupportedError (scrittura rifiutata), NetworkError (scrittura non riuscita o non connesso), NotFoundError.