水着の股間に仕込む DIY スイムトラッカー。自由形のラップ・距離・ストローク率を記録し、 泳ぎながら 100m ごとに振動で知らせ、上がったらスマホ経由で Strava に上げる、ための小型デバイスを自作するプロジェクト。 デバイスは股間(仙骨付近)に装着する。
日本の市民プールの多くは Garmin 等のスマートウォッチを着けて泳ぐのを禁止している。 だが泳いだログ(何本泳いだか・距離・ペース)は取りたいし、Strava にも上げたい。
そこで水着に仕込める小さなデバイスを自作する。手首が使えないので股間に着ける。 水着のインナーに収まる小型 IMU(Seeed XIAO nRF52840 Sense, 6軸)なら、見た目に出さずに泳げる。 ブログの代わりに、コードと README で発信するスタイルで進めている。
泳ぐ 100m ごとに 上がる スマホで
┌────────┐ 記録 ┌──────────────┐ 振動 ┌───────┐ BLE ┌──────────┐ → Strava
│ 股間IMU │ ──────▶ │ オンデバイス │ ─────▶ │ 触覚 │ ────▶ │ アプリ │
│ 6軸/52Hz│ │ 壁検出・集計 │ 通知 │ で把握│ 吸出 │ ログ閲覧 │
└────────┘ └──────────────┘ └───────┘ └──────────┘
ひとつの泳ぎセッションがこう流れるのが完成形:
- ロガー — 股間の IMU が自由形のラップ・距離・ストローク率(SPM)をオンデバイスで記録する。 スマホもスマートウォッチも水に持ち込まず、デバイスひとつで完結する。
- リアルタイム触覚通知 — 泳ぎながら 100m ごとに股間の振動モーターで知らせる。 壁で足をついた瞬間に振動が届くので、休むか続けるかをその場で判断できる。
- 水上で吸い出し — 上がったら BLE でスマホへ記録を送る (水中は 2.4GHz が吸収されるので BLE は水上専用)。
- スマホアプリ → 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)。
- 開発初期に、この公開データ(手首装着・30Hz)で閾値ベースのターン検知を試作した(
- Delhaye et al., "Swimming Stroke and Turn Detection Using a Single Sacral-Mounted IMU", Sensors 2022
- 仙骨装着の単一IMUでの先行研究。前処理パイプライン(2次バターワースLP)を参考にした。