エムスリーテックブログ

エムスリー(m3)のエンジニア・開発メンバーによる技術ブログです

Bluetoothを使った筋トレ用デバイスを作ろう

こんにちは、エムスリーエンジニアリングGの岩佐由喜です。新卒として今年の4月から入社し、現在は医療系ポータルサイト m3.com の開発・運営を担当する Unit4 というところにいます。

みなさん、運動してますか?リモートワークが進む昨今、運動不足を解消するために筋トレを始めた方も多いのではないでしょうか。私は趣味でストリートワークアウトというスポーツをやっております。ストリートワークアウトとは下の写真のように体を宙に固定する技を競うスポーツです。 この競技の練習は普通の筋トレと異なり「空中に何秒(分)浮いていられるか」を追って練習することが多いのですが、体が宙に浮いたタイミングでスタートしてくれるようなタイマーが無く、とても練習しづらい状況にありました。

そこで今回は、ストリートワークアウトの練習環境改善のために個人開発したBluetoothデバイスについて紹介します。

f:id:Yoshiki-Iwasa222:20210922152210p:plain
バックレバーという技 by 著者
f:id:Yoshiki-Iwasa222:20210923084532p:plain
実際に作ったデバイス

デバイスを使っている様子は以下の動画からご確認ください。動画にはデバイスと通信しているアプリが登場しますが、本記事ではアプリ側の説明は省きます(撮影方法の都合上、デバイスとアプリの同期にタイムラグがかなりあるように見えますが、実際はもっとリアルタイムです)。

www.youtube.com

1. 今回作りたかったもの

デバイスの話をする前に、今までどう練習していたか・何に困っていたのか・どう練習が改善されたのかなど開発の背景を紹介させてください。

1.1 背景

今までどう練習していたか

今までは地面にタイマーを置き、決められた時間から何秒間浮いていられるかを測定していました。例えば「10秒のところから足を上げて、30秒まで浮いていられるかやってみよう」と、こんな感じです。

困っていたこと

上記の方法だと自分のタイミングで技を開始できず練習効率を大きく下げることになってしまいます。なぜならストリートワークアウトの練習では集中力がとても大切で、体の動きや力の入れ具合を強くイメージしてから練習をしないとあまり効果がないからです。完全に集中しきれていない環境で練習するのは精神的にもストレスがありました。

どう練習が改善されたのか

このデバイスを作成後は、タイマーについての意識を完全になくすことができたので、100% の集中力を維持したまま練習できるようになりました。そうすると、使う筋肉の動きをより意識できるようになったことで同じ秒数の練習でも筋肉痛がちゃんと来るようになりとても嬉しいです。

1.2 デバイスの要件

今回は荷重を測定しBluetoothでその値を送信できるデバイスを作れれば十分です。そのデータを加工するロジックはアプリ側に任せるという方針を取りました。

そのために必要な要件は以下の通りです。

  • デバイスが人の重量をセンサーから取得できること(筆者の体重は約75kgですので、センサーに100kgくらいまで測れるスペックがあれば十分です)。

  • 取得した重量情報をBluetooth通信で送信できること。

1.3 全体像とアーキテクチャ

全体像

ここでシステムの全体像を説明します。

登場するのはスマホと本記事で紹介するデバイスです。全体の流れは以下のようになります

  1. スマホとデバイスがBluetooth接続する。

  2. スマホがデバイスに対して荷重値の読み取り要求を出す。

  3. デバイスは読み取り要求に応じて荷重値をスマホに返す

  4. スマホは得た荷重値の変化を読み取ってタイマーの開始/停止の操作をする

2 ~ 3 の動作を繰り返すことで体がデバイスから離れるとタイマーを開始し、着地するとタイマーを停止するという処理を実現します。

簡単なアーキテクチャは下図です。

f:id:Yoshiki-Iwasa222:20210929113307p:plain
簡単なアーキテクチャ図

2. ハードウェアの作成

2.1 使用したもの

今回、所望の挙動を実現するために使用したもの

・ESP32-DevKit

waves ESP32 DevKitC V4 ESP-WROOM-32 ESP-32 WiFi BLE 技適取得済 国内発送 - Waves

・ロードセル (重さセンサー)

https://akizukidenshi.com/catalog/g/gP-13043/

