micro:bitとWebBluetoothで通信してみました

micro:bitを手に入れたので、こいつを使って個人的に開発をしているプロダクト「8x9Craft」と何か連携できないかなと考えています。

そこで、PCとmicro:bitをつなげる手段としてmicro:bitが最初から使えるBluetoothに目をつけたのですが、やるなら、なるべく簡単な仕組みで実現できないかと思い、WebBluetoothを使うことにしました。

本投稿では、加速度センサーの値をテキストボックに表示するプログラムを作ります。

そこで、いくつかハマったポイントをシェアしておきます。

まず最初に、micro:bitとPCをWebBluetooth経由で接続する技術的な情報はこちらを参照しました。

## 参考リンク
「Web Bluetooth API を使ってブラウザだけでMicro:bitとBLE通信してみる。」
https://shimz.me/blog/microbit/5456

## 準備するもの
micro:bit
chrome osx

現在、WebBluetoothはchromeでしか動かないそうなので、ここに書かれていることを試すにはmacが必要です。

でも、下記のリンクにwindows10でも動かせそうなことを書いているので、もしMacをもっていなくてWindowsでもやってみたいという人がいたら、是非チャンレジしてみてください。僕はWindowsをもっていないのでためそうかなという動機がなかったです。
https://github.com/urish/web-bluetooth-polyfill

## 実装
実装は、micro:bit側の実装と、JavaScript側の実装のふたつが必要です。

まず、micro:bit側の実装
* ステップ１　BLEパッケージの追加
* ステップ２　BLEのペアリング方式の変更
* ステップ３　BLEの利用サービスを開始

### ステップ１
micro:bitのプロジェクトにBLEのパッケージを追加する必要があります、
これは、ブロックのリストの下部にある、パッケージを追加するの中にあります。
<img width="1016" alt="スクリーンショット 2017-12-12 14.06.06.png" src="https://qiita-image-store.s3.amazonaws.com/0/6798/bbc25389-7fc9-d789-6a0b-ea0bc46c5090.png">


### ステップ２
プロジェクトの設定でBluetoothのペアリング方法を、「No Pairing Required: Anyone can connect via Bluetooth.」
に変更する。
プロジェクトの設定はエディタの右上の歯車のアイコンから移動できます。

<img width="747" alt="スクリーンショット 2017-12-12 14.15.13.png" src="https://qiita-image-store.s3.amazonaws.com/0/6798/9ed7764a-bd86-4764-112d-66396c6a07f4.png">

### ステップ３
micro:bit側の実装は、BLEのサービスを有効にしたhexをいれておかないといけないです。
具体的には次のようなコードです、

