ウェブ Bluetooth で PLAYBULB キャンドルを操作する

1. どのような設定ですか?

IMG_19700101_023537~2~2.jpg

この Codelab では、Web Bluetooth API を使用して、JavaScript のみで PLAYBULB LED フレームレス キャンドルを制御する方法を学びます。また、クラス、アロー関数、Map、Promise などの JavaScript ES2015 の機能も使用します。

学習内容

  • JavaScript で近くの Bluetooth デバイスを操作する方法
  • ES2015 のクラス、アロー関数、Map、Promise の使用方法

必要なもの

2. 最初に再生

この Codelab に取りかかる前に、https://googlecodelabs.github.io/candle-bluetooth で、これから作成するアプリの最終バージョンを確認し、手元にある PLAYBULB Candle Bluetooth デバイスを操作してみることをおすすめします。

色を変える様子は、https://www.youtube.com/watch?v=fBCPA9gIxlU でもご覧いただけます。

3. セットアップする

サンプルコードをダウンロードする

このコードのサンプルコードを入手するには、次のいずれかの方法を使用します。

または、次の git リポジトリのクローンを作成します。

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

ソースを zip 形式でダウンロードした場合は、解凍するとルートフォルダ candle-bluetooth-master が作成されます。

ウェブサーバーをインストールして確認する

独自のウェブサーバーを自由に使用できますが、この Codelab は Chrome ウェブサーバーで適切に動作するように設計されています。目的のアプリをまだインストールしていない場合は、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

[choose folder] ボタンをクリックし、クローンを作成した(またはアーカイブを解除した)リポジトリのルートを選択します。これにより、ウェブサーバー ダイアログ([ウェブサーバーの URL] セクション)でハイライト表示されている URL を使用して、処理中の作業を行うことができます。

[オプション] で、[index.html を自動的に表示] の横のチェックボックスをオンにします(下図を参照)。

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

ウェブブラウザでサイトにアクセスします(ハイライト表示されたウェブサーバーの URL をクリックします)。次のようなページが表示されます。

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

このアプリが Android スマートフォンでどのように表示されるかを確認するには、Android でのリモート デバッグを有効にして、ポート転送を設定する必要があります(デフォルトのポート番号は 8887)。その後、Android スマートフォンで Chrome の新しいタブを開き、http://localhost:8887 にアクセスします。

次のステップ

現時点では、このウェブアプリはあまり機能しません。Bluetooth サポートを追加しましょう。

4. キャンドルを見つける

まず、PLAYBULB Candle Bluetooth デバイス用の JavaScript ES2015 クラスを使用するライブラリを作成します。

落ち着いて対応します。クラス構文は、JavaScript に新しいオブジェクト指向の継承モデルを導入するものではありません。以下で説明するように、オブジェクトを作成して継承を処理するための、より明確な構文が提供されるだけです。

まず、playbulbCandle.js で PlaybulbCandle クラスを定義し、後で app.js ファイルで使用できるように playbulbCandle インスタンスを作成します。

playbulbCandle.js

(function() {
  'use strict';

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

  window.playbulbCandle = new PlaybulbCandle();

})();

付近の Bluetooth デバイスへのアクセスをリクエストするには、navigator.bluetooth.requestDevice を呼び出す必要があります。PLAYBULB Candle デバイスは、0xFF02 という短い形式で知られる一定の Bluetooth GATT サービス UUID を(まだペア設定されていない場合は)継続的にアドバタイズするため、定数を定義して、PlaybulbCandle クラスの新しいパブリック connect メソッドのフィルタ サービス パラメータに追加するだけで済みます。

また、必要に応じて後でアクセスできるように、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 を使用して近くの Bluetooth デバイスを検出するには、タップやマウスクリックなどのユーザー ジェスチャーを介して呼び出す必要があります。そのため、ユーザーが app.js ファイルの [Connect] ボタンをクリックしたときに 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);
  });
});

アプリを実行する

この時点で、ウェブブラウザでサイトにアクセスするか(ウェブサーバー アプリでハイライト表示されているウェブサーバー URL をクリック)、既存のページを更新します。緑色の [Connect] ボタンをクリックし、選択ツールでデバイスを選択して、Ctrl+Shift+J キーボード ショートカットでお気に入りの DevTools コンソールを開きます。BluetoothDevice オブジェクトがログに記録されていることがわかります。

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

Bluetooth がオフになっている場合や、PLAYBULB Candle Bluetooth デバイスがオフになっている場合は、エラーが表示されることがあります。その場合は、オンにしてから再度進めてください。

必須ボーナス

このコードにはすでに function() {} が多すぎます。代わりに () => {} JavaScript 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();

})();

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

次のステップ

- OK... このキャンドルと話せるの?

- わかりました。次のステップに進みます

よくある質問

5. 何かを読む

では、navigator.bluetooth.requestDevice の Promise から返された BluetoothDevice を使用して何を行うのでしょうか。device.gatt.connect() を呼び出して、Bluetooth サービスと特性の定義を保持する Bluetooth リモート 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();
      });
    }
  }

