التحكّم في شمعة PLAYBULB باستخدام Web Bluetooth

1. الفكرة الرئيسية للّعبة

IMG_19700101_023537~2~2.jpg

في هذا الدرس التطبيقي المدروس حول الترميز، ستتعرّف على كيفية التحكّم في شمعة عاكسة لإضاءة LED في PLAYBULB باستخدام JavaScript فقط، وذلك بفضل Web Bluetooth API. بالإضافة إلى ذلك، ستستمتع أيضًا بميزات JavaScript ES2015، مثل الفئات والدوال السهمية والخريطة والوعود.

المعلومات التي ستطّلع عليها

  • كيفية التفاعل مع جهاز قريب يتضمّن بلوتوث في JavaScript
  • كيفية استخدام فئات ES2015، ودوال الأسهم، والخرائط، والوعود

المتطلبات

2. اللعب أولاً

ننصحك بالاطّلاع على الإصدار النهائي من التطبيق الذي تريد إنشائه من خلال الرابط https://googlecodelabs.github.io/candle-bluetooth واللعب باستخدام جهاز PLAYBULB Candle الذي يعمل بتقنية البلوتوث تحت تصرفك قبل الانتقال إلى هذا الدرس التطبيقي حول الترميز.

يمكنك أيضًا متابعتي أثناء تغيير الألوان على الرابط https://www.youtube.com/watch?v=fBCPA9gIxlU

3- الإعداد

تنزيل نموذج الرمز

يمكنك الحصول على نموذج الرمز لهذا الرمز عن طريق تنزيل الرمز المضغوط من هنا:

أو من خلال استنساخ مستودع git هذا:

git clone https://github.com/googlecodelabs/candle-bluetooth.git

إذا نزّلت المصدر كملف zip، سيؤدي فك ضغطه إلى الحصول على المجلد الجذر candle-bluetooth-master.

تثبيت خادم الويب وإثبات ملكيته

على الرغم من أنّه يمكنك استخدام خادم الويب الخاص بك بحرية، صُمِّم هذا الدرس التطبيقي ليعمل بشكل جيد مع خادم الويب Chrome. إذا لم يكن هذا التطبيق مثبّتًا، يمكنك تثبيته من "سوق Chrome الإلكتروني".

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

لقطة شاشة يوم 16-11-2016 في الساعة 4.10.42 مساءً.png

في النافذة التالية، انقر فوق أيقونة خادم الويب:

9f3c21b2cf6cbfb5.png

سيظهر لك مربع الحوار هذا بعد ذلك، والذي يتيح لك ضبط خادم الويب المحلي:

لقطة شاشة يوم 16-11-2016 في الساعة 3.40.47 مساءً.png

انقر على الزر اختيار مجلد، واختَر جذر المستودع المنسوخ (أو الذي تم إخراجه من الأرشيف). سيمكّنك هذا من عرض عملك قيد التقدم من خلال عنوان URL المظلل في مربع حوار خادم الويب (في قسم عناوين URL لخادم الويب).

ضمن "الخيارات"، ضع علامة في المربّع بجانب "عرض index.html تلقائيًا"، كما هو موضّح أدناه:

لقطة شاشة يوم 16-11-2016 في الساعة 3.40.56 مساءً.png

انتقِل الآن إلى موقعك الإلكتروني من خلال متصفّح الويب (بالنقر على عنوان URL الخاص بخادم الويب) لتظهر لك صفحة على النحو التالي:

لقطة شاشة يوم 16-11-2016 في الساعة 3.20.22 مساءً.png

إذا أردت معرفة كيف سيبدو هذا التطبيق على هاتف Android، يجب تفعيل تصحيح الأخطاء عن بُعد على Android وإعداد إعادة توجيه المنفذ (رقم المنفذ هو 8887 تلقائيًا). وبعد ذلك، يمكنك ببساطة فتح علامة تبويب جديدة في Chrome للوصول إلى http://localhost:8887 على هاتف Android.

التالي

في الوقت الحالي، لا يقدّم تطبيق الويب هذا الكثير من الإجراءات. لنبدأ في إضافة دعم البلوتوث.

