Buat APK Gratis

API jembatan JavaScript

Web Bluetooth

Bentuk navigator.bluetooth, dijawab secara native sehingga halaman yang ditulis untuk Chrome berfungsi tanpa perubahan.

navigator.bluetooth standar web#

navigator.bluetooth — Web Bluetooth

Contoh

Web Bluetooth standar: kode navigator.bluetooth yang sama seperti yang Anda tulis untuk Chrome berfungsi di aplikasi terpasang, untuk perangkat Bluetooth Low Energy apa pun.

Hasil: Promise, seperti pada standar web. Nilainya berupa DataView. Membutuhkan: Bluetooth. Tidak ada sakelar: saat Anda build dari ZIP atau halaman HTML, build mengaktifkannya sendiri bila kode Anda menyebut navigator.bluetooth (atau BluetoothUUID). Build situs web (URL) tidak bisa mengaktifkannya. Tanpa itu, navigator.bluetooth tidak ada. requestDevice() pertama meminta izin Perangkat di sekitar (Lokasi di Android 11 dan lebih lama). Jika Bluetooth mati, aplikasi meminta pengguna menyalakannya lalu membuka daftar perangkat sendiri; di Android 6 sampai 11 hal yang sama dilakukan untuk pengaturan Lokasi, karena versi itu tidak menemukan perangkat Bluetooth saat Lokasi mati.

Sabuk detak jantung, dari ketukan tombol. navigator.bluetooth sudah ada sebelum skrip pertama Anda berjalan, jadi deteksi fitur di bagian atas halaman berfungsi; memeriksa di dalam handler, seperti di sini, tetap kebiasaan paling aman:

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

Menulis. writeValue dan writeValueWithResponse menunggu konfirmasi perangkat; writeValueWithoutResponse tidak:

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

Penjelajah perangkat. Pilih perangkat apa pun di sekitar dan tampilkan setiap layanan dan karakteristiknya, beserta kemampuan masing-masing:

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

Sensor kecepatan dan kadensi bersepeda. Satu sensor (atau sepasang sensor kecepatan dan kadensi terpisah) pada layanan CSC standar. Nilainya berupa total berjalan, jadi kecepatan dan kadensi dihitung dari selisih antara dua notifikasi:

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

Menampilkan setiap kegagalan di layar. Setiap langkah ditolak dengan kesalahan bernama, sehingga halaman dapat memberi tahu pengguna apa yang harus dilakukan alih-alih diam saja:

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

Catatan: Pemilih perangkat digambar oleh aplikasi dan memindai selama 30 detik, lalu menawarkan "Pindai lagi"; halaman hanya bisa menjangkau perangkat yang dipilih pengguna. getAvailability() menjawab apakah ponsel memiliki Bluetooth sama sekali (seperti di Chrome), bukan apakah Bluetooth menyala: requestDevice() meminta untuk menyalakannya. Sensor yang mengirim namanya hanya dalam respons pindai tetap ditampilkan dengan namanya dan cocok dengan namePrefix. Koneksi pertama yang langsung diputus Android akan dicoba ulang sebelum connect() menolak. Nama layanan yang salah eja di filters atau optionalServices ditolak dengan TypeError. Filter services hanya mencocokkan layanan yang diiklankan perangkat. Setelah connect(), semua layanan yang dimiliki perangkat dapat digunakan, bahkan yang tidak ada di optionalServices (tetap cantumkan, Chrome membutuhkannya). Tersedia juga: getAvailability(), getPrimaryServices(), getCharacteristics(), stopNotifications(), characteristic.properties, device.forget(), BluetoothUUID.getService/getCharacteristic/canonicalUUID. getDevices(), requestLEScan(), watchAdvertisements() dan deskriptor ditolak dengan NotSupportedError. Untuk sensor kebugaran standar dengan angka yang sudah diurai, lihat bleStartScan.

wbConnect jembatan#

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

Transport mentah untuk WebBluetoothPolyfill. Halaman tidak pernah memanggilnya secara langsung - halaman memanggil navigator.bluetooth, persis seperti di browser.

