Créer un APK gratuit

API du pont JavaScript

Web Bluetooth

La forme de navigator.bluetooth, gérée nativement pour qu'une page écrite pour Chrome fonctionne sans modification.

navigator.bluetooth standard web#

navigator.bluetooth — Web Bluetooth

Exemple

Web Bluetooth standard : le même code navigator.bluetooth que vous écririez pour Chrome fonctionne dans l'application installée, pour tout appareil Bluetooth Low Energy.

Renvoie : des Promises, comme dans le standard web. Les valeurs sont des DataView. Nécessite : Bluetooth. Il n'y a pas de réglage : quand vous compilez depuis un ZIP ou une page HTML, la compilation l'active d'elle-même si votre code mentionne navigator.bluetooth (ou BluetoothUUID). Une compilation de site web (URL) ne peut pas l'activer. Sans cela, navigator.bluetooth n'existe pas. Le premier requestDevice() demande l'autorisation Appareils à proximité (Localisation sur Android 11 et versions antérieures). Si le Bluetooth est désactivé, l'application demande à l'utilisateur de l'activer puis ouvre d'elle-même la liste des appareils ; sur Android 6 à 11, elle fait de même pour le réglage Localisation, car ces versions ne trouvent aucun appareil Bluetooth tant que la Localisation est désactivée.

Ceinture de fréquence cardiaque, depuis une pression sur un bouton. navigator.bluetooth existe avant l'exécution de votre premier script, donc la détection de fonctionnalité en haut de la page fonctionne ; vérifier dans le gestionnaire, comme ici, reste la plus sûre des habitudes :

var hrDevice = null;

document.getElementById('connect').addEventListener('click', async function () {
  if (!navigator.bluetooth) { showStatus('Bluetooth is not available in this app.'); return; }
  try {
    hrDevice = await navigator.bluetooth.requestDevice({
      filters: [{ services: ['heart_rate'] }],        // or { name: 'Polar H10' } / { namePrefix: 'Polar' }
      optionalServices: ['battery_service']
    });
    hrDevice.addEventListener('gattserverdisconnected', function () {
      showStatus('Strap disconnected');
    });
    var server = await hrDevice.gatt.connect();
    var hr = await server.getPrimaryService('heart_rate');
    var chr = await hr.getCharacteristic('heart_rate_measurement');
    chr.addEventListener('characteristicvaluechanged', function (e) {
      var v = e.target.value;                                   // DataView
      var bpm = (v.getUint8(0) & 1) ? v.getUint16(1, true) : v.getUint8(1);
      document.getElementById('bpm').textContent = bpm;
    });
    await chr.startNotifications();

    var battery = await server.getPrimaryService('battery_service');
    var level = await (await battery.getCharacteristic('battery_level')).readValue();
    document.getElementById('battery').textContent = level.getUint8(0) + '%';
  } catch (err) {
    // NotFoundError: user cancelled the chooser, Bluetooth is off, or no such service.
    // SecurityError: permission denied.  NetworkError: connection lost.
    showStatus(err.name + ': ' + err.message);
  }
});

document.getElementById('disconnect').addEventListener('click', function () {
  if (hrDevice && hrDevice.gatt.connected) hrDevice.gatt.disconnect();
});

Écriture. writeValue et writeValueWithResponse attendent la confirmation de l'appareil ; writeValueWithoutResponse non :

async function sendCommands(server) {
  // Heart Rate Control Point: 0x01 = reset energy expended
  var hr = await server.getPrimaryService('heart_rate');
  var ctrl = await hr.getCharacteristic('heart_rate_control_point');
  await ctrl.writeValueWithResponse(new Uint8Array([0x01]));

  // A custom UART-style service (full 128-bit UUIDs work everywhere)
  var uart = await server.getPrimaryService('6e400001-b5a3-f393-e0a9-e50e24dcca9e');
  var rx = await uart.getCharacteristic('6e400002-b5a3-f393-e0a9-e50e24dcca9e');
  await rx.writeValueWithoutResponse(new TextEncoder().encode('LED ON\n'));
}

Explorateur d'appareil. Choisissez un appareil à proximité et listez tous ses services et caractéristiques, avec ce que chacun sait faire :