4. استكشِف الشمعة

سنبدأ بكتابة مكتبة تستخدم فئة JavaScript ES2015 لجهاز PLAYBULB Candle بلوتوث.

حافظ على هدوئك. لا تقدِّم بنية الفئة نموذجًا جديدًا للاكتساب الموجَّه إلى العناصر في JavaScript. إنه يوفر ببساطة بناء جملة أوضح جدًا لإنشاء الكائنات والتعامل مع التوريث، كما يمكنك قراءة أدناه.

أولاً، لنحدِّد فئة PlaybulbCandle في playbulbCandle.js وننشئ مثيل playbulbCandle سيكون متاحًا في ملف app.js لاحقًا.

playbulbCandle.js

(function() {
  'use strict';

  class PlaybulbCandle {
    constructor() {
      this.device = null;
    }
  }

  window.playbulbCandle = new PlaybulbCandle();

})();

لطلب الوصول إلى جهاز قريب يتضمّن بلوتوث، علينا الاتصال بـ navigator.bluetooth.requestDevice. نظرًا لأن جهاز PLAYBULB Candle يعلن باستمرار (إذا لم يكن مقترنًا من قبل) عن معرّف UUID ثابت لخدمة Bluetooth GATT ويُعرف في شكله القصير باسم 0xFF02، يمكننا بكل بساطة تحديد قيمة ثابتة وإضافة هذه القيمة إلى معلَمة خدمات الفلاتر في طريقة connect عامة جديدة من فئة PlaybulbCandle.

سنتتبّع أيضًا عنصر BluetoothDevice داخليًا حتى نتمكّن من الوصول إليه لاحقًا عند الحاجة. بما أنّ navigator.bluetooth.requestDevice تعرض إصدار JavaScript ES2015 Promise، سنتّخذ هذا الإجراء بطريقة then.

playbulbCandle.js

(function() {
  'use strict';

  const CANDLE_SERVICE_UUID = 0xFF02;

  class PlaybulbCandle {
    constructor() {
      this.device = null;
    }
    connect() {
      let options = {filters:[{services:[ CANDLE_SERVICE_UUID ]}]};
      return navigator.bluetooth.requestDevice(options)
      .then(function(device) {
        this.device = device;
      }.bind(this)); 
    }
  }

  window.playbulbCandle = new PlaybulbCandle();

})();

كميزة أمان، يجب طلب رصد الأجهزة القريبة التي تتضمّن بلوتوث من خلال "navigator.bluetooth.requestDevice" من خلال إيماءة المستخدم، مثل اللمس أو النقر بالماوس. هذا هو السبب في أننا سنسمي الطريقة connect عندما ينقر المستخدم على "اتصال" الزر في ملف app.js:

app.js

document.querySelector('#connect').addEventListener('click', function(event) {
  document.querySelector('#state').classList.add('connecting');
  playbulbCandle.connect()
  .then(function() {
    console.log(playbulbCandle.device);
    document.querySelector('#state').classList.remove('connecting');
    document.querySelector('#state').classList.add('connected');
  })
  .catch(function(error) {
    console.error('Argh!', error);
  });
});

تشغيل التطبيق

في هذه المرحلة، انتقِل إلى موقعك الإلكتروني في متصفّح الويب (بالنقر على عنوان URL لخادم الويب المميّز في تطبيق خادم الويب) أو ما عليك سوى إعادة تحميل الصفحة الحالية. انقر فوق الزر الأخضر "اتصال" اختَر الجهاز من أداة الاختيار، وافتح وحدة التحكّم المفضّلة لديك في "أدوات مطوّري البرامج" باستخدام اختصار لوحة المفاتيح Ctrl + Shift + J ولاحظ عنصر "BluetoothDevice" الذي تم تسجيله.

لقطة شاشة يوم 16-11-2016 في الساعة 3.27.12 مساءً.png

قد تظهر لك رسالة خطأ عند إيقاف البلوتوث و/أو إيقاف جهاز بلوتوث PLAYBULB Candle. وفي هذه الحالة، يُرجى تفعيله والمتابعة من جديد.