デバイス名を読み上げる

ここでは、PLAYBULB キャンドル Bluetooth デバイスの GATT サーバーに接続しています。次に、プライマリ 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;
}

この時点で、ウェブブラウザでサイトにアクセスするか(ウェブサーバー アプリでハイライト表示されているウェブサーバー URL をクリック)、既存のページを更新します。PLAYBULB Candle の電源が入っていることを確認してから、ページ上の [Connect] ボタンをクリックすると、カラー選択ツールの下にデバイス名が表示されます。

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

バッテリー残量を読み上げる

PLAYBULB Candle Bluetooth デバイスには、デバイスのバッテリー残量を含む標準のバッテリー残量 Bluetooth 特性もあります。つまり、Bluetooth GATT サービス UUID には battery_service、Bluetooth GATT 特性 UUID には battery_level などの標準名を使用できます。

PlaybulbCandle クラスに新しい getBatteryLevel メソッドを追加して、バッテリー残量をパーセントで読み取りましょう。

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 JavaScript オブジェクトを更新して、バッテリー サービスを optionalServices キーに含める必要があります。これは、PLAYBULB Candle Bluetooth デバイスによってアドバタイズされませんが、アクセスするには必須です。

playbulbCandle.js

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

以前と同様に、デバイス名を取得したら playbulbCandle.getBatteryLevel を呼び出して 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)
    .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] ボタンをクリックすると、デバイス名とバッテリー残量の両方が表示されます。

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

次のステップ

- この電球の色を変更するにはどうすればよいですか?そのために私がいるのです。

- あと少しだから、約束する...

よくある質問

6. 色を変更する

色を変更するには、0xFF02 としてアドバタイズされるプライマリ GATT サービスの Bluetooth 特性(0xFFFC)に特定のコマンドセットを書き込むだけです。たとえば、PLAYBULB Candle を赤色にするには、[0x00, 255, 0, 0] に等しい 8 ビット符号なし整数の配列を書き込みます。ここで、0x00 は白色の彩度、255, 0, 0 はそれぞれ赤、緑、青の値です。

characteristic.writeValue を使用して、PlaybulbCandle クラスの新しい setColor パブリック メソッドで Bluetooth 特性にデータを実際に書き込みます。また、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]);
    }

app.js の changeColor 関数を更新して、[No Effect] ラジオボタンがオンになっているときに 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 をクリック)、既存のページを更新します。ページ上の [Connect] ボタンをクリックし、カラー選択ツールをクリックして、PLAYBULB Candle の色を何度でも変更できます。

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

キャンドル エフェクトの追加

ろうそくに火を灯したことがある方なら、その光が静止していないことをご存じでしょう。幸いなことに、0xFF02 としてアドバタイズされるプライマリ GATT サービスには、ユーザーがキャンドル効果を設定できる別の Bluetooth 特性(0xFFFB)があります。

たとえば、「キャンドル効果」を設定するには、[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]);
    }

app.js の changeColor 関数を更新して、[Candle Effect] ラジオボタンがオンになっている場合は playbulbCandle.setCandleEffectColor を呼び出し、[Flashing] ラジオボタンがオンになっている場合は 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 をクリック)、既存のページを更新します。ページ上の [Connect] ボタンをクリックして、キャンドル エフェクトとフラッシュ エフェクトを試します。

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

次のステップ

- それだけ?3 つの不適切なキャンドル エフェクト?そのために私がここにいるのですか?

- 他にもありますが、今回はご自身で対応していただきます。

7. さらに効果を上げるためのヒント

さあ、これでほぼ終わりと思われるかもしれませんが、アプリはまだ完成していません。この Codelab でコピーして貼り付けた内容を実際に理解できたかどうかを確認してみましょう。このアプリをさらに良くするために、自分でできることを紹介します。

不足しているエフェクトを追加する

欠落している効果のデータは次のとおりです。

  • 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 で呼び出すことを意味します。

「効果なし」を修正する

お気づきかもしれませんが、「効果なし」オプションでは、進行中の効果はリセットされません。これは軽微な問題ですが、この問題を修正しましょう。setColor メソッドでは、まず新しいクラス変数 _isEffectSet を使用してエフェクトが進行中かどうかを確認する必要があります。true の場合は、[0x00, r, g, b, 0x05, 0x00, 0x01, 0x00] のデータで新しい色を設定する前にエフェクトをオフにします。

デバイス名を入力

これは簡単です。カスタム デバイス名の書き込みは、以前の Bluetooth デバイス名の特性への書き込みと同じくらい簡単です。TextEncoder encode メソッドを使用して、デバイス名を含む Uint8Array を取得することをおすすめします。

次に、document.querySelector('#deviceName') に「入力」eventListener を追加し、playbulbCandle.setDeviceName を呼び出してシンプルに保ちます。

私は「PLAY💡 CANDLE!」と名付けました。

8. これで完了です。

学習した内容

  • JavaScript で付近の Bluetooth デバイスを操作する方法
  • ES2015 のクラス、アロー関数、Map、Promise の使用方法

次のステップ