document.getElementById('explore').addEventListener('click', async function () {
  if (!navigator.bluetooth) { showStatus('Bluetooth is not available in this app.'); return; }
  try {
    var device = await navigator.bluetooth.requestDevice({ acceptAllDevices: true });
    var server = await device.gatt.connect();
    var lines = [];
    for (var service of await server.getPrimaryServices()) {
      lines.push(service.uuid === BluetoothUUID.getService('battery_service') ? 'Battery' : service.uuid);
      var chars = await service.getCharacteristics().catch(function () { return []; });
      chars.forEach(function (c) {
        var can = Object.keys(c.properties).filter(function (k) { return c.properties[k]; });
        lines.push('  ' + c.uuid + '  ' + can.join(', '));
      });
    }
    document.getElementById('tree').textContent = lines.join('\n');
    device.gatt.disconnect();
  } catch (err) {
    showStatus(err.name + ': ' + err.message);
  }
});

Capteur de vitesse et de cadence de vélo. Un capteur (ou une paire distincte vitesse et cadence) sur le service CSC standard. Les valeurs sont des totaux cumulés : la vitesse et la cadence se calculent donc à partir de la différence entre deux notifications :

var WHEEL_MM = 2105;                       // 700x25c; set your own wheel circumference
var last = null;

async function connectCsc() {
  var device = await navigator.bluetooth.requestDevice({
    filters: [{ services: ['cycling_speed_and_cadence'] }]      // same as { services: [0x1816] }
  });
  device.addEventListener('gattserverdisconnected', function () { showStatus('Sensor disconnected'); });
  var server = await device.gatt.connect();
  var csc = await server.getPrimaryService('cycling_speed_and_cadence');
  var chr = await csc.getCharacteristic('csc_measurement');
  chr.addEventListener('characteristicvaluechanged', function (e) {
    var v = e.target.value, flags = v.getUint8(0), i = 1, now = {};
    if (flags & 1) { now.wheels = v.getUint32(i, true); now.wheelTime = v.getUint16(i + 4, true); i += 6; }
    if (flags & 2) { now.cranks = v.getUint16(i, true); now.crankTime = v.getUint16(i + 2, true); }
    if (last) {
      if (now.wheels !== undefined && now.wheelTime !== last.wheelTime) {
        var dt = ((now.wheelTime - last.wheelTime) & 0xffff) / 1024;          // seconds
        var kmh = (now.wheels - last.wheels) * WHEEL_MM / 1e6 / (dt / 3600);
        document.getElementById('speed').textContent = kmh.toFixed(1) + ' km/h';
      }
      if (now.cranks !== undefined && now.crankTime !== last.crankTime) {
        var ct = ((now.crankTime - last.crankTime) & 0xffff) / 1024;
        var rpm = ((now.cranks - last.cranks) & 0xffff) / ct * 60;
        document.getElementById('cadence').textContent = Math.round(rpm) + ' rpm';
      }
    }
    last = Object.assign({}, last, now);
  });
  await chr.startNotifications();
}

Afficher chaque échec à l'écran. Chaque étape est rejetée avec une erreur nommée, de sorte que la page peut dire à l'utilisateur quoi faire au lieu de rester silencieuse :

async function connectWithMessage(connect) {
  try {
    await connect();
    showStatus('Connected');
  } catch (err) {
    var msg = {
      NotFoundError: 'No sensor chosen, Bluetooth was left off, or the sensor lacks that service.',
      SecurityError: 'Bluetooth permission was refused. Allow "Nearby devices" in the app settings.',
      NetworkError: 'The sensor went out of range or is connected to another app or watch. Try again.',
      TypeError: 'Programming error: ' + err.message
    }[err.name] || err.message;
    showStatus(err.name + ': ' + msg);
  }
}
document.getElementById('scan-hr').addEventListener('click', function () { connectWithMessage(connectHeartRate); });
document.getElementById('scan-csc').addEventListener('click', function () { connectWithMessage(connectCsc); });

