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

1. Nội dung 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 LED không lửa PLAYBULB chỉ bằng JavaScript nhờ API Web Bluetooth. Trong quá trình này, bạn cũng sẽ sử dụng các tính năng của JavaScript ES2015 như Lớp, Hàm mũi tên, Bản đồ và 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 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 nên 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à thử nghiệm thiết bị Bluetooth PLAYBULB Candle mà bạn có trước khi thực sự tìm hiểu lớp học lập trình này.

Bạn cũng có thể xem tôi đổi màu 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 lớp học lập trình 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ì khi giải nén, bạn sẽ thấy 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 Chrome 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 Máy chủ web:

9f3c21b2cf6cbfb5.png

Tiếp theo, bạn sẽ thấ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 choose folder (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ữ). Thao tác này sẽ cho phép bạn phân phát tác phẩm đang thực hiện thông qua URL được làm nổi bật trong hộp thoại máy chủ web (trong phần URL máy chủ web).

Trong phần Tuỳ chọn, hãy đánh dấu vào hộp bên cạnh "Tự động hiển thị 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 giao diện của ứng dụng này trên điện thoại Android, bạn cần bật tính năng Gỡ lỗi từ xa trên Android và thiết lập tính năng Chuyển tiếp cổng (số cổng mặc định là 8887). Sau đó, bạn chỉ cần mở một thẻ Chrome mới đến 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 gì 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ô 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 quảng cáo liên tục (nếu chưa được ghép nối) một UUID dịch vụ GATT Bluetooth không đổi được biết đến ở dạng ngắn là 0xFF02, nên 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 nội bộ đối tượng BluetoothDevice để có thể truy cập vào đối tượng đó sau này nếu cần. Vì navigator.bluetooth.requestDevice trả về một 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();

})();

Để đảm bảo tính năng bảo mật, việc khám phá các thiết bị Bluetooth ở gần bằng navigator.bluetooth.requestDevice phải được gọi thông qua cử chỉ của người dùng, chẳng hạn như 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 "Connect" (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 của 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 "Connect" (Kết nối) màu xanh lục, chọn thiết bị trong bộ chọn rồi mở bảng điều khiển Công cụ cho nhà phát triển mà bạn muốn bằng phím tắt Ctrl + Shift + J và chú ý đến đố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 Bluetooth và tiếp tục.

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 đoạn mã này. Hãy chuyển sang dùng () => {} Hàm mũi tên JavaScript ES2015. Chúng là cứu cánh tuyệt đối: Tất cả sự đáng yêu của các 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

– Được rồi... tôi có thể nói chuyện với cây nến này không?

– 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ì sau khi nhận được BluetoothDevice từ lời hứa của navigator.bluetooth.requestDevice? Hãy kết nối với Máy chủ GATT 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ị

Tại đâ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 (được quảng cáo là 0xFF02 trước đó) 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 thực hiện việc này bằng cách thêm một phương thức mới getDeviceName vào lớp PlaybulbCandle và sử dụng device.gatt.getPrimaryService và service.getCharacteristic. Phương thức characteristic.readValue sẽ thực sự trả về một DataView mà chúng ta 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 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 của 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 đã bật, sau đó nhấp vào nút "Kết nối" trên trang và bạn sẽ thấy tên thiết bị bên dưới công cụ 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 về mức pin tiêu chuẩn trong thiết bị Bluetooth PLAYBULB Candle, chứa 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 chuẩn như battery_service cho UUID của Dịch vụ GATT Bluetooth và battery_level cho UUID của Đặc điểm GATT Bluetooth.

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 dịch vụ này nhưng vẫn bắt buộc phải truy cập vào dịch vụ này.

playbulbCandle.js

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

Như trước, hãy đưa thông tin 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 của 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, 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 của bóng đèn này? Đó là lý do tôi có mặt ở đây!

– Bạn sắp hoàn tất rồi, tôi hứa đấy...

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

6. Thay đổi màu

Việc thay đổi màu sắc cũng dễ dàng như việc viết một tập hợp lệnh cụ thể cho 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 cần 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à màu trắng và 255, 0, 0 tương ứng là các giá trị màu đỏ, xanh lục và xanh dương .

Chúng ta sẽ 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 dương 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 "No Effect" (Không có hiệu ứng) được chọn. 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 của 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 PLAYBULB Candle bao nhiêu lần tuỳ ý.

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

Thêm hiệu ứng nến

Nếu đã từng thắp nến, bạn sẽ biết rằng ánh sáng không đứng yên. May mắn cho chúng ta là 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 đặt một số hiệu ứng nến.

Ví dụ: bạn có thể đặt "hiệu ứng 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 setCandleEffectColor và setFlashingColor 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 "Hiệu ứng nến" được chọn và playbulbCandle.setFlashingColor khi nút chọn "Nhấp nháy" được chọn. 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 của 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 thử nghiệm 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 ở đây không?

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

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

Và đây chính là Bốn trụ cột thương mại dành cho bạn! Có thể bạn nghĩ rằng ứng dụng sắp hoàn tất nhưng thực ra vẫn chưa xong. Hãy xem bạn có thực sự hiểu những gì bạn đã 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

Dưới đây là dữ liệu về các hiệu ứng bị thiếu:

  • Pulse: [0x00, r, g, b, 0x01, 0x00, 0x09, 0x00] (bạn có thể điều chỉnh các giá trị r, g, b tại đây)
  • 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)
  • Cầu vồng mờ dần: [0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x26, 0x00]

Về cơ bản, việc này có nghĩa là thêm các phương thức setPulseColor, setRainbow và setRainbowFade 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 lực"

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 vấn đề 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 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 ghi 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 mọi thứ đơn giản.

Tôi đặt tên cho nế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 Lớp ES2015, Hàm mũi tên, Bản đồ và Lời hứa

Các bước tiếp theo