M5StickC PLUSを使ってジャイロセンサーの値を取得・保存する[WIP]
M5Stickとは
M5Stack(エムファイブスタック)は、約5cm×5cmの正方形のケースの中にWi-FiとBluetoothによる無線通信機能を備えたCPU(ESP32)をはじめ、カラー液晶ディスプレイ・プッシュボタン・スピーカ・microSDカードスロット・バッテリーなどの周辺部品がひとつのモジュールにまとまっている、小型のマイコンモジュールです。
M5stackをさらに小型化したのがM5Stickシリーズです。
今回使ったのは、腕に装着して使えるように時計用のアタッチメントがついたものを購入しました。
また、時計用のバンドがついてきますが、Fitbit Versa2用の交換バンドのサイズがピッタリだったので交換しました。
ジャイロセンサーの値を取ろうとした経緯
キャップ野球
という野球型の競技をしており、いろいろ研究してみているのですが、投げる蓋(キャップ)が小さく、現状自分の動画/画像解析のスキルではどうしても精度が上がらず、じゃあ投手のモーションキャプチャをしてみてはどうだろう、という発想のもと、安価にジャイロセンサーの値が取れそうなM5stickを選択しました。想定しているのは腕に装着した状態で、シャドウピッチングをして加速度や軸の傾きの変化を取ることです。
試したこと
arduino自体ほとんど触ったことがなかったので、まずは簡単そうなUIFlowでコードを組んでみることにしました。
サーバへ直接POST
最初に試したのは、スマホのテザリングなどでネットワーク接続を確保し、逐一サーバに値をPOSTすることです。デバイス側にある程度測定値を貯めて一括で送ろうと思っていたのですが、メモリの制限があるようで動的に配列を確保することができず、ミリ秒単位でサーバにPOSTすることにしました。
コード
送信した後、応答待ちの間の値が取れない
送信はできたものの、だいぶ歯抜けが多いデータしか保存されていませんでした。
送信してサーバから応答があるまでプログラムが応答待ち状態になってしまい、送れる最小間隔に限界があるのだろうと推測しました。
MQTT
次にMQTTを試してみました。
dockerでMosquittoサーバを立てているので環境構築自体はそれほど難しくはなかったです。
M5Stickのメモリ確保エラー
今度は送信を試みるとメモリ確保エラーでプログラムが落ちるのでこれも諦めました。
BLE(Bluetooth Low Energy)接続
UIFlowではBLE接続ができない
M5Stick用のUIFlowではBLE接続がサポートされていなかったので、arduino IDEへ移行し、
スマホにBLE接続でセンサの値を貯めてサーバに送ろうと考えました。
コード(Arduino)
#include "M5StickCPlus.h"
#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEServer.h>
#include <ArduinoJson.h>
#define SERVICE_UUID "9e89d032-5ba3-11ed-9b6a-0242ac120002"
#define CHARACTERISTIC_UUID "d098ef14-1b01-4f5c-b6c0-cbea2adb9c32"
BLECharacteristic *pCharacteristic = NULL;
BLEServer *pServer = NULL;
float acc[3];
float gyro[3];
float mag[3];
float pitch;
float roll;
float yaw;
RTC_TimeTypeDef timeStamp;
void setup() {
M5.begin(115200);
M5.Lcd.setTextColor(TFT_BLUE);
M5.Lcd.setTextSize(3);
M5.Lcd.println("BLE Turned on");
BLEDevice::init("M5StickC");
pServer = BLEDevice::createServer();
BLEService *pService = pServer->createService(SERVICE_UUID);
pCharacteristic = pService->createCharacteristic(
CHARACTERISTIC_UUID,
BLECharacteristic::PROPERTY_READ |
BLECharacteristic::PROPERTY_WRITE
);
pService->start();
BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();
pAdvertising->addServiceUUID(SERVICE_UUID);
pAdvertising->setScanResponse(true);
pAdvertising->setMinPreferred(0x06);
pAdvertising->setMinPreferred(0x12);
BLEDevice::startAdvertising();
M5.Lcd.println("M5StickC online");
}
void loop() {
String out;
M5.Imu.Init();
DynamicJsonDocument doc(200);
//M5.Rtc.GetTime(&timeStamp);
M5.Imu.getGyroData(&gyro[0], &gyro[1], &gyro[2]);
M5.Imu.getAccelData(&acc[0], &acc[1], &acc[2]);
M5.Imu.getAhrsData(&pitch, &roll, &yaw);
JsonArray gyro_arr = doc.createNestedArray("gyro");
gyro_arr.add(gyro[0]);
gyro_arr.add(gyro[1]);
gyro_arr.add(gyro[2]);
JsonArray acc_arr = doc.createNestedArray("acc");
acc_arr.add(acc[0]);
acc_arr.add(acc[1]);
acc_arr.add(acc[2]);
JsonArray data = doc.createNestedArray("rotation");
data.add(roll);
data.add(pitch);
data.add(yaw);
char buffer[255];
serializeJson(doc, buffer, sizeof(buffer));
Serial.print(buffer);
pCharacteristic->setValue(buffer);
pCharacteristic->notify(true);
delay(100);
}
web bluetooth APIとの接続
ReactNativeは書けるので、テスト用にスマホアプリ作る覚悟をしていたのですが、Web Bluetooth API
という便利なものがあることを知ったので、スマホのブラウザ経由でサーバにPOSTすることを目標にしました。
コード(NextJS)
import {
useRequestDevice,
useGetServer,
useGetCharacteristic,
useReadValue,
useGetPrimaryService,
} from "react-web-bluetooth";
const { onClick, device } = useRequestDevice({
acceptAllDevices: true,
optionalServices: [SERVICE_UUID],
});
const server = useGetServer(device);
const service = useGetPrimaryService(server, SERVICE_UUID);
const char = useGetCharacteristic(service, CHAR_UUID);
const value = useReadValue(char);
characteristicvaluechanged
イベントが来ない
便利なReact HooksreadValue()
が1回しか動かないので色々調べたところ、デバイスからの値の変更検知ができないバグがあるようです。
これから
成功例ではないので恐縮ですが、デバイスからの値の送出は上手くいってるので、web bluetooth APIを諦めて別の方針に転換するのか...などは未定です。
うわ...Fitbitもジャイロセンサー取れるな...。
上手くいけば加筆しようと思います。
Discussion