التواصل مع الأجهزة التي تتضمّن بلوتوث عبر JavaScript

تسمح Web Bluetooth API للمواقع الإلكترونية بالتواصل مع الأجهزة التي تتضمّن بلوتوث.

François Beaufort
François Beaufort

ماذا لو أخبرتك أن مواقع الويب يمكن أن تتصل بأجهزة بلوتوث القريبة بطريقة آمنة تحافظ على الخصوصية؟ بهذه الطريقة، يمكن لأجهزة مراقبة معدل ضربات القلب، وغناء مصابيح المصابيح الكهربائية، وحتى السلاحف التفاعل مباشرةً مع الموقع الإلكتروني.

حتى الآن، كانت إمكانية التفاعل مع الأجهزة التي تتضمّن بلوتوث متاحة فقط للتطبيقات المخصّصة للمنصة. تهدف Web Bluetooth API إلى تغيير ذلك و توفيرها لمتصفّحات الويب أيضًا.

قبل البدء

يفترض هذا المستند أنّ لديك بعض المعرفة الأساسية بكيفية عمل تقنية Bluetooth Low Energy (BLE) وGeneric Attribute Profile.

على الرغم من أنّ مواصفات Web Bluetooth API لم تكتمل بعد، يبحث مؤلفو المواصفات عن مطوّرين متحمّسين لتجربة هذه الواجهة وتقديم ملاحظاتهم حول المواصفات وتقديم ملاحظاتهم حول التنفيذ.

تتوفّر مجموعة فرعية من Web Bluetooth API في ChromeOS وChrome لنظام التشغيل Android 6.0 وMac (Chrome 56) وWindows 10 (Chrome 70). وهذا يعني أنّه يجب أن يكون بإمكانك طلب والاتصال بأجهزة Bluetooth Low Energy المجاورة وقراءة/write خصائص البلوتوث وتلقي إشعارات GATT، ومعرفة عند انقطاع اتصال جهاز بلوتوث، وحتى القراءة والكتابة إلى أدوات وصف البلوتوث. اطّلِع على جدول توافق المتصفّحات في MDN للحصول على مزيد من المعلومات.

بالنسبة إلى نظام التشغيل Linux والإصدارات الأقدم من نظام التشغيل Windows، فعِّل علامة #experimental-web-platform-features في about://flags.

متوفّر لعمليات التجربة والتقييم

للحصول على أكبر قدر ممكن من الملاحظات من المطوّرين الذين يستخدمون واجهة برمجة التطبيقات Web Bluetooth API في المجال، أضاف Chrome هذه الميزة سابقًا في Chrome 53 كإصدار تجريبي أولي لنظام التشغيل ChromeOS وAndroid وMac.

وانتهت الفترة التجريبية بنجاح في كانون الثاني (يناير) 2017.

متطلبات الأمان

لفهم المفاضلات الأمنية، أنصحك بمقالة Web Bluetooth Security Model التي كتبها جيفري ياسكين، وهو مهندس برامج في فريق Chrome، ويشرف على مواصفات Web Bluetooth API.

HTTPS فقط

وبما أنّ واجهة برمجة التطبيقات التجريبية هذه هي ميزة جديدة وفعّالة تمت إضافتها إلى الويب، فإنّه لا تتوفّر إلا للسياقات الآمنة. ويعني هذا أنّك ستحتاج إلى الإنشاء مع وضع TLS في الاعتبار.

يجب أن يُجري المستخدم إيماءة

كميزة أمان، يجب أن يتم بدء اكتشاف أجهزة البلوتوث باستخدام navigator.bluetooth.requestDevice من خلال إيماءة المستخدم، مثل اللمس أو النقر بالماوس. نقصد الاستماع إلى أحداث pointerup وclick وtouchend.

button.addEventListener('pointerup', function(event) {
  // Call navigator.bluetooth.requestDevice
});

الاطّلاع على الرمز

تعتمد Web Bluetooth API اعتمادًا كبيرًا على وعود JavaScript. إذا لم تكن على دراية بها، يمكنك الاطّلاع على هذا الدليل التعليمي الرائع حول الوعود. هناك شيء آخر، وهو أنّ () => {} هي دوالّ الأسهم في ECMAScript 2015.

طلب أجهزة تتضمّن بلوتوث

يسمح هذا الإصدار من مواصفات Web Bluetooth API للمواقع الإلكترونية التي تعمل في الدور المركزي بالاتصال بخوادم GATT عن بُعد عبر اتصال BLE. ويسمح بالاتصال بين الأجهزة التي تستخدم البلوتوث 4.0 أو الإصدارات الأحدث.