・ロードセル用ADコンバータモジュール基盤 (HX711使用)

https://akizukidenshi.com/catalog/g/gK-12370/

・4ビット双方向ロジックレベル変換モジュール

https://akizukidenshi.com/catalog/g/gK-13837/

・いらない体重計

2.2 それぞれの役割

ESP32-DevKit

USBポート・Bluetooth通信モジュールなどが搭載されているマイコンボードです。使用例やドキュメントがwebに豊富にあることや、Arudino開発環境を使えるので自分のような初心者には有り難かったです。

ロードセル

重りセンサー 。 50kg まで測れるものを4つ繋げて 200kgまで測れるようにしました。

ロードセル用ADコンバータモジュール基盤 (HX711使用)

ロードセルから送られるアナログ信号をデジタル信号に変換してくれる基盤です。HX711とはロードセルの電気信号を読み取るのに適したアンプです。

4ビット双方向ロジックレベル変換モジュール

ESP32とロードセル用ADコンバータは稼働電圧が違うので、その2つのモジュールの橋渡しをしてくれます。 ちなみに、ESP32は 3.3v で ロードセル用ADコンバータは5v で動きます。

いらない体重計

ロードセルと基板を埋め込むための枠として使用しました。元から入っていた基盤とロードセルは規格が不明だったので破棄しました。

2.3 簡単な回路図

これらを組み合わせた結果、以下のような回路が完成しました。

図を見ていただくと、ロードセルが ADコンバータ(HX711と記載) につながっていて、ADコンバータがロジックレベル変換モジュールを介してESP32と繋がっているのがわかるかと思います。

f:id:Yoshiki-Iwasa222:20210924134311p:plain
作成したデバイスの回路図

実際の写真は下です。

f:id:Yoshiki-Iwasa222:20210924134625j:plain
実際に組んだ回路 (枠は体重計)

3. デバイスの制御プログラムの作成

開発環境

・MacBook Air 2019モデル Big Sur 11.2.3

・Ardino IDE 1.8.16

制御プログラムの簡単な説明をする前にいくつかBluetooth通信の専門用語を簡単に説明させてください。「もう知ってるよ」という方は読み飛ばして大丈夫です。

3.1 いくつかの専門用語

BLE (Bluetooth Low Energy)

BLE(Bluetooth Low Energy) とは、Bluetooth 4.0から搭載された低消費電力機能です。Bluetooth 1.0 ~ 3.0(Bluetooth Classicと呼ばれる)まではデータ通信の高速化が進められ、4.0 以降では省エネ機能が搭載された新たな規格となりました。BLEの電力消費量は、Bluetooth Classicに比べ10~30分の1程度まで減らすことができるので、ウェアラブルデバイスの基礎技術としてBLEは急速に普及しました。本記事で「Bluetooth」と表現するときはBLEを意味しています。

Central (セントラル)

Bluetooth通信における親機のことです。今回の場合、スマホがセントラルとなります。

Peripheral (ペリフェラル)

Bluetooth通信における子機のことです。今回の場合、ESP32がペリフェラルとなります。

接続待機時にペリフェラルから発信されるブロードキャスト通信のことです。アドバタイズで送られる情報の中にはデバイス名や扱うサービス情報が含まれています。これをセントラルがスキャンして接続要求を送信することでBluetooth通信が始まります。

Service (サービス)

Bluetooth通信によって提供される機能を意味します。ワイヤレスイヤホンなら「音楽の送信」・「音楽の再生・停止」などのサービスを持つことになります。

Characteristic (キャラクタリスティック)

Bluetooth通信でやりとりする実態です。キャラスタリスティックに書き込み・読み込みをすることでデータのやり取りをします。

3.3 ロードセルからの情報を取得

まずはロードセルから送信される荷重情報をESP32で取得する必要があります。 この時使用するライブラリはこちら

github.com

ではスケッチを書いていきましょう。 スケッチとは Ardino IDEでプログラムを記述するためのファイルのことです。ESP32の起動時に呼ばれるvoid setup()に各種初期化設定を、その後 void loop() でデータの取得など繰り返し行いたい処理を記述していきます。

初期化設定

#include "HX711.h"

// ESP32のデータ入力ピン
const int LOADCELL_DOUT_PIN = 32;

