Điều khiển ngọn nến PLAYBULB bằng Bluetooth trên web

1. Nội dung của trò chơi này là gì?

IMG_19700101_023537~2~2.jpg

Trong lớp học lập trình này, bạn sẽ tìm hiểu cách điều khiển nến điện tử PLAYBULB chỉ bằng JavaScript nhờ Web Bluetooth API. Trong quá trình này, bạn cũng sẽ sử dụng các tính năng JavaScript ES2015 như Lớp, Hàm mũi tên, Bản đồLời hứa.

Kiến thức bạn sẽ học được

  • Cách tương tác với thiết bị Bluetooth ở gần bằng JavaScript
  • Cách sử dụng các lớp ES2015, hàm mũi tên, bản đồ và lời hứa

Bạn cần có

2. Phát trước

Bạn có thể xem phiên bản cuối cùng của ứng dụng mà bạn sắp tạo tại https://googlecodelabs.github.io/candle-bluetooth và dùng thử thiết bị Bluetooth PLAYBULB Candle mà bạn có trước khi thực sự bắt đầu lớp học lập trình này.

Bạn cũng có thể xem tôi thay đổi màu sắc tại https://www.youtube.com/watch?v=fBCPA9gIxlU

3. Bắt đầu thiết lập

Tải mã mẫu xuống

Bạn có thể lấy mã mẫu cho mã này bằng cách tải tệp zip xuống tại đây:

hoặc bằng cách sao chép kho lưu trữ git này:

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

Nếu bạn tải nguồn xuống dưới dạng tệp zip, thì việc giải nén sẽ tạo ra một thư mục gốc candle-bluetooth-master.

Cài đặt và xác minh máy chủ web

Mặc dù bạn có thể sử dụng máy chủ web của riêng mình, nhưng lớp học lập trình này được thiết kế để hoạt động hiệu quả với Máy chủ web Chrome. Nếu chưa cài đặt ứng dụng đó, bạn có thể cài đặt từ Cửa hàng Chrome trực tuyến.

Sau khi cài đặt ứng dụng Web Server for Chrome, hãy nhấp vào lối tắt Ứng dụng trên thanh dấu trang:

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

Trong cửa sổ tiếp theo, hãy nhấp vào biểu tượng Web Server (Máy chủ web):

9f3c21b2cf6cbfb5.png

Tiếp theo, bạn sẽ thấy hộp thoại này. Hộp thoại này cho phép bạn định cấu hình máy chủ web cục bộ:

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

Nhấp vào nút chọn thư mục rồi chọn thư mục gốc của kho lưu trữ đã sao chép (hoặc chưa lưu trữ). Điều này sẽ cho phép bạn phân phát công việc đang tiến hành thông qua URL được đánh dấu trong hộp thoại máy chủ web (trong phần URL máy chủ web).

Trong phần Options (Tuỳ chọn), hãy đánh dấu vào ô bên cạnh "Automatically show index.html" (Tự động hiện index.html), như minh hoạ dưới đây:

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

Bây giờ, hãy truy cập vào trang web của bạn trong trình duyệt web (bằng cách nhấp vào URL Máy chủ web được đánh dấu) và bạn sẽ thấy một trang có dạng như sau:

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

Nếu muốn xem ứng dụng này trông như thế nào trên điện thoại Android, bạn cần bật chế độ Gỡ lỗi từ xa trên Androidthiết lập tính năng chuyển tiếp cổng (theo mặc định, số cổng là 8887). Sau đó, bạn chỉ cần mở một thẻ Chrome mới để truy cập vào http://localhost:8887 trên điện thoại Android.

Tiếp theo

Tại thời điểm này, ứng dụng web này không làm được nhiều. Hãy bắt đầu thêm tính năng hỗ trợ Bluetooth!

4. Khám phá nến

Chúng ta sẽ bắt đầu bằng cách viết một thư viện sử dụng Lớp JavaScript ES2015 cho thiết bị Bluetooth PLAYBULB Candle.

Giữ bình tĩnh. Cú pháp lớp không giới thiệu một mô hình kế thừa hướng đối tượng mới cho JavaScript. Nó chỉ đơn giản là cung cấp một cú pháp rõ ràng hơn nhiều để tạo các đối tượng và xử lý tính kế thừa, như bạn có thể đọc bên dưới.