عندما يطلب موقع إلكتروني الوصول إلى الأجهزة المجاورة باستخدام navigator.bluetooth.requestDevice، يعرض المتصفّح على المستخدم أداة اختيار devices يمكنه من خلالها اختيار جهاز واحد أو إلغاء الطلب.

طلب من مستخدم جهاز البلوتوث

تأخذ الدالة navigator.bluetooth.requestDevice() عنصرًا إلزاميًا يحدد الفلاتر. تُستخدَم هذه الفلاتر لعرض الأجهزة التي تتطابق فقط مع بعض خدمات GATT للبلوتوث المُعلَن عنها و/أو اسم الجهاز.

فلتر الخدمات

على سبيل المثال، لطلب أجهزة بلوتوث تعرض إعلانات عن Bluetooth GATT Battery Service:

navigator.bluetooth.requestDevice({ filters: [{ services: ['battery_service'] }] })
.then(device => { /* … */ })
.catch(error => { console.error(error); });

إذا لم تكن خدمة Bluetooth GATT مدرَجة في قائمة خدمات GATT Bluetooth العادية، يمكنك تقديم معرّف UUID الكامل لبروتوكول Bluetooth أو نموذج قصير بحجم 16 أو 32 بت.

navigator.bluetooth.requestDevice({
  filters: [{
    services: [0x1234, 0x12345678, '99999999-0000-1000-8000-00805f9b34fb']
  }]
})
.then(device => { /* … */ })
.catch(error => { console.error(error); });

فلتر الاسم

يمكنك أيضًا طلب أجهزة بلوتوث استنادًا إلى اسم الجهاز الذي يتم الإعلان عنه باستخدام مفتاح الفلاتر name، أو حتى بادئة لهذا الاسم باستخدام مفتاح الفلاتر namePrefix. تجدر الإشارة إلى أنّه في هذه الحالة، ستحتاج أيضًا إلى تحديد مفتاح optionalServices لتتمكّن من الوصول إلى أي خدمات غير مضمّنة في أحد فلاتر الخدمات. وإذا لم تفعل ذلك، فستتلقى خطأ لاحقًا عند محاولة الوصول إليها.

navigator.bluetooth.requestDevice({
  filters: [{
    name: 'Francois robot'
  }],
  optionalServices: ['battery_service'] // Required to access service later.
})
.then(device => { /* … */ })
.catch(error => { console.error(error); });

فلتر بيانات الشركة المصنّعة

من الممكن أيضًا طلب أجهزة بلوتوث استنادًا إلى بيانات محدّدة الخاصة بالمصنّع يتم الإعلان عنها باستخدام مفتاح فلاتر manufacturerData. هذا المفتاح هو مصفوفة من العناصر التي تحتوي على مفتاح معرّف شركة Bluetooth إلزامي باسم companyIdentifier. يمكنك أيضًا توفير بادئة بيانات تعمل على تصفية بيانات الشركة المصنعة من أجهزة Bluetooth التي تبدأ بها. يُرجى العلم أنّه عليك تحديد مفتاح optionalServices أيضًا لتتمكّن من الوصول إلى أي خدمات غير مضمّنة في فلتر الخدمات. وإذا لم تفعل ذلك، ستظهر لك رسالة خطأ لاحقًا عند محاولة الوصول إليها.

// Filter Bluetooth devices from Google company with manufacturer data bytes
// that start with [0x01, 0x02].
navigator.bluetooth.requestDevice({
  filters: [{
    manufacturerData: [{
      companyIdentifier: 0x00e0,
      dataPrefix: new Uint8Array([0x01, 0x02])
    }]
  }],
  optionalServices: ['battery_service'] // Required to access service later.
})
.then(device => { /* … */ })
.catch(error => { console.error(error); });

يمكن أيضًا استخدام قناع مع بادئة بيانات لمطابقة بعض الأنماط في بيانات المصنّع. اطّلِع على الشرح المفصّل لفلاتر بيانات البلوتوث للاطّلاع على مزيد من المعلومات.

فلاتر الاستبعاد

يتيح لك الخيار exclusionFilters في navigator.bluetooth.requestDevice() استبعاد بعض الأجهزة من أداة اختيار المتصفّحات. ويمكن استخدامه لاستبعاد الأجهزة التي تتطابق مع فلتر أوسع ولكنّها غير متوافقة.

