機能 #185
完了機能 #162: OBS連携機能
【OBS連携】心拍数表示
エージェント ちゃん さんが23日前に追加. 22日前に更新.
説明
親: #162
概要¶
OBS オーバーレイに心拍数(HR)を表示する将来要望。配信者の心拍をオーバーレイに出し、緊張感を可視化する。
現状としての着手(#182 側)¶
- 今回の #182 overlay デザインで**心拍数表示用の予約枠(ブロック)**をレイアウト上に用意済み(既定非表示)。本チケットでデータ源と表示を有効化する。
検討事項(未着手)¶
- データ源: 心拍センサーの連携方法。例:Pulsoid / HypeRate の WebSocket/HTTP API、あるいは OS の BLE 心拍デバイス。beatoraja 本体が取得して /state.json に乗せるか、overlay.js が直接外部 API を叩くかを決める。
-
/state.json 拡張案:
heartRate: { bpm: 142, zone: "..." }等。overlay 側はheartRate.bpmを data-field で受ける予定。 - localhost 固定・低頻度更新の方針は #162 を踏襲。
マイルストーン¶
未定(将来)。今回は予約枠のみ。
エージェント ちゃん さんが23日前に更新
操作
#3
- ステータス を 未着手 から 実装中 に変更
- 進捗率 を 0 から 10 に変更
詳細設計(2026-06-13)/実装着手¶
ユーザー方針確認の結果:データ源=本体が直接BLE、取得経路=本体が取得し /state.json に乗せる、表示=現在BPM+過去数分グラフ。
技術調査の結論¶
- Windows 上の純Java で BLE 心拍計を読む実用手段は無い(OSS の純Java BLE は Linux/BlueZ 専用、Windows 対応は商用 IPWorks のみ)。
- 「本体が直接BLE(クラウド非経由)」を保つため、小さなネイティブ BLE ブリッジを同梱する方式を採用(ユーザー承認済み)。
アーキテクチャ¶
HW706 (BLE GATT 0x180D/0x2A37)
│ BLE notify
▼
hrm-bridge (Rust+btleplug, 単一バイナリ) ── stdout NDJSON {"bpm":142} ──► beatoraja本体
▲ heartRateEnabled 時に子プロセス起動 HeartRateService(子プロセス管理/stdout読取/再接続)
└ ObsState: 1秒サンプリングのリングバッファ+ゾーン算出
└ /state.json: heartRate{...}
▲ overlay 1秒ポーリング
overlay: #heartrate 値+#hrgraph 折れ線+ゾーン色
ネイティブブリッジ tools/hrm-bridge/(Rust + btleplug)¶
- btleplug(MIT/Apache, WinRT/CoreBluetooth/BlueZ をラップ)で 0x180D を広告する HRM をスキャン→接続→0x2A37 を購読。
- 0x2A37 の先頭フラグバイト bit0 で UINT8/UINT16(LE) を判別して bpm を抽出。
- stdout に1行1JSON(
{"bpm":142}/ 診断用{"event":"connected|disconnected|error",...})。切断時はバックオフ再接続、stdin EOF / SIGTERM で終了。 - デバイス選択:既定は最初に見つけた HRM。
--name/--addressフィルタ任意。 -
CI に Rust を入れず、既存
natives/*.dllと同様にプリビルドバイナリをnatives/にコミット(Windows 版は実機 Windows でcargo build --release)。tools/hrm-bridge/READMEにビルド手順。
本体 Java 側¶
- 設定追加(
Config/ConfigPersistenceDto):heartRateEnabled=false、heartRateMaxHr=190(ゾーン算出用)、heartRateDeviceFilter=""(任意)、heartRateBridgePath=""(空=OS/arch から natives/ 自動解決)。 - 新規
HeartRateService(stream/obs):ブリッジ子プロセスの起動/監視/再起動、stdout 読取→bpm 解析→ObsState.setHeartRate(bpm)。バイナリ不在時は警告ログのみで no-op(グレースフル)。 -
ObsState:心拍を Snapshot に保持。1秒ティックで最新 bpm をリングバッファ(既定180サンプル=3分)へ。10秒以上更新が無ければ stale→heartRate=null。ゾーンは maxHr に対する%で Z1..Z5。 -
/state.json拡張:"heartRate": null | { "bpm":142, "zone":"Z3", "zoneIndex":3, "min":120, "max":150, "sampleSec":1, "history":[...] }(history は古い→新しい順の bpm 整数列、x軸は sampleSec で暗黙)。 - 起動条件:
obsEnabled && heartRateEnabled。ObsController.start()/dispose()で起動/破棄。無効時ゼロ影響。 - 設定 UI:既存 OBS 設定と同じ
PlayConfigurationView(+FXML, i18n) に「心拍数表示」「最大心拍数」「デバイスフィルタ」を追加。
オーバーレイ(web)¶
- 予約枠
#heartrateの数値表示を有効化(heartRate.bpm既存バインド)。zoneIndexで色分け(CSS変数 Z1..Z5)。 - 新規
#hrgraph:heartRate.historyを依存ライブラリ無しの inline SVG/canvas で折れ線描画(差分時のみ再描画、min/max/現在値ラベル)。?blocks=トグル対応。テーマは既存 control deck HUD に統一。 - README のブロック説明を「予約枠」→ライブ機能に更新、
state.sample.jsonに heartRate サンプル追加。
テスト/検証¶
- Java 単体:heartRate の JSON 形(null/充填)、ゾーン境界、リングバッファ上限/stale、ブリッジ stdout 行解析(不正行は無視)。
xvfb-run -a ./gradlew test。 - ブリッジ:0x2A37 フラグ解析(UINT8/UINT16 LE)の Rust 単体テスト。
- オーバーレイ:
node --check+ DOM シムで history→グラフ点の契約検証(#182 と同方式)。 -
BLE 実機(HW706→ブリッジ→グラフ)は Windows ホスト+実機でのみ検証可能。コンテナでは Java compile/test・overlay 静的検査・ダミーブリッジ(
{"bpm":...}出力)での end-to-end を担保。
本コメントは詳細設計であり実装着手の記録。作業ブランチ
feature/185-heart-rate。
エージェント ちゃん さんが23日前に更新
操作
#4
実装・レビュー完了(ブランチ feature/185-heart-rate を push 済み)¶
コミット2本:
-
b33f75dbバックエンド+BLE ブリッジ(Codex 実装→Claude レビュー:ObsState の snapshot 更新を単一ロックで直列化し、非同期心拍スレッドと changeState スレッドの lost-update を修正済み) -
88e383f5オーバーレイ UI(AGY ブリッジ実装→Claude レビュー・仕上げ:vector-effect 修正・サンプル整合)
成果物¶
-
tools/hrm-bridge:Rust+btleplug の BLE ブリッジ(0x180D スキャン→接続→0x2A37 購読→stdout に{"bpm":N}、再接続/終了処理、フラグ解析の単体テスト) -
HeartRateService:ブリッジ子プロセスの起動/stdout読取/1秒サンプリング/バックオフ再起動、バイナリ不在時は no-op -
ObsState//state.json:heartRate{bpm,zone,zoneIndex,min,max,sampleSec,history[]}(null/stale 復帰・リングバッファ・ゾーン算出、Java 単体テストあり) - 設定 UI:
PlayConfigurationViewに心拍表示/最大心拍数/デバイスフィルタ(i18n 日英) - overlay:
#heartrateで現在BPM+MIN/MAX+ゾーン色(Z1寒色〜Z5暖色)+inline SVG 折れ線グラフ(差分再描画・null/空で例外なし)。README/state.sample.json 更新
検証¶
-
xvfb-run -a ./gradlew compileJava test→ BUILD SUCCESSFUL(並行性修正後に再検証) - overlay:
node --check・JSON parse・CSS ブレース整合・DOM シムの契約テスト(bpm/zone/polyline/null/単一点/空 history)全通過
残作業(ユーザー側)¶
-
ネイティブバイナリ未同梱:WinRT 依存のため Windows バイナリは実機 Windows でビルドが必要。
cd tools/hrm-bridge && cargo build --release→target/release/hrm-bridge.exeをnatives/hrm-bridge-win-x64.exeとしてコミット。それまでは HeartRateService は警告ログのみで no-op。 - BLE 実機動作確認(HW706→ブリッジ→overlay グラフ)は Windows ホストで実施
MR はユーザー承認後に作成予定。
エージェント ちゃん さんが22日前に更新
操作
#5
MR 作成¶
実装・レビュー・実機確認(CooSpo HW706 + Windows)完了につき MR を作成しました。
- MR: !27 https://gitlab.izu-san.com/games/beatoraja/-/merge_requests/27 (
feature/185-heart-rate→develop、assignee/reviewer = izu-san)
実装概要¶
本体が BLE 心拍計(標準 GATT 0x180D/0x2A37)にネイティブブリッジ(Rust+btleplug, natives/ に同梱)経由で直接接続し、/state.json の heartRate{bpm,zone,zoneIndex,min,max,sampleSec,history[]} に乗せて overlay が現在BPM+直近数分グラフを表示(クラウド非経由・obsEnabled=false 時ゼロ影響)。設定 UI(有効/最大心拍数/デバイスフィルタ)追加。
テスト¶
xvfb-run -a ./gradlew test → BUILD SUCCESSFUL、Rust フラグ解析単体テスト、overlay の DOM シム契約テスト、実機動作確認。
補足¶
- Windows バイナリは WinRT 依存のため実機 Windows でビルド・コミット済み。Linux/Mac 版は未同梱(将来)。
- 配色は overlay の他ブロックと同一テーマ色に統一。
MR 承認・マージ後に 完了 へ移行予定。
エージェント ちゃん さんが22日前に更新
操作
#6
- ステータス を 実装中 から 完了 に変更
- 進捗率 を 10 から 100 に変更
MR !27 承認・develop へマージ完了につき完了。
実装サマリ(OBS連携 心拍数表示)¶
beatoraja 本体が BLE 心拍計(標準 GATT Heart Rate Service 0x180D / Measurement 0x2A37、例:CooSpo HW706)に、同梱ネイティブブリッジ(Rust+btleplug)経由で直接接続し、/state.json の heartRate{bpm,zone,zoneIndex,min,max,sampleSec,history[]} に乗せて OBS オーバーレイが現在 BPM ⊕ 直近数分の推移グラフを表示。クラウド非経由、obsEnabled=false 時ゼロ影響。
- ブリッジ:
tools/hrm-bridge(スキャン→接続→0x2A37購読→フラグ解析→stdout NDJSON、再接続/終了処理)。Windows バイナリnatives/hrm-bridge-win-x64.exeを同梱。 - 本体:
HeartRateService(子プロセス管理/1秒サンプリング/バックオフ再起動/不在時 no-op)、ObsState拡張(リングバッファ3分・ゾーン算出・stale復帰・snapshot 単一ロック直列化)。 - 設定:心拍表示有効 / 最大心拍数 / デバイスフィルタ(PlayConfigurationView + i18n)。
- overlay:
#heartrateに BPM ⊕ MIN/MAX ⊕ SVG 折れ線グラフ。配色は他ブロックと同一テーマ色に統一。
テスト:xvfb-run -a ./gradlew test 成功、Rust フラグ解析単体テスト、overlay DOM シム契約テスト、実機(HW706 + Windows)動作確認済み。
今後の余地:Linux/Mac 版ブリッジバイナリの同梱、heartRate.zone/zoneIndex を用いたテーマ別のゾーン色分け(データは既に /state.json に存在)。