مكافأة إلزامية

لا أعرف شيئًا عنك، لكنني أرى عددًا كبيرًا جدًا من function() {} في هذا الرمز. لنبدِّل إلى () => {} JavaScript ES2015 Arrow Functions بدلاً من ذلك. إنّها موفرات مطلقة للحياة: من قبيل حب الوظائف المجهولة التي لا غنى لها عن الربط.

playbulbCandle.js

(function() {
  'use strict';

  const CANDLE_SERVICE_UUID = 0xFF02;

  class PlaybulbCandle {
    constructor() {
      this.device = null;
    }
    connect() {
      let options = {filters:[{services:[ CANDLE_SERVICE_UUID ]}]};
      return navigator.bluetooth.requestDevice(options)
      .then(device => {
        this.device = device;
      }); 
    }
  }

  window.playbulbCandle = new PlaybulbCandle();

})();

app.js

document.querySelector('#connect').addEventListener('click', event => {
  playbulbCandle.connect()
  .then(() => {
    console.log(playbulbCandle.device);
    document.querySelector('#state').classList.remove('connecting');
    document.querySelector('#state').classList.add('connected');
  })
  .catch(error => {
    console.error('Argh!', error);
  });
});

التالي

- حسنًا... هل يمكنني التحدّث إلى هذه الشمعة أو ماذا؟

- بالتأكيد... الانتقال إلى الخطوة التالية

الأسئلة الشائعة

5- قراءة محتوى

إذًا، ما هي الإجراءات التي يمكنك اتّخاذها بعد إرجاع BluetoothDevice من وعد navigator.bluetooth.requestDevice؟ للاتصال بخادم GATT عن بُعد في البلوتوث والذي يحتفظ بخدمة البلوتوث وتعريفات الخصائص من خلال الاتصال بـ device.gatt.connect():

playbulbCandle.js

  class PlaybulbCandle {
    constructor() {
      this.device = null;
    }
    connect() {
      let options = {filters:[{services:[ CANDLE_SERVICE_UUID ]}]};
      return navigator.bluetooth.requestDevice(options)
      .then(device => {
        this.device = device;
        return device.gatt.connect();
      });
    }
  }

قراءة اسم الجهاز

نحن متصلون هنا بخادم GATT الخاص بجهاز PLAYBULB Candle Bluetooth. نريد الآن الحصول على خدمة GATT الأساسية (التي تم الإعلان عنها باسم 0xFF02 من قبل) وقراءة خاصية اسم الجهاز (0xFFFF) التي تنتمي إلى هذه الخدمة. ويمكن تحقيق ذلك بسهولة من خلال إضافة طريقة جديدة getDeviceName إلى الفئة PlaybulbCandle واستخدام device.gatt.getPrimaryService وservice.getCharacteristic. ستعرض الطريقة characteristic.readValue في الواقع DataView وسيتم فك ترميزه باستخدام TextDecoder.

playbulbCandle.js

  const CANDLE_DEVICE_NAME_UUID = 0xFFFF;

  ...

    getDeviceName() {
      return this.device.gatt.getPrimaryService(CANDLE_SERVICE_UUID)
      .then(service => service.getCharacteristic(CANDLE_DEVICE_NAME_UUID))
      .then(characteristic => characteristic.readValue())
      .then(data => {
        let decoder = new TextDecoder('utf-8');
        return decoder.decode(data);
      });
    }

لنضيف هذا إلى app.js من خلال الاتصال بالرقم playbulbCandle.getDeviceName بعد الاتصال وعرض اسم الجهاز.

app.js

document.querySelector('#connect').addEventListener('click', event => {
  playbulbCandle.connect()
  .then(() => {
    console.log(playbulbCandle.device);
    document.querySelector('#state').classList.remove('connecting');
    document.querySelector('#state').classList.add('connected');
    return playbulbCandle.getDeviceName().then(handleDeviceName);
  })
  .catch(error => {
    console.error('Argh!', error);
  });
});

