車椅子IoT - GPTと一緒に車椅子センサーを作ってみた


骨折して入院して車椅子になりました。関連した困りごとを実装してみました。

IoTLTでの発表内容補足的な感じです。

> https://speakerdeck.com/n0bisuke2/gu-zhe-toru-yuan-toiot-number-iotlt


## 骨折しました。

骨折して入院して車椅子になりました。

## 車椅子以外と疲れる問題

入院中は車椅子での移動だったのですが、車椅子は意外と疲れます。

> 入院で運動量がガクッと落ちたというiPhoneの計測
> ![スクリーンショット 2024-07-17 23.46.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35387/95f056ad-cb27-8a4d-e4f7-cddeb98f1b1c.png "スクリーンショット 2024-07-17 23.46.09.png")

疲れるんですけど、ヘルスケアアプリでの活動量はだいぶ低いという結果に。

**車椅子でどれくらい移動しているかを計測したい**なと思いました。

松葉杖よりは疲れないです。今のところ松葉杖が最上級。

## 車椅子にAtomS3を取り付け

たまたま持っていたAtomS3を取り付けました。

> ![スクリーンショット 2024-07-17 23.42.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35387/23447ffd-6231-1178-462c-1d4b977e9fee.png "スクリーンショット 2024-07-17 23.42.59.png")


## 実際のコード

M5Unifiedを使っています。

GPT-4oに聞きながらやってみましたが、M5unifideでの書き方もバッチリでした。

```cpp
#include <M5Unified.h>

float gyroZOffset = 0.0;

void calibrateGyro() {
  const int numReadings = 1000;
  float sum = 0.0;

  for (int i = 0; i < numReadings; i++) {
    float gyroX, gyroY, gyroZ;
    M5.Imu.getGyro(&gyroX, &gyroY, &gyroZ);
    sum += gyroZ;
    delay(1);
  }

  gyroZOffset = sum / numReadings;
}

void setup(void) {
  auto cfg = M5.config();
  M5.begin(cfg);

  M5.Display.setTextSize(2);  // 文字サイズを1.5倍に設定

  const char* name;
  switch (M5.getBoard()) {
    case m5::board_t::board_M5AtomS3:
      name = "ATOM S3";
      break;
    default:
      name = "Who am I ?";
      break;
  }
  M5.Display.startWrite();
  M5.Display.print("Core:");
  M5.Display.println(name);
  Serial.printf("core:%s\n", name);

  switch (M5.Imu.getType()) {
    case m5::imu_t::imu_mpu6886:
      name = "MPU6886";
      break;
    default:
      name = "none";
      break;
  }
  M5.Display.print("IMU:");
  M5.Display.println(name);
  M5.Display.endWrite();
  Serial.printf("imu:%s\n", name);

  // Calibrate the gyro
  calibrateGyro();
}

float calculateRotationAngle(float gyroRate, float deltaTime) {
  return gyroRate * deltaTime;
}

void loop(void) {
  delay(1);
  int h = M5.Display.height() / 8;

  M5.update();

  static constexpr const int colors[] = { TFT_WHITE, TFT_CYAN, TFT_RED, TFT_YELLOW, TFT_BLUE, TFT_GREEN };
  static constexpr const char* const names[] = { "none", "wasHold", "wasClicked", "wasPressed", "wasReleased", "wasDeciedCount" };

  M5.Display.startWrite();

  auto state = M5.BtnA.wasHold()          ? 1
          : M5.BtnA.wasClicked()          ? 2
          : M5.BtnA.wasPressed()          ? 3
          : M5.BtnA.wasReleased()         ? 4
          : M5.BtnA.wasDeciedClickCount() ? 5
                                          : 0;
  if (state) {
    Serial.printf("BtnA:%s  count:%d\n", names[state], M5.BtnA.getClickCount());
    if (!M5.Display.displayBusy()) {
      M5.Display.fillRect(0, h * 3, h, h - 1, colors[state]);
      M5.Display.setCursor(0, h * 3);
      M5.Display.printf("%d", M5.BtnA.getClickCount());
    }
  }

  M5.Display.endWrite();

  static float accumulatedAngle = 0;
  static int rotationCount = 0;
  static int partialTurns = 0;

  if (M5.Imu.isEnabled()) {
    float gyroX, gyroY, gyroZ;
    M5.Imu.getGyro(&gyroX, &gyroY, &gyroZ);

    // Apply gyro offset and reverse the sign
    gyroZ = - (gyroZ - gyroZOffset);

    static unsigned long lastTime = 0;
    unsigned long currentTime = millis();
    float deltaTime = (currentTime - lastTime) / 1000.0; // delta time in seconds
    lastTime = currentTime;

    // Assuming the wheelchair rotates around the Z-axis (yaw)
    float rotationAngle = calculateRotationAngle(gyroZ, deltaTime);
    accumulatedAngle += rotationAngle;

    // Keep accumulatedAngle within 0 to 359 degrees
    if (accumulatedAngle >= 360.0) {
      rotationCount++;
      accumulatedAngle -= 360.0;
    } else if (accumulatedAngle < 0.0) {
      rotationCount--;
      accumulatedAngle += 360.0;
    }

    // Calculate partial turns within the range of 0 to 59
    partialTurns = static_cast<int>(accumulatedAngle / 6.0); // 1/60 turn = 6 degrees

    M5.Display.startWrite();
    M5.Display.setCursor(0, h * 5);
    M5.Display.printf("Rotations: %d", rotationCount);
    M5.Display.setCursor(0, h * 6);
    M5.Display.printf("Partial Turns: %d", partialTurns);
    M5.Display.endWrite();

    Serial.printf("Rotations: %d, Partial Turns: %d\n", rotationCount, partialTurns);
  }
  M5.Display.display();
}

```

## 結構良い感じに計測ができました

> ![スクリーンショット 2024-07-17 23.51.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35387/0674cc40-4757-c810-0c4a-f2680d672e16.png "スクリーンショット 2024-07-17 23.51.48.png")

## 今後

計測した値をスプレッドシートに記録するなどやりたいですが、病院で使えるWi-Fiがなかったのでそこは断念してました。

上手いやり方検討したい。。