
【電子工作】M5ATOMとBLE その2
サムネとタイトルがミスマッチ・・・w
初めてのESP32でのBLE
micro:bit の加速度をBLE経由で、M5ATOM に送るというサンプルを先人の知恵そのまま参考にさせてもらいました
また、BLE接続するには、micro:bitのMAC Addressが必要です
MAC Addressを確認するには、「ESP32 BLE Arduino」のBLE_scanのスケッチ例を実行することで取得できました
https://github.com/nkolban/ESP32_BLE_Arduino/blob/master/examples/BLE_scan/BLE_scan.ino
MAC Addressを自分の端末のものに書き換えても、先人のコピペしたコードではどうも接続ができない・・・
さらに知恵を借りると、connect関数の第2引数は、「BLE_ADDR_TYPE_RANDOM」を指定する必要があるようです
https://github.com/nkolban/ESP32_BLE_Arduino/blob/934702b6169b92c71cbc850876fd17fb9ee3236d/src/BLEClient.h#L37
加速度のサービスを使って、M5ATOM側で加速度の通知を受け、値を取得するサンプルですが、ここまでで、なんだか接続できている風なログが出ている・・・
が、プログラムが進んでいないっぽいので、ログを追加して更に追いかけてみます
・・・?
Notifyのコールバックが呼ばれていないようですね
何もわからずにコピペしたから、何が悪いのかもイマイチ把握できず・・・
・・・おや!?micro:bitの様子が・・・
micro:bitは2台(赤・緑)ともに、v2.21を持っています
(この色バリエ、なんだか既視感がありますねw)
どうもmicro:bit(赤)側が調子が悪い感じがします
Bluetooth接続ができない?
接続したチェック「レ」のLEDは表示されても、それ以降やはり接続が切れてしまう・・・
A+B+リセットボタン押下した時のペアリングのみであれば、接続できる・・・
もう一方のMicro:bit(緑)で、Bluetooth接続と、全く同じプログラムをインストールして試してみる・・・
こちらは問題なく、Bluetooth接続できて、Notify通知をもらえて、ATOMと通信できていると思われる・・・
あれ?
もしかして(赤)、壊れてる・・・?
試しに、MakeCodeで加速度を取得するブロックを積むプログラムをアップすると、どうも加速度センサを参照する箇所らしきところでフリーズしてる様子・・・
工場出荷時にインストールされているデモ(Out of box experience)をインストールしても「SHAKE」で反応しなくなる・・・
えええ・・・
買ったばかりに、デモが動く事を確認した記憶はあるので、やはり故障か・・・?
試しに、Arduino の環境で加速度を取得してみることにします
前に悩まされた加速度が取れないnoteで、作った加速度表示プログラムをいざ、(赤)にインストールすると・・・
・・・しっかり加速度を出力できてるじゃん・・・
マジでわからん・・・
micro:bitのファームウェアを再インストールし直してもダメ・・・
もしかして、これまでいろいろ試してみて、何かやってしまったかな・・・
残念ですが、加速度センサーのトラブルっぽいと判断しました
(赤)で加速度センサーは、諦めることにします・・・😿
Low Energy
BLEは、Bluetooth Low Energy の頭文字を取っている表現だそうです
Low Energy・・・省エネってことですかね
プログラマも同じで、Low Energyで生きたいものです
プログラムはなるべく再利用、もしくは微修正で動作をさせたいものです
フォロを動かすのに、GAMEPADのデモを参考にしたので、イベント受信のブロックを使っています
iPhone(BBC公式?アプリ) から micro:bit へ ID と Event を送信しているっぽいやつです

なので、ATOM側からイベントが送信できれば、micro:bitのプログラムを修正せずにフォロを動かせるはずです!
oh! Low Energy!!!
イベントってどうやって送るの?
公式?アプリのコントローラー画面上で、物理A/Bボタンや、LED、
GAMEPADのUIを押すことでmicro:bitが反応するので、
Bluetooth接続を経て、Bluetoothを使って何かしらデータを送っていることは間違いありません
でも、公式?アプリ側からmicro:bitへデータを送っている方法が全くわかりません・・・
さらに、Bluetooth は 調べてみると、「Bluetooth Classic」と「Bluetooth Low Energy(BLE)」の2種類あって、
しかもBLEには、ペリフェラル、セントラル、アドバタイズ、キャラクタリスティック、ディスクリプタ、GATT、GAPなど、馴染みのない単語がわんさかです・・・
素人の私には、すんなり入ってこないのです・・・
BLE関連
まずは勉強し直しました
ぐぐると優しく詳しく説明してくれる記事がたくさん見つけられます
私みたいな素人でも理解できるページがたくさんありました
「ものものテック」というブログが、おすすめです
「セントラルがペリフェラルのデータを取得する」
という表現は
「atomがmicro:bit加速度センサのデータを取得する」
に置き換えられそうです(国語か!)
したがって今回用意したサンプルは
atom => セントラル
micro:bit => ペリフェラル
になるのでしょう
BBC公式?アプリの例でいうと
iPhone => セントラル
micro:bit => ペリフェラル
という理解です
サービスとキャラクタリスティックに関しては
「加速度センサのデータを取得する」の部分で理解できそうです
ペリフェラルが「加速度サービス営業中ですよー」と言うのであれば、
・加速度センサの値
・加速度センサの更新間隔
が、提供される具体的な機能になるということでした
また、この「ペリフェラルが「加速度サービス営業中ですよー」と言う」事が重要で、ペリフェラル側の実装でサービスを公開し、セントラルから視認できるようになっている必要があります
具体的に確認するには、「nRF Connect」「BlueLight」などのアプリを使って、micro:bit に接続すると、そのmicro:bitのプログラムが提供するサービスとキャラクタリスティックが確認できます(これも先人の知恵です、ありがたいです)
キャラクタリスティックには、read / notify / write というプロパティ(というよりは属性みたいなモノに近い感じ)があって、
・キャラクタリスティックに対してできること
・キャラクタリスティックができること
を示しているようですね
・read はキャラクタリスティックがもつ値を読み取れること
・notify は値が変わった時にペリフェラルから、セントラルへ通知できること
・write はセントラルからペリフェラルの設定を変更できること
のようです
ディスクリプタがいまいちわからないですね・・・
ソースコードを見直すと、notifyを受け取れるようにするには、ディスクリプタを通してデータを変更することが必要なようです
実際に公開しているサービス
実際に「nRF Connect」アプリ上で確認できるものは、こんな感じのものでした