// Request access to a bluetooth device whose name starts with "Created by".
// The device named "Created by Francois" has been reported as unsupported.
navigator.bluetooth.requestDevice({
  filters: [{
    namePrefix: "Created by"
  }],
  exclusionFilters: [{
    name: "Created by Francois"
  }],
  optionalServices: ['battery_service'] // Required to access service later.
})
.then(device => { /* … */ })
.catch(error => { console.error(error); });

عدم استخدام الفلاتر

أخيرًا، يمكنك استخدام المفتاح acceptAllDevices لعرض جميع الأجهزة القريبة التي تتضمّن بلوتوث بدلاً من filters. ستحتاج أيضًا إلى تحديد مفتاح optionalServices للتمكّن من الوصول إلى بعض الخدمات. وفي حال عدم إجراء ذلك، ستظهر لك رسالة خطأ في وقت لاحق عند محاولة الوصول إلى هذه العناصر.

navigator.bluetooth.requestDevice({
  acceptAllDevices: true,
  optionalServices: ['battery_service'] // Required to access service later.
})
.then(device => { /* … */ })
.catch(error => { console.error(error); });

الاتصال بجهاز بلوتوث

ما هي الإجراءات التي يجب اتّخاذها الآن بعد أن حصلت على BluetoothDevice؟ لنتصل بخادم GATT عن بُعد بالبلوتوث الذي يحمل الخدمة والتعريفات المميزة.

navigator.bluetooth.requestDevice({ filters: [{ services: ['battery_service'] }] })
.then(device => {
  // Human-readable name of the device.
  console.log(device.name);

  // Attempts to connect to remote GATT Server.
  return device.gatt.connect();
})
.then(server => { /* … */ })
.catch(error => { console.error(error); });

قراءة سمة بلوتوث

هنا نتصل بخادم GATT الخاص بجهاز البلوتوث البعيد. الآن نريد الحصول على خدمة GATT الأساسية وقراءة إحدى الخصائص التي تنتمي إلى هذه الخدمة. لنحاول مثلاً قراءة مستوى الشحن الحالي ل battery الجهاز.

في المثال القادم، battery_level هي سمة مستوى البطارية الموحّدة.

navigator.bluetooth.requestDevice({ filters: [{ services: ['battery_service'] }] })
.then(device => device.gatt.connect())
.then(server => {
  // Getting Battery Service…
  return server.getPrimaryService('battery_service');
})
.then(service => {
  // Getting Battery Level Characteristic…
  return service.getCharacteristic('battery_level');
})
.then(characteristic => {
  // Reading Battery Level…
  return characteristic.readValue();
})
.then(value => {
  console.log(`Battery percentage is ${value.getUint8(0)}`);
})
.catch(error => { console.error(error); });

إذا كنت تستخدم سمة GATT مخصّصة في البلوتوث، يمكنك تقديم إما معرّف UUID الكامل للبلوتوث أو نموذجًا قصيرًا مكوّنًا من 16 أو 32 بتًا لملف service.getCharacteristic.

تجدر الإشارة إلى أنّه يمكنك أيضًا إضافة مستمع لأحداث characteristicvaluechanged في سمة للتعامل مع قراءة قيمتها. يمكنك الاطّلاع على قراءة نموذج تغيير قيمة الخصائص لمعرفة كيفية التعامل بشكل اختياري مع إشعارات GATT القادمة أيضًا.


.then(characteristic => {
  // Set up event listener for when characteristic value changes.
  characteristic.addEventListener('characteristicvaluechanged',
                                  handleBatteryLevelChanged);
  // Reading Battery Level…
  return characteristic.readValue();
})
.catch(error => { console.error(error); });

function handleBatteryLevelChanged(event) {
  const batteryLevel = event.target.value.getUint8(0);
  console.log('Battery percentage is ' + batteryLevel);
}

الكتابة إلى إحدى خصائص البلوتوث

إنّ الكتابة في سمة GATT في البلوتوث سهلة مثل قراءتها. هذه المرة، دعنا نستخدم نقطة التحكم في معدل ضربات القلب لإعادة تعيين قيمة حقل "Energy Expended" (الطاقة المستفادة) إلى 0 على جهاز مراقبة معدل ضربات القلب.

أعدك أنه ليس هناك سحر هنا. ويمكن شرح ذلك بالتفصيل في صفحة "خصائص نقطة التحكّم في معدّل نبضات القلب".

navigator.bluetooth.requestDevice({ filters: [{ services: ['heart_rate'] }] })
.then(device => device.gatt.connect())
.then(server => server.getPrimaryService('heart_rate'))
.then(service => service.getCharacteristic('heart_rate_control_point'))
.then(characteristic => {
  // Writing 1 is the signal to reset energy expended.
  const resetEnergyExpended = Uint8Array.of(1);
  return characteristic.writeValue(resetEnergyExpended);
})
.then(_ => {
  console.log('Energy expended has been reset.');
})
.catch(error => { console.error(error); });