function handleDeviceName(deviceName) {
  document.querySelector('#deviceName').value = deviceName;
}

في هذه المرحلة، انتقِل إلى موقعك الإلكتروني في متصفّح الويب (بالنقر على عنوان URL لخادم الويب المميّز في تطبيق خادم الويب) أو ما عليك سوى إعادة تحميل الصفحة الحالية. تأكد من تشغيل شمعة PLAYBULB، ثم انقر على "اتصال" في الصفحة، ومن المفترض أن يظهر اسم الجهاز أسفل منتقي الألوان.

لقطة شاشة يوم 16-11-2016 في الساعة 3.29.21 مساءً.png

قراءة مستوى البطارية

تتوفر أيضًا ميزة بلوتوث عادية لمستوى البطارية في جهاز PLAYBULB Candle Bluetooth الذي يحتوي على مستوى بطارية الجهاز. ويعني هذا أنّه يمكننا استخدام الأسماء العادية، مثل battery_service للمعرّف الفريد العالمي (UUID) لخدمة Bluetooth GATT وbattery_level للمعرّف الفريد العالمي (UUID) لخاصية GATT للبلوتوث.

لنضيف طريقة جديدة للسمة getBatteryLevel إلى فئة PlaybulbCandle ونقرأ بيانات مستوى البطارية بالنسبة المئوية.

playbulbCandle.js

    getBatteryLevel() {
      return this.device.gatt.getPrimaryService('battery_service')
      .then(service => service.getCharacteristic('battery_level'))
      .then(characteristic => characteristic.readValue())
      .then(data => data.getUint8(0));
    }

نحتاج أيضًا إلى تحديث كائن JavaScript options ليشمل خدمة البطارية على مفتاح optionalServices لأنّه لا يتم الإعلان عنه من خلال جهاز PLAYBULB Candle الذي يتضمّن بلوتوث، ولكنه لا يزال إلزاميًا للوصول إليه.

playbulbCandle.js

      let options = {filters:[{services:[ CANDLE_SERVICE_UUID ]}],
                     optionalServices: ['battery_service']};
      return navigator.bluetooth.requestDevice(options)

كما في السابق، لنوصِّل هذا الجهاز بـ "app.js" من خلال الاتصال بالرقم playbulbCandle.getBatteryLevel بعد الحصول على اسم الجهاز وعرض مستوى البطارية.

app.js

document.querySelector('#connect').addEventListener('click', event => {
  playbulbCandle.connect()
  .then(() => {
    console.log(playbulbCandle.device);
    document.querySelector('#state').classList.remove('connecting');
    document.querySelector('#state').classList.add('connected');
    return playbulbCandle.getDeviceName().then(handleDeviceName)
    .then(() => playbulbCandle.getBatteryLevel().then(handleBatteryLevel));
  })
  .catch(error => {
    console.error('Argh!', error);
  });
});

function handleDeviceName(deviceName) {
  document.querySelector('#deviceName').value = deviceName;
}

function handleBatteryLevel(batteryLevel) {
  document.querySelector('#batteryLevel').textContent = batteryLevel + '%';
}

في هذه المرحلة، انتقل إلى موقعك الإلكتروني في متصفّح الويب (بالنقر على عنوان URL لخادم الويب المميّز في تطبيق خادم الويب) أو ما عليك سوى إعادة تحميل الصفحة الحالية. انقر على زر "اتصال" في الصفحة، ويُفترض أن يظهر لك اسم الجهاز ومستوى البطارية.

لقطة شاشة يوم 16-11-2016 في الساعة 3.29.21 مساءً.png

التالي

- كيف يمكنني تغيير لون هذه المصباح؟ هذا سبب وجودي هنا.

- أنت قريب جدًا منّي...

الأسئلة الشائعة

6- تغيير اللون

