はじめに
PixelWatchで心拍数をVRChatに送りたいなーと漠然と思っていたときに
ドンピシャな記事(Pixel Watchで取得した心拍数をリアルタイムで配信に表示する方法)が掲載されたのでやってみた
今回はHeartRateOnStream for OBS +αを利用して心拍数を持ち込みます
なお、ソースコード等はgithubで公開しています
準備
実際のコードだけ見たい人は次の章まで飛ばしてください
ここでは各環境やアプリの設定等を取り扱います
環境
本記事での開発環境は以下のとおりです
利用アプリ
- HeartRateOnStream for OBS
必須じゃないけどあると便利なもの(疎通確認用)
- OBS
- wscat
開発環境
- Kotlin 1.6.10
- Java11(corretto-11.0.14.1)
- Gradle
- javaOSC 0.8
- tyrus 2.1.1
InteliJを用いて進めていきます
疎通設定
HeartRateOnStream for OBSが通信できるようにPCとアプリ側それぞれ設定を行います
基本的には参考元記事と同じ作業をするのでそちらを参考にすると良いかも
アプリ側設定
まずgoogleplayからアプリをスマートフォンとPixelwatchにダウンロードします
ダウンロード後、スマートフォン側のアプリを開いて"Edit"を選択します
設定画面を開いたら、
"obs-websocket v5+"を選択後にIPアドレスの欄に接続するPCのIPアドレスを入力します
その他の欄はデフォルトのままにします
設定が完了したら、右上の"SAVE"で保存してこの画面を抜けます
PC側設定
おそらくファイアーウォールが原因でこのままでは接続できないので、設定を行います
windowsの検索窓に"ファイアーウォール"と打ち込むと"ファイアーウォールの状態の確認が出るのでこれをクリックします
開いたら左側にある詳細設定を選択します
選択後、新しいウインドウが開いたら左側の"受信の規則"→右側の"新しい規則"を選択します
規則の種類をポートに、TCPで特定のポートに"4455"を指定
後はそのまま次へを押し、最後の名前を自分がわかりすい名前にします
設定後、受信の規則一覧に設定した名前が出ればおkです
疎通確認
ここでは疎通確認にOBSを利用します
OBSを起動し、上メニューから"ツール"→"obs-websocket設定"を選択します
ここでは、元記事と違い"認証を有効にする"のチェックを外します
この状態で、スマートフォンアプリ側の"Connect"を押して接続されれば疎通確認はおkです
プロジェクト設定
InteliJにてKotlinプロジェクトを作成します
次にbuild.gradle.ktsのdependenciesに以下を追記します
implementation("com.illposed.osc:javaosc-core:0.8")
implementation("org.glassfish.tyrus:tyrus-server:2.1.1")
implementation("org.glassfish.tyrus:tyrus-container-grizzly-server:2.1.1")
プログラム
実装
websocket通信を行うサーバーエンドポイントを作成し、その上でOSC送信を行います
package endpoint
import com.illposed.osc.OSCMessage
import com.illposed.osc.transport.OSCPortOut
import jakarta.websocket.*
import jakarta.websocket.server.ServerEndpoint
import java.net.InetAddress
import java.net.InetSocketAddress
import java.util.concurrent.CopyOnWriteArraySet
@ServerEndpoint("/")
class ServerEndPoint {
private val sendPort = 9000
private val ip = InetAddress.getLoopbackAddress()
private val sender = OSCPortOut(InetSocketAddress(ip, sendPort))
private val address = "/avatar/parameters/HeartRateFloat01"
private val openResponse = "{\"d\":{\"obsWebSocketVersion\":\"5.0.1\",\"rpcVersion\":1},\"op\":0}"
private val handShakeRequest = "{\"d\":{\"eventSubscriptions\":0,\"rpcVersion\":1},\"op\":1}"
private val handShakeResponse = "{\"d\":{\"negotiatedRpcVersion\":1},\"op\":2}"
companion object {
private val sessions = CopyOnWriteArraySet<Session>()
}
@OnOpen
fun onOpen(session: Session){
session.basicRemote.sendText(openResponse)
sessions.add(session)
}
@OnMessage
fun onMessage(message: String, session: Session){
if(message == handShakeRequest){
session.basicRemote.sendText(handShakeResponse)
return
}
val heartRate = extractHeatRate(message) ?: return
val oscMessage = OSCMessage(address, mutableListOf((heartRate / 255.0f).coerceAtMost(1.0f)))
sender.send(oscMessage)
}
@OnClose
fun onClose(session: Session) {
sessions.remove(session)
}
@OnError
fun onError(session: Session, t: Throwable) {
}
private fun extractHeatRate(message: String): Int? {
val regex = Regex("\"text\":\"[0-9]{1,3}\"")
val param = regex.find(message)
val data = param?.value?.replace("\"", "")?.split(":") ?: return null
if (data.size != 2) return null
return data[1].toInt()
}
}
あとは、発火地点のMain.ktを実装します
import endpoint.ServerEndPoint
import org.glassfish.tyrus.server.Server
import java.lang.Thread.sleep
fun main(args: Array<String>) {
val hostname = "localhost"
val port = 4455
val server = Server(hostname, port, "/", mapOf(), ServerEndPoint::class.java)
server.start()
sleep(1000000)
server.start()
}
実行
PixelWatchとスマートフォンそれぞれでアプリを起動し、PixelWatch側で再生ボタンを押すと心拍数がスマートフォン側に表示されます
この状態で作成したツールを起動し、最後にスマートフォンからconnectを押すと接続されます
ログに心拍数が表示されたら成功です
また、VRC側ではOSCHeartRate_AvatarSampleというワールドに設置されているアバターで確認することができます
解説
解説という解説を行うところはないんですが、使用アプリケーション独自の仕様によるものだけ解説します
HeartRateOnStream for OBSでは、心拍数の送信を行う前にOBSバージョンの取得等の通信を行っているようです。
まず、最初の接続後にアプリ側はサーバからOBSバージョンの関する応答を待ちます
それが送られてきたら、次にアプリ側からイベントサブスクライブ要求を行います
最後にサーバ側から応答を返すことで接続処理が完了されます
これらの処理を行った後、心拍数の送信が行われるようです
そのため、サーバ側で一連の処理を模倣する必要があります
さいごに
HeartRateOnStream for OBSはPixelWatch以外にも様々なスマートウォッチに対応しているようで、普段使用しているスマートウォッチをそのまま心拍数取得ツールとして利用できそうです
夢が広がりますね!!
余談ですが、私は心拍数をしっぽの揺れに連携させてみました
なんか意外と好評みたい