Skip to content

ojimpo/mokkori-iq

Repository files navigation

mokkori-iq

水着の股間に仕込む DIY スイムトラッカー。自由形のラップ・距離・ストローク率を記録し、 泳ぎながら 100m ごとに振動で知らせ、上がったらスマホ経由で Strava に上げる、ための小型デバイスを自作するプロジェクト。 デバイスは股間(仙骨付近)に装着する。


なぜ作るか

日本の市民プールの多くは Garmin 等のスマートウォッチを着けて泳ぐのを禁止している。 だが泳いだログ(何本泳いだか・距離・ペース)は取りたいし、Strava にも上げたい。

そこで水着に仕込める小さなデバイスを自作する。手首が使えないので股間に着ける。 水着のインナーに収まる小型 IMU(Seeed XIAO nRF52840 Sense, 6軸)なら、見た目に出さずに泳げる。 ブログの代わりに、コードと README で発信するスタイルで進めている。


完成形(目指すシステム)

   泳ぐ                  100m ごとに              上がる            スマホで
 ┌────────┐  記録   ┌──────────────┐  振動  ┌───────┐  BLE  ┌──────────┐  →  Strava
 │ 股間IMU │ ──────▶ │ オンデバイス │ ─────▶ │ 触覚  │ ────▶ │ アプリ   │
 │ 6軸/52Hz│         │ 壁検出・集計 │  通知  │ で把握│ 吸出  │ ログ閲覧 │
 └────────┘         └──────────────┘        └───────┘       └──────────┘

ひとつの泳ぎセッションがこう流れるのが完成形:

  1. ロガー — 股間の IMU が自由形のラップ・距離・ストローク率(SPM)をオンデバイスで記録する。 スマホもスマートウォッチも水に持ち込まず、デバイスひとつで完結する。
  2. リアルタイム触覚通知 — 泳ぎながら 100m ごとに股間の振動モーターで知らせる。 壁で足をついた瞬間に振動が届くので、休むか続けるかをその場で判断できる。
  3. 水上で吸い出し — 上がったら BLE でスマホへ記録を送る (水中は 2.4GHz が吸収されるので BLE は水上専用)。
  4. スマホアプリ → Strava — ラップログを閲覧し、Strava にアップロードする。

ハードは水着に仕込みやすい防水筐体にパッケージングする。


仕組み(壁検出 = 重力オリエンテーション)

距離とラップの肝は壁(ターン)をどう数えるか。このデバイスは重力ベクトルの z 成分で壁を取る。

この泳者はクイックターンをせず、毎回プールの底に足をついて立ってから壁を蹴る(オープンターン)。 立つと股間デバイスの体軸が水平(伏せ泳ぎ)から垂直に変わり、重力 z が 泳ぎ -7.5 ↔ 立位 +9.5 m/s² と振れる。 この振れが、最も明確で曖昧さの少ない壁シグネチャになる。

信号 意味
gz(acc_z の causal EMA)< -2 伏せて泳いでいる
gz > +5(ピーク) 直立=壁で足をついて立っている=壁1枚
立位 7〜17秒 + トントン 100m 境界(数秒静止して股間を3タップ)
立位 90秒超 ハーフ間のベンチ長休憩

立位エピソードの継続時間で「100m 途中のターン(約3秒)」と「100m 境界(約7〜17秒)」が分離できる。 トントンは境界で股間を3回タップする自前のマーカーで、立位を条件にすると jerk のスパイクとして拾える。

この検出は絶対重力しきい値だけで動き、適応閾値も全セッション較正も要らない。 O(1) メモリ・先読みなし・約20行の状態機械なので、そのまま MCU で因果実行でき、リアルタイム振動通知の土台になる。

派生して取れるもの:

  • ストローク率(SPM) — 股間は体ロールのリズムが乗る。ジャイロのロール軸のスペクトルピークで約30 cyc/min。
  • 保守バイアスの集計 — 距離は過小なら可・過大は不可、ペースは遅めなら可・速めは不可(記録を盛らない方針)。 レングス数は min(壁カウント, ペース推定) で偽分割の過大を抑えつつ、検証済みの綺麗な 100m は保持する。

analysis/swim_report.py に吸い出した CSV を渡すと、これらが一発で出る:

📅 2026-06-21  プールスイム(自由形・25mプール)
  距離         1150 m   (控えめ。実際はこれ以上)
  ペース       約1:57 /100m   (遅め寄りの安全値)
  泳ぎ時間     22:20(moving time)
  ストローク率  約30 cyc/min(≒60 strokes/min)

