まぜるな危険?
白状します。かなり前に帯につられて「プログラミングElixir」という本を買ったです。が、いろいろあって放置してました。時は過ぎて、最近PhoenixのLiveViewが私のツイッタラー界隈で盛り上がっているので面白そうだなと思いElixirの勉強がてら遊んでました。JavaScriptってJavaと何が違うのレベルですけど私 :-)
で、JavaScriptといえばWeb Bluetoothがあったなーと思い、Bluetoothは少しだけ分かりますので、つい混ぜてしまいました。一応動いたのでここにメモしますのです。
概要
- Web Bluetoothを使ってBLE(Bluetooth Low Energy)の心拍計データをJavaScriptで集めます。
- そのデータをPhoenix LiveViewを使ってサーバに送り込むです。
- サーバ側のElixirでそれを受けて、値を元に表示させる絵とテキストを作成。
するとChromeからのアクセスでこんな感じになるですよ。
ちなみにWeb Bluetoothは執筆時現在、Chromeで動きます。FireFoxとかでは動きません。後今回はiOSの心拍計シミュレータを使いました。これが一秒間隔でデータが出る奴なので、これ前提です。10msec間隔とかだとこのデモ多分破綻します(汗)
ビルドと実行方法など
コードなどはGitHubに上げてあります。
環境やビルド方法などはこちらを見ていただければと思います。
実験用なのでsaltもまんまぶっ込んでます注意。
BLEの雑な解説
-
仕様書はここにあります。
https://www.bluetooth.com/ja-jp/specifications/bluetooth-core-specification
-
簡単な解説はJellyWare株式会社さんのこことかが参考になるかと思います。
ま、要は無線通信の規格です。通信やデータの規定が仕様で定められているので、それを守って作られたデバイス(パソコン含)同士であれば通信ができますよというものです。
ここでは接続する側(パソコンとか)をセントラル、接続される側(センサとかマウスとかとか)をペリフェラルと呼びます。また所謂GATTデバイスの概要を以下に書きます。
Advertise, Scan
Advertiseは自己紹介です。ペリフェラルは「僕はこんなデバイスですー」という感じで電波を出す仕組みです。ScanはセントラルがAdvertiseデータを集める時に発行する仕組みで「おい、おまえら自己紹介しろー」って感じです。実際のデータは例えばデバイスの名前だったりするです。
Connect
接続です。ペリフェラルが発信する毒電波、もといAdvertiseデータを見て「よし、こいつとお話しよう」と決めて接続するです。EthernetでおなじみのMACアドレスがBluetoothにもあって、それで接続するです。
接続語、セントラルは対象ペリフェラルのデータを収集します。ペリフェラルの持つデータの種類と性質などを知るためです。具体的には以下の各ServiceとCharacterstricを把握します。
ServiceとCharacterstric
ペリフェラルを操作する際はServiceとCharacterstricという概念で扱います。Characterstricは各データつまりプログラムでいう変数みたいなイメージです。Serviceは複数のCharacterstricから構成されますのでクラスとか構造体みたいなイメージで捉えてもらうといいのかもしれません。
例えば心拍計を例にとると、心拍計というServiceがあり、心拍数データ、センサの位置(体のどこにつけているか)、センサの仕様(送信間隔等)というCharacterstricから構成されているイメージです。ちなみにこれらのServiceとかCharacterstricにはそれぞれUUIDが割り振られていまして、そのUUIDでアクセスするやり方になります。
データ通信
Connectすれば、後はデータ通信ができます。大雑把には以下の種類のデータ通信が可能です。
タイプ | 説明 |
---|---|
read | セントラルからペリフェラルのデータを読み出す |
write | セントラルからデータを書き込む |
notify | ペリフェラルからデータを通知(※1) |
indicate | ペリフェラルからデータを通知(※1)(※2) |
- ※1:セントラルがnotify/indicateを吐き出せと指示するとペリフェラルが適当にデータを出してくれます。なのでイベント的なデータを受ける際に使います。
- ※2:indicateはIndicateはセントラルからの応答も要求します。
これまた心拍計を例にとると、Bluetoothの仕様で心拍数データはnotifyで定義されます。なので、セントラルから心拍計サービスの心拍数データ(共にUUIDで指定)に対してnotifyを吐き出せと指示します。するとペリフェラルから心拍数データが適時上がる仕組みです。
独自のSeriviceやCharacterstricを定義することも可能です(専用のUUIDがあるイメージ)
セキュアな通信する時は
更にセキュアな通信(暗号化通信)を行う際には接続後にペアリングを行います。例えば番号を入力させるとかそんなのです(この説明は更に雑です)
Web Bluetoothの雑な説明
本家はここです。ちなみにWeb Bluetoothは https じゃないと使えないみたいです。
Web Bluetoothのデモプログラム(以下URL)の中にHertrateのデモがありまして、今回はそこで使っているJavaScrit(heartRateSensor.js)をそのまま使わせて戴きました。
以下、簡単に。
heartRateSensor.jsの使い方
今回は接続して心拍数を取るだけですので、わりに簡単です。以下のコードで出来ました。が、JavaScriptさっぱしわかんねw
// ボタンクリックで接続に行くコードです。
// HeartRateSensorはheartRateSensor.jsで生成されたクラス
function OnButtonClick() {
heartRateSensor.connect() // つなぎに行きます(ペアリング込)。
// 中の人が heart rate のUUIDでやるようにしてます
//つながったので、notificationを出せと指示するです
.then(() => heartRateSensor.startNotificationsHeartRateMeasurement()
//で下に書いた関数を呼び出せと
.then(handleHeartRateMeasurement))
//例外(失敗)したら流行りの alertで。ループはしないので無罪。
.catch(error => {
alert("BLUETOOTH_ERROR>" + error);
});
}
// 上のconnectで接続されると呼び出されます。
function handleHeartRateMeasurement(heartRateMeasurement) {
// notifyデータがペリフェラルから上がるとイベント来るのでそれを受け取る
heartRateMeasurement.addEventListener('characteristicvaluechanged', event => {
// ペリフェラルから来た心拍データを解析して心拍数を取得
var heartRateMeasurement = heartRateSensor.parseHeartRate(event.target.value);
// で後はこの heartRateMeasurement を表示とかさせる感じですねー
});
}
細かい内容に興味があるようでしたら、heartRateSensor.js等も見てみると良いかと思います。
heartRateSensor.jsの感じでUUIDを指定して使うコードに書き換えると、他のデバイスも似たような形でハンドリングできるかもーとか漠然と思ってみたり。
Phoenixの雑な解説
PhoenixはElixir言語をベースにしてWeb開発のフレームワークと理解しています。オフィシャルは以下。ぶっちゃけ「ElixirでRuby on Railsっぽい事が出来るもの」って感じ?
Elixirを使っている意義、その可能性などは少し古いですが以下の記事が参考になるです。
LiveViewの雑な解説
GitHubは以下。
例えばbuttonをクリックした事をサーバに通知する時にはJavaScriptでそういうコードを書く感じになるらしいのですよ。最初に書いたように、私はWeb系は素人でかつ老害なもんで「キリ番ゲット判定ライブラリとかはあるですか?」とかやってしまうダメな人なんで良くは知らないのですがw
LiveViewを使うとそういう事が無くなり、サーバーサイド(つまりElixir)のコードで出来るという特徴があるようです。ざっと見る限り、LiveViewを処理するJavaScriptがあって、それがWebScoketを貼ってブラウザで起きるイベントをひっつかまえて送っているようです。
実際どういう感じで使えるのかは、以下にデモが置いてあります。ゲームの雛形みたいなのもありますね。実際ツイッターでもゲーム書くのに使えそうだという意見は見たです。
で、上記にあるカウンタのデモのコードを引用して特徴を見てみます。このデモではHTMLのButtonをこんな感じで書きます。これは数値を+1するボタンですね。これで良いです。特にJavaScriptdeで何か書く必要はないみたいです。
<button phx-click="inc">+</button>
このボタンをクリックすると、LiveViewの仕組みでサーバまでデータが来て、結果Elixirコードのhandle_eventという関数が呼び出されます。上記のデモコードだとこう。
def handle_event("inc", _, socket) do
{:noreply, update(socket, :val, &(&1 + 1))}
end
なので私のようにElixirでちょっと遊びたいだけの素人にも優しい仕様といえるでしょう。作られた方がそんな事まで意識して下さっているかは知りませんがw
で、この仕組みを使ってBLEのデータを送り込むことが出来ないかなーと思ったのです。そうするとBLE用にソケット/APIを割り当てる事無くそのデータをサーバに送り込み、それに基づく結果を表示できるのではないかと。例えば振動検知センサを使ったゲームとか。あと機械学習系で使うのも出来そうですし。
LiveView込みのプロジェクトに仕立てるまで
2020/07/15現在
LiveViewの機能を使ったプロジェクトを作るのは割に簡単で、公式にもありますが
$ mix archive.install hex phx_new
$ mix phx.new demo --live
で、出来ます。私はこのプロジェクトを
$ mix phx.new ble_live_sample --live --no-ecto
で作りました。するとサンプルが出来るので、それとかドキュメント等を見つつ組み上げていく感じです。
前はどうだったのか
ってだけの話でなんでわざわざ項目を作ったのかというと、前は --live みたいなのは無くて、関連部分を以下のように細々修正する必要があったです。まー前はどうだったのかという参考で晒しておきます。
執筆時(2019/04/06)、LiveViewを動かすようにするには phx.new でプロジェクト作ってから結構ごにょごにょ修正する感じです。以下おおざっぱに。フォルダは本コードの例です。
パス | 概要 |
---|---|
mix.exs | depsに追記 |
config/config.exs | salt情報の記載 |
lib/ble_live_sample_web/router.ex | pipelineに追記 |
ble_live_sample_web.ex | viewとrouterに追記 |
lib/ble_live_sample_web/endpoint.ex | socketを追記 |
assets/package.json | dependenciesに追加 |
assets/js/app.js | LiveSocketをimport |
config/dev.exs | configに追記 |
assets/css/app.css | live_view.cssをimport |
私が書いたコードでは package.json 以外、 add ble sample のコメントを入れてありますので、参考にしていただいてもいいかもです。
この修正方法については本家でも説明されています。
以下の記事でも丁寧な説明があり参考になると思います(つか凄い参考になりました!)
- https://qiita.com/kikuyuta/items/c5b0788ad5d09c210c0a
- https://qiita.com/piacere_ex/items/21b0e308a36e486d8b25
Elixirで書かないといけない人たち
サーバ側で書かないといけないのはザックリ以下。
昔はElixirコードにHTMLも埋め込む形だったのですが、HTML系は2020/07/15現在page_live.html.leexにて記述するようになってます。ここにHTML(一部JSも。手抜きですみません…)の記述しました。
後はElixirのコード、私のコードはpage_live.exなので、興味があればそこを見てもらえれば。
-
mount
こちら初期化時に呼ばれます。上記renderで書いたHTMLに配置したパーツに紐付いた変数の初期化とかを行います。
-
handle_event
イベントが発生するとこれが呼び出されます。
-
handle_info
これ、私のコードでは上記のhandle_eventの後で呼び出してもらようにお願いしていますw。send関数を使ってお願いしています。やり方はコードを見てもらった方が良いと思います。今回のデモではカウント値のテキストや画像の表示処理で使っています。ちなみにタイマ処理で使うとか他のやりざまもあるようです。
Web BluetoothとLiveViewをどう混ぜたか
こっからは素人が適当にコードを書いちゃいましたのコーナーです。
心拍数のデータは既にJavaScriptの変数まで持って来ています。これをどうLiveViewに運ぶかなのですが、今回はテキストボックスにデータをぶっ込んで発火するという方法にしました。だってこれならLiveView側のコードを変更せずに動くんだもん(ひでー)。つまりこれ。
// valに心拍数が来るとしませう
function IssueBLEEvent(val) {
// heart_rateがテキストボックスなので、そこに値を入れる
document.ble_test.heart_rate.value = val;
// で、changeイベント発火させてLiveViewに伝えます
// LiveViewから見ればテキストボックスに何か打ち込まれたと
// 思ってデータをサーバに送ります
var evt = document.createEvent( "MouseEvents" );
evt.initEvent( "change", true, true );
document.ble_test.heart_rate.dispatchEvent(evt);
}
valをLiveView経由でサーバに送り込みます。あと、このテキストボックスは人様の相手なんかしないのでhiddenにしてます。
で、Elixir側でこう受けます。
def handle_event( "change", %{ "heart_rate" => heart_rate }, socket ) do
now = String.to_integer(heart_rate)
#
# now(integer)にBLEから来た心拍数がセットされます。
#
実際のコードは以下です。これでBLEのデータをサーバまで取り入れ、表示は出来ています。
マジレスすると良い方法があったらおまえら教えろ下さいなのです。
最後に
これだと、実際にはあまりElixirの勉強になってません(汗)。ゲームとかでも良いですがもう少しElixirをいじるような何かがあった方が良いかなーとか思ってます。
長い文章をここまでよんで戴いてありがとうございました。