برقراری ارتباط با دستگاه های بلوتوث از طریق جاوا اسکریپت

رابط برنامه‌نویسی کاربردی وب بلوتوث به وب‌سایت‌ها اجازه می‌دهد تا با دستگاه‌های بلوتوث ارتباط برقرار کنند.

فرانسوا بوفور
François Beaufort

چه می‌شود اگر به شما بگویم وب‌سایت‌ها می‌توانند با دستگاه‌های بلوتوث مجاور به روشی امن و با حفظ حریم خصوصی ارتباط برقرار کنند؟ به این ترتیب، مانیتورهای ضربان قلب، لامپ‌های آوازخوان و حتی لاک‌پشت‌ها می‌توانند مستقیماً با یک وب‌سایت تعامل داشته باشند؟

تاکنون، امکان تعامل با دستگاه‌های بلوتوث فقط برای برنامه‌های مخصوص پلتفرم امکان‌پذیر بوده است. API بلوتوث وب قصد دارد این وضعیت را تغییر دهد و آن را به مرورگرهای وب نیز بیاورد.

قبل از اینکه شروع کنیم

این سند فرض می‌کند که شما دانش اولیه‌ای در مورد نحوه عملکرد بلوتوث کم‌مصرف (BLE) و پروفایل ویژگی عمومی دارید.

اگرچه مشخصات API بلوتوث وب هنوز نهایی نشده است، نویسندگان مشخصات به طور فعال به دنبال توسعه‌دهندگان مشتاق هستند تا این API را امتحان کنند و در مورد مشخصات و پیاده‌سازی بازخورد ارائه دهند.

زیرمجموعه‌ای از API بلوتوث وب در ChromeOS، Chrome برای اندروید ۶.۰، مک (Chrome 56) و ویندوز ۱۰ (Chrome 70) موجود است. این بدان معناست که شما باید بتوانید دستگاه‌های بلوتوث کم‌مصرف نزدیک را درخواست و به آنها متصل شوید ، ویژگی‌های بلوتوث را بخوانید / بنویسید ، اعلان‌های GATT را دریافت کنید ، از قطع شدن اتصال یک دستگاه بلوتوث مطلع شوید و حتی توصیف‌گرهای بلوتوث را بخوانید و بنویسید . برای اطلاعات بیشتر به جدول سازگاری مرورگر MDN مراجعه کنید.

برای لینوکس و نسخه‌های قبلی ویندوز، پرچم #experimental-web-platform-features را در about://flags فعال کنید.

برای آزمایش‌های اولیه موجود است

برای دریافت هرچه بیشتر بازخورد از توسعه‌دهندگانی که از رابط برنامه‌نویسی وب بلوتوث در این زمینه استفاده می‌کنند، کروم قبلاً این ویژگی را در کروم ۵۳ به عنوان یک نسخه آزمایشی اصلی برای ChromeOS، اندروید و مک اضافه کرده بود.

این محاکمه با موفقیت در ژانویه ۲۰۱۷ به پایان رسید.

الزامات امنیتی

برای درک بده‌بستان‌های امنیتی، پست « مدل امنیتی بلوتوث وب» از جفری یاسکین، مهندس نرم‌افزار تیم کروم که روی مشخصات رابط برنامه‌نویسی کاربردی بلوتوث وب کار می‌کند، را توصیه می‌کنم.

فقط HTTPS

از آنجا که این API آزمایشی یک ویژگی قدرتمند جدید اضافه شده به وب است، فقط برای ایمن‌سازی زمینه‌ها در دسترس قرار گرفته است. این بدان معناست که شما باید TLS را در نظر داشته باشید.

اشاره کاربر مورد نیاز است

به عنوان یک ویژگی امنیتی، کشف دستگاه‌های بلوتوث با navigator.bluetooth.requestDevice باید توسط یک حرکت کاربر مانند لمس یا کلیک ماوس انجام شود. ما در مورد گوش دادن به رویدادهای pointerup ، click و touchend صحبت می‌کنیم.

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

وارد کد شوید

رابط برنامه‌نویسی کاربردی وب بلوتوث به شدت به Promises جاوااسکریپت متکی است. اگر با آنها آشنا نیستید، این آموزش عالی Promises را بررسی کنید. یک نکته دیگر، () => {} توابع Arrow در ECMAScript 2015 هستند.

درخواست دستگاه‌های بلوتوث

این نسخه از مشخصات رابط برنامه‌نویسی کاربردی بلوتوث وب به وب‌سایت‌هایی که در نقش مرکزی اجرا می‌شوند، اجازه می‌دهد تا از طریق اتصال BLE به سرورهای GATT از راه دور متصل شوند. این نسخه از ارتباط بین دستگاه‌هایی که بلوتوث ۴.۰ یا بالاتر را پیاده‌سازی می‌کنند، پشتیبانی می‌کند.