// クロックの出力ピン
const int LOADCELL_SCK_PIN = 33;

// 送られてくるデータを補正するための値
const long SCALE = -26;

HX711 scale;

void setup() {
  Serial.begin(57600);

  // HX711との通信開始
  scale.begin(LOADCELL_DOUT_PIN, LOADCELL_SCK_PIN);

  //  補正を適用
  scale.set_scale(SCALE);

 // この段階での重量を0とする
  scale.tare();
}

HX711からのデータを扱うためのインスタンスscaleを宣言して、そのインスタンスを介して初期化設定をします。

これで初期化は完了したので、データが正しく取得できているか5kgの荷重をして見てみましょう。

データの取得

#include "HX711.h"

// ESP32のデータ入力ピン
const int LOADCELL_DOUT_PIN = 32;

// クロックの出力ピン
const int LOADCELL_SCK_PIN = 33;

// HX711 を制御するためのインスタンス
HX711 scale;

void setup() {
  Serial.begin(57600);
  scale.begin(LOADCELL_DOUT_PIN, LOADCELL_SCK_PIN); 

}

void loop() {
  
 // HX711とESP32の間で通信準備が完了したら実行
  if (scale.is_ready()) {

    // HX711から受け取る値の10回平均を返す
    int reading = scale.get_units(10);
    Serial.print("value is ");
    Serial.println(reading);
   } else {
    Serial.println("Scal is not ready");
  }

  // 100ミリ秒毎にloopが実行される
  delay(100);
}

出力結果

value 5196
value 5286
value 5285
value 5284
value 5282

g 単位で取得

はい。ちゃんと値が取れていることがわかりました。100g単位で誤差がありますが今回はそれほど精度は必要ないので許容します。

では、次はこれをBluetooth経由でセントラルに送信するパートです。

3.4 Bluetoothで情報送信

今回使用したライブラリはこちらです。

github.com

Ardion IDEにライブラリをインストールした後、BLE_server というスケッチ例を使用すると初期設定のコードの大半は自動で作ることができます。 自動で生成されたコードを少し修正したものが以下です。

初期化設定

#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEServer.h>

#define SERVICE_UUID        "4fafc201-1fb5-459e-8fcc-c5c9c331914b" 
#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8"

void setup() {
  Serial.begin(57600);

  // デバイスの名前を決める
  BLEDevice::init("Street Workout");

  // BLEサーバを生成
  BLEServer *pServer = BLEDevice::createServer();

  // サービスのUUIDを指定してサービス生成
  BLEService *pService = pServer->createService(SERVICE_UUID);

  // UUID を指定して Read と Write ができるキャラクタリスティックを生成
  BLECharacteristic *pCharacteristic = pService->createCharacteristic(
                                         CHARACTERISTIC_UUID,
                                         BLECharacteristic::PROPERTY_READ |
                                         BLECharacteristic::PROPERTY_WRITE
                                       );

  // キャラクタリスティックに関するコールバック
  pCharacteristic->setCallbacks(new MyCallback());

   // BLEサーバー起動
  pService->start();

  // Bluetooth接続に関するコールバック
  pServer->setCallbacks(new MyServerCallbacks());

  //アドバタイジングのためのオブジェクトを生成
  BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();

  // アドバタイジング情報に入れるサービスを指定
  pAdvertising->addServiceUUID(SERVICE_UUID);
  pAdvertising->setScanResponse(true); 
  pAdvertising->setMinPreferred(0x06); 
  pAdvertising->setMinPreferred(0x12);

  // アドバタイジング開始
  BLEDevice::startAdvertising(); 
}

ハンドラの設定

注目すべき点は、 pCharacteristic->setCallbacks(new MyCallback()); pServer->setCallbacks(new MyServerCallbacks()); です。 それぞれにコールバックハンドラを指定することによって、セントラルからのキャラクタリスティックの読み込み・書き込み要求や、接続・接続終了時にハンドラに記載したコールバック関数を実行させることができます。

実際のハンドラは以下です。

class MyCallback: public BLECharacteristicCallbacks {

     // 値の読み込み要求に対して起動する関数
     // ロードセルの値を送信する
     void onRead(BLECharacteristic* pCharacteristic) {
      if (scale.is_ready()) {
        int reading = scale.get_units(10);
        std::ostringstream os;
        os << reading;

        // キャラクタリスティックに値をセットおよび送信
        pCharacteristic->setValue(os.str().c_str());
    }
  }
};