Dijelaskan oleh grupnya, Web Bluetooth (navigator.bluetooth), bukan secara terpisah.

Contoh

Transport internal di balik device.gatt.connect(). Halaman memakai navigator.bluetooth; jangan panggil ini secara langsung.

Hasil: (melalui gatt.connect()) Promise berisi BluetoothRemoteGATTServer, setelah layanan perangkat ditemukan. Membutuhkan: Bluetooth (diaktifkan oleh build ZIP atau HTML bila kode Anda menyebut 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;
  }
}

Sambungkan kembali saat perangkat terputus. Memanggil connect() lagi pada perangkat yang sama berfungsi:

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

Terhubung ke dua sensor sekaligus, misalnya sabuk detak jantung dan pengukur daya:

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

Catatan: Tanda tangan mentah wbConnect(requestId, deviceId). Menghubungkan ke perangkat yang sudah terhubung langsung selesai lagi. getPrimaryService(s) ditolak dengan NetworkError sebelum connect() selesai. Beberapa perangkat dapat terhubung sekaligus.

wbDisconnect jembatan#

window.WebToApk.wbDisconnect(deviceId: String)

Transport mentah untuk WebBluetoothPolyfill. Halaman tidak pernah memanggilnya secara langsung - halaman memanggil navigator.bluetooth, persis seperti di browser.

Dijelaskan oleh grupnya, Web Bluetooth (navigator.bluetooth), bukan secara terpisah.

Contoh

Transport internal di balik device.gatt.disconnect() (dan device.forget()). Halaman menggunakan navigator.bluetooth; jangan panggil ini secara langsung.

Mengembalikan: tidak ada apa pun (disconnect() bersifat sinkron, seperti pada standar web). Perangkat lalu memicu gattserverdisconnected. Membutuhkan: Bluetooth (diaktifkan oleh build ZIP atau HTML saat kode Anda menyebut 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));
  });
}

Bedakan tombol Stop dari Anda sendiri dengan sensor yang keluar jangkauan, dan sambungkan ulang hanya pada kasus kedua:

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

Tombol "Hapus sensor": forget() memutus dan melepas perangkat, dan tidak ada gattserverdisconnected yang menyusul:

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

Catatan: Tanda tangan mentah wbDisconnect(deviceId). Setelah terputus, objek service dan characteristic lama berhenti menerima notifikasi; setelah connect() berikutnya, ambil lagi dan panggil startNotifications() lagi. Aplikasi memutus semua koneksi saat ditutup.

wbGetCharacteristics jembatan#

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

Transport mentah untuk WebBluetoothPolyfill. Halaman tidak pernah memanggilnya secara langsung - halaman memanggil navigator.bluetooth, persis seperti di browser.

Dijelaskan oleh grupnya, Web Bluetooth (navigator.bluetooth), bukan secara terpisah.

Contoh

Transport internal di balik service.getCharacteristic(uuid) dan service.getCharacteristics(). Halaman memakai navigator.bluetooth; jangan panggil ini secara langsung.

Hasil: (melalui service) Promise berisi satu BluetoothRemoteGATTCharacteristic, atau array-nya, masing-masing dengan uuid dan properties (read, write, writeWithoutResponse, notify, indicate, broadcast, authenticatedSignedWrites). Membutuhkan: Bluetooth (diaktifkan oleh build ZIP atau HTML bila kode Anda menyebut 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;
}

Temukan karakteristik dari layanan kustom berdasarkan fungsinya, bukan dengan menulis UUID-nya secara permanen di kode:

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

Baca karakteristik opsional dan anggap NotFoundError sebagai "perangkat ini tidak memilikinya":

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

Catatan: Tanda tangan mentah wbGetCharacteristics(requestId, deviceId, service). UUID yang tidak dimiliki layanan ditolak dengan NotFoundError; nama yang tidak dikenal melempar TypeError (berikan UUID lengkap untuk karakteristik kustom). Daftar diambil sekali per layanan lalu digunakan ulang.