Remarques : le sélecteur d'appareil est dessiné par l'application et recherche pendant 30 secondes, puis propose « Rechercher à nouveau » ; une page ne peut accéder qu'à un appareil choisi par l'utilisateur. getAvailability() indique si le téléphone dispose du Bluetooth (comme dans Chrome), pas s'il est activé : requestDevice() demande de l'activer. Un capteur qui n'envoie son nom que dans sa réponse de balayage est tout de même listé par son nom et correspond à namePrefix. Une première connexion qu'Android interrompt aussitôt est retentée avant que connect() ne soit rejetée. Un nom de service mal orthographié dans filters ou optionalServices est rejeté avec TypeError. Un filtre services ne correspond qu'aux services que l'appareil annonce. Après connect(), tous les services de l'appareil sont utilisables, même ceux absents de optionalServices (gardez-les quand même, Chrome en a besoin). Également disponibles : getAvailability(), getPrimaryServices(), getCharacteristics(), stopNotifications(), characteristic.properties, device.forget(), BluetoothUUID.getService/getCharacteristic/canonicalUUID. getDevices(), requestLEScan(), watchAdvertisements() et les descripteurs sont rejetés avec NotSupportedError. Pour les capteurs de fitness standard avec les valeurs déjà analysées, voir bleStartScan.

wbConnect pont#

window.WebToApk.wbConnect(requestId: String, deviceId: String)

Transport brut pour WebBluetoothPolyfill. Une page ne les appelle jamais directement : elle appelle navigator.bluetooth, exactement comme dans un navigateur.

Décrit par son groupe, Web Bluetooth (navigator.bluetooth), plutôt que seul.

Exemple

Transport interne derrière device.gatt.connect(). Les pages utilisent navigator.bluetooth ; ne l'appelez pas directement.

Renvoie : (via gatt.connect()) une Promise du BluetoothRemoteGATTServer, après la découverte des services de l'appareil. Nécessite : Bluetooth (activé par une compilation ZIP ou HTML quand votre code mentionne 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;
  }
}

Se reconnecter quand l'appareil se déconnecte. Appeler à nouveau connect() sur le même appareil fonctionne :

device.addEventListener('gattserverdisconnected', async function () {
  showStatus('Reconnecting...');
  try { await connectAndList(device); showStatus('Connected'); } catch (e) {}
});

Se connecter à deux capteurs en même temps, par exemple une ceinture de fréquence cardiaque et un capteur de puissance :

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')
  };
}

Remarques : Signature brute wbConnect(requestId, deviceId). Se connecter à un appareil déjà connecté se résout à nouveau immédiatement. getPrimaryService(s) rejette avec NetworkError avant la fin de connect(). Plusieurs appareils peuvent être connectés en même temps.

wbDisconnect pont#

window.WebToApk.wbDisconnect(deviceId: String)

Transport brut pour WebBluetoothPolyfill. Une page ne les appelle jamais directement : elle appelle navigator.bluetooth, exactement comme dans un navigateur.

Décrit par son groupe, Web Bluetooth (navigator.bluetooth), plutôt que seul.

Exemple

Transport interne derrière device.gatt.disconnect() (et device.forget()). Les pages utilisent navigator.bluetooth ; ne l'appelez pas directement.

Renvoie : rien (disconnect() est synchrone, comme dans la norme web). L'appareil déclenche ensuite gattserverdisconnected. Nécessite : Bluetooth (activé par une compilation ZIP ou HTML lorsque votre code mentionne 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));
  });
}

Distinguez votre propre arrêt de la sortie de portée du capteur, et ne vous reconnectez que dans le second cas :

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 bouton « Retirer le capteur » : forget() déconnecte et oublie l'appareil, et aucun gattserverdisconnected ne suit :

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.');
});

Remarques : Signature brute wbDisconnect(deviceId). Après une déconnexion, les anciens objets de service et de caractéristique cessent de recevoir des notifications ; après le prochain connect(), récupérez-les à nouveau et rappelez startNotifications(). L'application abandonne toutes les connexions à sa fermeture.

wbGetCharacteristics pont#

window.WebToApk.wbGetCharacteristics(requestId: String, deviceId: String, service: String)

Transport brut pour WebBluetoothPolyfill. Une page ne les appelle jamais directement : elle appelle navigator.bluetooth, exactement comme dans un navigateur.

Décrit par son groupe, Web Bluetooth (navigator.bluetooth), plutôt que seul.

Exemple

Transport interne derrière service.getCharacteristic(uuid) et service.getCharacteristics(). Les pages utilisent navigator.bluetooth ; ne l'appelez pas directement.

Renvoie : (via le service) une Promise d'un BluetoothRemoteGATTCharacteristic, ou d'un tableau de ceux-ci, chacun avec uuid et properties (read, write, writeWithoutResponse, notify, indicate, broadcast, authenticatedSignedWrites). Nécessite : Bluetooth (activé par une compilation ZIP ou HTML quand votre code mentionne 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;
}

