Skip to content

通信プロトコル仕様

対象 Issue: #5 ステータス: Draft v0.1 (実装前の設計合意用)

スタックちゃん本体(M5Stack CoreS3)と Mac Studio 上の管理画面サーバーの間で交わされる通信仕様。 音声対話・状態同期・通知・MCP ツール呼び出しすべての土台となる。


1. 設計方針

観点採用理由
物理層Wi-Fi (2.4GHz)CoreS3 内蔵、家庭内 LAN 前提
トランスポートWebSocket over TLS双方向ストリーミング、CoreS3 ライブラリ豊富
アプリ層MCP (Model Context Protocol) をスタックちゃん→Mac Studio に。状態通知は独自イベント管理画面の機能を Claude にも直接渡せるため、二度書きしない
エンコードJSON (UTF-8)デバッグ容易、音声バイナリだけ別経路でバイナリフレーム
検出mDNS (_stackchan._tcp.local)固定 IP 不要、ユーザー設定なしで自動接続
認証共有 PSK(事前共有鍵)+ デバイス ID家庭内 LAN 前提、複雑化を避ける

将来 BLE / iPhone リレーを追加する可能性は v2 で別途検討(v0.1 は Wi-Fi 直結のみ)。


2. 接続シーケンス

スタックちゃん                          Mac Studio
     │                                       │
     │  ① mDNS Query: _stackchan._tcp        │
     │ ──────────────────────────────────▶  │
     │                                       │
     │  ② mDNS Reply: 192.168.x.x:7780       │
     │ ◀──────────────────────────────────  │
     │                                       │
     │  ③ WSS Upgrade: /ws                   │
     │      + Header: X-Device-Id            │
     │      + Header: X-Auth (HMAC-SHA256)   │
     │ ──────────────────────────────────▶  │
     │                                       │
     │  ④ 101 Switching Protocols            │
     │ ◀──────────────────────────────────  │
     │                                       │
     │  ⑤ {"type":"hello", ...}              │
     │ ──────────────────────────────────▶  │
     │                                       │
     │  ⑥ {"type":"welcome", "session": ...} │
     │ ◀──────────────────────────────────  │
     │                                       │
     │  ⑦ MCP セッション開始 + 状態同期        │
     │ ◀──────────────────────────────────▶ │

mDNS サービス情報

フィールド
サービス名_stackchan._tcp.local
ポート7780
TXT レコードversion=0.1, tls=1, mcp=1

認証

  • セットアップ時に 管理画面で1回 PSK(32バイト)を生成 → 本体に NFC or QR で転送(v0.1 はシリアル経由で焼き込み)
  • 接続時のヘッダ:
    • X-Device-Id: ESP32 の MAC アドレスから生成した固定値
    • X-Auth: HMAC-SHA256(PSK, device_id + ":" + timestamp)
    • X-Timestamp: 接続試行時の Unix ミリ秒(±60s のずれは許容、リプレイ攻撃対策)

3. メッセージ・スキーマ

3.1 共通エンベロープ

jsonc
{
  "id": "uuid-v4",        // リクエスト/レスポンス対応用
  "type": "voice.chunk",  // ドット区切りで階層化
  "ts": 1731988800123,    // 送信側のミリ秒タイムスタンプ
  "payload": { ... }      // type 固有のデータ
}

3.2 ハンドシェイク

hello (スタックちゃん → Mac Studio)

jsonc
{
  "type": "hello",
  "payload": {
    "fw_version": "0.1.0",
    "device_id": "stackchan-xxxx",
    "capabilities": ["voice.in", "voice.out", "expression", "servo", "led"]
  }
}

welcome (Mac Studio → スタックちゃん)

jsonc
{
  "type": "welcome",
  "payload": {
    "session_id": "sess_xxxx",
    "server_version": "0.1.0",
    "mcp_tools": ["task.list", "mail.unread", "news.latest", ...]
  }
}

3.3 状態・通知

state (双方向 / pub-sub)

スタックちゃん本体の状態(表情・処理中フラグ)を同期。

jsonc
{
  "type": "state",
  "payload": {
    "expression": "thinking",    // idle | thinking | talking | happy | sad | sleep
    "battery": 0.87,             // 0.0 - 1.0
    "wifi_rssi": -52,
    "busy": true
  }
}

notify (Mac Studio → スタックちゃん)

管理画面側で発生したイベント通知(メール・タスク完了)。

jsonc
{
  "type": "notify",
  "payload": {
    "kind": "mail.important",    // mail.important | task.done | task.overdue | agent.session_end
    "summary": "山田さんから返信",
    "speak": true,                // true なら TTS で読み上げ
    "led": {"color": "blue", "pattern": "blink_2"}
  }
}

3.4 音声経路

音声は別チャンネル(WebSocket バイナリフレーム)で送り、メタ情報だけ JSON で送る。

