1. ما هو موضوعه؟

في هذا الدرس التطبيقي حول الترميز، ستتعرّف على كيفية التحكّم في شمعة PLAYBULB LED بدون لهب باستخدام JavaScript فقط بفضل Web Bluetooth API. ستتعرّف أيضًا على ميزات JavaScript ES2015، مثل Classes وArrow functions وMap وPromises.
ما ستتعلّمه
- كيفية التفاعل مع جهاز يتضمّن بلوتوث قريب باستخدام JavaScript
- كيفية استخدام فئات ES2015 ودوال الأسهم والخريطة والوعود
المتطلبات
- فهم أساسي لتطوير الويب
- معرفة أساسية بتقنية بلوتوث منخفض الطاقة (BLE) والملف الشخصي للسمات العامة (GATT)
- محرِّر نصوص من اختيارك
- جهاز Mac أو Chromebook أو جهاز Android M مزوّد بتطبيق متصفّح Chrome وكابل USB micro إلى USB
2. التشغيل أولاً
قد ترغب في الاطّلاع على الإصدار النهائي من التطبيق الذي أنت على وشك إنشائه على https://googlecodelabs.github.io/candle-bluetooth واللعب باستخدام جهاز PLAYBULB Candle الذي يتضمّن بلوتوث والمتاح لك قبل الغوص فعليًا في هذا الدرس التطبيقي حول الترميز.
يمكنك أيضًا مشاهدة فيديو لي وأنا أغيّر الألوان على الرابط https://www.youtube.com/watch?v=fBCPA9gIxlU
3- طريقة الإعداد
تنزيل الرمز النموذجي
يمكنك الحصول على الرمز النموذجي لهذا الرمز من خلال تنزيل ملف zip من هنا:
أو من خلال استنساخ مستودع git هذا:
git clone https://github.com/googlecodelabs/candle-bluetooth.git
إذا نزّلت المصدر كملف zip، من المفترض أن يظهر لك مجلد جذر باسم candle-bluetooth-master عند فك ضغطه.
تثبيت خادم الويب والتحقّق منه
على الرغم من أنّه يمكنك استخدام خادم الويب الخاص بك، تم تصميم هذا الدرس التطبيقي حول الترميز ليعمل بشكل جيد مع خادم الويب Chrome. إذا لم يكن هذا التطبيق مثبّتًا لديك، يمكنك تثبيته من "سوق Chrome الإلكتروني".
بعد تثبيت تطبيق Web Server for Chrome، انقر على اختصار "تطبيقات Chrome" في شريط الإشارات المرجعية:

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

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

انقر على الزر اختيار مجلد، واختَر جذر المستودع المستنسَخ (أو الذي تم إلغاء أرشفته). سيتيح لك ذلك عرض عملك قيد التقدّم من خلال عنوان URL المميّز في مربّع حوار خادم الويب (في قسم عناوين URL لخادم الويب).
ضِمن "الخيارات"، ضَع علامة في المربّع بجانب "عرض index.html تلقائيًا"، كما هو موضّح أدناه:

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

إذا أردت الاطّلاع على شكل هذا التطبيق على هاتف Android، عليك تفعيل تصحيح الأخطاء عن بُعد على Android وإعداد إعادة توجيه المنفذ (رقم المنفذ التلقائي هو 8887). بعد ذلك، يمكنك ببساطة فتح علامة تبويب جديدة في Chrome على الرابط http://localhost:8887 على هاتف Android.
التالي
في هذه المرحلة، لا يقدّم تطبيق الويب هذا الكثير من الميزات. Let's start adding Bluetooth support!
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 الذي تم تسجيله.

قد يظهر لك خطأ إذا كان البلوتوث غير مفعّل و/أو إذا كان جهاز PLAYBULB Candle الذي يتضمّن بلوتوث غير مفعّل. في هذه الحالة، فعِّله وتابِع العملية مرة أخرى.
المكافأة الإلزامية
لا أعرف ما إذا كان الأمر نفسه ينطبق عليك، ولكنّني أرى الكثير من function() {} في هذا الرمز. لننتقل إلى () => {} دوال JavaScript ES2015 السهمية بدلاً من ذلك. They are absolute life savers: All the loveliness of anonymous functions, none of the sadness of binding.
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 الذي يتضمّن بلوتوث. نريد الآن الحصول على خدمة 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 Candle، ثم انقر على الزر "ربط" في الصفحة، ومن المفترض أن يظهر اسم الجهاز أسفل علبة الألوان.

