أنشئ APK مجانًا

واجهة JavaScript bridge API

Web Bluetooth

شكل navigator.bluetooth، يُجاب عنه بشكل أصلي ليعمل أي موقع مكتوب لـ Chrome دون تغيير.

navigator.bluetooth معيار الويب#

navigator.bluetooth — Web Bluetooth

مثال

Web Bluetooth القياسي: شيفرة navigator.bluetooth نفسها التي تكتبها لـ Chrome تعمل في التطبيق المثبَّت، لأي جهاز Bluetooth Low Energy.

يُرجع: Promises، كما في معيار الويب. القيم من نوع DataView. يتطلب: Bluetooth. لا يوجد مفتاح: عند الإنشاء من ZIP أو صفحة HTML، يفعّله الإنشاء من تلقاء نفسه إذا ذكرت شيفرتك navigator.bluetooth (أو BluetoothUUID). أما إنشاء موقع (URL) فلا يستطيع تفعيله. بدونه، لا يوجد navigator.bluetooth. أول requestDevice() يطلب إذن الأجهزة القريبة (الموقع على Android 11 وأقدم). إذا كان Bluetooth مطفأً، يطلب التطبيق من المستخدم تشغيله ثم يفتح قائمة الأجهزة من تلقاء نفسه؛ وعلى Android 6 إلى 11 يفعل الشيء نفسه لإعداد الموقع، لأن تلك الإصدارات لا تجد أي جهاز Bluetooth عندما يكون الموقع مطفأً.

حزام نبض القلب، من نقرة زر. navigator.bluetooth موجود قبل تشغيل أول سكربت لك، فالكشف عن الميزة في أعلى الصفحة يعمل؛ والفحص داخل المعالج، كما هنا، يبقى أأمن عادة:

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

الكتابة. writeValue وwriteValueWithResponse ينتظران تأكيد الجهاز؛ أما writeValueWithoutResponse فلا:

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

مستكشف الأجهزة. اختر أي جهاز قريب واعرض كل خدماته وخصائصه مع ما تستطيع كل منها فعله:

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

مستشعر سرعة ودورات الدراجة. مستشعر واحد (أو زوج منفصل للسرعة والدورات) على خدمة CSC القياسية. القيم مجاميع تراكمية، لذا تُحسَب السرعة والدورات من الفرق بين إشعارين:

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

عرض كل فشل على الشاشة. تُرفَض كل خطوة بخطأ له اسم، فتستطيع الصفحة إخبار المستخدم بما عليه فعله بدلًا من الصمت:

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

ملاحظات: يرسم التطبيق نافذة اختيار الجهاز ويبحث لمدة 30 ثانية ثم يعرض «البحث مجددًا»؛ ولا تستطيع الصفحة الوصول إلا إلى جهاز اختاره المستخدم. تُجيب getAvailability() عمّا إذا كان الهاتف يملك Bluetooth أصلًا (كما في Chrome)، لا عمّا إذا كان مفعّلًا: أما requestDevice() فتطلب تفعيله. المستشعر الذي يرسل اسمه في استجابة البحث فقط يظهر في القائمة باسمه ويطابق namePrefix. يُعاد الاتصال الأول الذي يقطعه Android فورًا قبل أن ترفض connect(). اسم خدمة بإملاء خاطئ في filters أو optionalServices يُرفَض بالخطأ TypeError. يطابق مرشح services الخدمات التي يعلن عنها الجهاز فقط. بعد connect() يمكن استخدام كل خدمة لدى الجهاز، حتى التي ليست في optionalServices (أبقِها على أي حال، فـ Chrome يحتاجها). متاح أيضًا: getAvailability() وgetPrimaryServices() وgetCharacteristics() وstopNotifications() وcharacteristic.properties وdevice.forget() وBluetoothUUID.getService/getCharacteristic/canonicalUUID. أما getDevices() وrequestLEScan() وwatchAdvertisements() والواصفات (descriptors) فتُرفَض بالخطأ NotSupportedError. لأجهزة اللياقة القياسية مع أرقام جاهزة التحليل، راجع bleStartScan.

wbConnect الجسر (bridge)#

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

طبقة النقل الخام لـ WebBluetoothPolyfill. لا تستدعي الصفحة هذه الدوال مباشرة، بل تستدعي navigator.bluetooth تمامًا كما تفعل في المتصفح.

موصوف ضمن مجموعته، Web Bluetooth (navigator.bluetooth)، وليس على حدة.

مثال

ناقل داخلي وراء device.gatt.connect(). الصفحات تستخدم navigator.bluetooth؛ لا تستدعِ هذا مباشرة.

يُرجع: (عبر gatt.connect()) Promise بـ BluetoothRemoteGATTServer، بعد اكتشاف خدمات الجهاز. يتطلب: Bluetooth (يفعّله إنشاء 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) يُرفض بـ NetworkError قبل أن ينتهي connect(). يمكن اتصال عدة أجهزة في الوقت نفسه.

wbDisconnect الجسر (bridge)#

window.WebToApk.wbDisconnect(deviceId: String)

طبقة النقل الخام لـ WebBluetoothPolyfill. لا تستدعي الصفحة هذه الدوال مباشرة، بل تستدعي navigator.bluetooth تمامًا كما تفعل في المتصفح.

موصوف ضمن مجموعته، Web Bluetooth (navigator.bluetooth)، وليس على حدة.

مثال

طبقة نقل داخلية خلف device.gatt.disconnect() (وdevice.forget()). تستخدم الصفحات navigator.bluetooth؛ لا تستدعِ هذه الدالة مباشرة.

