التواصل مع الأجهزة التي تتضمّن بلوتوث عبر 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). وهذا يعني أنّه من المفترض أن تتمكّن من طلب والاتصال بالأجهزة القريبة التي تتضمّن بلوتوث منخفض الطاقة، قراءة/كتابة سمات البلوتوث، وتلقّي إشعارات 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. يمكنك أيضًا تقديم بادئة بيانات لفلترة data بيانات المصنّع من أجهزة البلوتوث التي تبدأ بها. يُرجى العلم أنّه عليك تحديد مفتاح 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 إلى سمة 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 في البلوتوث سهلة مثل قراءتها. هذه المرة، لنستخدم نقطة التحكّم في معدل ضربات القلب لإعادة ضبط قيمة حقل "الطاقة المستهلكة" على 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 خدمة 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 الأكثر رواجًا. يتيح لك ذلك استخدام webpack/browserify noble بدون الحاجة إلى خادم 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 من بولدر، الولايات المتحدة الأمريكية.