۱. درباره چیست؟

در این آزمایشگاه کدنویسیِ آموزنده، یاد خواهید گرفت که چگونه یک شمع بدون شعلهی LED از نوع PLAYBULB را تنها با جاوااسکریپت و به لطف رابط برنامهنویسی کاربردی بلوتوث وب کنترل کنید. در طول مسیر، با ویژگیهای جاوااسکریپت ES2015 مانند کلاسها ، توابع Arrow ، Map و Promises نیز کار خواهید کرد.
آنچه یاد خواهید گرفت
- نحوه تعامل با دستگاه بلوتوث مجاور در جاوا اسکریپت
- نحوه استفاده از کلاسها، توابع Arrow، Map و Promiseهای ES2015
آنچه نیاز دارید
- درک اولیه از توسعه وب
- دانش پایه در مورد بلوتوث کممصرف (BLE) و پروفایل ویژگی عمومی (GATT)
- یک ویرایشگر متن به انتخاب شما
- یک مک، کرومبوک یا دستگاه اندروید M با برنامه مرورگر کروم و یک کابل میکرو به USB.
۲. اول بازی کنید
شاید بخواهید نسخه نهایی برنامهای که قرار است بسازید را در https://googlecodelabs.github.io/candle-bluetooth بررسی کنید و قبل از اینکه واقعاً وارد این آزمایشگاه کد شوید، با دستگاه بلوتوث PLAYBULB Candle که در اختیار دارید کار کنید.
همچنین میتوانید تغییر رنگهای من را در https://www.youtube.com/watch?v=fBCPA9gIxlU تماشا کنید.
۳. آماده شوید
کد نمونه را دانلود کنید
میتوانید نمونه کد این کد را با دانلود فایل زیپ از اینجا دریافت کنید:
یا با کلون کردن این مخزن گیت:
git clone https://github.com/googlecodelabs/candle-bluetooth.git
اگر سورس را به صورت زیپ دانلود کردهاید، پس از باز کردن آن باید یک پوشه ریشه به candle-bluetooth-master داشته باشید.
نصب و بررسی وب سرور
اگرچه میتوانید از وب سرور خودتان استفاده کنید، این codelab طوری طراحی شده است که به خوبی با وب سرور کروم کار کند. اگر هنوز آن برنامه را نصب نکردهاید، میتوانید آن را از فروشگاه وب کروم نصب کنید.
پس از نصب برنامه وب سرور برای کروم، روی میانبر برنامهها در نوار نشانکها کلیک کنید:

در پنجره بعدی، روی آیکون وب سرور کلیک کنید:

در مرحله بعد این کادر محاورهای را مشاهده خواهید کرد که به شما امکان پیکربندی سرور وب محلیتان را میدهد:

روی دکمهی انتخاب پوشه کلیک کنید و ریشهی مخزن کلونشده (یا بایگانینشده) را انتخاب کنید. این کار به شما امکان میدهد تا کار در حال انجام خود را از طریق URL هایلایتشده در کادر گفتگوی وب سرور (در بخش URL(های) وب سرور ) ارائه دهید.
در قسمت Options، کادر کنار « Automatically show index.html » را علامت بزنید، همانطور که در زیر نشان داده شده است:

اکنون سایت خود را در مرورگر وب خود (با کلیک بر روی URL وب سرور هایلایت شده) مشاهده کنید، صفحهای مانند تصویر زیر خواهید دید:

اگر میخواهید ببینید این برنامه در گوشی اندروید شما چگونه به نظر میرسد، باید اشکالزدایی از راه دور را در اندروید فعال کنید و Port forwarding را تنظیم کنید (شماره پورت به طور پیشفرض ۸۸۸۷ است). پس از آن، میتوانید به سادگی یک تب جدید کروم را با آدرس http://localhost:8887 در گوشی اندروید خود باز کنید.
بعدی
در حال حاضر این برنامه وب کار زیادی انجام نمیدهد. بیایید شروع به اضافه کردن پشتیبانی بلوتوث کنیم!
۴. شمع را کشف کنید
ما با نوشتن یک کتابخانه که از کلاس ES2015 جاوا اسکریپت برای دستگاه بلوتوث PLAYBULB Candle استفاده میکند، شروع خواهیم کرد.
آرام باشید. سینتکس کلاس، یک مدل ارثبری شیءگرای جدید را به جاوااسکریپت معرفی نمیکند . این سینتکس صرفاً سینتکس بسیار واضحتری برای ایجاد اشیاء و مدیریت ارثبری ارائه میدهد، همانطور که میتوانید در زیر بخوانید.
ابتدا، بیایید یک کلاس 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 Service UUID که به شکل خلاصه 0xFF02 شناخته میشود را تبلیغ میکند، میتوانیم به سادگی یک ثابت تعریف کنیم و آن را به پارامتر filters services در یک متد connect عمومی جدید از کلاس PlaybulbCandle اضافه کنیم.
ما همچنین به صورت داخلی شیء BluetoothDevice را پیگیری خواهیم کرد تا در صورت نیاز بتوانیم بعداً به آن دسترسی داشته باشیم. از آنجایی که navigator.bluetooth.requestDevice یک Promise جاوا اسکریپت ES2015 را برمیگرداند، این کار را در متد 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 باید از طریق یک حرکت کاربر مانند لمس یا کلیک ماوس فراخوانی شود. به همین دلیل است که وقتی کاربر روی دکمه "اتصال" در فایل app.js کلیک میکند، متد connect را فراخوانی میکنیم:
برنامه.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);
});
});
برنامه را اجرا کنید
در این مرحله، سایت خود را در مرورگر وب خود مشاهده کنید (با کلیک روی آدرس وب سرور که در برنامه وب سرور هایلایت شده است) یا به سادگی صفحه موجود را رفرش کنید. روی دکمه سبز "اتصال" کلیک کنید، دستگاه را در انتخابگر انتخاب کنید و کنسول Dev Tools مورد علاقه خود را با میانبر صفحه کلید Ctrl + Shift + J باز کنید و متوجه شوید که شیء BluetoothDevice ثبت شده است.

اگر بلوتوث خاموش باشد و/یا دستگاه بلوتوث PLAYBULB Candle خاموش باشد، ممکن است با خطا مواجه شوید. در این صورت، آن را روشن کنید و دوباره ادامه دهید.
پاداش اجباری
نمیدانم شما چطور، اما من همین الان هم تعداد زیادی function() {} در این کد میبینم. بیایید به جای آن به () => {} توابع Arrow جاوا اسکریپت ES2015 برویم. آنها واقعاً نجاتدهنده هستند: تمام زیبایی توابع ناشناس را دارند، بدون هیچ یک از غم و اندوه اتصال.
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();
})();
برنامه.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);
});
});
بعدی
- باشه... میتونم واقعاً با این شمع صحبت کنم یا چی؟
- مطمئناً... برو مرحله بعدی
سوالات متداول
۵. چیزی بخوانید
خب حالا که یک BluetoothDevice از navigator.bluetooth.requestDevice برگردانده شده دارید، چه کار میکنید؟ بیایید با فراخوانی device.gatt.connect() به سرور GATT از راه دور بلوتوث که سرویس بلوتوث و تعاریف مشخصهها را در خود نگه میدارد، متصل شویم:
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);
});
}
بیایید این را با فراخوانی playbulbCandle.getDeviceName پس از اتصال به app.js اضافه کنیم و نام دستگاه را نمایش دهیم.
برنامه.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;
}
در این مرحله، از سایت خود در مرورگر وب خود بازدید کنید (با کلیک روی آدرس وب سرور که در برنامه وب سرور هایلایت شده است) یا به سادگی صفحه موجود را رفرش کنید. مطمئن شوید که PLAYBULB Candle روشن است، سپس روی دکمه "اتصال" در صفحه کلیک کنید. باید نام دستگاه را در زیر انتخابگر رنگ مشاهده کنید.