تلقّي إشعارات GATT

لنطّلِع الآن على كيفية تلقّي إشعار عند تغيُّر سمة قياس معدل ضربات القلب على الجهاز:

navigator.bluetooth.requestDevice({ filters: [{ services: ['heart_rate'] }] })
.then(device => device.gatt.connect())
.then(server => server.getPrimaryService('heart_rate'))
.then(service => service.getCharacteristic('heart_rate_measurement'))
.then(characteristic => characteristic.startNotifications())
.then(characteristic => {
  characteristic.addEventListener('characteristicvaluechanged',
                                  handleCharacteristicValueChanged);
  console.log('Notifications have been started.');
})
.catch(error => { console.error(error); });

function handleCharacteristicValueChanged(event) {
  const value = event.target.value;
  console.log('Received ' + value);
  // TODO: Parse Heart Rate Measurement value.
  // See https://github.com/WebBluetoothCG/demos/blob/gh-pages/heart-rate-sensor/heartRateSensor.js
}

يوضّح لك نموذج الإشعارات كيفية إيقاف الإشعارات باستخدام stopNotifications() وإزالة مستمع أحداث characteristicvaluechanged المُضاف بشكلٍ صحيح.

قطع الاتصال بجهاز بلوتوث

لتوفير تجربة أفضل للمستخدم، ننصحك بالاستماع إلى أحداث انقطاع الاتصال ودعوة المستخدم إلى إعادة الاتصال:

navigator.bluetooth.requestDevice({ filters: [{ name: 'Francois robot' }] })
.then(device => {
  // Set up event listener for when device gets disconnected.
  device.addEventListener('gattserverdisconnected', onDisconnected);

  // Attempts to connect to remote GATT Server.
  return device.gatt.connect();
})
.then(server => { /* … */ })
.catch(error => { console.error(error); });

function onDisconnected(event) {
  const device = event.target;
  console.log(`Device ${device.name} is disconnected.`);
}

يمكنك أيضًا الاتصال برقم device.gatt.disconnect() لإلغاء ربط تطبيق الويب بجهاز البلوتوث. سيؤدي ذلك إلى تشغيل المستمعين الحاليين إلى أحداث "gattserverdisconnected". يُرجى العِلم أنّه لن يتم إيقاف التواصل مع جهاز البلوتوث إذا كان هناك تطبيق آخر يتواصل مع جهاز البلوتوث. اطّلِع على مثال على إلغاء ربط الجهاز ومثال على إعادة الربط التلقائي للاطّلاع على مزيد من التفاصيل.

قراءة وكتابة أوصاف البلوتوث

أوصاف GATT في البلوتوث هي سمات تصف قيمة سمة. ويمكنك قراءتها وكتابتها بطريقة مشابهة لخصائص GATT للبلوتوث.

لنلقِ نظرة على كيفية قراءة وصف المستخدم لمُعدّل قياس الحرارة في ميزان الحرارة الصحي للجهاز.

في المثال الموضَّح أدناه، health_thermometer هي خدمة ميزان الحرارة الصحي، measurement_interval وسمة الفاصل الزمني للقياس، gatt.characteristic_user_description وواصف وصف المستخدم المميّز.

navigator.bluetooth.requestDevice({ filters: [{ services: ['health_thermometer'] }] })
.then(device => device.gatt.connect())
.then(server => server.getPrimaryService('health_thermometer'))
.then(service => service.getCharacteristic('measurement_interval'))
.then(characteristic => characteristic.getDescriptor('gatt.characteristic_user_description'))
.then(descriptor => descriptor.readValue())
.then(value => {
  const decoder = new TextDecoder('utf-8');
  console.log(`User Description: ${decoder.decode(value)}`);
})
.catch(error => { console.error(error); });

بعد أن اطّلعنا على وصف المستخدم لفترة القياس لميزان حرارة الصعوبة الصحية في الجهاز، لنلقِ نظرة على كيفية تعديلها وكتابة قيمة مخصّصة.

navigator.bluetooth.requestDevice({ filters: [{ services: ['health_thermometer'] }] })
.then(device => device.gatt.connect())
.then(server => server.getPrimaryService('health_thermometer'))
.then(service => service.getCharacteristic('measurement_interval'))
.then(characteristic => characteristic.getDescriptor('gatt.characteristic_user_description'))
.then(descriptor => {
  const encoder = new TextEncoder('utf-8');
  const userDescription = encoder.encode('Defines the time between measurements.');
  return descriptor.writeValue(userDescription);
})
.catch(error => { console.error(error); });