Trước tiên, hãy xác định một lớp PlaybulbCandle trong playbulbCandle.js và tạo một thực thể playbulbCandle sẽ có trong tệp app.js sau này.

playbulbCandle.js

(function() {
  'use strict';

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

  window.playbulbCandle = new PlaybulbCandle();

})();

Để yêu cầu quyền truy cập vào một thiết bị Bluetooth ở gần, chúng ta cần gọi navigator.bluetooth.requestDevice. Vì thiết bị PLAYBULB Candle liên tục quảng cáo (nếu chưa được ghép nối) một hằng số Bluetooth GATT Service UUID (được biết đến dưới dạng 0xFF02), chúng ta có thể chỉ cần xác định một hằng số và thêm hằng số này vào tham số dịch vụ bộ lọc trong một phương thức connect công khai mới của lớp PlaybulbCandle.

Chúng ta cũng sẽ theo dõi đối tượng BluetoothDevice trong nội bộ để có thể truy cập vào đối tượng này sau nếu cần. Vì navigator.bluetooth.requestDevice trả về JavaScript ES2015 Promise, nên chúng ta sẽ thực hiện việc này trong phương thức 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();

})();

Là một tính năng bảo mật, bạn phải gọi tính năng khám phá các thiết bị Bluetooth ở gần bằng navigator.bluetooth.requestDevice thông qua một cử chỉ của người dùng, chẳng hạn như thao tác chạm hoặc nhấp chuột. Đó là lý do chúng ta sẽ gọi phương thức connect khi người dùng nhấp vào nút "Kết nối" trong tệp 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);
  });
});

Chạy ứng dụng

Tại thời điểm này, hãy truy cập vào trang web của bạn trong trình duyệt web (bằng cách nhấp vào URL máy chủ web được đánh dấu trong ứng dụng máy chủ web) hoặc chỉ cần làm mới trang hiện có. Nhấp vào nút "Kết nối" màu xanh lục, chọn thiết bị trong bộ chọn và mở bảng điều khiển Công cụ cho nhà phát triển mà bạn yêu thích bằng phím tắt Ctrl + Shift + J, sau đó bạn sẽ thấy đối tượng BluetoothDevice được ghi lại.

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

Bạn có thể gặp lỗi nếu Bluetooth đang tắt và/hoặc thiết bị Bluetooth PLAYBULB Candle đang tắt. Trong trường hợp đó, hãy bật thiết bị và tiếp tục.

Điểm thưởng bắt buộc

Tôi không biết bạn thế nào, nhưng tôi đã thấy quá nhiều function() {} trong mã này. Hãy chuyển sang () => {} JavaScript ES2015 Arrow Functions. Đây là những hàm cứu tinh tuyệt đối: Tất cả sự đáng yêu của hàm ẩn danh, không có sự buồn bã của việc liên kết.

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

Tiếp theo

– OK... can I actually talk to this candle or what?

– Chắc chắn rồi... chuyển sang bước tiếp theo

Câu hỏi thường gặp

5. Đọc nội dung nào đó

Vậy bạn sẽ làm gì khi có một BluetoothDevice được trả về từ lời hứa của navigator.bluetooth.requestDevice? Hãy kết nối với Máy chủ GATT của điều khiển từ xa Bluetooth chứa các định nghĩa về dịch vụ và đặc điểm Bluetooth bằng cách gọi 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();
      });
    }
  }

Đọc tên thiết bị

Ở đây, chúng ta kết nối với Máy chủ GATT của thiết bị Bluetooth PLAYBULB Candle. Bây giờ, chúng ta muốn lấy Dịch vụ GATT chính (trước đây được quảng cáo là 0xFF02) và đọc đặc điểm tên thiết bị (0xFFFF) thuộc dịch vụ này. Bạn có thể dễ dàng đạt được điều này bằng cách thêm một phương thức getDeviceName mới vào lớp PlaybulbCandle và sử dụng device.gatt.getPrimaryServiceservice.getCharacteristic. Phương thức characteristic.readValue sẽ thực sự trả về một DataView mà chúng ta sẽ chỉ cần giải mã bằng 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);
      });
    }