Trouvez les caractéristiques d'un service personnalisé selon ce qu'elles savent faire, plutôt que d'écrire leurs UUID en dur :

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 };
}

Lisez une caractéristique facultative et considérez NotFoundError comme « cet appareil ne l'a pas » :

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;
  }
}

Remarques : signature brute wbGetCharacteristics(requestId, deviceId, service). Un UUID que le service n'a pas est rejeté avec NotFoundError ; un nom inconnu lève TypeError (passez un UUID complet pour les caractéristiques personnalisées). La liste est récupérée une seule fois par service, puis réutilisée.

wbIsAvailable pont#

window.WebToApk.wbIsAvailable(): Boolean

getAvailability() : indique si le téléphone POSSÈDE le Bluetooth (la réponse de Chrome), pas s'il est activé. requestDevice() demande à l'utilisateur de l'activer ; répondre false quand il était simplement désactivé faisait dire « Bluetooth non pris en charge » aux pages qui vérifient d'abord (« if (!await getAvailability()) ») sans jamais atteindre cette invite (rapport fitness 2026-10-03).

Exemple

Transport interne derrière navigator.bluetooth.getAvailability(). Les pages utilisent navigator.bluetooth ; ne l'appelez pas directement.

Renvoie : (via getAvailability()) une Promise de true quand la compilation inclut Bluetooth et que le Bluetooth du téléphone est activé, sinon false. Nécessite : Bluetooth (activé par une compilation ZIP ou HTML quand votre code mentionne 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();
});

Activer le bouton Connecter uniquement quand le Bluetooth est activé, en vérifiant à nouveau chaque fois que l'utilisateur revient dans l'application :

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();
});

Donner un message clair « Le Bluetooth est désactivé » avant d'ouvrir le sélecteur, au lieu d'une NotFoundError ensuite :

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);
  }
});

Remarques : Signature brute wbIsAvailable(): Boolean (synchrone). Elle ne vérifie que l'adaptateur : elle ne demande aucune autorisation. L'événement availabilitychanged du standard web n'est pas déclenché ; vérifiez donc à nouveau quand la page devient visible.

wbNotify pont#

window.WebToApk.wbNotify(

Transport brut pour WebBluetoothPolyfill. Une page ne les appelle jamais directement : elle appelle navigator.bluetooth, exactement comme dans un navigateur.

Décrit par son groupe, Web Bluetooth (navigator.bluetooth), plutôt que seul.

Exemple

Transport interne derrière characteristic.startNotifications() et stopNotifications(). Les pages utilisent navigator.bluetooth ; ne l'appelez pas directement.

Renvoie : (via la caractéristique) une Promise de la caractéristique elle-même. Chaque nouvelle valeur déclenche characteristicvaluechanged sur celle-ci, avec la valeur dans event.target.value (un DataView). Nécessite : Bluetooth (activé par une compilation ZIP ou HTML quand votre code mentionne 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 thermomètre dont la mesure ne prend en charge que les indications s'abonne de la même manière, ici via la propriété 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();
}

Recevoir des lignes de texte d'un appareil de type série, en rejoignant les notifications qui coupent une ligne en plein milieu :

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;
}

Remarques : Signature brute wbNotify(requestId, deviceId, service, characteristic, enable). Une caractéristique qui ne prend en charge que les indications est abonnée automatiquement avec des indications. Une caractéristique qui ne peut pas notifier rejette avec NotSupportedError. oncharacteristicvaluechanged = fn fonctionne aussi. Après une déconnexion, abonnez-vous à nouveau.

wbRead pont#

window.WebToApk.wbRead(requestId: String, deviceId: String, service: String, characteristic: String)

Transport brut pour WebBluetoothPolyfill. Une page ne les appelle jamais directement : elle appelle navigator.bluetooth, exactement comme dans un navigateur.

Décrit par son groupe, Web Bluetooth (navigator.bluetooth), plutôt que seul.

Exemple

Transport interne derrière characteristic.readValue(). Les pages utilisent navigator.bluetooth ; ne l'appelez pas directement.

Renvoie : (via readValue()) une Promise d'un DataView ; characteristic.value est aussi mis à jour. Nécessite : Bluetooth (activé par une compilation ZIP ou HTML quand votre code mentionne 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);
  }
}

Interrogez régulièrement un capteur qui ne peut qu'être lu, ici la caractéristique standard Temperature (0x2a6e, en centièmes de degré) :

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);
}

