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

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

IMG_19700101_023537~2~2.jpg

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

学習内容

  • JavaScript で付近の Bluetooth デバイスとやり取りする方法
  • ES2015 クラス、アロー関数、Map、Promise の使い方

必要なもの

2. Play first

この Codelab に取り組む前に、作成するアプリの最終版(https://googlecodelabs.github.io/candle-bluetooth)を確認し、手元にある PLAYBULB Candle Bluetooth デバイスを試してみることをおすすめします。

https://www.youtube.com/watch?v=fBCPA9gIxlU で、私が色を変更している様子をご覧いただけます。

3. セットアップする

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

このコードのサンプルコードは、こちらから ZIP をダウンロードするか、

または、この 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

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

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

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

ウェブブラウザでサイトにアクセスします(ハイライト表示された [Web Server 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.jsPlaybulbCandle クラスを定義し、後で 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.requestDeviceJavaScript 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 をクリック)、既存のページを更新します。緑色の [接続] ボタンをクリックし、選択ツールでデバイスを選択して、Ctrl+Shift+J キーボード ショートカットでお気に入りの DevTools コンソールを開き、BluetoothDevice オブジェクトがログに記録されていることを確認します。

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

Bluetooth がオフになっている場合や、PLAYBULB Candle の Bluetooth デバイスがオフになっている場合は、エラーが表示されることがあります。その場合は、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);
  });
});

次のステップ

- わかりました。このキャンドルと実際に話すことはできますか?

- 承知いたしました。次のステップに進みます

よくある質問

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 Candle Bluetooth デバイスの GATT サーバーに接続しています。次に、プライマリ GATT サービス(以前は 0xFF02 としてアドバタイズ)を取得し、このサービスに属するデバイス名特性(0xFFFF)を読み取ります。これは、PlaybulbCandle クラスに新しいメソッド getDeviceName を追加し、device.gatt.getPrimaryServiceservice.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));
    }

また、PLAYBULB Candle Bluetooth デバイスではバッテリー サービスがアドバタイズされていませんが、アクセスするには必須であるため、options JavaScript オブジェクトを更新して optionalServices キーにバッテリー サービスを含める必要があります。

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 をクリック)、既存のページを更新します。ページで [接続] ボタンをクリックすると、デバイス名とバッテリー残量の両方が表示されます。

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

[効果なし] ラジオボタンがオンになったときに playbulbCandle.setColor を呼び出すように、app.jschangeColor 関数を更新しましょう。ユーザーがカラー選択ツールのキャンバスをクリックすると、グローバル 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

Moar candle effects

ろうそくに火を灯したことがある方なら、ろうそくの炎が静止していないことはご存じでしょう。幸いなことに、0xFF02 としてアドバタイズされる Primary GATT Service には、ユーザーがキャンドル効果を設定できる別の 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.jschangeColor 関数を更新して、[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 をクリック)、既存のページを更新します。ページの [接続] ボタンをクリックして、キャンドルと点滅の効果を試してみましょう。

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]

これは基本的に、新しい setPulseColorsetRainbowsetRainbowFade メソッドを PlaybulbCandle クラスに追加し、changeColor で呼び出すことを意味します。

「効果なし」を修正

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

デバイスの名前を入力

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

次に、document.querySelector('#deviceName') に「input」eventListener を追加し、playbulbCandle.setDeviceName を呼び出して簡素化します。

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

8. これで完了です。

学習した内容

  • JavaScript で付近の Bluetooth デバイスとやり取りする方法
  • ES2015 クラス、アロー関数、Map、Promise の使い方

次のステップ