Hãy thêm thông tin này vào app.js bằng cách gọi playbulbCandle.getDeviceName sau khi chúng ta kết nối và hiển thị tên thiết bị.

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

Tại thời điểm này, hãy truy cập vào trang web của bạn trong trình duyệt web (bằng cách nhấp vào URL máy chủ web được đánh dấu trong ứng dụng máy chủ web) hoặc chỉ cần làm mới trang hiện có. Đảm bảo PLAYBULB Candle đã được bật, sau đó nhấp vào nút "Kết nối" trên trang. Bạn sẽ thấy tên thiết bị bên dưới bộ chọn màu.

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

Đọc mức pin

Ngoài ra, còn có một đặc điểm Bluetooth tiêu chuẩn về mức pin trong thiết bị Bluetooth PLAYBULB Candle, cho biết mức pin của thiết bị. Điều này có nghĩa là chúng ta có thể sử dụng các tên tiêu chuẩn như battery_service cho Bluetooth GATT Service UUID và battery_level cho Bluetooth GATT Characteristic UUID.

Hãy thêm một phương thức getBatteryLevel mới vào lớp PlaybulbCandle và đọc mức pin theo phần trăm.

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

Chúng ta cũng cần cập nhật đối tượng JavaScript options để thêm dịch vụ pin vào khoá optionalServices vì thiết bị Bluetooth PLAYBULB Candle không quảng cáo khoá này nhưng vẫn bắt buộc phải truy cập vào khoá.

playbulbCandle.js

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

Như trước đây, hãy cắm thành phần này vào app.js bằng cách gọi playbulbCandle.getBatteryLevel sau khi có tên thiết bị và hiển thị mức pin.

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 + '%';
}

Tại thời điểm này, hãy truy cập vào trang web của bạn trong trình duyệt web (bằng cách nhấp vào URL máy chủ web được đánh dấu trong ứng dụng máy chủ web) hoặc chỉ cần làm mới trang hiện có. Nhấp vào nút "Kết nối" trên trang này, bạn sẽ thấy cả tên thiết bị và mức pin.

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

Tiếp theo

– Làm cách nào để thay đổi màu sắc của bóng đèn này? Đó là lý do tôi có mặt ở đây!

– Tôi hứa là bạn sắp hoàn tất rồi...

Câu hỏi thường gặp

6. Thay đổi màu

Bạn có thể thay đổi màu sắc một cách dễ dàng bằng cách viết một bộ lệnh cụ thể vào một Đặc điểm Bluetooth (0xFFFC) trong Dịch vụ GATT chính được quảng cáo là 0xFF02. Ví dụ: để chuyển PLAYBULB Candle sang màu đỏ, bạn sẽ viết một mảng gồm các số nguyên không dấu 8 bit bằng [0x00, 255, 0, 0], trong đó 0x00 là độ bão hoà trắng và 255, 0, 0 lần lượt là các giá trị màu đỏ, xanh lục và xanh dương .

Chúng ta sẽ dùng characteristic.writeValue để thực sự ghi một số dữ liệu vào đặc điểm Bluetooth trong phương thức công khai setColor mới của lớp PlaybulbCandle. Chúng ta cũng sẽ trả về các giá trị thực tế của màu đỏ, xanh lục và xanh lam khi lời hứa được thực hiện để có thể sử dụng các giá trị đó trong app.js sau này:

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

Hãy cập nhật hàm changeColor trong app.js để gọi playbulbCandle.setColor khi nút chọn "Không có hiệu ứng" được đánh dấu. Các biến màu r, g, b chung đã được đặt khi người dùng nhấp vào canvas của công cụ chọn màu.

app.js

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

Tại thời điểm này, hãy truy cập vào trang web của bạn trong trình duyệt web (bằng cách nhấp vào URL máy chủ web được đánh dấu trong ứng dụng máy chủ web) hoặc chỉ cần làm mới trang hiện có. Nhấp vào nút "Kết nối" trên trang rồi nhấp vào công cụ chọn màu để thay đổi màu của nến PLAYBULB bao nhiêu lần tuỳ thích.

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

Hiệu ứng nến Moar

Nếu đã từng thắp nến, bạn sẽ biết ánh sáng của nến không tĩnh tại. May mắn thay, có một đặc điểm Bluetooth khác (0xFFFB) trong Dịch vụ GATT chính được quảng cáo là 0xFF02 cho phép người dùng thiết lập một số hiệu ứng nến.