النماذج والعروض التوضيحية والدروس التطبيقية حول الترميز

تم اختبار جميع عيّنات Web Bluetooth أدناه بنجاح. للاستفادة من هذه عيّنات على أكمل وجه، أنصحك بتثبيت [BLE Peripheral Simulator Android App] الذي يحاكي جهازًا طرفيًا للبلوتوث منخفض الطاقة باستخدام خدمة البطارية أو خدمة معدل ضربات القلب أو خدمة الترمومتر الصحي.

مبتدئ

دمج عمليات متعددة

يمكنك أيضًا الاطّلاع على العروض التوضيحية المنظَّمة لواجهة Web Bluetooth والإصدارات الرسمية من Web Bluetooth Codelabs.

المكتبات

  • web-bluetooth-utils هي وحدة npm تضيف بعض وظائف تسهيل الاستخدام إلى واجهة برمجة التطبيقات.
  • تتوفّر حزمة Web Bluetooth API shim في noble، وهي وحدة Node.js BLE central الأكثر رواجًا. ويتيح لك ذلك استخدام حزم الويب أو استخدام المتصفِّحات النبيلة بدون الحاجة إلى خادم WebSocket أو مكوّنات إضافية أخرى.
  • angular-web-bluetooth هي وحدة Angular تُنشئ نموذجًا موحّدًا لجميع الإجراءات الأساسية اللازمة لضبط Web Bluetooth API.

الأدوات

  • البدء باستخدام Web Bluetooth هو تطبيق ويب بسيط سينشئ كل الرمز البرمجي الجاهز لـ JavaScript لبدء التفاعل مع جهاز يتضمّن بلوتوث. أدخِل اسم جهاز أو خدمة أو سمة وحدِّد خصائصها ويمكنك المتابعة.
  • إذا كنت مطوِّر تطبيقات بلوتوث، سينشئ مكوّن Web Bluetooth Developer Studio الإضافي أيضًا رمز JavaScript لتطبيق Web Bluetooth لجهازك الذي يتضمّن بلوتوث.

نصائح

تتوفّر صفحة الإعدادات الداخلية للبلوتوث في Chrome على about://bluetooth-internals كي تتمكّن من فحص كل البيانات المتعلّقة بالأجهزة القريبة التي تتضمّن بلوتوث، مثل الحالة والخدمات والخصائص والأدوات الوصفية.

لقطة شاشة للصفحة الداخلية لتصحيح أخطاء البلوتوث في Chrome
صفحة داخلية في Chrome لتصحيح أخطاء أجهزة البلوتوث

ننصحك أيضًا بالاطّلاع على صفحة كيفية الإبلاغ عن أخطاء Web Bluetooth الرسمية، لأنّ تصحيح أخطاء البلوتوث قد يكون صعبًا في بعض الأحيان.

الخطوات التالية

تحقَّق أولاً من حالة تنفيذ المتصفّح والنظام الأساسي لمعرفة الأجزاء من Web Bluetooth API التي يتم تنفيذها حاليًا.

على الرغم من أنّ هذه الميزة لا تزال غير مكتملة، إليك نظرة سريعة على ما يمكن توقّعه في القريب المستقبل:

  • سيتم إجراء البحث عن "إعلانات BLE" قريبة في navigator.bluetooth.requestLEScan().
  • سيتتبّع الحدث الجديد serviceadded خدمات GATT في البلوتوث التي تم اكتشافها حديثًا، بينما سيتتبّع الحدث serviceremoved الخدمات التي تمّت إزالتها. سيتم بدء حدث servicechanged جديد عند إضافة أيّ سمة و/أو وصف أو إزالتهما من خدمة GATT في البلوتوث.

إظهار الدعم لواجهة برمجة التطبيقات

هل تخطّط لاستخدام Web Bluetooth API؟ يساعد دعمك العلني فريق Chrome في تحديد الميزات ذات الأولوية وإظهار مدى أهمية توفيرها في المتصفّحات الأخرى.

أرسِل تغريدة إلى ‎@ChromiumDev باستخدام الهاشتاغ #WebBluetooth وأطلِعنا على مكان استخدامك للميزة وطريقة استخدامك لها.

الموارد

الشكر والتقدير

نشكر Kayce Basques على مراجعة هذه المقالة. صورة رئيسية بواسطة SparkFun Electronics من Bolder، الولايات المتحدة الأمريكية.