はじめに
前回の記事の続きです。
toioのjavascriptライブラリが公開されたのでexample動かしてみた
いつも趣味で使っているM5StackをBLE centralとして、toio(BLE peripheral)と通信してみました。
toioについては前回の記事を参照ください。
M5Stackとは
Wi-FiとBLE moduleを搭載した低価格のマイコンESP32に加えて、LEDやボタン、各通信ポート、SDカードスロットなどを搭載した一体型のモジュールです。
これ一つあれば組み込み系で遊びたいことは結構な範囲で実現出来ます。
拡張モジュールも多数出ており、いろいろな方がこれを使って開発されています。
M5Stackであそぼう
今回は、このM5Stackでtoioと通信してみます。
開発環境
- arduino IDE
-
arduino-esp32:1.0.1
- 後述のBLE周りの修正を加えています
- M5Stack library:0.2.5
(正直ちょっと古い環境です。最新にしても動作するかは後日確認予定です。)
BLE周りの変更点
ESP32のBLE周りは不安定だと以前からよく話題に上がっています。
実際に私も今回は初めてBLE触ってみましたが、最初うまく動かず、2点修正を取り込んでいます。
どちらもPR採用されていそうですので、arduino-esp32の次回リリースには反映されるかもしれないです。
arduino_esp32 1.0.1 が数日前にリリースされましたが、ダイソーモバイルシャッター等の BLE デバイスのほとんどが使えなくなってしまいました。
— こばさん (@wakwak_koba) 2019年1月13日
私が ESP32_BLE_Arduino で見つけた致命的なバグを github に上げましたので、お困りの方は持って行ってくださいませ。https://t.co/9McEHG1cUF#ESP32
また、上記の修正を加えて、ESP32のBLE central用のスケッチBLE_Client.ino
を起動しても下記のエラーで落ちてしまいました。
Characteristic: uuid: 10b20106-5b3b-4571-9508-cf3efcd7bbae, handle: 26 0x1a, props: broadcast: 0, read: 1, write_nr: 0, write: 0, notify: 1, indicate: 0, auth: 0CORRUPT HEAP: Bad head at 0x3ffe4634. Expected 0xabba1234 got 0x3ffe463c
assertion "head != NULL" failed: file "/Users/ficeto/Desktop/ESP32/ESP32/esp-idf-public/components/heap/multi_heap_poisoning.c", line 214, function: multi_heap_free
調べるとBLE通信やLoraでの通信で同様の問題が起きているらしく、ちょうどタイムリーに修正された方がいたようなのでその方の変更を取り込んだところ通信できました。
Fixed multi_heap_free failed during setting the value of the characteristic in BLE Library. by chcbaram · Pull Request #2789 · espressif/arduino-esp32 https://t.co/0sArdsy7jL
— 전민수 (@lucid8096) 2019年6月10日
その結果BLE_client.ino
が落ちなくなりました。
この環境をベースにtoioとの通信機能を追加していきます。
BLE通信機能
M5StackをCentralとしたBLE通信に関してはこちらの記事とその中のリンクを参考にしました。
NUCLEO BLEのアドバタイジングをM5STACKでスキャンしてみた話
toio全機能の実装は正直しんどいので、とりあえず下記の3つの機能をお試し実装してみました。
通信仕様については上のリンク先の公式仕様を参照ください。
サウンドの説明は本記事では割愛します。(モーターと同じノリで設定するだけです。)
toioのService UUIDの設定
toio自体をBLE Peripheralとして発見するためにService UUIDを設定します。
toio自体のService UUIDは通信概要に記載されています。
これを下記のようにこの値に設定します。
※変数名のルールなどはBLE_Client.ino
と変更してありますがご了承ください。
// toio service
static BLEUUID service_UUID("10B20100-5B3B-4571-9508-CF3EFCD7BBAE");
これでM5側がtoioのadvertiseを受けて検出できるようになります。
次に各機能(モーター、読み取りセンサー)の追加です。
モーター制御設定の送信
モーターの制御はM5Stackからtoioに制御値を送信します。
モーターの仕様を参考にcharacteristic UUIDを設定します。
// motor characteristic
static BLEUUID motor_char_UUID("10B20102-5B3B-4571-9508-CF3EFCD7BBAE");
今回は時間指定付きモーター制御を使用しました。
このリンク先のデータを送信します。
static void sendMotorControl(void)
{
uint8_t data[motor_data_size] = {0};
data[0] = 0x02; //motor control with specifiled time
data[1] = 0x01; //motor id 1 : left
data[2] = 0x01; //direction
data[3] = (is_req_move_forward ? 0x1E : 0x0); //speed
data[4] = 0x02; //motor id 2 : right
data[5] = 0x01; //direction
data[6] = (is_req_move_forward ? 0x1E : 0x0); //speed
data[7] = 0x0A; //control time
//Serial.println("write value");
motor_characteristic->writeValue(data, sizeof(data));
}
Aボタンが押されている間だけ、is_req_move_forward = true
として前進するようにしています。
static void checkPushButton(void)
{
static bool is_long_pressed = false;
is_long_pressed = M5.BtnA.pressedFor(100);
if(is_long_pressed){
M5.Lcd.drawString("MOVE", 0, 180);
is_req_move_forward = true;
}else{
M5.Lcd.drawString("STOP", 0, 180);
is_req_move_forward = false;
}
}
読み取りセンサー値の受信
読み取りセンサーで値を読み取るとtoioからそのデータが送信されます。
送信されるデータはこちらを参照ください。
主に4つのデータがあります。
上の2つはマット、アイテムに乗せたときに読み取れた値です。
下の2つはマット、アイテムから離したときに通知されます。
特にPosition IDはマット上の座標、角度も取れるので、これで絶対的な位置、姿勢が常に把握できるようです。
toioから受信するデータはcallback関数で処理するかたちになっていました。
各データをパースして保持、M5StackのLED上に表示してみました。
typedef struct {
uint16_t x_cube_center;
uint16_t y_cube_center;
uint16_t angle_cube_center;
uint16_t x_read_sensor;
uint16_t y_read_sensor;
uint16_t angle_read_sensor;
} position_id_t;
typedef struct {
uint32_t standard_id;
uint16_t angle_cube;
} standard_id_t;
static position_id_t position_id = {0};
static standard_id_t standard_id = {0};
static bool is_missed_position_id = false;
static bool is_missed_standard_id = false;
static void readPositionID(uint8_t *data, size_t length)
{
memcpy(&position_id, &data[1], sizeof(position_id));
}
static void readStandardID(uint8_t *data, size_t length)
{
memcpy(&standard_id, &data[1], sizeof(standard_id));
}
static void readPositionIDMissed(uint8_t *data, size_t length)
{
is_missed_position_id = true;
position_id.x_cube_center = 0;
position_id.y_cube_center = 0;
position_id.angle_cube_center = 0;
position_id.x_read_sensor = 0;
position_id.y_read_sensor = 0;
position_id.angle_read_sensor = 0;
}
static void readStandardIDMissed(uint8_t *data, size_t length)
{
is_missed_standard_id = true;
standard_id.standard_id = 0;
standard_id.angle_cube = 0;
}
void (*readFunctionTable[])(uint8_t *data, size_t length) = {
&readPositionID,
&readStandardID,
&readPositionIDMissed,
&readStandardIDMissed
};
static void selectReadFunction(uint8_t *data, size_t length)
{
uint8_t id = data[0];
readFunctionTable[id - 1](data, length);
}
static void notifyReadCallback(BLERemoteCharacteristic* ble_remote_characteristic,
uint8_t* data,
size_t length,
bool is_notify) {
selectReadFunction(data, length);
}
static void drawReadSensor(void)
{
String x_cube_str = String("cube x ") + String(position_id.x_cube_center) + String(" ");
M5.Lcd.drawString(x_cube_str, 0, 0);
String y_cube_str = String("cube y ") + String(position_id.y_cube_center) + String(" ");
M5.Lcd.drawString(y_cube_str, 0, 35);
String angle_cube_str = String("cube angle ") + String(position_id.angle_cube_center) + String(" ");
M5.Lcd.drawString(angle_cube_str, 0, 70);
String standard_id_str = String("std id ") + String(standard_id.standard_id)+ String(" ");
M5.Lcd.drawString(standard_id_str, 0, 105);
String standard_id_angle_str = String("std angle ") + String(standard_id.angle_cube) + String(" ");
M5.Lcd.drawString(standard_id_angle_str, 0, 140);
}
最後にこれらの処理をloop()
内で呼び出します。
void loop() {
M5.update();
// If the flag "do_connect" is true then we have scanned for and found the desired
// BLE Server with which we wish to connect. Now we connect to it. Once we are
// connected we set the connected flag to be true.
if (do_connect == true) {
if (connectToServer()) {
Serial.println("We are now connected to the BLE Server.");
} else {
Serial.println("We have failed to connect to the server; there is nothin more we will do.");
}
do_connect = false;
}
// If we are connected to a peer BLE Server, update the characteristic each time we are reached
// with the current time since boot.
if (connected) {
checkPushButton();
sendMotorControl();
drawReadSensor();
}else if(do_scan){
BLEDevice::getScan()->start(0); // this is just eample to start scan after disconnect, most likely there is better way to do it in arduino
}
delay(10); // Delay a second between loops.
} // End of loop
いくつか割愛している箇所もありますが、githubに公開しているこちらのコードを参照してください。
https://github.com/Katsushun89/M5Stack_BLE_toio_controller
動作結果
実際に動作させてみた結果はこちらです。
モーター
Aボタンを押している間だけ進みます。
#M5Stack をBLE centralにして、#toio のモーター制御出来た!
— Katsu Shun (@katsushun89) 2019年6月16日
コードはESP32のBLE_clientをベースにして公開されたモーター制御データを送信するようにしてる。 pic.twitter.com/1bJlvBWVlf
読み取りセンサー
LEDにマットとアイテムの読み取り値を表示させてみました。
昨日の続き、読み取りセンサーの値を表示してみた。離すとmissedって通知くるのでその時に値ゼロクリアしてみた。
— Katsu Shun (@katsushun89) 2019年6月18日
マットから座標読めるって面白いな😎#toio#M5stack pic.twitter.com/hVpejRJIrw
さいごに
M5StackでtoioとBLE通信してみました。
M5Stackはかなり拡張性高いのでサーボやセンサーとかいろいろ組み合わせて遊べそうな予感がします。
コードは今後整理予定ですが、現状でもBLE送受信のイメージは掴めると思いますので、参考にしていただければと思います。