(「nRF Connect」アプリのスクショ)
確かに、下記の公式の仕様と同じUUIDを発見できます
トラブル・・・
検証でいじいじしていたせいか、ATOMとmicro:bitがBluetooth接続できなくなる・・・
Arduinoでログを確認するとこんなログが見られる
gattClientEventHandler(): Failed to connect, status=Unknown ESP_ERR error
えーーー
・・・MakecodeでシンプルにBL接続だけ確認できるプログラムを作り直してもだめ・・・
近辺のBL接続端末を無効化・・・
Makecodeで再ビルドしまくる・・・
解決せず・・・
2日悩んだ結果、ArduinoIDEを再インストールしてみた
シンプルなプログラムで試し直した所、接続できた・・・
んんん・・・なんで?
でも、違うプログラムをインストールしてみると接続できなくなった・・・
また更にさっきのシンプルな方に戻してもだめだった・・・
原因がわからないけど、ペアリングのプロジェクト設定を変更したり、もどしたりすることで、接続できるようになりました・・・
micro:bit のペアリングの説明を見つけました ↓
確認しても、接続できない理由はわからなかったのですが、
もしかすると、下の設定がまずかったかも・・・?

え・・・? もしかして、ご近所さんが・・・?
だとしたら怖いですね・・・
何度も接続できない経験をしているので、しっかり仕様と、対処法を覚えておきたい箇所ですね・・・
如何にしてデータやりとりしてるの?
さて、おおよその知識を改めたとはいえ、実際には何を使って、どうやってるのか・・・?
ですが、MakeCode上では、
・サービスの宣言をする
・イベント受信ブロック
を使うのみなので、わかりません・・・
公開しているサービスは限定しているから、なにがしかの方法で送信しているはず
各キャラクタリスティックの英文の説明を読むも、あまり入ってこず・・・
MakeCodeで使っているイベント受信ブロックなるものと、
Eventというサービスがある
これらは同じ物を指すのだろうか・・・?
・micro:bit にあるA/Bの物理ボタンを押した時
・公式?アプリのコントローラーからA/Bボタンを押した時
は、動作が全く同じなので、同じ通知を受け取っているはず
ボタンのサービスとキャラクタリスティックも存在しているので、
これで通信しているのか・・・?
でも、ボタンのキャラクタリスティックには、「read」「notify」はあるが、「write」は無い・・・
notify があると無条件に通信できるの・・・?
わからない・・・
ソースコードを追ってみる
MakeCode のボタン入力検知のブロックは、どのような実装になっているのか?
def on_button_pressed_a():
serial.write_line("invoke-button-a")
input.on_button_pressed(Button.A, on_button_pressed_a)
・・・?これだけ?
コールバックを登録しているだけ・・・?
もう少し追ってみようとすると、MakeCodeに↓のようなものがあったのですね

