前回の記事(第1話:ハードウェア選定編)では、高温サウナに耐え、Zepp OSでJavaScript開発ができる「Amazfit T-Rex 3」を入手しました。
今回は、サウナウォッチの核となる「サウナ・水風呂・外気浴の全自動検知アルゴリズム」の泥臭い開発記録をお届けします。
目指したのは、「サウナに入る最初のスタートボタンと、最後の終了ボタン以外は一切ボタンを押さない」という究極のハンズフリー体験です。しかし、いざ開発を始めて実戦投入してみると、現場のサウナ環境は想像を絶する罠だらけでした……。
失敗談①:【体表温度センサーの罠】温度はすぐに上がらないし、下がらない!
開発当初、誰もが真っ先に思いつくアルゴリズムはこうでしょう。
「サウナ室は90℃〜100℃あるんだから、ウォッチの温度センサーが上がったらサウナ。水風呂は16℃前後なんだから、温度が急激に下がったら水風呂にすれば簡単勝負でしょ?」
意気揚々とウォッチをつけてサウナ室に入りました。しかし、画面のログを見て愕然とします。
「……ぜんぜん温度が上がらない!?」
人間には驚くべき体温調節機能があります。サウナ室に入ると全身から汗が噴き出し、気化熱で皮膚の表面温度を一定に保とうとするのです。そのため、部屋が95℃あっても、腕の皮膚表面につけているセンサー温度は 34℃〜36℃ 付近をノロノロと這うだけ。室温と同じように一気に急上昇などしてくれません。
さらに最悪だったのが水風呂から出た後です。
水風呂で冷やされた皮膚表面は血管がキュッと収縮しているため、水風呂を出て外気浴スペースの椅子に座っても、体表温度はなかなか上がってきません。じわじわとしか戻らないため、「水風呂を出てすでに外気浴でくつろいでいるのに、ウォッチはまだ水風呂中だと認識している」という完全なタイムラグ地獄に陥りました。
結論:体表温度の数値だけでフェーズを即座に切り替えるのは絶対に無理!
失敗談②:【固定心拍数の罠】1回目と2回目でスタート数値が違いすぎる!
「それなら心拍数だ!」と次に考えました。サウナに入れば心拍数が上がり、外気浴では落ち着きます。
「よし、心拍数100bpm以上になったらサウナ、90bpm以下になったら外気浴、という絶対値のしきい値で判定しよう!」
これも現場で大破綻しました。
サウナを愛する皆さんなら体感としてよく分かると思いますが、サウナの「1セット目」と「2セット目・3セット目」では、身体のベース状態がまったく違うのです。
- 1セット目に入る前: 安静状態なので、スタート心拍数は 65〜70 bpm 程度。
- 2セット目に入る前: 1回目の水風呂・外気浴を経て深部体温が温まり、血流が活発になっているため、リラックスして座っていても 85〜95 bpm くらいある。
もし「95bpm以上=サウナ」という固定数値を設定するとどうなるか?
1セット目はサウナに入って数分経ってもなかなか95に届かず検知が遅れ、逆に2セット目は水風呂から上がって外気浴の椅子で休んでいる最中に90bpmを超えて「サウナ室に入った」と誤検知してしまうのです。
人間の身体のリズムはセットを重ねるごとに変化します。固定された心拍数値で判定しようとするのは根本的な間違いでした。
大ブレイクスルー:サウナーの行動生態学「10歩移動 + 2秒停止」
温度もダメ、固定心拍数もダメ……。
「じゃあ一体、何をトリガーにすればサウナ・水風呂・外気浴を切り替えられるのか?」
ととのい椅子に深く腰掛け、ぼーっとサウナ室と水風呂を行き交う人々を観察していた時、突然頭の中に電撃が走りました。
「サウナのフェーズが変わる瞬間、人は必ず『歩いて移動して、座る(または入水して止まる)』じゃないか!」
サウナ施設での行動パターンは、驚くほど規律正しく固定されています。
- サウナ室へ: 洗い場やととのい椅子から立ち上がり、サウナ室までトコトコ歩く(10歩以上)。サウナ室のひな壇に座って腰を落ち着ける(静止)。
- 水風呂へ: サウナ室から出て、汗を流して水風呂へ歩く(10歩以上)。水風呂にチャポンと浸かって静止する(静止)。
- 外気浴へ: 水風呂から上がり、体を拭きながらととのい椅子へ歩く(10歩以上)。椅子に深く腰掛けて脱力する(静止)。
そう、フェーズの転換点には必ず「ある程度の歩行移動」と「その直後の静止(着席)」が100%セットで発生していたのです!
【10歩以上歩いた後、2秒以上静止したら、フェーズ判定を実行する!】
状態ラッチ(維持)の構築:誤検知をゼロにする決定打
この「10歩移動+2秒停止」トリガーに、もう一つの強力な鉄則を組み合わせました。
それが【状態ラッチ(状態の完全維持)】です。
サウナウォッチの誤動作で一番イライラするのは、サウナ室の中でじっと座っている最中に、心拍のブレや温度の揺らぎで勝手に「外気浴」や「水風呂」に切り替わってしまうことです。
そこでアルゴリズムを次のように厳格化しました:
- 一度「サウナ中」になったら、次に「10歩以上歩いて停止」するまで、何があってもサウナ状態を絶対に維持(ラッチ)する。
- 一度「水風呂中」になったら、次に「10歩以上歩いて停止」するまで、絶対に水風呂状態を維持する。
- 10歩移動+2秒停止を検知した瞬間だけ、直前の心拍急降下(水風呂突入)や急上昇、温度変化などの相対トレンドを評価して次のフェーズへ移行する。
このロジックを組み込んだ瞬間、それまで悩まされていた誤検知が完全にゼロになりました。
サウナ室でどれだけじっとしていても、水風呂でどれだけ身動きを取らなくても、時計は正確にそのフェーズを刻み続けます。
実際の検知ロジック(Zepp OS / JavaScript)
実際のコード(sauna_detector.js)の核心部分は、このようなステートマシンとして実装しています。
// 歩行移動トラッカー(10歩歩いて2秒静止した瞬間を着席イベントとする)
const deltaStep = Math.max(0, stepCount - this.lastStepCount);
this.lastStepCount = stepCount;
if (deltaStep > 0) {
this.walkingSteps += deltaStep;
this.stopDurationSec = 0;
if (this.walkingSteps >= 10) {
this.isMoving = true; // 10歩以上歩いた!移動フラグON
}
} else {
if (this.isMoving) {
this.stopDurationSec++;
// 10歩以上歩いた後に2秒以上静止した(座った / 水に浸かった)!
if (this.stopDurationSec >= 2) {
moveCompleted = true; // ★判定トリガー発火!
this.isMoving = false;
this.walkingSteps = 0;
this.stopDurationSec = 0;
}
}
}
// ------------------------------------------
// サウナ室滞在中のステート判定
// ------------------------------------------
if (this.currentPhase === PHASES.SAUNA) {
// ① 10歩以上歩いて停止した時だけ、次の状態を判定
if (moveCompleted) {
if (hrDiff5s <= -8 || recentTemp <= 28.0) {
// 水風呂に浸かって冷やされた ➔ 水風呂へ移行
this.setPhase(PHASES.COLD_BATH);
return;
} else {
// 水風呂をスキップしてととのい椅子へ座った ➔ 外気浴へ移行
this.setPhase(PHASES.REST);
return;
}
}
// ★それ以外の時間は、何があっても「サウナ中」を完全維持(ラッチ)!
return;
}
余計な推測を排除し、「人間のリアルな身体行動(歩行と着席)」にセンサー判定を同期させたことで、極めてシンプルかつ頑丈な検知エンジンができあがりました。
次回:UI設計と実戦投入テスト!
頭の中で描いたアルゴリズムが、見事に現場の課題をクリアしました。
しかし、サウナ室の薄暗い照明や、水滴でタッチ操作が効かない過酷な環境で、どうやって情報を表示し、危険な心拍アラートを届けるのか?
次回は、「視認性抜群のサウナ特化UI設計」と、実際にサウナ施設で3セット通して検証した実走レビューをお届けします。お楽しみに!