قراءة مستوى البطارية
تتوفّر أيضًا سمة بلوتوث عادية لمستوى البطارية في جهاز PLAYBULB Candle الذي يتضمّن بلوتوث ويحتوي على مستوى بطارية الجهاز. يعني ذلك أنّه يمكننا استخدام أسماء عادية، مثل battery_service لمعرّف UUID لخدمة 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 الخاص بخادم الويب المميّز في تطبيق خادم الويب) أو أعِد تحميل الصفحة الحالية ببساطة. انقر على الزر "ربط" (Connect) في الصفحة، وسيظهر لك اسم الجهاز ومستوى البطارية.
التالي
- كيف يمكنني تغيير لون هذا المصباح؟ لهذا السبب أنا هنا.
- أعدك بأنّك على وشك تحقيق هدفك...
الأسئلة الشائعة
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 Candle عدة مرات حسب رغبتك.
المزيد من تأثيرات الشموع
إذا سبق لك إشعال شمعة، ستعرف أنّ الضوء ليس ثابتًا. لحسن الحظ، هناك سمة أخرى لتقنية البلوتوث (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 الخاص بخادم الويب والمميّز في تطبيق خادم الويب) أو أعِد تحميل الصفحة الحالية ببساطة. انقر على الزر "ربط" في الصفحة واستمتِع بتأثيرات الشمعة والوميض.
التالي
- هل هذا كل شيء؟ 3 تأثيرات شموع سيئة؟ هل هذا سبب وجودي هنا؟
- هناك المزيد من الأسئلة، ولكن عليك الإجابة عنها بمفردك هذه المرة.
7. اتّخاذ خطوات إضافية
وها نحن هنا! قد تعتقد أنّنا وصلنا إلى النهاية، ولكنّ التطبيق لم ينتهِ بعد. لنتأكّد من أنّك فهمت ما نسخته ولصقته خلال هذا الدرس التطبيقي حول الترميز. إليك ما عليك فعله بنفسك الآن لإبراز هذا التطبيق.
إضافة المؤثرات الناقصة
في ما يلي بيانات التأثيرات غير المتوفّرة:
- Pulse:
[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.
إصلاح مشكلة "بدون تأثير"
As you may have noticed, the "no effect" option doesn't reset any effect in progress, this is minor but still. لنحاول إصلاح هذه المشكلة. في طريقة setColor، عليك أولاً التحقّق مما إذا كان هناك تأثير قيد التنفيذ من خلال متغيّر فئة جديد _isEffectSet، وإذا كان true، عليك إيقاف التأثير قبل ضبط لون جديد باستخدام هذه البيانات: [0x00, r, g, b, 0x05, 0x00, 0x01, 0x00].
كتابة اسم الجهاز
هذا السؤال سهل. تُعدّ كتابة اسم جهاز مخصّص أمرًا بسيطًا مثل كتابة سمة اسم الجهاز السابق الذي يتضمّن بلوتوث. ننصح باستخدام طريقة TextEncoder encode للحصول على Uint8Array يحتوي على اسم الجهاز.
بعد ذلك، سأضيف "إدخال" eventListener إلى document.querySelector('#deviceName') وأسمّيه playbulbCandle.setDeviceName لتبسيط الأمر.
أطلقتُ على شمعتي اسم PLAY💡 CANDLE.
8. هذا كل شيء!
ما تعلّمته
- كيفية التفاعل مع جهاز يتضمّن بلوتوث قريب باستخدام JavaScript
- كيفية استخدام فئات ES2015 ودوال الأسهم والخريطة والوعود
الخطوات التالية
- مزيد من المعلومات حول Web Bluetooth API
- تصفَّح نماذج Web Bluetooth والعروض التوضيحية الرسمية
- اطّلِع على القطة الغاضبة الطائرة