スタックちゃん                            Mac Studio
     │                                         │
     │  {"type":"voice.start", id:"v1",        │
     │   "format":"pcm_s16le_16khz_mono"}      │
     │ ──────────────────────────────────────▶│
     │                                         │
     │  [binary frame: 16KB PCM chunk]         │
     │ ──────────────────────────────────────▶│
     │  [binary frame: 16KB PCM chunk]         │
     │ ──────────────────────────────────────▶│
     │   ...                                   │
     │                                         │
     │  {"type":"voice.end", id:"v1"}          │
     │ ──────────────────────────────────────▶│
     │                                         │
     │  {"type":"voice.transcript",            │
     │   "id":"v1", "text":"今日の予定は?"}    │
     │ ◀──────────────────────────────────────│
     │                                         │
     │  {"type":"tts.start", "id":"r1",        │
     │   "format":"pcm_s16le_24khz_mono"}      │
     │ ◀──────────────────────────────────────│
     │  [binary frame: TTS PCM]                │
     │ ◀──────────────────────────────────────│
     │  {"type":"tts.end", "id":"r1"}          │
     │ ◀──────────────────────────────────────│

バイナリフレームは「直前の voice.start / tts.start で宣言された id」に紐付ける。 並走する音声を分離するため、混在しない設計(送信側で逐次化)。

3.5 MCP ツール呼び出し

スタックちゃんが「タスクを読み上げて」と認識した場合、ローカル LLM が MCP ツール呼び出し に変換 → Mac Studio に投げる。

jsonc
{
  "type": "mcp.call",
  "id": "req-001",
  "payload": {
    "tool": "task.list",
    "args": {"date": "today"}
  }
}

レスポンス:

jsonc
{
  "type": "mcp.result",
  "id": "req-001",
  "payload": {
    "ok": true,
    "data": [
      {"id": "t1", "title": "ナレッジページを書く", "due": "2026-05-19T18:00:00+09:00"}
    ]
  }
}

エラー時:

jsonc
{
  "type": "mcp.result",
  "id": "req-001",
  "payload": {
    "ok": false,
    "error": {"code": "tool_not_found", "message": "..."}
  }
}

3.6 深い思考の委譲 (Claude Agent SDK)

jsonc
{
  "type": "agent.run",
  "id": "ag-01",
  "payload": {
    "prompt": "今週の売上トレンドをまとめて、改善案を3つ",
    "stream": true
  }
}

ストリーミング応答:

jsonc
{"type":"agent.delta", "id":"ag-01", "payload":{"text":"先週比 +12% …"}}
{"type":"agent.delta", "id":"ag-01", "payload":{"text":"の伸びが見られ …"}}
{"type":"agent.done",  "id":"ag-01", "payload":{"usage":{"input":1024,"output":2048,"cost_usd":0.018}}}

4. 接続管理

ハートビート

  • 間隔: 15秒
  • スタックちゃんから {"type":"ping"} を送る
  • Mac Studio は {"type":"pong"} で返す
  • 30秒応答が無ければスタックちゃん側で切断 → 再接続

再接続戦略

状況動作
初回接続失敗1秒 → 2秒 → 4秒 → 8秒 ... の指数バックオフ(上限60秒)
接続中の切断即時 1回トライ → 失敗したら指数バックオフ
Wi-Fi 自体が切れたWi-Fi 再接続が成立するまで mDNS は走らせない

接続状態 UI

スタックちゃんの LCD 右上に小さく:

  • 🟢 接続中
  • 🟡 再接続中
  • 🔴 オフライン

5. エラーコード(参考)

code意味
auth_failedPSK ミスマッチ、Timestamp ズレすぎ
unsupported_versionバージョン不一致
tool_not_foundMCP ツール未登録
internal_errorサーバー側未捕捉例外
rate_limited過剰呼び出し(v1 以降)

6. 未決事項 (v0.2 以降)

  • [ ] PSK のローテーション手順
  • [ ] BLE フォールバック(外出時、Wi-Fi 圏外時の最小通信)
  • [ ] 複数スタックちゃん時のサーバー側の名前空間
  • [ ] 音声を Opus 圧縮にして帯域削減
  • [ ] iPhone リレー対応(モバイル時の安定化案、議論中)

7. 実装メモ(Phase ごとの最小実装)

Phase実装範囲
P1-aWSS 接続 + hello/welcome + ping/pong だけ
P1-bmDNS 検出 + 認証ヘッダ
P2-avoice.start/end + バイナリ PCM 送受信
P2-btts 再生(I2S 出力)と state 同期
P3MCP ツール呼び出し(task / mail / news)
P3agent.run と Claude Agent SDK バックエンド
P4notify と LED/サーボ連動

8. 参考

aieo-product / stack-chan project