وقتی یک وب‌سایت با استفاده از navigator.bluetooth.requestDevice درخواست دسترسی به دستگاه‌های مجاور را می‌دهد، مرورگر از کاربر می‌خواهد که دستگاه مورد نظر را انتخاب کند یا درخواست را لغو کند.

درخواست کاربر دستگاه بلوتوث.

تابع navigator.bluetooth.requestDevice() یک شیء اجباری می‌گیرد که فیلترها را تعریف می‌کند. این فیلترها برای برگرداندن فقط دستگاه‌هایی استفاده می‌شوند که با برخی از سرویس‌های GATT بلوتوث تبلیغ‌شده و/یا نام دستگاه مطابقت دارند.

فیلتر خدمات

برای مثال، برای درخواست دستگاه‌های بلوتوث که سرویس باتری بلوتوث GATT را تبلیغ می‌کنند:

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

اگر سرویس بلوتوث GATT شما در فهرست سرویس‌های استاندارد بلوتوث GATT نیست، می‌توانید UUID کامل بلوتوث یا یک فرم کوتاه ۱۶ یا ۳۲ بیتی را ارائه دهید.

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

فیلتر نام

همچنین می‌توانید دستگاه‌های بلوتوث را بر اساس نام دستگاهی که با کلید name filters تبلیغ می‌شود، یا حتی پیشوندی از این نام با کلید namePrefix filters درخواست کنید. توجه داشته باشید که در این حالت، برای دسترسی به هر سرویسی که در فیلتر سرویس گنجانده نشده است، باید کلید optionalServices را نیز تعریف کنید. اگر این کار را نکنید، بعداً هنگام تلاش برای دسترسی به آنها با خطا مواجه خواهید شد.

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

فیلتر داده‌های تولیدکننده

همچنین می‌توان دستگاه‌های بلوتوث را بر اساس داده‌های خاص سازنده که با کلید فیلترهای manufacturerData تبلیغ می‌شوند، درخواست کرد. این کلید، آرایه‌ای از اشیاء با یک کلید شناسایی شرکت بلوتوث اجباری به نام companyIdentifier است. همچنین می‌توانید یک پیشوند داده ارائه دهید که داده‌های سازنده را از دستگاه‌های بلوتوثی که با آن شروع می‌شوند، فیلتر کند. توجه داشته باشید که برای دسترسی به هر سرویسی که در فیلتر سرویس گنجانده نشده است، باید کلید 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); });

بدون فیلتر

در نهایت، به جای filters می‌توانید از کلید acceptAllDevices برای نمایش همه دستگاه‌های بلوتوث نزدیک استفاده کنید. همچنین برای دسترسی به برخی سرویس‌ها باید کلید 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_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 کامل بلوتوث یا یک فرم کوتاه ۱۶ یا ۳۲ بیتی را به service.getCharacteristic ارائه دهید.

توجه داشته باشید که می‌توانید یک شنونده رویداد characteristicvaluechanged را نیز روی یک مشخصه اضافه کنید تا خواندن مقدار آن را مدیریت کند. برای مشاهده نحوه مدیریت اختیاری اعلان‌های آتی GATT، نمونه «خواندن مقدار مشخصه تغییریافته» (Read Characteristic Value Changed Sample) را بررسی کنید.


.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 به آسانی خواندن آن است. این بار، بیایید از نقطه کنترل ضربان قلب برای تنظیم مجدد مقدار فیلد انرژی مصرفی در دستگاه مانیتور ضربان قلب به ۰ استفاده کنیم.

قول می‌دهم هیچ جادویی در کار نیست. همه چیز در صفحه «مشخصات نقطه کنترل ضربان قلب» توضیح داده شده است.

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 مشخصه Measurement Interval و gatt.characteristic_user_description توصیفگر مشخصه 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); });

نمونه‌ها، دموها و آزمایشگاه‌های کد

تمام نمونه‌های بلوتوث وب زیر با موفقیت آزمایش شده‌اند. برای لذت بردن کامل از این نمونه‌ها، توصیه می‌کنم [برنامه اندروید شبیه‌ساز لوازم جانبی BLE] را نصب کنید که یک وسیله جانبی BLE را با سرویس باتری، سرویس ضربان قلب یا سرویس دماسنج سلامت شبیه‌سازی می‌کند.