ハードウェア

  • MCU: Seeed XIAO nRF52840 Sense(Cortex-M4F, 6軸 IMU = LSM6DS3TR-C, 2MB QSPI フラッシュ内蔵)
  • 電源: LiPo + インラインのスライドスイッチ
  • ファーム(firmware/flash_logger: VBUS にライブ追従。USB を抜く=記録 / 挿す=コンソール。 「短く泳ぐ → 更衣室で挿して吸い出す → また泳ぐ」が成立する。52Hz で約52分記録できる
  • 吸い出し(tools/: pyserial でフラッシュを CSV ダンプ。更衣室では Ubuntu タブレットで運用 (flash_gui.py の大ボタン: 吸い出して保存 / INFO / ERASE)

ビルド/書込手順や落とし穴(IMU 電源レール、QSPI の明示デバイス指定、データ対応ケーブル必須 等)は CLAUDE.md に集約。


いまどこまでできているか

  • ✅ 実機を股間装着で組み上げ、初のプール採取に成功(46分・1セッション)
  • ✅ gz 壁検出・距離/ペース/ラップ・ストローク率・保守集計の一発レポート(analysis/swim_report.py
  • ✅ リアルタイム振動の因果シミュレーションで実現可能性を確認(analysis/realtime_sim.py
  • ⏳ 振動モーター実装(モーター入手待ち)
  • ⏳ BLE オフロード / スマホアプリ / Strava 連携
  • ⏳ 防水筐体へのパッケージング

技術的な所見・試した検出案と不採用の経緯は analysis/ のドキュメント群に残している。


リポジトリ構成

mokkori-iq/
├── CLAUDE.md                     # 開発ノート・運用手順・落とし穴の集約(実質の作業ログ)
├── config/default.json           # 検出器パラメータ(閾値・窓長・フィルタ係数)
├── src/                          # コアモジュール(dataio / preprocessing / detector / lap_logger / evaluate)
├── firmware/
│   ├── imu_bringup/              # 104Hz で6軸 CSV を USB 出力(ブリングアップ)
│   └── flash_logger/             # 実データ採取用。VBUS追従で記録/コンソール切替、QSPIに追記
├── tools/                        # ホスト側(serial_capture / flash_dump / flash_gui)
├── analysis/
│   ├── swim_report.py            # 取込CSV→距離/ペース/ラップ/SPM/Strava を一発出力(保守バイアス内蔵)
│   ├── turn_stand.py             # gz オリエンテーション壁検出(detect_walls)
│   ├── stroke_rate.py            # ストローク率(SPM)
│   ├── realtime_sim.py           # リアルタイム振動の因果シミュレーション
│   ├── feasibility_haptic_feedback.md # 振動フィードバックの実現可能性検討
│   ├── findings_2026-06-21_*.md / approaches_log_2026-06-21.md # 初採取の所見・検出案の経緯
│   └── findings.md / tune*.py / fig_*.png  # データ探索・チューニング
├── data/
│   ├── brunner/                  # 公開データセット(git clone, .gitignore)
│   └── swim/                     # 実機で採取したセッション CSV + セッション記録(notes)
├── results/                      # 評価結果 CSV
└── requirements.txt

セットアップ・実行

# 環境構築
python3 -m venv .venv && .venv/bin/pip install -r requirements.txt

# 採取済み CSV からレポートを出す
.venv/bin/python analysis/swim_report.py data/swim/<session>.csv --date 2026-06-21
.venv/bin/python analysis/turn_stand.py  data/swim/<session>.csv   # gz壁検出の可視化
.venv/bin/python analysis/stroke_rate.py data/swim/<session>.csv   # ストローク率の可視化

実機ファームのビルド/書込(arduino-cli + Seeed nRF52 コア)は CLAUDE.md を参照。


参考文献

  • Brunner et al., "Swimming Style Recognition and Lane Counting Using a Smartwatch", ISWC 2019
    • 開発初期に、この公開データ(手首装着・30Hz)で閾値ベースのターン検知を試作した(src/, analysis/findings.md)。
  • Delhaye et al., "Swimming Stroke and Turn Detection Using a Single Sacral-Mounted IMU", Sensors 2022
    • 仙骨装着の単一IMUでの先行研究。前処理パイプライン(2次バターワースLP)を参考にした。

About

DIY swim tracker worn in the swimsuit: freestyle lap, distance and stroke-rate logging from a single crotch-mounted IMU, with 100m haptic pacing and Strava sync

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors