この記事で書いている内容は、Web Bluetooth API を使ったブラウザと BLEデバイスのやりとりの話です。
「せっかくなので、どこかのアドベントカレンダーへの登録ができれば」と思い、急遽、JavaScript のアドベントカレンダーの 21日目に登録をしてみました。
少し補足をすると、M5Stackシリーズの M5Stack Core2 と、ビジュアルプログラミング環境の UIFlow を組み合わせて、最近実装された BLE UART を試した話の続きです。
●#UIFlow の BLE UART を使った文字のやりとりを #M5Stack_Core2 で試してみた( #M5Stack ) - Qiita
https://qiita.com/youtoy/items/0aeac01927d60c33f421
上記の記事では、M5Stack Core2 と BLE通信のやりとりをしていたのは Mac用のアプリの「Bluetooth Explorer」だったのですが、この部分を Web Bluetooth API を使った JavaScript実装にしてみる、という話です。
今回の結果について書いておくと、ブラウザから M5Stack Core2 への文字列の送信は成功しました。
M5Stack側の BLE関連のプログラム
M5Stack側のプログラムですが、UIFlow で作ったものを使います。
冒頭で紹介した記事を書く際に作ったものが、Macアプリとの間での動作確認ができているので、これを用います。
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 を試している方の記事で、以下のような記載がありました。
「この方法は、つないでみる部分の試行錯誤をする際には、とても手軽で良いな」と思い、試してみました。
冒頭で「うまくいかない状態になった」という話では、過去に micro:bit や toio をつなげて使っていた際にデバイス名(今回の例では UIFlow上で自分で設定した文字列)でやっていたように思い、デバイス名でフィルタしたところ、スキャンできたデバイスのリストに M5Stack Core2 が出てこない、という状況でした。
Chrome の開発者ツールを開いて、上記の記事に書かれていた以下のコマンドをコンソールで実行してみます。
navigator.bluetooth.requestDevice({ acceptAllDevices: true })
フィルタなしでスキャンすると、リストの中に UIFlow で設定した「m5ble」という名前が出てきました。
過去に使っていた Web Bluetooth API のフィルタ設定
上記の画像を見ても分かるとおり、フィルタなしでスキャンすると、BLE を使っている様々なデバイスが一緒にでてきます。そのため、「過去の事例では特定のデバイス名を持つものでフィルタしていたはず」と、あらためて見直してみました。
見直してみると、デバイス名と UUID の一方か両方を使っているようでした。
「toio でコメントアウトしてる部分は、何か理由があったんだっけ?」と思い出せない部分もあり、今回の検証のついでに、こちらも開発者ツールのコンソールでの挙動を確認してみます。
micro:bit の検出を試す
micro:bit側のプログラムは、以前書いた記事の内容である以下をそのまま使おうと思います。
その際に 1点注意があり、以下の設定を適用して、ペアリングなしでもやりとりができるようにしてください。
とりあえず、名前をベースにしたフィルタを試してみます。以下を開発者ツールのコンソールで実行します。
navigator.bluetooth.requestDevice({filters:[{namePrefix: 'BBC'}]})
無事、想定通りの結果となりました。周囲には別の BLEデバイスもありますが、micro:bit のみがリストに出てきました。
toio の検出を試す
toio は、電源をいれただけで BLE通信ができる状態になるので、toioの電源をいれた後にスキャンを試してみます。以下を開発者ツールのコンソールで実行します。
navigator.bluetooth.requestDevice({filters:[{namePrefix: 'toio'}]})
無事、想定通りの結果となりました。
M5Stack Core2 の検出を試す
それでは M5Stack Core2 で試してみます。
いったん、フィルタの設定なしでスキャンしてみます。
navigator.bluetooth.requestDevice({ acceptAllDevices: true })
上記を試した結果、以下のとおり検出できました。
次はフィルタを設定した以下を試します。
navigator.bluetooth.requestDevice({filters:[{namePrefix: 'm5ble'}]})
そして上記を試すと、micro:bit や toio の時よりも少し時間がかかった気もしましたが、以下のとおり検出できました。
JavaScript のプログラムでの処理: M5Stack Core2 との通信
次に、以下の HTML+JavaScript のプログラムで接続等を試してみます。
(※ Notify用の UUID を書いているものの、それを使った処理は未実装)
<!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>
上記のプログラムで接続処理を実行すると、コンソールに以下のメッセージが表示されました。
接続はできたものの、以下のメッセージ・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 のページを見てみたところ、このあたりが関係しそうです。
先ほどのメッセージの内容からすると、「filters」と合わせて「optionalServices」も設定し、「optionalServices」にはサービスの UUID を指定する、ということを行えば良さそうです。
上で掲載していたプログラムの一部を書きかえます。以下に、変更をした部分のみ抜粋し、変更前後の内容を掲載します。
こちらが変更前、「navigator.bluetooth.requestDevice」の後の部分が変更対象です。
async function onStartButtonClick() {
navigator.bluetooth.requestDevice({filters:[{namePrefix: 'm5ble'}]})
.then(device => {
bluetoothDevice = device
以下が変更後の内容です。
async function onStartButtonClick() {
navigator.bluetooth.requestDevice({
filters:[{namePrefix: 'm5ble'}],
optionalServices: [UUID_1]
})
.then(device => {
これで再度、試してみます。
その結果、無事に以下のツイートの動画にあるように、ブラウザから M5Stack側への通信を行うことができました。
前に toio や micro:bit でやった事例がそのまま適用できなかったけれど、 #UIFlow + #M5Stack_Core2 での BLE UART、
— you (@youtoy) December 20, 2020
基本的な確認から進めて試行錯誤して、 Web Bluetooth API を使った ブラウザ ⇒ #M5Stack 側への文字列送信にようやく成功した! pic.twitter.com/os9SD1PlHA
思ったより時間がかかったため、本当は M5Stack Core2 からブラウザへの通信の部分も試したかったのですが、今回はここまでにします。やり残した部分は、別途記事にできればと思います。