class MyServerCallbacks: public BLEServerCallbacks {
  
  // 接続確立時に呼ばれる
  // スケールを初期化
  void onConnect(BLEServer* pServer) {
    scale.set_scale(SCALE);
    scale.tare();
  }

  // 接続切断時に呼ばれる
  // アドバタイズを送信して接続待機状態にする
  void onDisconnect(BLEServer* pServer) {
   
    // アドバタイジングの終了
    BLEDevice::stopAdvertising(); 
   
   // アドバタイジングの開始
    BLEDevice::startAdvertising(); 
  }
};

以上であらゆるセントラルとデバイスの通信が可能になりました。

全体像

ここまで説明したコードを合わせると、スケッチの全体像は以下のようになります。

#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEServer.h>
#include "HX711.h"
#include <iostream>
#include <sstream>

const int LOADCELL_DOUT_PIN = 32;
const int LOADCELL_SCK_PIN = 33;

const long SCALE = -26;

HX711 scale;

#define SERVICE_UUID        "0e2a7cf5-2554-4e3d-8350-84f0d4fca0b6"
#define CHARACTERISTIC_UUID "dd812de9-e514-416a-97a5-d30ad6087a9f"

class MyCallback: public BLECharacteristicCallbacks {
     void onRead(BLECharacteristic* pCharacteristic) {
      if (scale.is_ready()) {
        int reading = scale.get_units(10);
        std::ostringstream os;
        os << reading;
        pCharacteristic->setValue(os.str().c_str());
    }
  }
};

class MyServerCallbacks: public BLEServerCallbacks {
  void onConnect(BLEServer* pServer) {
    scale.set_scale(SCALE);
    scale.tare();
  }
  void onDisconnect(BLEServer* pServer) {
    BLEDevice::stopAdvertising();
    BLEDevice::startAdvertising();
  }
};

void setup() {
  Serial.begin(57600);
  scale.begin(LOADCELL_DOUT_PIN, LOADCELL_SCK_PIN);
  scale.set_scale(SCALE);
  scale.tare();

  BLEDevice::init("Street Workout");
  BLEServer *pServer = BLEDevice::createServer();
  BLEService *pService = pServer->createService(SERVICE_UUID);
  BLECharacteristic *pCharacteristic = pService->createCharacteristic(
                                         CHARACTERISTIC_UUID,
                                         BLECharacteristic::PROPERTY_READ |
                                         BLECharacteristic::PROPERTY_WRITE
                                       );

  pCharacteristic->setCallbacks(new MyCallback());
  pService->start();
  pServer->setCallbacks(new MyServerCallbacks());
  BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();
  pAdvertising->addServiceUUID(SERVICE_UUID);
  pAdvertising->setScanResponse(true);
  pAdvertising->setMinPreferred(0x06);
  pAdvertising->setMinPreferred(0x12);
  BLEDevice::startAdvertising();
}

void loop() {

  delay(100);
}

以上でセントラルからの読み込み要求があるたびロードセルの値を取得して返す制御プログラムが完成しました。

これであらゆるセントラルと通信できます。

今回、iOSアプリ側の説明は省きましたが、実際には Core Bluetooth というライブラリを使用してアプリ側の実装をしました。興味のある方はぜひ見てみてください。

developer.apple.com

まとめ

今回は簡単にですが、個人的に開発したデバイスとその制御プログラムについて紹介させていただきました。 実は私にとって電子制作は全くの初めてで手探りでの開発だったのですが、自分が欲しいものを作る過程の試行錯誤はとても楽しかったです。仕事に直結する開発ではないのにこのデバイスの開発中はなぜか仕事のモチベーションも上がるという嬉しい現象も起こりました。やっぱり自分が欲しいものを作るってとても大切ですね。 この記事を見て自分も何か作ってみようと思ってくれる方が一人でもいたら嬉しいです。最後まで読んでいただきありがとうございました。

We are hiring!

エムスリーでは好奇心旺盛で技術が好きなエンジニアを絶賛募集中です。少しでも興味を持っていだたけら以下のリンクよりカジュアル面談にぜひご応募ください。

jobs.m3.com