Remplissez un panneau « À propos de cet appareil » en lançant plusieurs lectures à la fois ; elles s'exécutent l'une après l'autre pour vous :

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
  });
}

Remarques : signature brute wbRead(requestId, deviceId, service, characteristic). Les lectures, les écritures et les changements de notification sur un même appareil s'exécutent les uns après les autres, dans l'ordre où vous les appelez ; vous pouvez donc en lancer plusieurs sans attendre entre chacun.

wbRequestDevice pont#

window.WebToApk.wbRequestDevice(requestId: String, optionsJson: String)

Transport brut pour WebBluetoothPolyfill. Une page ne les appelle jamais directement : elle appelle navigator.bluetooth, exactement comme dans un navigateur.

Décrit par son groupe, Web Bluetooth (navigator.bluetooth), plutôt que seul.

Exemple

Transport interne derrière navigator.bluetooth.requestDevice(options) : il affiche le sélecteur d'appareils de l'application. Les pages utilisent navigator.bluetooth.requestDevice ; ne l'appelez pas directement.

Renvoie : (via requestDevice) une Promise d'un BluetoothDevice {id, name, gatt}. Nécessite : Bluetooth (activé par une compilation ZIP ou HTML quand votre code mentionne navigator.bluetooth) et l'autorisation Appareils à proximité, demandée à la première utilisation.

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
  }
});

Pour lister tous les appareils à proximité au lieu de filtrer : requestDevice({ acceptAllDevices: true }).

Choisissez un appareil personnalisé par son nom exact ou son préfixe de nom, en listant son propre service 128 bits afin de pouvoir l'utiliser après la connexion :

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;
  }
}

Demandez l'appareil une seule fois par session et réutilisez-le lors des appuis suivants (getDevices() n'est pas disponible, conservez donc l'objet obtenu) :

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);
  }
});

Remarques : Signature brute wbRequestDevice(requestId, optionsJson) ; la réponse revient au polyfill, pas à la page. Appelez requestDevice depuis une pression. Les filtres utilisent services (doit être annoncé), name (exact) et namePrefix ; un filtre correspond quand toutes ses clés correspondent, et l'appareil correspond quand un filtre quelconque correspond. Ni filters ni acceptAllDevices → TypeError. Annuler le sélecteur rejette avec NotFoundError. Le sélecteur recherche pendant 30 secondes. device.id est l'adresse Bluetooth de l'appareil.

wbWrite pont#

window.WebToApk.wbWrite(

Transport brut pour WebBluetoothPolyfill. Une page ne les appelle jamais directement : elle appelle navigator.bluetooth, exactement comme dans un navigateur.

Décrit par son groupe, Web Bluetooth (navigator.bluetooth), plutôt que seul.

Exemple

Transport interne derrière characteristic.writeValue(), writeValueWithResponse() et writeValueWithoutResponse(). Les pages utilisent navigator.bluetooth ; ne l'appelez pas directement.

Renvoie : (via la caractéristique) une Promise de undefined. Nécessite : Bluetooth (activé par une compilation ZIP ou HTML lorsque votre code mentionne navigator.bluetooth).

Avec réponse (writeValue est identique) : se résout lorsque l'appareil confirme l'écriture.

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);
}

Sans réponse : plus rapide, pour les flux où la perte d'un paquet n'a pas d'importance.

async function sendText(characteristic, text) {
  if (!characteristic.properties.writeWithoutResponse) throw new Error('This characteristic needs writeValue');
  await characteristic.writeValueWithoutResponse(new TextEncoder().encode(text));
}

Envoyer une longue commande texte par morceaux de 20 octets. L'application ne demande pas à l'appareil une taille de paquet plus grande, donc 20 octets est ce qui tient dans une seule écriture :

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');

Remarques : signature brute wbWrite(requestId, deviceId, service, characteristic, valueBase64, withResponse). La valeur peut être un ArrayBuffer, n'importe quel tableau typé, un DataView ou un simple tableau d'octets ; toute autre chose lève TypeError. Erreurs : NotSupportedError (écriture refusée), NetworkError (écriture échouée ou non connecté), NotFoundError.

Généré à partir de l'environnement d'exécution de l'application et de ses fichiers d'exemple à chaque compilation de la documentation. Lire en Markdown · Toutes les familles.

Vérifié par rapport au pont publié le 22/10/2026.