wbIsAvailable jembatan#

window.WebToApk.wbIsAvailable(): Boolean

getAvailability(): apakah ponsel MEMILIKI Bluetooth (jawaban Chrome), bukan apakah menyala. requestDevice() meminta pengguna menyalakannya; menjawab false saat Bluetooth hanya mati membuat halaman yang memeriksa lebih dulu ("if (!await getAvailability())") menampilkan "Bluetooth tidak didukung" dan tidak pernah mencapai prompt itu (laporan kebugaran 2026-10-03).

Contoh

Transport internal di balik navigator.bluetooth.getAvailability(). Halaman memakai navigator.bluetooth; jangan panggil ini secara langsung.

Hasil: (melalui getAvailability()) Promise berisi true bila build memiliki Bluetooth dan Bluetooth ponsel menyala, jika tidak false. Membutuhkan: Bluetooth (diaktifkan oleh build ZIP atau HTML bila kode Anda menyebut 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();
});

Aktifkan tombol Hubungkan hanya saat Bluetooth menyala, periksa lagi setiap kali pengguna kembali ke aplikasi:

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

Berikan pesan "Bluetooth mati" yang jelas sebelum membuka pemilih, alih-alih NotFoundError sesudahnya:

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

Catatan: Tanda tangan mentah wbIsAvailable(): Boolean (sinkron). Hanya memeriksa adaptor - tidak meminta izin apa pun. Event availabilitychanged dari standar web tidak dipicu, jadi periksa lagi saat halaman menjadi terlihat.

wbNotify jembatan#

window.WebToApk.wbNotify(

Transport mentah untuk WebBluetoothPolyfill. Halaman tidak pernah memanggilnya secara langsung - halaman memanggil navigator.bluetooth, persis seperti di browser.

Dijelaskan oleh grupnya, Web Bluetooth (navigator.bluetooth), bukan secara terpisah.

Contoh

Transport internal di balik characteristic.startNotifications() dan stopNotifications(). Halaman memakai navigator.bluetooth; jangan panggil ini secara langsung.

Hasil: (melalui characteristic) Promise berisi characteristic itu sendiri. Setiap nilai baru memicu characteristicvaluechanged padanya, dengan nilai di event.target.value (sebuah DataView). Membutuhkan: Bluetooth (diaktifkan oleh build ZIP atau HTML bila kode Anda menyebut 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) {}
}

Termometer yang pengukurannya hanya mendukung indikasi dilanggan dengan cara yang sama, di sini melalui properti 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();
}

Terima baris teks dari perangkat bergaya serial, menggabungkan notifikasi yang memecah satu baris di tengah:

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

Catatan: Tanda tangan mentah wbNotify(requestId, deviceId, service, characteristic, enable). Characteristic yang hanya mendukung indication otomatis dilanggan dengan indication. Characteristic yang tidak bisa memberi notifikasi ditolak dengan NotSupportedError. oncharacteristicvaluechanged = fn juga berfungsi. Setelah terputus, berlangganan lagi.

wbRead jembatan#

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

Transport mentah untuk WebBluetoothPolyfill. Halaman tidak pernah memanggilnya secara langsung - halaman memanggil navigator.bluetooth, persis seperti di browser.

Dijelaskan oleh grupnya, Web Bluetooth (navigator.bluetooth), bukan secara terpisah.

Contoh

Transport internal di balik characteristic.readValue(). Halaman memakai navigator.bluetooth; jangan panggil ini secara langsung.

Hasil: (melalui readValue()) Promise berisi DataView; characteristic.value juga diperbarui. Membutuhkan: Bluetooth (diaktifkan oleh build ZIP atau HTML bila kode Anda menyebut 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);
  }
}

Polling sensor yang hanya bisa dibaca, di sini karakteristik Temperature standar (0x2a6e, dalam seperseratus derajat):

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

Isi panel "Tentang perangkat ini" dengan memulai beberapa pembacaan sekaligus; semuanya dijalankan satu per satu untuk Anda:

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

Catatan: Tanda tangan mentah wbRead(requestId, deviceId, service, characteristic). Pembacaan, penulisan, dan perubahan notifikasi pada satu perangkat dijalankan satu per satu sesuai urutan Anda memanggilnya, jadi Anda bisa memanggil beberapa sekaligus tanpa menunggu di antaranya.

wbRequestDevice jembatan#

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

Transport mentah untuk WebBluetoothPolyfill. Halaman tidak pernah memanggilnya secara langsung - halaman memanggil navigator.bluetooth, persis seperti di browser.

Dijelaskan oleh grupnya, Web Bluetooth (navigator.bluetooth), bukan secara terpisah.

Contoh

Transport internal di balik navigator.bluetooth.requestDevice(options): menampilkan pemilih perangkat aplikasi. Halaman memakai navigator.bluetooth.requestDevice; jangan panggil ini secara langsung.

Hasil: (melalui requestDevice) Promise berisi BluetoothDevice {id, name, gatt}. Membutuhkan: Bluetooth (diaktifkan oleh build ZIP atau HTML bila kode Anda menyebut navigator.bluetooth) dan izin Perangkat di sekitar, diminta saat pertama dipakai.

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

Untuk menampilkan semua perangkat di sekitar tanpa filter: requestDevice({ acceptAllDevices: true }).

Pilih perangkat kustom berdasarkan nama persis atau awalan namanya, dengan mencantumkan layanan 128-bit miliknya agar bisa digunakan setelah tersambung:

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

Minta perangkat sekali per sesi dan gunakan ulang pada ketukan berikutnya (getDevices() tidak tersedia, jadi simpan objek yang Anda dapatkan):

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

Catatan: Tanda tangan mentah wbRequestDevice(requestId, optionsJson); jawabannya kembali ke polyfill, bukan ke halaman. Panggil requestDevice dari ketukan. Filter memakai services (harus diiklankan), name (tepat), dan namePrefix; filter cocok bila semua kuncinya cocok, dan perangkat cocok bila salah satu filter cocok. Tanpa filters maupun acceptAllDevices → TypeError. Membatalkan pemilih ditolak dengan NotFoundError. Pemilih memindai selama 30 detik. device.id adalah alamat Bluetooth perangkat.

wbWrite jembatan#

window.WebToApk.wbWrite(

Transport mentah untuk WebBluetoothPolyfill. Halaman tidak pernah memanggilnya secara langsung - halaman memanggil navigator.bluetooth, persis seperti di browser.

Dijelaskan oleh grupnya, Web Bluetooth (navigator.bluetooth), bukan secara terpisah.

Contoh

Transport internal di balik characteristic.writeValue(), writeValueWithResponse(), dan writeValueWithoutResponse(). Halaman memakai navigator.bluetooth; jangan panggil ini secara langsung.

Mengembalikan: (melalui karakteristik) sebuah Promise dari undefined. Membutuhkan: Bluetooth (diaktifkan oleh build ZIP atau HTML saat kode Anda menyebut navigator.bluetooth).

Dengan respons (writeValue sama saja): selesai saat perangkat mengonfirmasi penulisan.

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

Tanpa respons: lebih cepat, untuk aliran data di mana paket yang hilang tidak masalah.

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

Kirim perintah teks panjang dalam potongan 20 byte. Aplikasi tidak meminta ukuran paket lebih besar dari perangkat, jadi 20 byte adalah yang muat dalam satu penulisan:

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

Catatan: Tanda tangan mentah wbWrite(requestId, deviceId, service, characteristic, valueBase64, withResponse). Nilainya bisa berupa ArrayBuffer, array bertipe apa pun, DataView, atau array byte biasa; selain itu akan melempar TypeError. Kesalahan: NotSupportedError (penulisan ditolak), NetworkError (penulisan gagal atau tidak tersambung), NotFoundError.

Dibuat dari runtime aplikasi dan file contohnya pada setiap build dokumentasi. Baca sebagai Markdown · Semua keluarga.

Diperiksa terhadap jembatan yang dirilis pada 2026-10-22.