【JavaScript 2020】 #UIFlow の BLE UART を使ったブラウザから #M5Stack_Core2 （ #M5Stack ）への文字の送信

この記事で書いている内容は、[Web Bluetooth API](https://webbluetoothcg.github.io/web-bluetooth/#dom-requestdeviceoptions-optionalservices) を使ったブラウザと BLEデバイスのやりとりの話です。
「せっかくなので、どこかのアドベントカレンダーへの登録ができれば」と思い、急遽、[JavaScript のアドベントカレンダー](https://qiita.com/advent-calendar/2020/javascript2)の 21日目に登録をしてみました。

少し補足をすると、M5Stackシリーズの M5Stack Core2 と、ビジュアルプログラミング環境の [UIFlow](https://m5stack.github.io/UIFlow_doc/ja/) を組み合わせて、最近実装された BLE UART を試した話の続きです。

●#UIFlow の BLE UART を使った文字のやりとりを #M5Stack_Core2 で試してみた（ #M5Stack ） - Qiita
　https://qiita.com/youtoy/items/0aeac01927d60c33f421

上記の記事では、M5Stack Core2 と BLE通信のやりとりをしていたのは Mac用のアプリの「Bluetooth Explorer」だったのですが、この部分を [Web Bluetooth API](https://webbluetoothcg.github.io/web-bluetooth/#dom-requestdeviceoptions-optionalservices) を使った JavaScript実装にしてみる、という話です。

今回の結果について書いておくと、ブラウザから M5Stack Core2 への文字列の送信は成功しました。

## M5Stack側の BLE関連のプログラム
M5Stack側のプログラムですが、UIFlow で作ったものを使います。
[冒頭で紹介した記事](https://qiita.com/youtoy/items/0aeac01927d60c33f421)を書く際に作ったものが、Macアプリとの間での動作確認ができているので、これを用います。

![UIFlow.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/815a505a-b0ef-51e5-b0b0-087b1d44067b.jpeg)

## Web Bluetooth API での通信
以前、micro:bit や toio といったデバイスを、BLE を使ってブラウザとの間で通信させたことがあったので、「以前と同様に進めれば良いかな」と思っていたのですが、思ったとおりに進みませんでした。ちなみに、その記事の一部が以下のものになります。

●toio を音で制御してみた（Audio用の Teachable Machine でベルやタンバリンの音を機械学習） - Qiita
　https://qiita.com/youtoy/items/37f70bb4ce630e6cbd92
●Web Bluetooth API で BLE（Chrome と micro:bit をつなぐ） - Qiita
　https://qiita.com/youtoy/items/cd2c3d4770d4ad75a321

デバイスのスキャンまわり（おそらく、フィルタ設定まわり）で何か問題が出ていそうな感じがしたので、上記の記事を書いた際に試していたことを見直してみたり、そもそもフィルタしない設定でまずは試したりなど、基本的な部分から見直してみることにしました。

### 開発者ツールからのスキャン
スキャン関連の話を調べていた時に、[Chrome で Web Bluetooth API を試している方の記事](https://neos21.hatenablog.com/entry/2020/07/04/080000)で、以下のような記載がありました。
「この方法は、つないでみる部分の試行錯誤をする際には、とても手軽で良いな」と思い、試してみました。

![Chromeでペアリング.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/acb1dc9d-821b-27b5-c277-d1a7e350d815.jpeg)

冒頭で「うまくいかない状態になった」という話では、過去に micro:bit や toio をつなげて使っていた際にデバイス名（今回の例では UIFlow上で自分で設定した文字列）でやっていたように思い、デバイス名でフィルタしたところ、スキャンできたデバイスのリストに M5Stack Core2 が出てこない、という状況でした。

Chrome の開発者ツールを開いて、上記の記事に書かれていた以下のコマンドをコンソールで実行してみます。

```JavaScript
navigator.bluetooth.requestDevice({ acceptAllDevices: true })
```

フィルタなしでスキャンすると、リストの中に UIFlow で設定した「m5ble」という名前が出てきました。

![BLEでのペアリング.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/e71d0eac-c93c-4aa8-9625-dcad1fdf5954.jpeg)

### 過去に使っていた Web Bluetooth API のフィルタ設定
上記の画像を見ても分かるとおり、フィルタなしでスキャンすると、BLE を使っている様々なデバイスが一緒にでてきます。そのため、「過去の事例では特定のデバイス名を持つものでフィルタしていたはず」と、あらためて見直してみました。

見直してみると、デバイス名と UUID の一方か両方を使っているようでした。

toio でのフィルタ設定の事例：
![toioの事例.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/1a458712-cac2-53b4-dfa8-dc1558576007.jpeg)

micro:bit でのフィルタ設定の事例：
![マイクロビットの事例.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/2320ca4a-30fa-ae77-34fb-591fb14d8989.jpeg)

「toio でコメントアウトしてる部分は、何か理由があったんだっけ？」と思い出せない部分もあり、今回の検証のついでに、こちらも開発者ツールのコンソールでの挙動を確認してみます。

#### micro:bit の検出を試す
micro:bit側のプログラムは、[以前書いた記事](https://qiita.com/youtoy/items/46502b9bd17fad16d427)の内容である以下をそのまま使おうと思います。

![BLE_UART_マイクロビット.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/4301d406-7f7c-9205-00f9-768d55d0b099.jpeg)

その際に 1点注意があり、以下の設定を適用して、ペアリングなしでもやりとりができるようにしてください。

![BLEの設定.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/4ad0780c-1746-8970-7967-c5d9533d2fe9.jpeg)

とりあえず、名前をベースにしたフィルタを試してみます。以下を開発者ツールのコンソールで実行します。

```JavaScript
navigator.bluetooth.requestDevice({filters:[{namePrefix: 'BBC'}]})
```

無事、想定通りの結果となりました。周囲には別の BLEデバイスもありますが、micro:bit のみがリストに出てきました。

![マイクロビットのペアリング.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/7cb24c06-5930-0989-f23a-3640bd837db4.jpeg)

#### toio の検出を試す
toio は、電源をいれただけで BLE通信ができる状態になるので、toioの電源をいれた後にスキャンを試してみます。以下を開発者ツールのコンソールで実行します。

```JavaScript
navigator.bluetooth.requestDevice({filters:[{namePrefix: 'toio'}]})
```

無事、想定通りの結果となりました。

![toioのペアリング.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/a2b7968b-438f-78e0-bc1d-fb2e6b6c6643.jpeg)

#### M5Stack Core2 の検出を試す
それでは M5Stack Core2 で試してみます。

いったん、フィルタの設定なしでスキャンしてみます。

```JavaScript
navigator.bluetooth.requestDevice({ acceptAllDevices: true })
```

上記を試した結果、以下のとおり検出できました。

![M5Stackのスキャン.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/265b08a4-eb9e-375f-0339-e4e762cfa446.jpeg)

次はフィルタを設定した以下を試します。

```JavaScript
navigator.bluetooth.requestDevice({filters:[{namePrefix: 'm5ble'}]})
```

そして上記を試すと、micro:bit や toio の時よりも少し時間がかかった気もしましたが、以下のとおり検出できました。

![M5Stackのスキャン.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/961038eb-167d-f71e-667e-a325680bf698.jpeg)

### JavaScript のプログラムでの処理： M5Stack Core2 との通信
次に、以下の HTML＋JavaScript のプログラムで接続等を試してみます。
（※ Notify用の UUID を書いているものの、それを使った処理は未実装）

```HTML
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>UIFlow と BLE</title>
</head>

<body>
  <h1>UIFlow と BLE</h1>
  <button onclick="onStartButtonClick()">接続</button>
  <button onclick="sendMessage()">テキスト書き込み</button>

<script>
const UUID_1 = '6e400001-b5a3-f393-e0a9-e50e24dcca9e';
const UUID_2 = '6e400002-b5a3-f393-e0a9-e50e24dcca9e'; // Write
const UUID_3 = '6e400003-b5a3-f393-e0a9-e50e24dcca9e'; // Notify

var bluetoothDevice;
var characteristic;

async function onStartButtonClick() {

  navigator.bluetooth.requestDevice({filters:[{namePrefix: 'm5ble'}]})
  .then(device => {
    bluetoothDevice = device;
    console.log("device", device);
    return device.gatt.connect();
  })
  .then(server =>{
    console.log("server", server)
    return server.getPrimaryService(UUID_1);
  })
  .then(service => {
    console.log("service", service)
    return service.getCharacteristic(UUID_2)
  })
  .then(chara => {
    console.log("characteristic", chara)
    alert("BLE接続が完了しました。");
    characteristic = chara;
  })
  .catch(error => {
    console.log(error);
  });
}

function sendMessage() {
  if (!bluetoothDevice || !bluetoothDevice.gatt.connected || !characteristic) return ;
  var text = "aaa";
  var arrayBuffe = new TextEncoder().encode(text);
  characteristic.writeValue(arrayBuffe);
}
</script>
</body>
</html>
```

上記のプログラムで接続処理を実行すると、コンソールに以下のメッセージが表示されました。

![BLEで接続.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/2eb28155-04c8-e94b-4024-025af057a1d8.jpeg)

接続はできたものの、以下のメッセージ・URL がでてきました。

> DOMException: Origin is not allowed to access any service. Tip: Add the service UUID to 'optionalServices' in requestDevice() options.
> 
> https://webbluetoothcg.github.io/web-bluetooth/#dom-requestdeviceoptions-optionalservices

表示された URL のページを見てみたところ、このあたりが関係しそうです。

![Bluetooth_requestDevice___-_Web_APIs___MDN.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/83694041-43b7-779c-e5ba-bd1278e95236.jpeg)

先ほどのメッセージの内容からすると、「filters」と合わせて「optionalServices」も設定し、「optionalServices」にはサービスの UUID を指定する、ということを行えば良さそうです。

上で掲載していたプログラムの一部を書きかえます。以下に、変更をした部分のみ抜粋し、変更前後の内容を掲載します。

こちらが変更前、「navigator.bluetooth.requestDevice」の後の部分が変更対象です。

```JavaScript
async function onStartButtonClick() {

  navigator.bluetooth.requestDevice({filters:[{namePrefix: 'm5ble'}]})
  .then(device => {
    bluetoothDevice = device
```

以下が変更後の内容です。

```JavaScript
async function onStartButtonClick() {

  navigator.bluetooth.requestDevice({
    filters:[{namePrefix: 'm5ble'}],
    optionalServices: [UUID_1]
  })
  .then(device => {
```

これで再度、試してみます。
その結果、無事に以下のツイートの動画にあるように、ブラウザから M5Stack側への通信を行うことができました。

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">前に toio や micro:bit でやった事例がそのまま適用できなかったけれど、 <a href="https://twitter.com/hashtag/UIFlow?src=hash&amp;ref_src=twsrc%5Etfw">#UIFlow</a> ＋ <a href="https://twitter.com/hashtag/M5Stack_Core2?src=hash&amp;ref_src=twsrc%5Etfw">#M5Stack_Core2</a> での BLE UART、<br>基本的な確認から進めて試行錯誤して、 Web Bluetooth API を使った ブラウザ ⇒ <a href="https://twitter.com/hashtag/M5Stack?src=hash&amp;ref_src=twsrc%5Etfw">#M5Stack</a> 側への文字列送信にようやく成功した！ <a href="https://t.co/os9SD1PlHA">pic.twitter.com/os9SD1PlHA</a></p>&mdash; you (@youtoy) <a href="https://twitter.com/youtoy/status/1340639497394946049?ref_src=twsrc%5Etfw">December 20, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

思ったより時間がかかったため、本当は M5Stack Core2 からブラウザへの通信の部分も試したかったのですが、今回はここまでにします。やり残した部分は、別途記事にできればと思います。