القيمة المُرجَعة: لا شيء (disconnect() متزامنة، كما في معيار الويب). ثم يُطلق الجهاز gattserverdisconnected. المتطلبات: Bluetooth (يُفعَّل تلقائيًا في بناء 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 الجسر (bridge)#

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). يتطلب: Bluetooth (يفعّله إنشاء 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 الجسر (bridge)#

window.WebToApk.wbIsAvailable(): Boolean

getAvailability(): ما إذا كان الهاتف يملك Bluetooth (إجابة Chrome)، وليس ما إذا كان مشغّلًا. يطلب requestDevice() من المستخدم تشغيله؛ وكان الرد بـ false لمجرد أنه مطفأ يجعل الصفحات التي تفحص أولًا ("if (!await getAvailability())") تقول "Bluetooth غير مدعوم" ولا تصل أبدًا إلى تلك المطالبة (تقرير اللياقة 2026-10-03).

مثال

ناقل داخلي وراء navigator.bluetooth.getAvailability(). الصفحات تستخدم navigator.bluetooth؛ لا تستدعِ هذا مباشرة.

يُرجع: (عبر getAvailability()) Promise بـ true عندما يحتوي الإنشاء على Bluetooth وبلوتوث الهاتف مفعّل، وإلا false. يتطلب: Bluetooth (يفعّله إنشاء 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();
});

فعّل زر الاتصال فقط أثناء تشغيل Bluetooth، وافحص مجددًا كلما عاد المستخدم إلى التطبيق:

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

أعطِ رسالة واضحة "Bluetooth مطفأ" قبل فتح المنتقي، بدلًا من 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 الجسر (bridge)#

window.WebToApk.wbNotify(

طبقة النقل الخام لـ WebBluetoothPolyfill. لا تستدعي الصفحة هذه الدوال مباشرة، بل تستدعي navigator.bluetooth تمامًا كما تفعل في المتصفح.

موصوف ضمن مجموعته، Web Bluetooth (navigator.bluetooth)، وليس على حدة.

مثال

ناقل داخلي وراء characteristic.startNotifications() وstopNotifications(). الصفحات تستخدم navigator.bluetooth؛ لا تستدعِ هذا مباشرة.

يُرجع: (عبر الخاصية) Promise بالخاصية نفسها. كل قيمة جديدة تُطلق characteristicvaluechanged عليها، مع القيمة في event.target.value (من نوع DataView). يتطلب: Bluetooth (يفعّله إنشاء 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) {}
}

يمكن الاشتراك بالطريقة نفسها في مقياس حرارة لا يدعم قياسه سوى المؤشرات (indications)، وهنا عبر الخاصية 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). الخاصية التي تدعم الإشعارات الراجعة (indications) فقط يُشترك فيها بها تلقائيًا. والخاصية التي لا تستطيع الإشعار تُرفض بـ NotSupportedError. وoncharacteristicvaluechanged = fn يعمل أيضًا. بعد الانقطاع، اشترك مجددًا.

wbRead الجسر (bridge)#

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 أيضًا. يتطلب: Bluetooth (يفعّله إنشاء 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);
  }
}

استطلع مستشعرًا يمكن قراءته فقط، وهنا خاصية Temperature القياسية (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 الجسر (bridge)#

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}. يتطلب: Bluetooth (يفعّله إنشاء 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 }).

اختر جهازًا مخصصًا باسمه الدقيق أو ببادئة اسمه، مع إدراج خدمته الخاصة ذات 128 بت ليمكن استخدامها بعد الاتصال:

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)؛ وتعود الإجابة إلى البديل وليس إلى الصفحة. استدعِ requestDevice من نقرة. تستخدم المرشحات services (يجب أن تكون معلنة) وname (مطابقة تامة) وnamePrefix؛ ويطابق المرشح عندما تطابق كل مفاتيحه، ويطابق الجهاز عندما يطابق أي مرشح. إذا لم يوجد filters ولا acceptAllDevices ← TypeError. إلغاء المنتقي يُرفض بـ NotFoundError. يمسح المنتقي 30 ثانية. device.id هو عنوان Bluetooth للجهاز.

wbWrite الجسر (bridge)#

window.WebToApk.wbWrite(

طبقة النقل الخام لـ WebBluetoothPolyfill. لا تستدعي الصفحة هذه الدوال مباشرة، بل تستدعي navigator.bluetooth تمامًا كما تفعل في المتصفح.

موصوف ضمن مجموعته، Web Bluetooth (navigator.bluetooth)، وليس على حدة.

مثال

ناقل داخلي وراء characteristic.writeValue() وwriteValueWithResponse() وwriteValueWithoutResponse(). الصفحات تستخدم navigator.bluetooth؛ لا تستدعِ هذا مباشرة.

القيمة المُرجَعة: (عبر الخاصية) وعد Promise بـ undefined. المتطلبات: Bluetooth (يُفعَّل تلقائيًا في بناء 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));
}

أرسل أمرًا نصيًا طويلًا على قطع بحجم 20 بايت. لا يطلب التطبيق من الجهاز حجم حزمة أكبر، فـ 20 بايت هو ما يتسع في كتابة واحدة:

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.

يتم إنشاؤه من بيئة تشغيل التطبيق وملفات الأمثلة الخاصة به عند كل بناء للمستندات. اقرأه بصيغة Markdown · جميع العائلات.

تمت المراجعة مقابل الجسر المُرفق بتاريخ 2026-10-22.