کنترل کردن شمع PLAYBULB با «بلوتوث وب»

۱. موضوع آن چیست؟

IMG_19700101_023537~2~2.jpg

در این codelab روشنگر، یاد خواهید گرفت که چگونه یک شمع بدون شعله PLAYBULB LED را فقط با جاوا اسکریپت و با کمک Web Bluetooth API کنترل کنید. درطول مسیر، با ویژگی‌های JavaScript ES2015 مانند کلاس‌ها، توابع پیکانی، نقشه، و قول‌ها نیز کار خواهید کرد.

آنچه خواهید آموخت

  • نحوه تعامل با دستگاه بلوتوث اطراف در جاوا اسکریپت
  • نحوه استفاده از کلاس‌های ES2015، توابع Arrow،‏ Map، و Promises

آنچه نیاز خواهید داشت

۲. اول پخش شود

شاید بخواهید نسخه نهایی برنامه‌ای را که می‌خواهید ایجاد کنید در https://googlecodelabs.github.io/candle-bluetooth بررسی کنید و قبل‌از اینکه واقعاً وارد این codelab شوید، با دستگاه بلوتوث PLAYBULB Candle که دراختیار دارید بازی کنید.

همچنین می‌توانید تغییر رنگ من را در https://www.youtube.com/watch?v=fBCPA9gIxlU تماشا کنید

۳. راه‌اندازی کردن

بارگیری کد نمونه

می‌توانید کد نمونه این کد را با بارگیری فایل zip از اینجا دریافت کنید:

یا با همسانه‌سازی این مخزن git:

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

اگر منبع را به‌صورت فایل zip بارگیری کرده‌اید، با از حالت فشرده خارج کردن آن باید پوشه ریشه candle-bluetooth-master را دریافت کنید.

نصب و درستی‌سنجی سرور وب

درحالی‌که می‌توانید از سرور وب خودتان استفاده کنید، این codelab برای کار کردن با Chrome Web Server طراحی شده است. اگر هنوز آن برنامه را نصب نکرده‌اید، می‌توانید آن را از «نت‌بازار Chrome» نصب کنید.

پس‌از نصب برنامه «وب‌سرور برای Chrome»، روی میان‌بر «برنامه‌ها» در نوار نشانک‌ها کلیک کنید:

Screen Shot 2016-11-16 at 4.10.42 PM.png

در پنجره بعدی، روی نماد «سرور وب» کلیک کنید:

9f3c21b2cf6cbfb5.png

سپس این کادر گفتگو را خواهید دید که به شما اجازه می‌دهد سرور وب محلی‌تان را پیکربندی کنید:

Screen Shot 2016-11-16 at 3.40.47 PM.png

روی دکمه انتخاب پوشه کلیک کنید و ریشه مخزن کلون‌شده (یا از بایگانی خارج‌شده) را انتخاب کنید. این کار به شما امکان می‌دهد کار درحال انجام خود را ازطریق نشانی وب برجسته‌شده در کادر گفتگوی سرور وب (در بخش نشانی(های) وب سرور وب) ارائه دهید.

در بخش «گزینه‌ها»، چارگوش کنار «نمایش خودکار index.html» را علامت بزنید، همان‌طور که در زیر نشان داده شده است:

Screen Shot 2016-11-16 at 3.40.56 PM.png

اکنون در مرورگر وب خود از سایتتان بازدید کنید (با کلیک کردن روی «نشانی وب سرور وب» برجسته‌شده) و باید صفحه‌ای را ببینید که شبیه این است:

Screen Shot 2016-11-16 at 3.20.22 PM.png

اگر می‌خواهید ببینید این برنامه در تلفن Android شما چگونه به‌نظر می‌رسد، باید اشکال‌زدایی ازراه‌دور در Android را فعال کنید و «بازارسال درگاه» را راه‌اندازی کنید (شماره درگاه به‌طور پیش‌فرض ۸۸۸۷ است). پس‌از آن، می‌توانید به‌راحتی برگه جدیدی در Chrome در تلفن Android خود باز کنید و به http://localhost:8887 بروید.

بعدی

در این مرحله، این برنامه وب کار زیادی انجام نمی‌دهد. بیایید افزودن پشتیبانی بلوتوث را شروع کنیم!

۴. شمع را کاوش کنید

با نوشتن کتابخانه‌ای شروع می‌کنیم که از کلاس JavaScript 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 سرویس 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 باید ازطریق اشاره کاربر مانند لمس یا کلیک موشواره فراخوانی شود. به همین دلیل است که وقتی کاربر روی دکمه «اتصال» در فایل app.js کلیک می‌کند، روش connect را فراخوانی می‌کنیم:

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);
  });
});

اجرا کردن برنامه

در این مرحله، در مرورگر وب خود از سایتتان بازدید کنید (با کلیک کردن روی نشانی وب «سرور وب» که در برنامه سرور وب برجسته شده است) یا به‌سادگی صفحه موجود را بازآوری کنید. روی دکمه سبز «اتصال» کلیک کنید، دستگاه را در انتخابگر انتخاب کنید و کنسول Dev Tools موردعلاقه‌تان را با میان‌بر صفحه‌کلید Ctrl + Shift + J باز کنید و به شیء BluetoothDevice ثبت‌شده توجه کنید.