Ví dụ: bạn có thể đạt được hiệu ứng "ánh nến" bằng cách viết [0x00, r, g, b, 0x04, 0x00, 0x01, 0x00]. Bạn cũng có thể đặt "hiệu ứng nhấp nháy" bằng [0x00, r, g, b, 0x00, 0x00, 0x1F, 0x00].

Hãy thêm các phương thức setCandleEffectColorsetFlashingColor vào lớp 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]);
    }

Hãy cập nhật hàm changeColor trong app.js để gọi playbulbCandle.setCandleEffectColor khi nút chọn "Candle Effect" (Hiệu ứng nến) được đánh dấu và playbulbCandle.setFlashingColor khi nút chọn "Flashing" (Nhấp nháy) được đánh dấu. Lần này, chúng ta sẽ dùng switch nếu bạn đồng ý.

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

Tại thời điểm này, hãy truy cập vào trang web của bạn trong trình duyệt web (bằng cách nhấp vào URL máy chủ web được đánh dấu trong ứng dụng máy chủ web) hoặc chỉ cần làm mới trang hiện có. Nhấp vào nút "Kết nối" trên trang rồi chơi với hiệu ứng Nến và Nhấp nháy.

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

Tiếp theo

– Chỉ có vậy thôi sao? 3 hiệu ứng nến kém? Đây có phải là lý do tôi có mặt ở đây không?

– Còn nhiều nữa nhưng lần này bạn sẽ tự mình tìm hiểu.

7. Nỗ lực hơn nữa

Vậy là chúng ta đã đến đây! Bạn có thể nghĩ rằng gần đến cuối nhưng ứng dụng vẫn chưa kết thúc. Hãy xem bạn có thực sự hiểu những gì mình đã sao chép và dán trong lớp học lập trình này hay không. Sau đây là những việc bạn cần tự làm để ứng dụng này trở nên nổi bật.

Thêm hiệu ứng còn thiếu

Sau đây là dữ liệu cho các hiệu ứng bị thiếu:

  • Nhịp: [0x00, r, g, b, 0x01, 0x00, 0x09, 0x00] (bạn có thể muốn điều chỉnh các giá trị r, g, b ở đó)
  • Cầu vồng: [0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x01, 0x00] (Người bị động kinh nên tránh hiệu ứng này)
  • Phai cầu vồng: [0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x26, 0x00]

Về cơ bản, điều này có nghĩa là thêm các phương thức setPulseColor, setRainbowsetRainbowFade mới vào lớp PlaybulbCandle rồi gọi các phương thức đó trong changeColor.

Khắc phục lỗi "không có hiệu ứng"

Như bạn có thể nhận thấy, lựa chọn "không có hiệu ứng" không đặt lại bất kỳ hiệu ứng nào đang diễn ra. Đây là một điểm nhỏ nhưng vẫn cần lưu ý. Hãy khắc phục vấn đề này. Trong phương thức setColor, trước tiên, bạn cần kiểm tra xem hiệu ứng có đang diễn ra hay không thông qua một biến lớp mới _isEffectSet và nếu true, hãy tắt hiệu ứng trước khi đặt màu mới bằng những dữ liệu sau: [0x00, r, g, b, 0x05, 0x00, 0x01, 0x00].

Viết tên thiết bị

Câu này rất dễ! Việc viết tên thiết bị tuỳ chỉnh cũng đơn giản như việc ghi vào đặc điểm tên thiết bị Bluetooth trước đó. Bạn nên sử dụng phương thức TextEncoder encode để lấy Uint8Array chứa tên thiết bị.

Sau đó, tôi sẽ thêm một "đầu vào" eventListener vào document.querySelector('#deviceName') và gọi playbulbCandle.setDeviceName để giữ cho nó đơn giản.

Tôi đặt tên cho chiếc đèn của mình là PLAY💡 CANDLE!

8. Vậy là xong!

Kiến thức bạn học được

  • Cách tương tác với thiết bị Bluetooth ở gần bằng JavaScript
  • Cách sử dụng các lớp ES2015, hàm mũi tên, bản đồ và lời hứa

Các bước tiếp theo