Add comprehensive detailed design documents
This commit is contained in:
602
src-python/docs/details/osc.md
Normal file
602
src-python/docs/details/osc.md
Normal file
@@ -0,0 +1,602 @@
|
||||
# osc.py - OSC通信・OSCQueryプロトコル管理
|
||||
|
||||
## 概要
|
||||
|
||||
VRChatとの高度なOSC(Open Sound Control)通信を管理する包括的なシステムです。基本的なOSCメッセージ送信に加え、OSCQueryプロトコルによる双方向通信、パラメータ監視、自動サービス発見機能を提供します。
|
||||
|
||||
## 主要機能
|
||||
|
||||
### OSC通信機能
|
||||
- VRChatチャットボックスへのメッセージ送信
|
||||
- タイピング状態の制御
|
||||
- パラメータ値の動的取得
|
||||
|
||||
### OSCQuery対応
|
||||
- 自動サービス発見・接続
|
||||
- リアルタイムパラメータ監視
|
||||
- 双方向エンドポイント公開
|
||||
|
||||
### 堅牢性機能
|
||||
- 防御的プログラミング設計
|
||||
- 欠損ライブラリの優雅な処理
|
||||
- 自動エラー復旧機構
|
||||
|
||||
## クラス構造
|
||||
|
||||
### OSCHandler クラス
|
||||
|
||||
```python
|
||||
class OSCHandler:
|
||||
def __init__(self, ip_address: str = "127.0.0.1", port: int = 9000) -> None:
|
||||
self.is_osc_query_enabled: bool
|
||||
self.osc_ip_address: str
|
||||
self.osc_port: int
|
||||
self.udp_client: udp_client.SimpleUDPClient
|
||||
self.osc_server: Optional[osc_server.ThreadingOSCUDPServer]
|
||||
self.osc_query_service: Optional[OSCQueryService]
|
||||
self.browser: Optional[OSCQueryBrowser]
|
||||
```
|
||||
|
||||
OSC通信の中核管理クラス
|
||||
|
||||
#### 属性
|
||||
- **is_osc_query_enabled**: OSCQuery機能の有効性フラグ
|
||||
- **osc_ip_address**: 送信先IPアドレス
|
||||
- **osc_port**: UDP通信ポート
|
||||
- **udp_client**: OSC送信クライアント
|
||||
- **osc_server**: ローカルOSCサーバー
|
||||
- **osc_query_service**: OSCQueryサービスインスタンス
|
||||
- **browser**: OSCQueryブラウザー
|
||||
|
||||
## 主要メソッド
|
||||
|
||||
### メッセージ送信
|
||||
|
||||
```python
|
||||
def sendMessage(self, message: str = "", notification: bool = True) -> None
|
||||
```
|
||||
|
||||
VRChatチャットボックスにメッセージを送信
|
||||
|
||||
#### パラメータ
|
||||
- **message**: 送信するテキストメッセージ
|
||||
- **notification**: 通知フラグ(音・表示の有無)
|
||||
|
||||
```python
|
||||
def sendTyping(self, flag: bool = False) -> None
|
||||
```
|
||||
|
||||
タイピング状態をVRChatに送信
|
||||
|
||||
#### パラメータ
|
||||
- **flag**: タイピング中フラグ
|
||||
|
||||
### パラメータ監視
|
||||
|
||||
```python
|
||||
def getOSCParameterMuteSelf() -> Optional[bool]
|
||||
```
|
||||
|
||||
VRChatのMuteSelfパラメータ値を取得
|
||||
|
||||
#### 戻り値
|
||||
- **Optional[bool]**: ミュート状態(取得失敗時はNone)
|
||||
|
||||
```python
|
||||
def getOSCParameterValue(self, address: str) -> Any
|
||||
```
|
||||
|
||||
任意のOSCパラメータ値を取得
|
||||
|
||||
#### パラメータ
|
||||
- **address**: OSCアドレス(例:"/avatar/parameters/MuteSelf")
|
||||
|
||||
#### 戻り値
|
||||
- **Any**: パラメータ値(取得失敗時はNone)
|
||||
|
||||
### 設定変更
|
||||
|
||||
```python
|
||||
def setOscIpAddress(self, ip_address: str) -> None
|
||||
```
|
||||
|
||||
送信先IPアドレスを変更し、サービスを再初期化
|
||||
|
||||
#### パラメータ
|
||||
- **ip_address**: 新しいIPアドレス
|
||||
|
||||
```python
|
||||
def setOscPort(self, port: int) -> None
|
||||
```
|
||||
|
||||
送信ポートを変更し、サービスを再初期化
|
||||
|
||||
#### パラメータ
|
||||
- **port**: 新しいUDPポート番号
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 基本的なメッセージ送信
|
||||
|
||||
```python
|
||||
from models.osc.osc import OSCHandler
|
||||
|
||||
# OSCハンドラーの初期化
|
||||
osc = OSCHandler(ip_address="127.0.0.1", port=9000)
|
||||
|
||||
# チャットボックスにメッセージを送信
|
||||
osc.sendMessage("こんにちは、VRChat!", notification=True)
|
||||
|
||||
# タイピング状態の制御
|
||||
osc.sendTyping(True) # タイピング開始
|
||||
# ... 実際のタイピング処理 ...
|
||||
osc.sendTyping(False) # タイピング終了
|
||||
|
||||
# 再度メッセージ送信
|
||||
osc.sendMessage("翻訳完了しました", notification=False)
|
||||
```
|
||||
|
||||
### リモートVRChatへの接続
|
||||
|
||||
```python
|
||||
# リモートVRChatインスタンスへの接続
|
||||
remote_osc = OSCHandler(ip_address="192.168.1.100", port=9000)
|
||||
|
||||
# OSCQuery機能は自動的に無効化される
|
||||
print(f"OSCQuery有効: {remote_osc.getIsOscQueryEnabled()}") # False
|
||||
|
||||
# 基本的なメッセージ送信は利用可能
|
||||
remote_osc.sendMessage("リモートからの翻訳結果", notification=True)
|
||||
```
|
||||
|
||||
### パラメータ監視(ローカル接続時のみ)
|
||||
|
||||
```python
|
||||
# ローカル接続でのパラメータ監視
|
||||
local_osc = OSCHandler(ip_address="127.0.0.1", port=9000)
|
||||
|
||||
if local_osc.getIsOscQueryEnabled():
|
||||
# MuteSelfパラメータの監視
|
||||
mute_status = local_osc.getOSCParameterMuteSelf()
|
||||
|
||||
if mute_status is not None:
|
||||
if mute_status:
|
||||
print("ユーザーはミュート中です")
|
||||
else:
|
||||
print("ユーザーはミュート解除中です")
|
||||
else:
|
||||
print("MuteSelfパラメータの取得に失敗しました")
|
||||
|
||||
# カスタムパラメータの監視
|
||||
custom_value = local_osc.getOSCParameterValue("/avatar/parameters/CustomParam")
|
||||
if custom_value is not None:
|
||||
print(f"カスタムパラメータ値: {custom_value}")
|
||||
```
|
||||
|
||||
### 双方向OSC通信の設定
|
||||
|
||||
```python
|
||||
def handle_mute_change(address, *args):
|
||||
"""ミュート状態変更のハンドラー"""
|
||||
print(f"ミュート状態が変更されました: {args}")
|
||||
|
||||
def handle_typing_change(address, *args):
|
||||
"""タイピング状態変更のハンドラー"""
|
||||
print(f"タイピング状態: {args}")
|
||||
|
||||
def handle_chatbox_input(address, *args):
|
||||
"""チャットボックス入力のハンドラー"""
|
||||
print(f"チャットボックス入力: {args}")
|
||||
|
||||
# OSCパラメータハンドラーの設定
|
||||
osc_handlers = {
|
||||
"/avatar/parameters/MuteSelf": handle_mute_change,
|
||||
"/chatbox/typing": handle_typing_change,
|
||||
"/chatbox/input": handle_chatbox_input
|
||||
}
|
||||
|
||||
osc = OSCHandler()
|
||||
osc.setDictFilterAndTarget(osc_handlers)
|
||||
|
||||
# OSCサーバー開始(OSCQuery自動公開)
|
||||
osc.receiveOscParameters()
|
||||
|
||||
print("OSC受信サーバーが開始されました")
|
||||
print("VRChatからのパラメータ変更を監視中...")
|
||||
|
||||
# メッセージ送信テスト
|
||||
import time
|
||||
time.sleep(2)
|
||||
osc.sendMessage("双方向通信テスト", notification=True)
|
||||
|
||||
# 長時間実行
|
||||
time.sleep(30)
|
||||
|
||||
# クリーンアップ
|
||||
osc.oscServerStop()
|
||||
```
|
||||
|
||||
### 動的設定変更
|
||||
|
||||
```python
|
||||
# 実行時のIP・ポート変更
|
||||
osc = OSCHandler(ip_address="127.0.0.1", port=9000)
|
||||
|
||||
# 初期設定でローカル接続
|
||||
osc.sendMessage("ローカル接続テスト")
|
||||
|
||||
print("リモート接続に切り替え中...")
|
||||
osc.setOscIpAddress("192.168.1.150") # 自動的にOSCQueryが無効化
|
||||
osc.sendMessage("リモート接続テスト")
|
||||
|
||||
print("ポート変更...")
|
||||
osc.setOscPort(9001)
|
||||
osc.sendMessage("新しいポートでのテスト")
|
||||
|
||||
print("ローカル接続に戻る...")
|
||||
osc.setOscIpAddress("127.0.0.1") # OSCQueryが再度有効化
|
||||
osc.sendMessage("ローカル接続復帰テスト")
|
||||
```
|
||||
|
||||
## OSCQuery詳細機能
|
||||
|
||||
### 自動サービス発見
|
||||
|
||||
```python
|
||||
class VRChatMonitor:
|
||||
"""VRChatサービス監視クラス"""
|
||||
|
||||
def __init__(self):
|
||||
self.osc = OSCHandler()
|
||||
self.monitoring = False
|
||||
|
||||
def start_monitoring(self):
|
||||
"""VRChatパラメータの継続監視開始"""
|
||||
|
||||
if not self.osc.getIsOscQueryEnabled():
|
||||
print("OSCQuery機能が無効です(ローカル接続のみサポート)")
|
||||
return
|
||||
|
||||
# OSCハンドラー設定
|
||||
handlers = {
|
||||
"/avatar/parameters/MuteSelf": self.on_mute_change,
|
||||
"/avatar/parameters/Voice": self.on_voice_change,
|
||||
"/avatar/parameters/Viseme": self.on_viseme_change,
|
||||
"/avatar/parameters/GestureLeft": self.on_gesture_left,
|
||||
"/avatar/parameters/GestureRight": self.on_gesture_right
|
||||
}
|
||||
|
||||
self.osc.setDictFilterAndTarget(handlers)
|
||||
self.osc.receiveOscParameters()
|
||||
|
||||
self.monitoring = True
|
||||
print("VRChatパラメータ監視を開始しました")
|
||||
|
||||
def on_mute_change(self, address, *args):
|
||||
print(f"ミュート状態変更: {args[0] if args else 'Unknown'}")
|
||||
|
||||
def on_voice_change(self, address, *args):
|
||||
print(f"音声レベル: {args[0] if args else 'Unknown'}")
|
||||
|
||||
def on_viseme_change(self, address, *args):
|
||||
print(f"口形変化: {args[0] if args else 'Unknown'}")
|
||||
|
||||
def on_gesture_left(self, address, *args):
|
||||
print(f"左手ジェスチャー: {args[0] if args else 'Unknown'}")
|
||||
|
||||
def on_gesture_right(self, address, *args):
|
||||
print(f"右手ジェスチャー: {args[0] if args else 'Unknown'}")
|
||||
|
||||
def stop_monitoring(self):
|
||||
"""監視停止"""
|
||||
self.osc.oscServerStop()
|
||||
self.monitoring = False
|
||||
print("VRChatパラメータ監視を停止しました")
|
||||
|
||||
# 使用例
|
||||
monitor = VRChatMonitor()
|
||||
monitor.start_monitoring()
|
||||
|
||||
# 監視中に他の処理を実行
|
||||
time.sleep(60) # 1分間監視
|
||||
|
||||
monitor.stop_monitoring()
|
||||
```
|
||||
|
||||
### リアルタイムパラメータ追跡
|
||||
|
||||
```python
|
||||
class ParameterTracker:
|
||||
"""パラメータ値の追跡・履歴管理"""
|
||||
|
||||
def __init__(self, osc_handler):
|
||||
self.osc = osc_handler
|
||||
self.parameter_history = {}
|
||||
self.tracking_active = False
|
||||
|
||||
def track_parameter(self, address, interval=0.1):
|
||||
"""指定されたパラメータを定期監視"""
|
||||
|
||||
import threading
|
||||
|
||||
def monitoring_loop():
|
||||
while self.tracking_active:
|
||||
try:
|
||||
value = self.osc.getOSCParameterValue(address)
|
||||
if value is not None:
|
||||
timestamp = time.time()
|
||||
|
||||
if address not in self.parameter_history:
|
||||
self.parameter_history[address] = []
|
||||
|
||||
# 値が変更された場合のみ記録
|
||||
if (not self.parameter_history[address] or
|
||||
self.parameter_history[address][-1][1] != value):
|
||||
|
||||
self.parameter_history[address].append((timestamp, value))
|
||||
print(f"{address}: {value} (時刻: {timestamp:.2f})")
|
||||
|
||||
# 履歴サイズ制限(最新100件まで)
|
||||
if len(self.parameter_history[address]) > 100:
|
||||
self.parameter_history[address] = self.parameter_history[address][-100:]
|
||||
|
||||
time.sleep(interval)
|
||||
|
||||
except Exception as e:
|
||||
print(f"パラメータ追跡エラー: {e}")
|
||||
time.sleep(interval)
|
||||
|
||||
self.tracking_active = True
|
||||
thread = threading.Thread(target=monitoring_loop, daemon=True)
|
||||
thread.start()
|
||||
|
||||
def stop_tracking(self):
|
||||
"""追跡停止"""
|
||||
self.tracking_active = False
|
||||
|
||||
def get_parameter_history(self, address):
|
||||
"""パラメータの履歴取得"""
|
||||
return self.parameter_history.get(address, [])
|
||||
|
||||
def get_latest_value(self, address):
|
||||
"""最新パラメータ値取得"""
|
||||
history = self.get_parameter_history(address)
|
||||
return history[-1][1] if history else None
|
||||
|
||||
# 使用例
|
||||
osc = OSCHandler()
|
||||
tracker = ParameterTracker(osc)
|
||||
|
||||
# MuteSelfパラメータの追跡開始
|
||||
tracker.track_parameter("/avatar/parameters/MuteSelf", interval=0.5)
|
||||
|
||||
# しばらく監視
|
||||
time.sleep(30)
|
||||
|
||||
# 結果確認
|
||||
mute_history = tracker.get_parameter_history("/avatar/parameters/MuteSelf")
|
||||
print(f"MuteSelf変更履歴: {len(mute_history)}件")
|
||||
|
||||
for timestamp, value in mute_history[-5:]: # 最新5件表示
|
||||
print(f" {time.ctime(timestamp)}: {value}")
|
||||
|
||||
tracker.stop_tracking()
|
||||
```
|
||||
|
||||
## エラーハンドリング・復旧機構
|
||||
|
||||
### 堅牢な接続管理
|
||||
|
||||
```python
|
||||
class RobustOSCHandler:
|
||||
"""堅牢性を高めたOSCハンドラー"""
|
||||
|
||||
def __init__(self, ip_address="127.0.0.1", port=9000):
|
||||
self.osc = OSCHandler(ip_address, port)
|
||||
self.connection_retries = 3
|
||||
self.retry_delay = 1.0
|
||||
|
||||
def safe_send_message(self, message, notification=True, max_retries=None):
|
||||
"""安全なメッセージ送信(リトライ機構付き)"""
|
||||
|
||||
retries = max_retries or self.connection_retries
|
||||
|
||||
for attempt in range(retries):
|
||||
try:
|
||||
self.osc.sendMessage(message, notification)
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"送信試行 {attempt + 1}/{retries} 失敗: {e}")
|
||||
|
||||
if attempt < retries - 1:
|
||||
time.sleep(self.retry_delay * (attempt + 1)) # 指数バックオフ
|
||||
|
||||
# 接続再初期化を試行
|
||||
try:
|
||||
self.osc.udp_client = udp_client.SimpleUDPClient(
|
||||
self.osc.osc_ip_address,
|
||||
self.osc.osc_port
|
||||
)
|
||||
except Exception as reconnect_error:
|
||||
print(f"再接続失敗: {reconnect_error}")
|
||||
|
||||
print(f"メッセージ送信に失敗しました: '{message}'")
|
||||
return False
|
||||
|
||||
def safe_get_parameter(self, address, timeout=5.0):
|
||||
"""安全なパラメータ取得(タイムアウト付き)"""
|
||||
|
||||
if not self.osc.getIsOscQueryEnabled():
|
||||
return None
|
||||
|
||||
import threading
|
||||
import queue
|
||||
|
||||
result_queue = queue.Queue()
|
||||
|
||||
def parameter_getter():
|
||||
try:
|
||||
value = self.osc.getOSCParameterValue(address)
|
||||
result_queue.put(value)
|
||||
except Exception as e:
|
||||
result_queue.put(e)
|
||||
|
||||
# タイムアウト付きでパラメータ取得
|
||||
thread = threading.Thread(target=parameter_getter, daemon=True)
|
||||
thread.start()
|
||||
|
||||
try:
|
||||
result = result_queue.get(timeout=timeout)
|
||||
if isinstance(result, Exception):
|
||||
raise result
|
||||
return result
|
||||
|
||||
except queue.Empty:
|
||||
print(f"パラメータ取得タイムアウト: {address}")
|
||||
return None
|
||||
|
||||
# 使用例
|
||||
robust_osc = RobustOSCHandler()
|
||||
|
||||
# 堅牢な送信
|
||||
success = robust_osc.safe_send_message("堅牢性テスト", notification=True)
|
||||
print(f"送信成功: {success}")
|
||||
|
||||
# 安全なパラメータ取得
|
||||
mute_value = robust_osc.safe_get_parameter("/avatar/parameters/MuteSelf", timeout=3.0)
|
||||
print(f"MuteSelf値: {mute_value}")
|
||||
```
|
||||
|
||||
## パフォーマンス最適化
|
||||
|
||||
### 効率的な通信管理
|
||||
|
||||
```python
|
||||
class OptimizedOSCHandler:
|
||||
"""パフォーマンス最適化OSCハンドラー"""
|
||||
|
||||
def __init__(self, ip_address="127.0.0.1", port=9000):
|
||||
self.osc = OSCHandler(ip_address, port)
|
||||
self.message_queue = []
|
||||
self.batch_size = 10
|
||||
self.batch_interval = 0.1
|
||||
self.last_batch_time = 0
|
||||
|
||||
def queue_message(self, message, notification=True):
|
||||
"""メッセージをキューに追加(バッチ送信用)"""
|
||||
|
||||
self.message_queue.append((message, notification))
|
||||
|
||||
# バッチサイズまたは時間間隔でフラッシュ
|
||||
current_time = time.time()
|
||||
|
||||
if (len(self.message_queue) >= self.batch_size or
|
||||
current_time - self.last_batch_time >= self.batch_interval):
|
||||
self.flush_messages()
|
||||
|
||||
def flush_messages(self):
|
||||
"""キューされたメッセージを一括送信"""
|
||||
|
||||
if not self.message_queue:
|
||||
return
|
||||
|
||||
# 最新のメッセージのみ送信(重複排除)
|
||||
if len(self.message_queue) > 1:
|
||||
# 最後のメッセージを優先
|
||||
last_message, last_notification = self.message_queue[-1]
|
||||
self.osc.sendMessage(last_message, last_notification)
|
||||
else:
|
||||
message, notification = self.message_queue[0]
|
||||
self.osc.sendMessage(message, notification)
|
||||
|
||||
# キューをクリア
|
||||
self.message_queue.clear()
|
||||
self.last_batch_time = time.time()
|
||||
|
||||
def send_immediate(self, message, notification=True):
|
||||
"""即座にメッセージ送信(キューをバイパス)"""
|
||||
self.flush_messages() # 既存キューを先にフラッシュ
|
||||
self.osc.sendMessage(message, notification)
|
||||
|
||||
# 使用例
|
||||
optimized_osc = OptimizedOSCHandler()
|
||||
|
||||
# 複数のメッセージを効率的に送信
|
||||
for i in range(20):
|
||||
optimized_osc.queue_message(f"バッチメッセージ {i}")
|
||||
time.sleep(0.05) # 短い間隔
|
||||
|
||||
# 残りのメッセージをフラッシュ
|
||||
optimized_osc.flush_messages()
|
||||
|
||||
# 即座に送信が必要な重要メッセージ
|
||||
optimized_osc.send_immediate("緊急メッセージ", notification=True)
|
||||
```
|
||||
|
||||
## 依存関係・要件
|
||||
|
||||
### 必須依存関係
|
||||
- `pythonosc`: 基本OSC通信ライブラリ
|
||||
- `threading`: 並行処理制御
|
||||
- `time`: 時間管理機能
|
||||
|
||||
### オプション依存関係
|
||||
- `tinyoscquery`: OSCQuery機能(ローカル接続時のみ)
|
||||
- `utils`: エラーログ機能(フォールバック処理あり)
|
||||
|
||||
### システム要件
|
||||
```python
|
||||
# 最小システム要件
|
||||
requirements = {
|
||||
"python_version": "3.7+",
|
||||
"network": "UDP通信対応",
|
||||
"vrchat_version": "OSCサポート版(2022年8月以降)",
|
||||
"local_ports": "空きUDP/TCPポート(OSCQuery使用時)"
|
||||
}
|
||||
|
||||
# 推奨環境
|
||||
recommended = {
|
||||
"network_latency": "< 10ms(ローカル接続)",
|
||||
"cpu_usage": "OSCQuery使用時は追加CPU負荷",
|
||||
"memory": "tinyoscquery使用時は追加メモリ"
|
||||
}
|
||||
```
|
||||
|
||||
## 注意事項・制限
|
||||
|
||||
### OSCQuery制限
|
||||
- ローカルホスト(127.0.0.1/localhost)接続時のみ利用可能
|
||||
- tinyoscqueryライブラリが必要
|
||||
- ファイアウォール設定によっては動作しない可能性
|
||||
|
||||
### 通信制限
|
||||
- UDPプロトコルのため送達保証なし
|
||||
- VRChatのOSC受信制限(レート制限あり)
|
||||
- ネットワーク環境による遅延・パケット loss
|
||||
|
||||
### プラットフォーム依存
|
||||
```python
|
||||
# 既知の制限事項
|
||||
limitations = {
|
||||
"windows": "Windowsファイアウォールの設定が必要な場合あり",
|
||||
"macos": "セキュリティ設定によるポート制限の可能性",
|
||||
"linux": "一部のLinuxディストリビューションでの互換性問題",
|
||||
"vrchat_platform": "PC版VRChatのみOSCサポート"
|
||||
}
|
||||
```
|
||||
|
||||
## 関連モジュール
|
||||
|
||||
- `config.py`: OSC設定管理
|
||||
- `controller.py`: OSC機能制御インターフェース
|
||||
- `model.py`: OSC機能統合
|
||||
- `utils.py`: エラーログ・ネットワークユーティリティ
|
||||
|
||||
## 将来の改善点
|
||||
|
||||
- より高度なOSCQueryパラメータ監視
|
||||
- カスタムOSCプロトコル拡張
|
||||
- パフォーマンス監視・分析機能
|
||||
- 自動再接続・復旧機構の改善
|
||||
- VRChatアバター固有パラメータ対応
|
||||
Reference in New Issue
Block a user