میزان باتری را بخوانید
همچنین یک مشخصه استاندارد بلوتوث سطح باتری در دستگاه بلوتوث PLAYBULB Candle موجود است که شامل سطح باتری دستگاه است. این بدان معناست که میتوانیم از نامهای استانداردی مانند battery_service برای Bluetooth GATT Service UUID و battery_level برای Bluetooth GATT Characteristic UUID استفاده کنیم.
بیایید یک متد 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));
}
ما همچنین باید شیء جاوا اسکریپت options را بهروزرسانی کنیم تا سرویس باتری را به کلید optionalServices اضافه کنیم، زیرا توسط دستگاه بلوتوث PLAYBULB Candle اعلام نمیشود، اما دسترسی به آن همچنان اجباری است.
playbulbCandle.js
let options = {filters:[{services:[ CANDLE_SERVICE_UUID ]}],
optionalServices: ['battery_service']};
return navigator.bluetooth.requestDevice(options)
مانند قبل، بیایید این را با فراخوانی playbulbCandle.getBatteryLevel به app.js وصل کنیم، زمانی که نام دستگاه را داریم و سطح باتری را نمایش میدهیم.
برنامه.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 + '%';
}
در این مرحله، از سایت خود در مرورگر وب خود بازدید کنید (با کلیک روی آدرس وب سرور که در برنامه وب سرور برجسته شده است) یا به سادگی صفحه موجود را رفرش کنید. روی دکمه "اتصال" در صفحه کلیک کنید تا نام دستگاه و میزان باتری را مشاهده کنید.
بعدی
- چطور میتونم رنگ این لامپ رو عوض کنم؟ برای همین اینجام!
- قول میدم خیلی بهم نزدیکی...
سوالات متداول
۶. تغییر رنگ
تغییر رنگ به آسانی نوشتن مجموعهای خاص از دستورات به یک مشخصه بلوتوث ( 0xFFFC ) در سرویس اصلی GATT است که به عنوان 0xFF02 تبلیغ میشود. برای مثال، تبدیل شمع PLAYBULB شما به قرمز، نوشتن آرایهای از اعداد صحیح 8 بیتی بدون علامت برابر با [0x00, 255, 0, 0] است که در آن 0x00 اشباع سفید و 255, 0, 0 به ترتیب مقادیر قرمز، سبز و آبی هستند.
ما characteristic.writeValue برای نوشتن مقداری داده در مشخصه بلوتوث در متد عمومی جدید setColor از کلاس PlaybulbCandle استفاده خواهیم کرد. و همچنین مقادیر واقعی قرمز، سبز و آبی را پس از انجام promise برمیگردانیم تا بتوانیم بعداً از آنها در 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 از قبل وقتی کاربر روی بوم انتخاب رنگ کلیک میکند، تنظیم شدهاند.
برنامه.js
function changeColor() {
var effect = document.querySelector('[name="effectSwitch"]:checked').id;
if (effect === 'noEffect') {
playbulbCandle.setColor(r, g, b).then(onColorChanged);
}
}
در این مرحله، از طریق مرورگر وب خود (با کلیک روی آدرس وب سرور که در برنامه وب سرور هایلایت شده است) به سایت خود مراجعه کنید یا صفحه موجود را رفرش کنید. روی دکمه "اتصال" در صفحه کلیک کنید و روی انتخابگر رنگ کلیک کنید تا رنگ شمع PLAYBULB خود را هر چند بار که میخواهید تغییر دهید.
جلوههای شمع موآر
اگر قبلاً شمعی روشن کردهاید، میدانید که نور آن ثابت نیست. خوشبختانه برای ما، یک مشخصه بلوتوث دیگر ( 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 بهروزرسانی کنیم تا وقتی دکمه رادیویی "Candle Effect" تیک خورده است، playbulbCandle.setCandleEffectColor و وقتی دکمه رادیویی "Flashing" تیک خورده است، playbulbCandle.setFlashingColor فراخوانی کند. این بار، اگر مشکلی ندارید، switch استفاده خواهیم کرد.
برنامه.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;
}
}
در این مرحله، از سایت خود در مرورگر وب خود بازدید کنید (با کلیک روی آدرس وب سرور که در برنامه وب سرور هایلایت شده است) یا به سادگی صفحه موجود را رفرش کنید. روی دکمه "اتصال" در صفحه کلیک کنید و با جلوههای شمع و چشمکزن بازی کنید.
بعدی
- همین؟ ۳ تا اثر شمع بیچاره؟ من برای همین اینجام؟
- تعداد بیشتری هم هست، اما این دفعه خودت تنها خواهی بود.
۷. فراتر از انتظار عمل کنید
خب، رسیدیم به اینجا! شاید فکر کنید تقریباً آخرش است، اما برنامه هنوز تمام نشده است. بیایید ببینیم آیا واقعاً متوجه شدهاید که در طول این آزمایش کد، چه چیزهایی را کپی و پیست کردهاید یا نه. حالا باید خودتان این کار را انجام دهید تا این برنامه بدرخشد.
جلوههای از دست رفته را اضافه کنید
در اینجا دادههای مربوط به اثرات از دست رفته آمده است:
- پالس:
[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 از نوع "input" به document.querySelector('#deviceName') اضافه میکنم و برای ساده نگه داشتن آن، playbulbCandle.setDeviceName فراخوانی میکنم.
من شخصاً اسم مال خودم رو گذاشتم PLAY💡 CANDLE!
۸. همین!
آنچه آموختهاید
- نحوه تعامل با دستگاه بلوتوث مجاور در جاوا اسکریپت
- نحوه استفاده از کلاسها، توابع Arrow، Map و Promiseهای ES2015
مراحل بعدی
- درباره API بلوتوث وب بیشتر بدانید
- نمونهها و دموهای رسمی بلوتوث تحت وب را مرور کنید
- گربه بدخلق در حال پرواز را ببینید