[BLE有効](https://makecode.microbit.org/_0fzaxEFK08y2)
<img width="573" alt="スクリーンショット 2017-12-12 14.03.19.png" src="https://qiita-image-store.s3.amazonaws.com/0/6798/e45766b5-c62e-0b1f-c6ca-02b5c1f29a4e.png">

onStartで使いたいBLEのサービスを開始するようにします。

プログラムをmicro:bitにいれておいてください。

micro:bit側は最低限これで十分です。

つぎは、WebBluetoothを使うJavaScript側の実装です。

* 接続ボタンなどの最低限の画面作成
* 使いたいBLEサービスのUUIDを調べる
* 接続処理で、BLEのサービスを取得する
* イベント処理

### 画面作成
画面はWebなので、普通にフォーム作成でいいです。次のような画面にしました。

```html:
<form name="js">
<input type="button" value="接続" onclick="connect();"/> 
<input type="button" value="切断" onclick="disconnect();" /> 
<input type="text" name="x" value="" />
<input type="text" name="y" value="" />
<input type="text" name="z" value="" />
</form>
```
### サービスのUUIDを調べる
WebBluetoothでBLEの接続を確立するために、サービスのUUIDを知っていなればなりません。
このUUIDはmicro:bitで固定のようなので、下記のサイトから参照することができます。

[micro:bit BlueToothプロファイル](https://lancaster-university.github.io/microbit-docs/resources/bluetooth/bluetooth_profile.html)

BLEの接続時に次のような処理をします

１　BLEサービスの検索
２　プライマリサービスの取得
３　キャラクタリスティックの取得

UUIDはサービスと、それに紐づくキャラクタリスティックがあるので、２つを調べておいてください。
たとえば、加速度センサーは
ACCELEROMETER SERVICE 
 UUIDE95D0753251D470AA062FA1922DFA9A8

ACCELEROMETER SERVICE - CHARACTERISTICS
 E95DCA4B251D470AA062FA1922DFA9A8

の２つです。
あと加速度センサーは、取得タイミングを設定するものと思われるAccelerometer Periodというのもあるみたいだけど、今回は使ってません。

micro:bitの仕様については下記に体系的にかかれています。
[micro:bit runtime](https://lancaster-university.github.io/microbit-docs/ble/profile/)

### BLEの接続処理
接続処理はPromiseを使って順番に処理をすると楽です。

```javascript
function connect() {  
  navigator.bluetooth.requestDevice({
    filters: [{
      namePrefix: 'BBC micro:bit',
    }],
    optionalServices: [ACCELEROMETERSERVICE_SERVICE_UUID]
  })
  .then(device => {
    accelerometer_device = device;
    console.log("device", device);
    return device.gatt.connect();
  })
  //ACCELEROMETER
  .then(server =>{
    console.log("server", server)
    return server.getPrimaryService(ACCELEROMETERSERVICE_SERVICE_UUID);
  })
  .then(service => {
    console.log("service", service)
    return service.getCharacteristic(ACCELEROMETERDATA_CHARACTERISTIC_UUID)
  })
  .then(chara => {
    console.log("ACCELEROMETER:", chara)
    alert("BLE接続が完了しました。");
    characteristic = chara;
    characteristic.startNotifications();
    characteristic.addEventListener('characteristicvaluechanged',onAccelerometerValueChanged);  
  })  
  .catch(error => {
    alert("BLE接続に失敗しました。もう一度試してみてください");
    console.log(error);
  });    
}
```
まず最初に
navigator.bluetooth.requestDevice
でBLEのサービスを検索します。
これを実行すると、サービス検索のダイアログが表示されます。

<img width="393" alt="スクリーンショット 2017-12-12 14.57.17.png" src="https://qiita-image-store.s3.amazonaws.com/0/6798/81524519-d393-353a-fcfa-cbc7e76b3c9e.png">

色々なサイトをみると、検索するときに filterをいれないとだめだとか書いてありますが、僕が試した限り filterがはいってなくても検索できました。
たとえば、
次のような検索でもうまくいきます。

```javascript
  navigator.bluetooth.requestDevice({ acceptAllDevices:true,optionalServices:[ACCELEROMETERSERVICE_SERVICE_UUID]}) 
```
これだと、BLEにかぎらずBlueToothの機器がたくさんヒットしますが、問題なかったです。
filterをいれたほうが楽なのは間違いないです。

検索して取得したデバイスをつかって、GATT接続を実行します。

```javascript
device.gatt.connect();
```
接続に成功したら、プライマリサービスを取得します。

```javascript
server.getPrimaryService(ACCELEROMETERSERVICE_SERVICE_UUID);
```
プライマリサービスの取得に成功したら、キャラクタリスティックを取得します

```javascript
service.getCharacteristic(ACCELEROMETERDATA_CHARACTERISTIC_UUID)
```

ここまでは、全てのBLEの初期処理として共通です。
キャラクタスティックの取得後は、サービスによってやることがことなります。
ここでは、加速度センサーの値を変更をリスンして、画面に表示したいので、

```javascript:
characteristic.startNotifications();
characteristic.addEventListener('characteristicvaluechanged',onAccelerometerValueChanged);
```
としています。
唐突に'characteristicvaluechanged'がでてきましたが、これらのイベントはGoogleでちょっと検索するとヒットしました。
下記の資料からもどんなイベントがあるのかわかります。
[bluetoothdevice](https://webbluetoothcg.github.io/web-bluetooth/#bluetoothdevice)

ここまでくればもうひといきです。
成功したら、BLEとの接続が確立して値をとったり書き込んだすることができるはずです。

プログラムの全ソースをここに貼っておきます。

```html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<title></title>
</head>
 
<body>
<form name="js">
<input type="button" value="接続" onclick="connect();"/> 
<input type="button" value="切断" onclick="disconnect();" /> 
<input type="text" name="x" value="" />
<input type="text" name="y" value="" />
<input type="text" name="z" value="" />
</form>

<script>
var accelerometer_device;
var accelerometer_characteristic;
 
//micro:bit BLE UUID
var ACCELEROMETERSERVICE_SERVICE_UUID       = 'e95d0753-251d-470a-a062-fa1922dfa9a8';
var ACCELEROMETERDATA_CHARACTERISTIC_UUID   = 'e95dca4b-251d-470a-a062-fa1922dfa9a8';

 
function connect() {
  navigator.bluetooth.requestDevice({
    filters: [{
      namePrefix: 'BBC micro:bit',
    }],
    optionalServices: [ACCELEROMETERSERVICE_SERVICE_UUID]
  })
  .then(device => {
    accelerometer_device = device;
    console.log("device", device);
    return device.gatt.connect();
  })
  //ACCELEROMETER
  .then(server =>{
    console.log("server", server)
    return server.getPrimaryService(ACCELEROMETERSERVICE_SERVICE_UUID);
  })
  .then(service => {
    console.log("service", service)
    return service.getCharacteristic(ACCELEROMETERDATA_CHARACTERISTIC_UUID)
  })
  .then(chara => {
    console.log("ACCELEROMETER:", chara)
    alert("BLE接続が完了しました。");
    characteristic = chara;
    characteristic.startNotifications();
    characteristic.addEventListener('characteristicvaluechanged',onAccelerometerValueChanged);  
  })  
  .catch(error => {
    alert("BLE接続に失敗しました。もう一度試してみてください");
    console.log(error);
  });    
}
 
function onAccelerometerValueChanged(event) {
    AcceleratorX = event.target.value.getUint16(0)/1000.0;
    console.log('x:' + AcceleratorX);
    document.js.x.value = AcceleratorX;

    AcceleratorY = event.target.value.getUint16(2)/1000.0;
    console.log('y:' + AcceleratorY);
    document.js.y.value = AcceleratorY;

    AcceleratorZ = event.target.value.getUint16(4)/1000.0;
    console.log('z:' + AcceleratorZ);
    document.js.z.value = AcceleratorZ;

} 
 
function disconnect() {
  if (!accelerometer_device || !accelerometer_device.gatt.connected) return ;
  accelerometer_device.gatt.disconnect();
  alert("BLE接続を切断しました。")
}
 
</script>    
</body>
</html>
```

###ハマった所

* WebBlueToothはHTTPSでないとダメと、いくつかのサイトにかいてありますが、ローカルフォルダから直接実行でも pythonでの簡易サーバーでも問題なく動きました。理由がわからない状態でプログラムが動かない場合、原因を特定するために色々するとおもいますが、動くという実証があれば、ここの部分はテストから省けるはずです。

* filterに設定する名前はnamePrefix: 'BBC micro:bit'にする。色々なサイトをみるとここの名前がname: "BBC micro:bit [vaget]"のように、[]の部分までついてますが、これは個体によって違う名前があるようなので、namePrefixで前方一致にしておいたほうがいいです。　

* ペアリングは成功しても、Characteristicの取得で失敗する場合がある。謎のエラーがでることがあります。失敗してもそのあともう一度トライするとうまくいく場合もあります。なのでよくわからないです。でも、ボタン処理をformにしたらトラブルがなくなりました。JavaScriptのbuttonClickを使っているのが問題なのかもと思っています。参考サイトのサンプルは大概これだったので、参照する場合は注意してください。このうごいたりうごかなかったりすることが、前述のhttpsのせいなのか、どうなのかよくわからない状況になり、はまりました。

*　あと、PCの設定からBlueToothのペアリングは不要です。これも色々ためしたうちの一つです。


###　追記
一つ言い忘れてました。ペアリングをするときは、A＋Bを押した状態で、後ろのリセットボタンを押してください。
リセットボタンを押したとに、A+Bボタンを離します。
これをしないとペアリングできないです。