Screen Shot 2016-11-16 at 3.27.12 PM.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);
  });
});

بعدی

- خب... واقعاً می‌توانم با این شمع صحبت کنم یا نه؟

- حتماً... به مرحله بعدی بروید

پرسشگان

۵. چیزی بخوانید

اکنون که 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 اضافه کنیم و نام دستگاه را نمایش دهیم.

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;
}

در این مرحله، در مرورگر وب خود از سایتتان بازدید کنید (با کلیک کردن روی نشانی وب «سرور وب» که در برنامه سرور وب برجسته شده است) یا به‌سادگی صفحه موجود را بازآوری کنید. مطمئن شوید PLAYBULB Candle روشن است، سپس روی دکمه «اتصال» در صفحه کلیک کنید و باید نام دستگاه را در زیر انتخاب‌گر رنگ ببینید.

Screen Shot 2016-11-16 at 3.29.21 PM.png

خواندن میزان شارژ باتری

همچنین یک مشخصه بلوتوث سطح باتری استاندارد در دستگاه بلوتوث PLAYBULB Candle وجود دارد که حاوی سطح باتری دستگاه است. این یعنی می‌توانیم از نام‌های استاندارد مثل battery_service برای «شناسه یکتای سراسری سرویس GATT بلوتوث» و battery_level برای «شناسه یکتای سراسری مشخصه 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));
    }

همچنین باید شیء جاوا اسکریپت 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 + '%';
}

در این مرحله، از سایتتان در مرورگر وب بازدید کنید (با کلیک کردن روی «نشانی وب سرور وب» که در برنامه سرور وب برجسته شده است) یا به‌سادگی صفحه موجود را بازآوری کنید. روی دکمه «اتصال» در صفحه کلیک کنید تا نام دستگاه و سطح باتری را ببینید.

Screen Shot 2016-11-16 at 3.29.21 PM.png

بعدی

- چطور می‌توانم رنگ این لامپ را تغییر دهم؟ به همین دلیل من اینجا هستم!

- قول می‌دهم خیلی نزدیک هستید...

پرسشگان

‫۶. تغییر رنگ

تغییر رنگ به‌سادگی نوشتن مجموعه‌ای خاص از فرمان‌ها برای «مشخصه بلوتوث» (0xFFFC) در «سرویس GATT اولیه» است که به‌عنوان 0xFF02 تبلیغ می‌شود. برای مثال، قرمز کردن PLAYBULB Candle شما نوشتن آرایه‌ای از اعداد صحیح بدون علامت ۸ بیتی برابر با [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);
  }
}

در این مرحله، در مرورگر وب خود از سایتتان بازدید کنید (با کلیک کردن روی نشانی وب «سرور وب» که در برنامه سرور وب برجسته شده است) یا به‌سادگی صفحه موجود را بازآوری کنید. روی دکمه «اتصال» در صفحه کلیک کنید و روی انتخابگر رنگ کلیک کنید تا رنگ PLAYBULB Candle را هر چند بار که می‌خواهید تغییر دهید.

Screen Shot 2016-11-16 at 3.31.37 PM.png

جلوه‌های شمع بیشتر

اگر قبلاً شمعی روشن کرده باشید، می‌دانید که نور آن ثابت نیست. خوشبختانه، مشخصه بلوتوث دیگری (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;
  }
}

در این مرحله، در مرورگر وب خود از سایتتان بازدید کنید (با کلیک کردن روی نشانی وب «سرور وب» که در برنامه سرور وب برجسته شده است) یا به‌سادگی صفحه موجود را بازآوری کنید. روی دکمه «اتصال» در صفحه کلیک کنید و با جلوه‌های «شمع» و «چشمک‌زن» بازی کنید.

Screen Shot 2016-11-16 at 3.33.23 PM.png

بعدی

- همین؟ ‫۳ جلوه شمع ضعیف؟ آیا به این دلیل است که من اینجا هستم؟

- موارد بیشتری وجود دارد اما این بار خودتان باید آن‌ها را پیدا کنید.

‫۷. بیش‌از حد انتظار تلاش کنید

بنابراین، این شما و این AgentSpace! شاید فکر کنید که تقریباً به پایان رسیده‌اید اما برنامه هنوز تمام نشده است. بیایید ببینیم آیا واقعاً آنچه را که درطول این codelab کپی و جای‌گذاری کرده‌اید متوجه شده‌اید یا نه. در اینجا کارهایی که باید خودتان انجام دهید تا این برنامه بدرخشد آورده شده است.

افزودن جلوه‌های جاافتاده

داده‌های مربوط به جلوه‌های گمشده در اینجا آمده است:

  • Pulse: [0x00, r, g, b, 0x01, 0x00, 0x09, 0x00] (you might want to adjust r, g, b values there)
  • رنگین‌کمان: [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 گذاشتم!

‫۸. فقط همین!

آنچه آموخته‌اید

  • نحوه تعامل با دستگاه بلوتوث اطراف در جاوا اسکریپت
  • نحوه استفاده از کلاس‌های ES2015، توابع Arrow،‏ Map، و Promises

مراحل بعدی