API jembatan JavaScript
Web Bluetooth
Bentuk navigator.bluetooth, dijawab secara native sehingga halaman yang ditulis untuk Chrome berfungsi tanpa perubahan.
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.