API پل جاوااسکریپت
Web Bluetooth
شکل navigator.bluetooth که بهصورت بومی پاسخ داده میشود تا صفحهای که برای Chrome نوشته شده بدون تغییر کار کند.
wbConnect پل#
window.WebToApk.wbConnect(requestId: String, deviceId: String)
انتقال خام برای WebBluetoothPolyfill. صفحه هرگز اینها را مستقیم فراخوانی نمیکند؛ مثل مرورگر، navigator.bluetooth را فراخوانی میکند.
بهجای خودش، با گروهش Web Bluetooth (navigator.bluetooth) توضیح داده میشود.
نمونه
انتقال داخلی پشت device.gatt.connect(). صفحهها از navigator.bluetooth استفاده میکنند؛ مستقیم این را صدا نزنید.
بازگشتی: (از طریق gatt.connect()) یک Promise از BluetoothRemoteGATTServer، پس از کشف سرویسهای دستگاه. نیازمند: بلوتوث (با ساخت از ZIP یا HTML وقتی کد شما ذکر کند خودش روشن میشود 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;
}
}وقتی دستگاه قطع میشود دوباره وصل شوید. صدا زدن دوبارهی connect() روی همان دستگاه کار میکند:
device.addEventListener('gattserverdisconnected', async function () {
showStatus('Reconnecting...');
try { await connectAndList(device); showStatus('Connected'); } catch (e) {}
});همزمان به دو حسگر وصل شوید، مثلاً یک کمربند ضربان قلب و یک توانسنج:
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')
};
}نکتهها: امضای خام wbConnect(requestId, deviceId). اتصال به دستگاهی که از قبل وصل است دوباره بلافاصله حل میشود. getPrimaryService(s) پیش از اینکه connect() تمام شود با NetworkError رد میشود. چند دستگاه را میتوان همزمان وصل کرد.
wbDisconnect پل#
window.WebToApk.wbDisconnect(deviceId: String)
انتقال خام برای WebBluetoothPolyfill. صفحه هرگز اینها را مستقیم فراخوانی نمیکند؛ مثل مرورگر، navigator.bluetooth را فراخوانی میکند.
بهجای خودش، با گروهش Web Bluetooth (navigator.bluetooth) توضیح داده میشود.
نمونه
انتقال داخلی پشت device.gatt.disconnect() (و device.forget()). صفحهها از navigator.bluetooth استفاده میکنند؛ این را مستقیم فراخوانی نکنید.
مقدار بازگشتی: هیچ (disconnect() همزمان است، مثل استاندارد وب). سپس دستگاه gattserverdisconnected را اجرا میکند. نیازمند: بلوتوث (وقتی کد شما به آن اشاره کند، در ساخت ZIP یا HTML روشن میشود 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));
});
}توقف خودتان را از خارج شدن حسگر از برد تشخیص دهید و فقط در حالت دوم دوباره وصل شوید:
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'); });
});
}یک دکمهی «حذف حسگر»: forget() قطع میکند و دستگاه را رها میکند و بعد از آن 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.');
});نکتهها: امضای خام wbDisconnect(deviceId). بعد از قطع اتصال، شیءهای قدیمی سرویس و مشخصه دیگر اعلان نمیگیرند؛ بعد از connect() بعدی دوباره آنها را بگیرید و startNotifications() را دوباره صدا بزنید. اپلیکیشن هنگام بسته شدن همهی اتصالها را رها میکند.
wbGetCharacteristics پل#
window.WebToApk.wbGetCharacteristics(requestId: String, deviceId: String, service: String)
انتقال خام برای WebBluetoothPolyfill. صفحه هرگز اینها را مستقیم فراخوانی نمیکند؛ مثل مرورگر، navigator.bluetooth را فراخوانی میکند.
بهجای خودش، با گروهش Web Bluetooth (navigator.bluetooth) توضیح داده میشود.
نمونه
انتقال داخلی پشت service.getCharacteristic(uuid) و service.getCharacteristics(). صفحهها از navigator.bluetooth استفاده میکنند؛ مستقیم این را صدا نزنید.
بازگشتی: (از طریق سرویس) یک Promise از یک BluetoothRemoteGATTCharacteristic، یا از آرایهای از آنها، هر کدام با uuid و properties (read، write، writeWithoutResponse، notify، indicate، broadcast، authenticatedSignedWrites). نیازمند: بلوتوث (با ساخت از ZIP یا HTML وقتی کد شما ذکر کند خودش روشن میشود 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;
}بهجای نوشتن ثابت UUIDها در کد، ویژگیهای یک سرویس سفارشی را بر اساس کاری که میتوانند انجام دهند پیدا کنید:
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 };
}یک ویژگی اختیاری را بخوانید و NotFoundError را به معنی «این دستگاه آن را ندارد» در نظر بگیرید:
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;
}
}نکات: امضای خام wbGetCharacteristics(requestId, deviceId, service). UUIDی که سرویس ندارد با NotFoundError رد میشود؛ نام ناشناخته TypeError پرتاب میکند (برای ویژگیهای سفارشی UUID کامل بدهید). فهرست یک بار برای هر سرویس گرفته میشود و بعد دوباره استفاده میشود.
wbIsAvailable پل#
window.WebToApk.wbIsAvailable(): Boolean
getAvailability(): اینکه آیا گوشی بلوتوث دارد (پاسخ Chrome)، نه اینکه روشن است. requestDevice() از کاربر میخواهد آن را روشن کند؛ جواب false دادن وقتی فقط خاموش بود باعث میشد صفحههایی که اول بررسی میکنند ("if (!await getAvailability())") بگویند «بلوتوث پشتیبانی نمیشود» و هرگز به آن درخواست نرسند (گزارش تناسب اندام ۲۰۲۶-۱۰-۰۳).
نمونه
انتقال داخلی پشت navigator.bluetooth.getAvailability(). صفحهها از navigator.bluetooth استفاده میکنند؛ مستقیم این را صدا نزنید.
بازگشتی: (از طریق getAvailability()) یک Promise از true وقتی ساخت بلوتوث دارد و بلوتوث گوشی روشن است، وگرنه false. نیازمند: بلوتوث (با ساخت از ZIP یا HTML وقتی کد شما ذکر کند خودش روشن میشود 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();
});دکمهی اتصال را فقط وقتی بلوتوث روشن است فعال کنید و هر بار که کاربر به اپلیکیشن برمیگردد دوباره بررسی کنید:
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();
});پیش از باز کردن انتخابگر یک پیام روشن «بلوتوث خاموش است» بدهید، بهجای NotFoundError بعد از آن:
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);
}
});نکتهها: امضای خام wbIsAvailable(): Boolean (همزمان). فقط آداپتور را بررسی میکند - هیچ مجوزی نمیخواهد. رویداد availabilitychanged استاندارد وب فعال نمیشود، پس وقتی صفحه قابل مشاهده شد دوباره بررسی کنید.
wbNotify پل#
window.WebToApk.wbNotify(
انتقال خام برای WebBluetoothPolyfill. صفحه هرگز اینها را مستقیم فراخوانی نمیکند؛ مثل مرورگر، navigator.bluetooth را فراخوانی میکند.
بهجای خودش، با گروهش Web Bluetooth (navigator.bluetooth) توضیح داده میشود.
نمونه
انتقال داخلی پشت characteristic.startNotifications() و stopNotifications(). صفحهها از navigator.bluetooth استفاده میکنند؛ مستقیم این را صدا نزنید.
بازگشتی: (از طریق مشخصه) یک Promise از خود مشخصه. هر مقدار جدید روی آن characteristicvaluechanged را فعال میکند، با مقدار در event.target.value (یک DataView). نیازمند: بلوتوث (با ساخت از ZIP یا HTML وقتی کد شما ذکر کند خودش روشن میشود 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) {}
}دماسنجی که اندازهگیری آن فقط از نوع indication پشتیبانی میکند، به همین روش مشترک میشود؛ اینجا از طریق ویژگی 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();
}خطوط متن را از یک دستگاه از نوع سریال دریافت کنید، با اتصال اعلانهایی که یک خط را وسط میشکنند:
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;
}نکتهها: امضای خام wbNotify(requestId, deviceId, service, characteristic, enable). مشخصهای که فقط از indication پشتیبانی میکند خودکار با indication مشترک میشود. مشخصهای که نمیتواند اعلان بدهد با NotSupportedError رد میشود. oncharacteristicvaluechanged = fn هم کار میکند. بعد از قطع اتصال، دوباره مشترک شوید.
wbRead پل#
window.WebToApk.wbRead(requestId: String, deviceId: String, service: String, characteristic: String)
انتقال خام برای WebBluetoothPolyfill. صفحه هرگز اینها را مستقیم فراخوانی نمیکند؛ مثل مرورگر، navigator.bluetooth را فراخوانی میکند.
بهجای خودش، با گروهش Web Bluetooth (navigator.bluetooth) توضیح داده میشود.
نمونه
انتقال داخلی پشت characteristic.readValue(). صفحهها از navigator.bluetooth استفاده میکنند؛ مستقیم این را صدا نزنید.
بازگشتی: (از طریق readValue()) یک Promise از یک DataView؛ characteristic.value هم بهروز میشود. نیازمند: بلوتوث (با ساخت از ZIP یا HTML وقتی کد شما ذکر کند خودش روشن میشود 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);
}
}حسگری را که فقط خواندنی است بهصورت دورهای بخوانید؛ اینجا ویژگی استاندارد دما (0x2a6e، به صدم درجه):
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);
}پنل «درباره این دستگاه» را با شروع چند خواندن همزمان پر کنید؛ آنها خودکار یکییکی اجرا میشوند:
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
});
}نکات: امضای خام wbRead(requestId, deviceId, service, characteristic). خواندن، نوشتن و تغییرات اعلان روی یک دستگاه به همان ترتیبی که فراخوانی میکنید، یکییکی اجرا میشوند؛ پس میتوانید چند مورد را بدون انتظار بین آنها پشت سر هم بفرستید.
wbRequestDevice پل#
window.WebToApk.wbRequestDevice(requestId: String, optionsJson: String)
انتقال خام برای WebBluetoothPolyfill. صفحه هرگز اینها را مستقیم فراخوانی نمیکند؛ مثل مرورگر، navigator.bluetooth را فراخوانی میکند.
بهجای خودش، با گروهش Web Bluetooth (navigator.bluetooth) توضیح داده میشود.
نمونه
انتقال داخلی پشت navigator.bluetooth.requestDevice(options): انتخابگر دستگاه اپلیکیشن را نشان میدهد. صفحهها از navigator.bluetooth.requestDevice استفاده میکنند؛ مستقیم این را صدا نزنید.
بازگشتی: (از طریق requestDevice) یک Promise از یک BluetoothDevice {id, name, gatt}. نیازمند: بلوتوث (با ساخت از ZIP یا HTML وقتی کد شما navigator.bluetooth را ذکر کند خودش روشن میشود) و مجوز دستگاههای اطراف، که بار اول پرسیده میشود.
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
}
});برای فهرست کردن همه دستگاههای اطراف بدون فیلتر: requestDevice({ acceptAllDevices: true }).
یک دستگاه سفارشی را با نام دقیق یا پیشوند نام انتخاب کنید و سرویس ۱۲۸ بیتی خودش را فهرست کنید تا بعد از اتصال قابل استفاده باشد:
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;
}
}دستگاه را یک بار در هر نشست درخواست کنید و در ضربههای بعدی دوباره از آن استفاده کنید (getDevices() در دسترس نیست، پس شیئی را که گرفتید نگه دارید):
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);
}
});نکتهها: امضای خام wbRequestDevice(requestId, optionsJson)؛ پاسخ به polyfill برمیگردد، نه به صفحه. requestDevice را از یک ضربه صدا بزنید. فیلترها از services (باید تبلیغ شده باشد)، name (دقیق) و namePrefix استفاده میکنند؛ یک فیلتر وقتی همهی کلیدهایش مطابقت دارند منطبق میشود و دستگاه وقتی هر فیلتری منطبق شود منطبق است. نه filters و نه acceptAllDevices → TypeError. لغو انتخابگر با NotFoundError رد میشود. انتخابگر ۳۰ ثانیه اسکن میکند. device.id نشانی بلوتوث دستگاه است.
wbWrite پل#
window.WebToApk.wbWrite(
انتقال خام برای WebBluetoothPolyfill. صفحه هرگز اینها را مستقیم فراخوانی نمیکند؛ مثل مرورگر، navigator.bluetooth را فراخوانی میکند.
بهجای خودش، با گروهش Web Bluetooth (navigator.bluetooth) توضیح داده میشود.
نمونه
انتقال داخلی پشت characteristic.writeValue()، writeValueWithResponse() و writeValueWithoutResponse(). صفحهها از navigator.bluetooth استفاده میکنند؛ مستقیم این را صدا نزنید.
مقدار بازگشتی: (از طریق ویژگی) یک Promise از undefined. نیازمند: بلوتوث (وقتی کد شما به آن اشاره کند، در ساخت ZIP یا HTML روشن میشود navigator.bluetooth).
با پاسخ (writeValue هم همینطور است): وقتی دستگاه نوشتن را تأیید کند، انجام میشود.
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);
}بدون پاسخ: سریعتر، برای جریانهایی که گم شدن یک بسته مهم نیست.
async function sendText(characteristic, text) {
if (!characteristic.properties.writeWithoutResponse) throw new Error('This characteristic needs writeValue');
await characteristic.writeValueWithoutResponse(new TextEncoder().encode(text));
}یک فرمان متنی بلند را در تکههای ۲۰ بایتی بفرستید. اپلیکیشن از دستگاه اندازهی بستهی بزرگتر نمیخواهد، پس ۲۰ بایت چیزی است که در یک نوشتن جا میشود:
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');نکات: امضای خام wbWrite(requestId, deviceId, service, characteristic, valueBase64, withResponse). مقدار میتواند ArrayBuffer، هر آرایه تایپشده، DataView یا آرایه سادهای از بایتها باشد؛ هر چیز دیگر TypeError پرتاب میکند. خطاها: NotSupportedError (نوشتن رد شد)، NetworkError (نوشتن ناموفق بود یا اتصال برقرار نیست)، NotFoundError.