يمكن تغيير اللون بسهولة مثل كتابة مجموعة محددة من الأوامر إلى خاصية البلوتوث (0xFFFC) في خدمة GATT الأساسية المعلن عنها باسم 0xFF02. على سبيل المثال، تحويل شمعة PLAYBULB إلى اللون الأحمر سيؤدي إلى كتابة مصفوفة من الأعداد الصحيحة غير الموقَّعة المكوّنة من 8 بت والتي تساوي [0x00, 255, 0, 0]، حيث يشير 0x00 إلى تشبّع اللون الأبيض وتمثّل 255, 0, 0 القيم الحمراء والأخضر والأزرق على التوالي .

سنستخدم characteristic.writeValue لكتابة بعض البيانات إلى خاصية البلوتوث في الطريقة العامة الجديدة setColor من فئة PlaybulbCandle. وسنعرض أيضًا القيم الفعلية للأحمر والأخضر والأزرق عند الوفاء بالوعد حتى نتمكّن من استخدامها في app.js لاحقًا:

playbulbCandle.js

  const CANDLE_COLOR_UUID = 0xFFFC;

  ...

    setColor(r, g, b) {
      let data = new Uint8Array([0x00, r, g, b]);
      return this.device.gatt.getPrimaryService(CANDLE_SERVICE_UUID)
      .then(service => service.getCharacteristic(CANDLE_COLOR_UUID))
      .then(characteristic => characteristic.writeValue(data))
      .then(() => [r,g,b]);
    }

دعونا نحدث الدالة changeColor في app.js لاستدعاء playbulbCandle.setColor عندما تظهر الرسالة "لا يوجد تأثير" تحديد زر الاختيار. سبق أن تم ضبط متغيّرات اللون r, g, b العامة عندما ينقر المستخدم على لوحة أداة اختيار الألوان.

app.js

function changeColor() {
  var effect = document.querySelector('[name="effectSwitch"]:checked').id;
  if (effect === 'noEffect') {
    playbulbCandle.setColor(r, g, b).then(onColorChanged);
  }
}

في هذه المرحلة، انتقِل إلى موقعك الإلكتروني في متصفّح الويب (بالنقر على عنوان URL لخادم الويب المميّز في تطبيق خادم الويب) أو ما عليك سوى إعادة تحميل الصفحة الحالية. انقر على زر "اتصال" في الصفحة وانقر على "منتقي الألوان" لتغيير لون شمعة PLAYBULB حسبما تريد.

لقطة شاشة يوم 16-11-2016 في الساعة 3.31.37 مساءً.png

تأثيرات شموع Moar

إذا كنت قد أضاءت شمعة من قبل، يعني ذلك أنّ الضوء ليس ثابتًا. لحسن الحظ، هناك خاصية بلوتوث أخرى (0xFFFB) في خدمة GATT الأساسية المُعلَن عنها على أنّها 0xFF02، وهي تسمح للمستخدم بضبط بعض تأثيرات الشموع.

ضبط "تأثير شمعة" على سبيل المثال، يمكن تحقيقه بكتابة [0x00, r, g, b, 0x04, 0x00, 0x01, 0x00]. ويمكنك أيضًا تعيين "التأثير الوامض" مع [0x00, r, g, b, 0x00, 0x00, 0x1F, 0x00].

لنضيف الطريقتَين setCandleEffectColor وsetFlashingColor إلى الفئة PlaybulbCandle.

playbulbCandle.js

  const CANDLE_EFFECT_UUID = 0xFFFB;

  ...

    setCandleEffectColor(r, g, b) {
      let data = new Uint8Array([0x00, r, g, b, 0x04, 0x00, 0x01, 0x00]);
      return this.device.gatt.getPrimaryService(CANDLE_SERVICE_UUID)
      .then(service => service.getCharacteristic(CANDLE_EFFECT_UUID))
      .then(characteristic => characteristic.writeValue(data))
      .then(() => [r,g,b]);
    }
    setFlashingColor(r, g, b) {
      let data = new Uint8Array([0x00, r, g, b, 0x00, 0x00, 0x1F, 0x00]);
      return this.device.gatt.getPrimaryService(CANDLE_SERVICE_UUID)
      .then(service => service.getCharacteristic(CANDLE_EFFECT_UUID))
      .then(characteristic => characteristic.writeValue(data))
      .then(() => [r,g,b]);
    }