このcoreやbluetoothの中にヒント、もしくはヒントのヒントがありそうです
では、inputはどんな感じでボタン入力を呼び出しているのかな?
input.ts と input.cpp がありますが、.tsの方でもコールバック呼出のみなので、今回欲しい処理とは違いそうです
次に、input.cpp を見てみました
namespace input {
/**
* Do something when a button (A, B or both A+B) is pushed down and released again.
* @param button the button that needs to be pressed
* @param body code to run when event is raised
*/
//% help=input/on-button-pressed weight=85 blockGap=16
//% blockId=device_button_event block="on button|%NAME|pressed"
//% parts="buttonpair"
void onButtonPressed(Button button, Action body) {
registerWithDal((int)button, MICROBIT_BUTTON_EVT_CLICK, body);
}
関数名、言語の表記は違えど、コメントや引数からみて、
ボタン入力処理に間違い無さそうです
registerWithDal
Dal・・・たしかBluetooth関係に、DAL・CODALというものがありましたよね
registerWithDal を追いかけてみます
これですかね?
https://github.com/microsoft/pxt-microbit-core/blob/master/source/pxt.cpp#L363-L371

実装を見ると、なんとなしに、わかるようなことは
・handlersMap に、同じid、event の Action があれば、参照カウンタをデクリメントする
・messageBus に id、event がきたら、dispatchEvent を呼ぶ?ようなことを購読する?
・handlersMap に、引数 id、event の Action を上書きする
ふむ
messageBus、dispatchEvent は、とても良さそうなヒントですね
dispatchEvent
dispatchEvent は registerWithDal 関数の真上に定義されていました
https://github.com/microsoft/pxt-microbit-core/blob/master/source/pxt.cpp#L347-L361

MicroBitEvent を引数に先程のhandlersMap からActionを参照しています
つづけて、参照したActionをrunAction1 していると・・・
ここでActionが実行されるのでしょう
id、event がint型だったので、ここにはあるルールが決まった値がはいってそうです
おそらく ↓のidとeventを指すのだとおもいます

リポジトリを行ったり来たりして探すと、やはり定義を見つけました
MES_DPAD_CONTROLLER_ID
だんだんと見えてきましたね
messageBus
ドキュメントありました・・・
https://lancaster-university.github.io/microbit-docs/ubit/messageBus/
これを読むのが一番近道でしたね・・・
(どうして見つけられなかったのか・・・)
micro:bitへのボタン含め、すべての操作はidとeventで定義されているようでした
https://lancaster-university.github.io/microbit-docs/ubit/button/#message-bus-id
なるほど
idとeventを指定してイベントを発生させられるのですから、
公式?アプリのコントローラーからA/Bボタンを押しても、そのidとeventの値を使えば、擬似的にmicro:bitに反応させられるはずです
ははーーん
なかなか美しいじゃないか!
肝心のイベントを送る方法は・・・?
ボタンのキャラクタリスティックには、セントラルからペリフェラルへデータを送るようなことはできなそうでした
でも、送ることはできている・・・
現段階では、なにかのサービスとキャラクタリスティックを使っていると推理しています
UART?
micro:bitでUARTサービスが使えるようにはしていませんし、
「nRF Connect」で検索しても見つかりません
ひとつ気になるサービスがあったのを覚えています
EVENT SERVICE
です
このBLEのprofileのドキュメントを見るに、idとevent と似ているデータフォーマットを要求していました
struct event {
uint16 event_type;
uint16 event_value;
};
これが正解だろう、と強く思いました・・・
ただ、どのキャラクタリスティックを参照しているのか、わからないところです
EVENTにはキャラクタリスティックが4つあります
MicroBit Requirements
MicroBit Event
Client Requirements
Client Event
この中で、セントラルからデータを送るために、write が有効なキャラクタリスティックを探しました
下2つの「Client」で始まるキャラクタリスティックです
この2つ、どう違うの?とわからなかったので、もう実装してみました
きっと、動いた方が正解です
実装してみると、「Client Event」が正解のようです!!
フォロ側のイベント受信ブロック内でログを出力するようにして確認しました
間違い無さそうです!
// ATOM側
static BLEUUID ubit_event_serviceUUID("E95D93AF-251D-470A-A062-FA1922DFA9A8"); //event service
static BLEUUID ubit_event_characteristicUUID("E95D5404-251D-470A-A062-FA1922DFA9A8"); //client event
:
:
void setup()
{
:
:
pRemoteService = pClient->getService(ubit_serviceUUID);
pRemoteCharacteristic = pRemoteService->getCharacteristic(ubit_event_characteristicUUID);
:
}
// 構造体定義
typedef struct event {
uint16_t event_type;
uint16_t event_value;
} event;
void loop()
{
// ATOMのLEDボタンを押したら、イベント送信する
if (M5.Btn.wasReleased())
{
event ev;
ev.event_type = 1104; // MES_DPAD_CONTROLLER_ID
ev.event_value = 0; // MICROBIT_EVT_ANY
pRemoteCharacteristic->writeValue((uint8_t*)&ev, sizeof(ev), true);
}
:
:
}
やっとできたーーーー・・・・!!
これで、フォロ側のプログラムを一切弄らずに、
ATOMからイベントを送信することで、フォロが動かせそうです!
後から見つけましたが、このEVENTサービスのキャラクタリスティックのドキュメントもありました
しっかり読めていないのですが、それ以外のキャラクタリスティックについてもわかりそうです
今更ですが、micro:bit と対を成して client という名前になっているので、利用方法は想像できそうですね・・・
(micro:bit側のキャラクタリスティックはread / notify があるので、micro:bit が通知したイベントを受け取れそうですね)
ぎゃわわわ・・・月曜になってしまった
今日はここまでにしたいとおもいます!
次回は、コード整理と各キャラクタリスティックの理解を深めて、
ATOM側からフォロを操作する実装をしようと思います
はーー、すごい理解が深まって嬉しい・・・