مبتدی

  • اطلاعات دستگاه - اطلاعات اولیه دستگاه را از یک دستگاه BLE بازیابی کنید.
  • سطح باتری - اطلاعات باتری را از دستگاه BLE که اطلاعات باتری را تبلیغ می‌کند، بازیابی کنید.
  • تنظیم مجدد انرژی - انرژی مصرف شده از دستگاه BLE که ضربان قلب را تبلیغ می‌کند، تنظیم مجدد می‌شود.
  • ویژگی‌های مشخصه - نمایش تمام ویژگی‌های یک مشخصه خاص از یک دستگاه BLE.
  • اعلان‌ها - اعلان‌های مشخص را از یک دستگاه BLE شروع و متوقف کنید.
  • قطع اتصال دستگاه - اتصال را قطع کنید و پس از اتصال به دستگاه BLE از قطع شدن آن مطلع شوید.
  • دریافت مشخصات - تمام مشخصات یک سرویس تبلیغ‌شده را از یک دستگاه BLE دریافت کنید.
  • دریافت توصیف‌گرها - دریافت تمام توصیف‌گرهای ویژگی‌های یک سرویس تبلیغ‌شده از یک دستگاه BLE.
  • فیلتر داده‌های سازنده - اطلاعات اولیه دستگاه را از یک دستگاه BLE که با داده‌های سازنده مطابقت دارد، بازیابی کنید.
  • فیلترهای حذف - اطلاعات اولیه دستگاه را از یک دستگاه BLE که دارای فیلترهای حذف اولیه است، بازیابی کنید.

ترکیب چندین عملیات

همچنین می‌توانید دموهای منتخب بلوتوث وب و آزمایشگاه‌های رسمی کد بلوتوث وب ما را بررسی کنید.

کتابخانه‌ها

  • web-bluetooth-utils یک ماژول npm است که برخی توابع کاربردی را به API اضافه می‌کند.
  • یک شیم رابط برنامه‌نویسی کاربردی بلوتوث وب در noble ، محبوب‌ترین ماژول مرکزی Node.js BLE، موجود است. این به شما امکان می‌دهد noble را بدون نیاز به سرور WebSocket یا سایر افزونه‌ها، webpack/browserify کنید.
  • angular-web-bluetooth یک ماژول برای Angular است که تمام کدهای تکراری مورد نیاز برای پیکربندی Web Bluetooth API را خلاصه می‌کند.

ابزارها

  • شروع کار با بلوتوث وب یک برنامه وب ساده است که تمام کدهای جاوا اسکریپت را برای شروع تعامل با یک دستگاه بلوتوث تولید می‌کند. نام دستگاه، سرویس و ویژگی آن را وارد کنید، ویژگی‌های آن را تعریف کنید و آماده شروع به کار هستید.
  • اگر از قبل توسعه‌دهنده بلوتوث هستید، افزونه Web Bluetooth Developer Studio کد جاوا اسکریپت Web Bluetooth را نیز برای دستگاه بلوتوث شما تولید می‌کند.

نکات

صفحه Bluetooth Internals در کروم با about://bluetooth-internals در دسترس است تا بتوانید همه چیز را در مورد دستگاه‌های بلوتوث مجاور بررسی کنید: وضعیت، سرویس‌ها، ویژگی‌ها و توصیف‌کننده‌ها.

تصویر صفحه داخلی برای اشکال‌زدایی بلوتوث در کروم
صفحه داخلی در کروم برای اشکال‌زدایی دستگاه‌های بلوتوث.

همچنین توصیه می‌کنم صفحه رسمی «نحوه ثبت اشکالات بلوتوث وب» را بررسی کنید، زیرا اشکال‌زدایی بلوتوث گاهی اوقات می‌تواند دشوار باشد.

قدم بعدی چیست؟

ابتدا وضعیت پیاده‌سازی مرورگر و پلتفرم را بررسی کنید تا بدانید کدام بخش‌های API بلوتوث وب در حال حاضر پیاده‌سازی شده‌اند.

اگرچه هنوز ناقص است، در اینجا نگاهی اجمالی به آنچه در آینده نزدیک انتظار می‌رود، می‌اندازیم:

  • اسکن تبلیغات BLE نزدیک با navigator.bluetooth.requestLEScan() انجام می‌شود.
  • یک رویداد جدید serviceadded سرویس‌های بلوتوث GATT که به تازگی کشف شده‌اند را ردیابی می‌کند، در حالی که رویداد serviceremoved سرویس‌های حذف شده را ردیابی می‌کند. یک رویداد جدید servicechanged زمانی فعال می‌شود که هر مشخصه و/یا توصیف‌گری به یک سرویس بلوتوث GATT اضافه یا حذف شود.

نمایش پشتیبانی از API

آیا قصد دارید از API بلوتوث وب استفاده کنید؟ حمایت عمومی شما به تیم کروم کمک می‌کند تا ویژگی‌ها را در اولویت قرار دهد و به سایر فروشندگان مرورگر نشان می‌دهد که پشتیبانی از آنها چقدر حیاتی است.

با استفاده از هشتگ #WebBluetooth یک توییت به @ChromiumDev ارسال کنید و به ما بگویید که کجا و چگونه از آن استفاده می‌کنید.

منابع

تقدیرنامه‌ها

با تشکر از کیس باسکز برای بررسی.