وسنعدّل الدالة changeColor في app.js لاستدعاء playbulbCandle.setCandleEffectColor عند ظهور "تأثير الشمعة". يتم تحديد زر الاختيار وplaybulbCandle.setFlashingColor عندما يظهر "وميض" تحديد زر الاختيار. هذه المرة، سنستخدم switch إذا لم يكن يناسبك ذلك.

app.js

function changeColor() {
  var effect = document.querySelector('[name="effectSwitch"]:checked').id;
  switch(effect) {
    case 'noEffect':
      playbulbCandle.setColor(r, g, b).then(onColorChanged);
      break;
    case 'candleEffect':
      playbulbCandle.setCandleEffectColor(r, g, b).then(onColorChanged);
      break;
    case 'flashing':
      playbulbCandle.setFlashingColor(r, g, b).then(onColorChanged);
      break;
  }
}

في هذه المرحلة، انتقِل إلى موقعك الإلكتروني في متصفّح الويب (بالنقر على عنوان URL لخادم الويب المميّز في تطبيق خادم الويب) أو ما عليك سوى إعادة تحميل الصفحة الحالية. انقر على زر "اتصال" في الصفحة وتشغيل تأثيرات الشموع والوميض.

لقطة شاشة يوم 16-11-2016 في الساعة 3.33.23 مساءً.png

التالي

- هذا كل شيء؟ 3 تأثيرات سيئة للشموع؟ هل هذا سبب تواجدي هنا؟

- هناك المزيد، ولكن ستكونين بمفردك هذه المرة.

7. خطوات إضافية

ها نحن ذا! قد تعتقد أن الانتهاء قد اقترب ولكن التطبيق لم ينته بعد. لنرَ ما إذا كنت قد فهمت بالفعل ما قمت بنسخه ولصقه في هذا الدرس التطبيقي حول الترميز. في ما يلي الإجراءات التي تريد أن تفعلها بنفسك الآن لجعل هذا التطبيق مميزًا.

إضافة تأثيرات مفقودة

في ما يلي بيانات التأثيرات المفقودة:

  • النبض: [0x00, r, g, b, 0x01, 0x00, 0x09, 0x00] (قد ترغب في تعديل قيم r, g, b هناك)
  • قوس قزح: [0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x01, 0x00] (قد يحتاج المصابون بصرع إلى تجنب هذا المرض)
  • تلاشي قوس قزح: [0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x26, 0x00]

ويعني هذا في الأساس إضافة طرق setPulseColor وsetRainbow وsetRainbowFade جديدة إلى صف PlaybulbCandle والاتصال بها في changeColor.

إصلاح مشكلة "بلا تأثير"

وكما لاحظت، فإن "لا تأثير" لم يؤدي إلى إعادة تعيين أي تأثير قيد التقدم، فهو بسيط ولكنه لا يزال قائمًا. دعنا نصلح هذه المشكلة. في طريقة setColor، يجب التحقّق أولاً مما إذا كان أحد التأثيرات قيد التقدم من خلال متغيّر فئة جديد _isEffectSet وإذا كان true، يجب إيقاف التأثير قبل ضبط لون جديد باستخدام هذه البيانات: [0x00, r, g, b, 0x05, 0x00, 0x01, 0x00].

كتابة اسم الجهاز

هذه الخطوة سهلة! يمكن كتابة اسم جهاز مخصص بسهولة مثل كتابة خاصية اسم جهاز بلوتوث السابقة. أنصح باستخدام طريقة encode TextEncoder للحصول على جهاز Uint8Array يحتوي على اسم الجهاز.

بعد ذلك، سأضيف "إدخال" eventListener إلى document.querySelector('#deviceName') والاتصال بـ playbulbCandle.setDeviceName لتبسيط الأمر.

أسمّي اسمي PLAY💡 CANDLE.

8. هذا كل شيء!

الدروس المستفادة

  • كيفية التفاعل مع جهاز قريب يتضمّن بلوتوث في JavaScript
  • كيفية استخدام فئات ES2015، ودوال الأسهم، والخرائط، والوعود

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