first commit
|
After Width: | Height: | Size: 96 KiB |
@@ -0,0 +1,269 @@
|
||||
# MCP (Model Context Protocol) 交互流程
|
||||
|
||||
NOTICE: AI 辅助生成, 在实现后台服务时, 请参照代码确认细节!!
|
||||
|
||||
本项目中的 MCP 协议用于后台 API(MCP 客户端)与 ESP32 设备(MCP 服务器)之间的通信,以便后台能够发现和调用设备提供的功能(工具)。
|
||||
|
||||
## 协议格式
|
||||
|
||||
根据代码 (`main/protocols/protocol.cc`, `main/mcp_server.cc`),MCP 消息是封装在基础通信协议(如 WebSocket 或 MQTT)的消息体中的。其内部结构遵循 [JSON-RPC 2.0](https://www.jsonrpc.org/specification) 规范。
|
||||
|
||||
整体消息结构示例:
|
||||
|
||||
```json
|
||||
{
|
||||
"session_id": "...", // 会话 ID
|
||||
"type": "mcp", // 消息类型,固定为 "mcp"
|
||||
"payload": { // JSON-RPC 2.0 负载
|
||||
"jsonrpc": "2.0",
|
||||
"method": "...", // 方法名 (如 "initialize", "tools/list", "tools/call")
|
||||
"params": { ... }, // 方法参数 (对于 request)
|
||||
"id": ..., // 请求 ID (对于 request 和 response)
|
||||
"result": { ... }, // 方法执行结果 (对于 success response)
|
||||
"error": { ... } // 错误信息 (对于 error response)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
其中,`payload` 部分是标准的 JSON-RPC 2.0 消息:
|
||||
|
||||
- `jsonrpc`: 固定的字符串 "2.0"。
|
||||
- `method`: 要调用的方法名称 (对于 Request)。
|
||||
- `params`: 方法的参数,一个结构化值,通常为对象 (对于 Request)。
|
||||
- `id`: 请求的标识符,客户端发送请求时提供,服务器响应时原样返回。用于匹配请求和响应。
|
||||
- `result`: 方法成功执行时的结果 (对于 Success Response)。
|
||||
- `error`: 方法执行失败时的错误信息 (对于 Error Response)。
|
||||
|
||||
## 交互流程及发送时机
|
||||
|
||||
MCP 的交互主要围绕客户端(后台 API)发现和调用设备上的“工具”(Tool)进行。
|
||||
|
||||
1. **连接建立与能力通告**
|
||||
|
||||
- **时机:** 设备启动并成功连接到后台 API 后。
|
||||
- **发送方:** 设备。
|
||||
- **消息:** 设备发送基础协议的 "hello" 消息给后台 API,消息中包含设备支持的能力列表,例如通过 `CONFIG_IOT_PROTOCOL_MCP` 配置来表明支持 MCP 协议 (`"mcp": true`)。
|
||||
- **示例 (非 MCP 负载,而是基础协议消息):**
|
||||
```json
|
||||
{
|
||||
"type": "hello",
|
||||
"version": ...,
|
||||
"features": {
|
||||
"mcp": true,
|
||||
...
|
||||
},
|
||||
"transport": "websocket", // 或 "mqtt"
|
||||
"audio_params": { ... },
|
||||
"session_id": "..." // 设备收到服务器hello后可能设置
|
||||
}
|
||||
```
|
||||
|
||||
2. **初始化 MCP 会话**
|
||||
|
||||
- **时机:** 后台 API 收到设备 "hello" 消息,确认设备支持 MCP 后,通常作为 MCP 会话的第一个请求发送。
|
||||
- **发送方:** 后台 API (客户端)。
|
||||
- **方法:** `initialize`
|
||||
- **消息 (MCP payload):**
|
||||
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"method": "initialize",
|
||||
"params": {
|
||||
"capabilities": {
|
||||
// 客户端能力,可选
|
||||
|
||||
// 摄像头视觉相关
|
||||
"vision": {
|
||||
"url": "...", //摄像头: 图片处理地址(必须是http地址, 不是websocket地址)
|
||||
"token": "..." // url token
|
||||
}
|
||||
|
||||
// ... 其他客户端能力
|
||||
}
|
||||
},
|
||||
"id": 1 // 请求 ID
|
||||
}
|
||||
```
|
||||
|
||||
- **设备响应时机:** 设备收到 `initialize` 请求并处理后。
|
||||
- **设备响应消息 (MCP payload):**
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 1, // 匹配请求 ID
|
||||
"result": {
|
||||
"protocolVersion": "2024-11-05",
|
||||
"capabilities": {
|
||||
"tools": {} // 这里的 tools 似乎不列出详细信息,需要 tools/list
|
||||
},
|
||||
"serverInfo": {
|
||||
"name": "...", // 设备名称 (BOARD_NAME)
|
||||
"version": "..." // 设备固件版本
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
3. **发现设备工具列表**
|
||||
|
||||
- **时机:** 后台 API 需要获取设备当前支持的具体功能(工具)列表及其调用方式时。
|
||||
- **发送方:** 后台 API (客户端)。
|
||||
- **方法:** `tools/list`
|
||||
- **消息 (MCP payload):**
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"method": "tools/list",
|
||||
"params": {
|
||||
"cursor": "" // 用于分页,首次请求为空字符串
|
||||
},
|
||||
"id": 2 // 请求 ID
|
||||
}
|
||||
```
|
||||
- **设备响应时机:** 设备收到 `tools/list` 请求并生成工具列表后。
|
||||
- **设备响应消息 (MCP payload):**
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 2, // 匹配请求 ID
|
||||
"result": {
|
||||
"tools": [ // 工具对象列表
|
||||
{
|
||||
"name": "self.get_device_status",
|
||||
"description": "...",
|
||||
"inputSchema": { ... } // 参数 schema
|
||||
},
|
||||
{
|
||||
"name": "self.audio_speaker.set_volume",
|
||||
"description": "...",
|
||||
"inputSchema": { ... } // 参数 schema
|
||||
}
|
||||
// ... 更多工具
|
||||
],
|
||||
"nextCursor": "..." // 如果列表很大需要分页,这里会包含下一个请求的 cursor 值
|
||||
}
|
||||
}
|
||||
```
|
||||
- **分页处理:** 如果 `nextCursor` 字段非空,客户端需要再次发送 `tools/list` 请求,并在 `params` 中带上这个 `cursor` 值以获取下一页工具。
|
||||
|
||||
4. **调用设备工具**
|
||||
|
||||
- **时机:** 后台 API 需要执行设备上的某个具体功能时。
|
||||
- **发送方:** 后台 API (客户端)。
|
||||
- **方法:** `tools/call`
|
||||
- **消息 (MCP payload):**
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"method": "tools/call",
|
||||
"params": {
|
||||
"name": "self.audio_speaker.set_volume", // 要调用的工具名称
|
||||
"arguments": {
|
||||
// 工具参数,对象格式
|
||||
"volume": 50 // 参数名及其值
|
||||
}
|
||||
},
|
||||
"id": 3 // 请求 ID
|
||||
}
|
||||
```
|
||||
- **设备响应时机:** 设备收到 `tools/call` 请求,执行相应的工具函数后。
|
||||
- **设备成功响应消息 (MCP payload):**
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 3, // 匹配请求 ID
|
||||
"result": {
|
||||
"content": [
|
||||
// 工具执行结果内容
|
||||
{ "type": "text", "text": "true" } // 示例:set_volume 返回 bool
|
||||
],
|
||||
"isError": false // 表示成功
|
||||
}
|
||||
}
|
||||
```
|
||||
- **设备失败响应消息 (MCP payload):**
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 3, // 匹配请求 ID
|
||||
"error": {
|
||||
"code": -32601, // JSON-RPC 错误码,例如 Method not found (-32601)
|
||||
"message": "Unknown tool: self.non_existent_tool" // 错误描述
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
5. **设备主动发送消息 (Notifications)**
|
||||
- **时机:** 设备内部发生需要通知后台 API 的事件时(例如,状态变化,虽然代码示例中没有明确的工具发送此类消息,但 `Application::SendMcpMessage` 的存在暗示了设备可能主动发送 MCP 消息)。
|
||||
- **发送方:** 设备 (服务器)。
|
||||
- **方法:** 可能是以 `notifications/` 开头的方法名,或者其他自定义方法。
|
||||
- **消息 (MCP payload):** 遵循 JSON-RPC Notification 格式,没有 `id` 字段。
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"method": "notifications/state_changed", // 示例方法名
|
||||
"params": {
|
||||
"newState": "idle",
|
||||
"oldState": "connecting"
|
||||
}
|
||||
// 没有 id 字段
|
||||
}
|
||||
```
|
||||
- **后台 API 处理:** 接收到 Notification 后,后台 API 进行相应的处理,但不回复。
|
||||
|
||||
## 交互图
|
||||
|
||||
下面是一个简化的交互序列图,展示了主要的 MCP 消息流程:
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Device as ESP32 Device
|
||||
participant BackendAPI as 后台 API (Client)
|
||||
|
||||
Note over Device, BackendAPI: 建立 WebSocket / MQTT 连接
|
||||
|
||||
Device->>BackendAPI: Hello Message (包含 "mcp": true)
|
||||
|
||||
BackendAPI->>Device: MCP Initialize Request
|
||||
Note over BackendAPI: method: initialize
|
||||
Note over BackendAPI: params: { capabilities: ... }
|
||||
|
||||
Device->>BackendAPI: MCP Initialize Response
|
||||
Note over Device: result: { protocolVersion: ..., serverInfo: ... }
|
||||
|
||||
BackendAPI->>Device: MCP Get Tools List Request
|
||||
Note over BackendAPI: method: tools/list
|
||||
Note over BackendAPI: params: { cursor: "" }
|
||||
|
||||
Device->>BackendAPI: MCP Get Tools List Response
|
||||
Note over Device: result: { tools: [...], nextCursor: ... }
|
||||
|
||||
loop Optional Pagination
|
||||
BackendAPI->>Device: MCP Get Tools List Request
|
||||
Note over BackendAPI: method: tools/list
|
||||
Note over BackendAPI: params: { cursor: "..." }
|
||||
Device->>BackendAPI: MCP Get Tools List Response
|
||||
Note over Device: result: { tools: [...], nextCursor: "" }
|
||||
end
|
||||
|
||||
BackendAPI->>Device: MCP Call Tool Request
|
||||
Note over BackendAPI: method: tools/call
|
||||
Note over BackendAPI: params: { name: "...", arguments: { ... } }
|
||||
|
||||
alt Tool Call Successful
|
||||
Device->>BackendAPI: MCP Tool Call Success Response
|
||||
Note over Device: result: { content: [...], isError: false }
|
||||
else Tool Call Failed
|
||||
Device->>BackendAPI: MCP Tool Call Error Response
|
||||
Note over Device: error: { code: ..., message: ... }
|
||||
end
|
||||
|
||||
opt Device Notification
|
||||
Device->>BackendAPI: MCP Notification
|
||||
Note over Device: method: notifications/...
|
||||
Note over Device: params: { ... }
|
||||
end
|
||||
```
|
||||
|
||||
这份文档概述了该项目中 MCP 协议的主要交互流程。具体的参数细节和工具功能需要参考 `main/mcp_server.cc` 中 `McpServer::AddCommonTools` 以及各个工具的实现。
|
||||
@@ -0,0 +1,115 @@
|
||||
# MCP 协议物联网控制用法说明
|
||||
|
||||
> 本文档介绍如何基于 MCP 协议实现 ESP32 设备的物联网控制。详细协议流程请参考 [`mcp-protocol.md`](./mcp-protocol.md)。
|
||||
|
||||
## 简介
|
||||
|
||||
MCP(Model Context Protocol)是新一代推荐用于物联网控制的协议,通过标准 JSON-RPC 2.0 格式在后台与设备间发现和调用"工具"(Tool),实现灵活的设备控制。
|
||||
|
||||
## 典型使用流程
|
||||
|
||||
1. 设备启动后通过基础协议(如 WebSocket/MQTT)与后台建立连接。
|
||||
2. 后台通过 MCP 协议的 `initialize` 方法初始化会话。
|
||||
3. 后台通过 `tools/list` 获取设备支持的所有工具(功能)及参数说明。
|
||||
4. 后台通过 `tools/call` 调用具体工具,实现对设备的控制。
|
||||
|
||||
详细协议格式与交互请见 [`mcp-protocol.md`](./mcp-protocol.md)。
|
||||
|
||||
## 设备端工具注册方法说明
|
||||
|
||||
设备通过 `McpServer::AddTool` 方法注册可被后台调用的"工具"。其常用函数签名如下:
|
||||
|
||||
```cpp
|
||||
void AddTool(
|
||||
const std::string& name, // 工具名称,建议唯一且有层次感,如 self.dog.forward
|
||||
const std::string& description, // 工具描述,简明说明功能,便于大模型理解
|
||||
const PropertyList& properties, // 输入参数列表(可为空),支持类型:布尔、整数、字符串
|
||||
std::function<ReturnValue(const PropertyList&)> callback // 工具被调用时的回调实现
|
||||
);
|
||||
```
|
||||
- name:工具唯一标识,建议用"模块.功能"命名风格。
|
||||
- description:自然语言描述,便于 AI/用户理解。
|
||||
- properties:参数列表,支持类型有布尔、整数、字符串,可指定范围和默认值。
|
||||
- callback:收到调用请求时的实际执行逻辑,返回值可为 bool/int/string。
|
||||
|
||||
## 典型注册示例(以 ESP-Hi 为例)
|
||||
|
||||
```cpp
|
||||
void InitializeTools() {
|
||||
auto& mcp_server = McpServer::GetInstance();
|
||||
// 例1:无参数,控制机器人前进
|
||||
mcp_server.AddTool("self.dog.forward", "机器人向前移动", PropertyList(), [this](const PropertyList&) -> ReturnValue {
|
||||
servo_dog_ctrl_send(DOG_STATE_FORWARD, NULL);
|
||||
return true;
|
||||
});
|
||||
// 例2:带参数,设置灯光 RGB 颜色
|
||||
mcp_server.AddTool("self.light.set_rgb", "设置RGB颜色", PropertyList({
|
||||
Property("r", kPropertyTypeInteger, 0, 255),
|
||||
Property("g", kPropertyTypeInteger, 0, 255),
|
||||
Property("b", kPropertyTypeInteger, 0, 255)
|
||||
}), [this](const PropertyList& properties) -> ReturnValue {
|
||||
int r = properties["r"].value<int>();
|
||||
int g = properties["g"].value<int>();
|
||||
int b = properties["b"].value<int>();
|
||||
led_on_ = true;
|
||||
SetLedColor(r, g, b);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
## 常见工具调用 JSON-RPC 示例
|
||||
|
||||
### 1. 获取工具列表
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"method": "tools/list",
|
||||
"params": { "cursor": "" },
|
||||
"id": 1
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 控制底盘前进
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"method": "tools/call",
|
||||
"params": {
|
||||
"name": "self.chassis.go_forward",
|
||||
"arguments": {}
|
||||
},
|
||||
"id": 2
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 切换灯光模式
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"method": "tools/call",
|
||||
"params": {
|
||||
"name": "self.chassis.switch_light_mode",
|
||||
"arguments": { "light_mode": 3 }
|
||||
},
|
||||
"id": 3
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 摄像头翻转
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"method": "tools/call",
|
||||
"params": {
|
||||
"name": "self.camera.set_camera_flipped",
|
||||
"arguments": {}
|
||||
},
|
||||
"id": 4
|
||||
}
|
||||
```
|
||||
|
||||
## 备注
|
||||
- 工具名称、参数及返回值请以设备端 `AddTool` 注册为准。
|
||||
- 推荐所有新项目统一采用 MCP 协议进行物联网控制。
|
||||
- 详细协议与进阶用法请查阅 [`mcp-protocol.md`](./mcp-protocol.md)。
|
||||
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 94 KiB |
|
After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 46 KiB |
|
After Width: | Height: | Size: 121 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 35 KiB |
|
After Width: | Height: | Size: 31 KiB |
|
After Width: | Height: | Size: 39 KiB |
|
After Width: | Height: | Size: 47 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 50 KiB |
|
After Width: | Height: | Size: 45 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 38 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 57 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 51 KiB |
|
After Width: | Height: | Size: 30 KiB |
@@ -0,0 +1,430 @@
|
||||
以下是一份基于代码实现整理的 WebSocket 通信协议文档,概述设备端与服务器之间如何通过 WebSocket 进行交互。
|
||||
|
||||
该文档仅基于所提供的代码推断,实际部署时可能需要结合服务器端实现进行进一步确认或补充。
|
||||
|
||||
---
|
||||
|
||||
## 1. 总体流程概览
|
||||
|
||||
1. **设备端初始化**
|
||||
- 设备上电、初始化 `Application`:
|
||||
- 初始化音频编解码器、显示屏、LED 等
|
||||
- 连接网络
|
||||
- 创建并初始化实现 `Protocol` 接口的 WebSocket 协议实例(`WebsocketProtocol`)
|
||||
- 进入主循环等待事件(音频输入、音频输出、调度任务等)。
|
||||
|
||||
2. **建立 WebSocket 连接**
|
||||
- 当设备需要开始语音会话时(例如用户唤醒、手动按键触发等),调用 `OpenAudioChannel()`:
|
||||
- 根据配置获取 WebSocket URL
|
||||
- 设置若干请求头(`Authorization`, `Protocol-Version`, `Device-Id`, `Client-Id`)
|
||||
- 调用 `Connect()` 与服务器建立 WebSocket 连接
|
||||
|
||||
3. **设备端发送 "hello" 消息**
|
||||
- 连接成功后,设备会发送一条 JSON 消息,示例结构如下:
|
||||
```json
|
||||
{
|
||||
"type": "hello",
|
||||
"version": 1,
|
||||
"features": {
|
||||
"mcp": true
|
||||
},
|
||||
"transport": "websocket",
|
||||
"audio_params": {
|
||||
"format": "opus",
|
||||
"sample_rate": 16000,
|
||||
"channels": 1,
|
||||
"frame_duration": 60
|
||||
}
|
||||
}
|
||||
```
|
||||
- 其中 `features` 字段为可选,内容根据设备编译配置自动生成。例如:`"mcp": true` 表示支持 MCP 协议。
|
||||
- `frame_duration` 的值对应 `OPUS_FRAME_DURATION_MS`(例如 60ms)。
|
||||
|
||||
4. **服务器回复 "hello"**
|
||||
- 设备等待服务器返回一条包含 `"type": "hello"` 的 JSON 消息,并检查 `"transport": "websocket"` 是否匹配。
|
||||
- 服务器可选下发 `session_id` 字段,设备端收到后会自动记录。
|
||||
- 示例:
|
||||
```json
|
||||
{
|
||||
"type": "hello",
|
||||
"transport": "websocket",
|
||||
"session_id": "xxx",
|
||||
"audio_params": {
|
||||
"format": "opus",
|
||||
"sample_rate": 24000,
|
||||
"channels": 1,
|
||||
"frame_duration": 60
|
||||
}
|
||||
}
|
||||
```
|
||||
- 如果匹配,则认为服务器已就绪,标记音频通道打开成功。
|
||||
- 如果在超时时间(默认 10 秒)内未收到正确回复,认为连接失败并触发网络错误回调。
|
||||
|
||||
5. **后续消息交互**
|
||||
- 设备端和服务器端之间可发送两种主要类型的数据:
|
||||
1. **二进制音频数据**(Opus 编码)
|
||||
2. **文本 JSON 消息**(用于传输聊天状态、TTS/STT 事件、MCP 协议消息等)
|
||||
|
||||
- 在代码里,接收回调主要分为:
|
||||
- `OnData(...)`:
|
||||
- 当 `binary` 为 `true` 时,认为是音频帧;设备会将其当作 Opus 数据进行解码。
|
||||
- 当 `binary` 为 `false` 时,认为是 JSON 文本,需要在设备端用 cJSON 进行解析并做相应业务逻辑处理(如聊天、TTS、MCP 协议消息等)。
|
||||
|
||||
- 当服务器或网络出现断连,回调 `OnDisconnected()` 被触发:
|
||||
- 设备会调用 `on_audio_channel_closed_()`,并最终回到空闲状态。
|
||||
|
||||
6. **关闭 WebSocket 连接**
|
||||
- 设备在需要结束语音会话时,会调用 `CloseAudioChannel()` 主动断开连接,并回到空闲状态。
|
||||
- 或者如果服务器端主动断开,也会引发同样的回调流程。
|
||||
|
||||
---
|
||||
|
||||
## 2. 通用请求头
|
||||
|
||||
在建立 WebSocket 连接时,代码示例中设置了以下请求头:
|
||||
|
||||
- `Authorization`: 用于存放访问令牌,形如 `"Bearer <token>"`
|
||||
- `Protocol-Version`: 固定示例中为 `"1"`,与 hello 消息体内的 `version` 字段保持一致
|
||||
- `Device-Id`: 设备物理网卡 MAC 地址
|
||||
- `Client-Id`: 软件生成的 UUID(擦除 NVS 或重新烧录完整固件会重置)
|
||||
|
||||
这些头会随着 WebSocket 握手一起发送到服务器,服务器可根据需求进行校验、认证等。
|
||||
|
||||
---
|
||||
|
||||
## 3. JSON 消息结构
|
||||
|
||||
WebSocket 文本帧以 JSON 方式传输,以下为常见的 `"type"` 字段及其对应业务逻辑。若消息里包含未列出的字段,可能为可选或特定实现细节。
|
||||
|
||||
### 3.1 设备端→服务器
|
||||
|
||||
1. **Hello**
|
||||
- 连接成功后,由设备端发送,告知服务器基本参数。
|
||||
- 例:
|
||||
```json
|
||||
{
|
||||
"type": "hello",
|
||||
"version": 1,
|
||||
"features": {
|
||||
"mcp": true
|
||||
},
|
||||
"transport": "websocket",
|
||||
"audio_params": {
|
||||
"format": "opus",
|
||||
"sample_rate": 16000,
|
||||
"channels": 1,
|
||||
"frame_duration": 60
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
2. **Listen**
|
||||
- 表示设备端开始或停止录音监听。
|
||||
- 常见字段:
|
||||
- `"session_id"`:会话标识
|
||||
- `"type": "listen"`
|
||||
- `"state"`:`"start"`, `"stop"`, `"detect"`(唤醒检测已触发)
|
||||
- `"mode"`:`"auto"`, `"manual"` 或 `"realtime"`,表示识别模式。
|
||||
- 例:开始监听
|
||||
```json
|
||||
{
|
||||
"session_id": "xxx",
|
||||
"type": "listen",
|
||||
"state": "start",
|
||||
"mode": "manual"
|
||||
}
|
||||
```
|
||||
|
||||
3. **Abort**
|
||||
- 终止当前说话(TTS 播放)或语音通道。
|
||||
- 例:
|
||||
```json
|
||||
{
|
||||
"session_id": "xxx",
|
||||
"type": "abort",
|
||||
"reason": "wake_word_detected"
|
||||
}
|
||||
```
|
||||
- `reason` 值可为 `"wake_word_detected"` 或其他。
|
||||
|
||||
4. **Wake Word Detected**
|
||||
- 用于设备端向服务器告知检测到唤醒词。
|
||||
- 在发送该消息之前,可提前发送唤醒词的 Opus 音频数据,用于服务器进行声纹检测。
|
||||
- 例:
|
||||
```json
|
||||
{
|
||||
"session_id": "xxx",
|
||||
"type": "listen",
|
||||
"state": "detect",
|
||||
"text": "你好小明"
|
||||
}
|
||||
```
|
||||
|
||||
5. **MCP**
|
||||
- 推荐用于物联网控制的新一代协议。所有设备能力发现、工具调用等均通过 type: "mcp" 的消息进行,payload 内部为标准 JSON-RPC 2.0(详见 [MCP 协议文档](./mcp-protocol.md))。
|
||||
|
||||
- **设备端到服务器发送 result 的例子:**
|
||||
```json
|
||||
{
|
||||
"session_id": "xxx",
|
||||
"type": "mcp",
|
||||
"payload": {
|
||||
"jsonrpc": "2.0",
|
||||
"id": 1,
|
||||
"result": {
|
||||
"content": [
|
||||
{ "type": "text", "text": "true" }
|
||||
],
|
||||
"isError": false
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3.2 服务器→设备端
|
||||
|
||||
1. **Hello**
|
||||
- 服务器端返回的握手确认消息。
|
||||
- 必须包含 `"type": "hello"` 和 `"transport": "websocket"`。
|
||||
- 可能会带有 `audio_params`,表示服务器期望的音频参数,或与设备端对齐的配置。
|
||||
- 服务器可选下发 `session_id` 字段,设备端收到后会自动记录。
|
||||
- 成功接收后设备端会设置事件标志,表示 WebSocket 通道就绪。
|
||||
|
||||
2. **STT**
|
||||
- `{"session_id": "xxx", "type": "stt", "text": "..."}`
|
||||
- 表示服务器端识别到了用户语音。(例如语音转文本结果)
|
||||
- 设备可能将此文本显示到屏幕上,后续再进入回答等流程。
|
||||
|
||||
3. **LLM**
|
||||
- `{"session_id": "xxx", "type": "llm", "emotion": "happy", "text": "😀"}`
|
||||
- 服务器指示设备调整表情动画 / UI 表达。
|
||||
|
||||
4. **TTS**
|
||||
- `{"session_id": "xxx", "type": "tts", "state": "start"}`:服务器准备下发 TTS 音频,设备端进入 "speaking" 播放状态。
|
||||
- `{"session_id": "xxx", "type": "tts", "state": "stop"}`:表示本次 TTS 结束。
|
||||
- `{"session_id": "xxx", "type": "tts", "state": "sentence_start", "text": "..."}`
|
||||
- 让设备在界面上显示当前要播放或朗读的文本片段(例如用于显示给用户)。
|
||||
|
||||
5. **MCP**
|
||||
- 服务器通过 type: "mcp" 的消息下发物联网相关的控制指令或返回调用结果,payload 结构同上。
|
||||
|
||||
- **服务器到设备端发送 tools/call 的例子:**
|
||||
```json
|
||||
{
|
||||
"session_id": "xxx",
|
||||
"type": "mcp",
|
||||
"payload": {
|
||||
"jsonrpc": "2.0",
|
||||
"method": "tools/call",
|
||||
"params": {
|
||||
"name": "self.light.set_rgb",
|
||||
"arguments": { "r": 255, "g": 0, "b": 0 }
|
||||
},
|
||||
"id": 1
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
6. **音频数据:二进制帧**
|
||||
- 当服务器发送音频二进制帧(Opus 编码)时,设备端解码并播放。
|
||||
- 若设备端正在处于 "listening" (录音)状态,收到的音频帧会被忽略或清空以防冲突。
|
||||
|
||||
---
|
||||
|
||||
## 4. 音频编解码
|
||||
|
||||
1. **设备端发送录音数据**
|
||||
- 音频输入经过可能的回声消除、降噪或音量增益后,通过 Opus 编码打包为二进制帧发送给服务器。
|
||||
- 如果设备端每次编码生成的二进制帧大小为 N 字节,则会通过 WebSocket 的 **binary** 消息发送这块数据。
|
||||
|
||||
2. **设备端播放收到的音频**
|
||||
- 收到服务器的二进制帧时,同样认定是 Opus 数据。
|
||||
- 设备端会进行解码,然后交由音频输出接口播放。
|
||||
- 如果服务器的音频采样率与设备不一致,会在解码后再进行重采样。
|
||||
|
||||
---
|
||||
|
||||
## 5. 常见状态流转
|
||||
|
||||
以下为常见设备端关键状态流转,与 WebSocket 消息对应:
|
||||
|
||||
1. **Idle** → **Connecting**
|
||||
- 用户触发或唤醒后,设备调用 `OpenAudioChannel()` → 建立 WebSocket 连接 → 发送 `"type":"hello"`。
|
||||
|
||||
2. **Connecting** → **Listening**
|
||||
- 成功建立连接后,若继续执行 `SendStartListening(...)`,则进入录音状态。此时设备会持续编码麦克风数据并发送到服务器。
|
||||
|
||||
3. **Listening** → **Speaking**
|
||||
- 收到服务器 TTS Start 消息 (`{"type":"tts","state":"start"}`) → 停止录音并播放接收到的音频。
|
||||
|
||||
4. **Speaking** → **Idle**
|
||||
- 服务器 TTS Stop (`{"type":"tts","state":"stop"}`) → 音频播放结束。若未继续进入自动监听,则返回 Idle;如果配置了自动循环,则再度进入 Listening。
|
||||
|
||||
5. **Listening** / **Speaking** → **Idle**(遇到异常或主动中断)
|
||||
- 调用 `SendAbortSpeaking(...)` 或 `CloseAudioChannel()` → 中断会话 → 关闭 WebSocket → 状态回到 Idle。
|
||||
|
||||
### 自动模式状态流转图
|
||||
|
||||
```mermaid
|
||||
stateDiagram
|
||||
direction TB
|
||||
[*] --> kDeviceStateUnknown
|
||||
kDeviceStateUnknown --> kDeviceStateStarting:初始化
|
||||
kDeviceStateStarting --> kDeviceStateWifiConfiguring:配置WiFi
|
||||
kDeviceStateStarting --> kDeviceStateActivating:激活设备
|
||||
kDeviceStateActivating --> kDeviceStateUpgrading:检测到新版本
|
||||
kDeviceStateActivating --> kDeviceStateIdle:激活完成
|
||||
kDeviceStateIdle --> kDeviceStateConnecting:开始连接
|
||||
kDeviceStateConnecting --> kDeviceStateIdle:连接失败
|
||||
kDeviceStateConnecting --> kDeviceStateListening:连接成功
|
||||
kDeviceStateListening --> kDeviceStateSpeaking:开始说话
|
||||
kDeviceStateSpeaking --> kDeviceStateListening:结束说话
|
||||
kDeviceStateListening --> kDeviceStateIdle:手动终止
|
||||
kDeviceStateSpeaking --> kDeviceStateIdle:自动终止
|
||||
```
|
||||
|
||||
### 手动模式状态流转图
|
||||
|
||||
```mermaid
|
||||
stateDiagram
|
||||
direction TB
|
||||
[*] --> kDeviceStateUnknown
|
||||
kDeviceStateUnknown --> kDeviceStateStarting:初始化
|
||||
kDeviceStateStarting --> kDeviceStateWifiConfiguring:配置WiFi
|
||||
kDeviceStateStarting --> kDeviceStateActivating:激活设备
|
||||
kDeviceStateActivating --> kDeviceStateUpgrading:检测到新版本
|
||||
kDeviceStateActivating --> kDeviceStateIdle:激活完成
|
||||
kDeviceStateIdle --> kDeviceStateConnecting:开始连接
|
||||
kDeviceStateConnecting --> kDeviceStateIdle:连接失败
|
||||
kDeviceStateConnecting --> kDeviceStateListening:连接成功
|
||||
kDeviceStateIdle --> kDeviceStateListening:开始监听
|
||||
kDeviceStateListening --> kDeviceStateIdle:停止监听
|
||||
kDeviceStateIdle --> kDeviceStateSpeaking:开始说话
|
||||
kDeviceStateSpeaking --> kDeviceStateIdle:结束说话
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 错误处理
|
||||
|
||||
1. **连接失败**
|
||||
- 如果 `Connect(url)` 返回失败或在等待服务器 "hello" 消息时超时,触发 `on_network_error_()` 回调。设备会提示"无法连接到服务"或类似错误信息。
|
||||
|
||||
2. **服务器断开**
|
||||
- 如果 WebSocket 异常断开,回调 `OnDisconnected()`:
|
||||
- 设备回调 `on_audio_channel_closed_()`
|
||||
- 切换到 Idle 或其他重试逻辑。
|
||||
|
||||
---
|
||||
|
||||
## 7. 其它注意事项
|
||||
|
||||
1. **鉴权**
|
||||
- 设备通过设置 `Authorization: Bearer <token>` 提供鉴权,服务器端需验证是否有效。
|
||||
- 如果令牌过期或无效,服务器可拒绝握手或在后续断开。
|
||||
|
||||
2. **会话控制**
|
||||
- 代码中部分消息包含 `session_id`,用于区分独立的对话或操作。服务端可根据需要对不同会话做分离处理。
|
||||
|
||||
3. **音频负载**
|
||||
- 代码里默认使用 Opus 格式,并设置 `sample_rate = 16000`,单声道。帧时长由 `OPUS_FRAME_DURATION_MS` 控制,一般为 60ms。可根据带宽或性能做适当调整。为了获得更好的音乐播放效果,服务器下行音频可能使用 24000 采样率。
|
||||
|
||||
4. **物联网控制推荐 MCP 协议**
|
||||
- 设备与服务器之间的物联网能力发现、状态同步、控制指令等,建议全部通过 MCP 协议(type: "mcp")实现。原有的 type: "iot" 方案已废弃。
|
||||
- MCP 协议可在 WebSocket、MQTT 等多种底层协议上传输,具备更好的扩展性和标准化能力。
|
||||
- 详细用法请参考 [MCP 协议文档](./mcp-protocol.md) 及 [MCP 物联网控制用法](./mcp-usage.md)。
|
||||
|
||||
5. **错误或异常 JSON**
|
||||
- 当 JSON 中缺少必要字段,例如 `{"type": ...}`,设备端会记录错误日志(`ESP_LOGE(TAG, "Missing message type, data: %s", data);`),不会执行任何业务。
|
||||
|
||||
---
|
||||
|
||||
## 8. 消息示例
|
||||
|
||||
下面给出一个典型的双向消息示例(流程简化示意):
|
||||
|
||||
1. **设备端 → 服务器**(握手)
|
||||
```json
|
||||
{
|
||||
"type": "hello",
|
||||
"version": 1,
|
||||
"features": {
|
||||
"mcp": true
|
||||
},
|
||||
"transport": "websocket",
|
||||
"audio_params": {
|
||||
"format": "opus",
|
||||
"sample_rate": 16000,
|
||||
"channels": 1,
|
||||
"frame_duration": 60
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
2. **服务器 → 设备端**(握手应答)
|
||||
```json
|
||||
{
|
||||
"type": "hello",
|
||||
"transport": "websocket",
|
||||
"session_id": "xxx",
|
||||
"audio_params": {
|
||||
"format": "opus",
|
||||
"sample_rate": 16000
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
3. **设备端 → 服务器**(开始监听)
|
||||
```json
|
||||
{
|
||||
"session_id": "xxx",
|
||||
"type": "listen",
|
||||
"state": "start",
|
||||
"mode": "auto"
|
||||
}
|
||||
```
|
||||
同时设备端开始发送二进制帧(Opus 数据)。
|
||||
|
||||
4. **服务器 → 设备端**(ASR 结果)
|
||||
```json
|
||||
{
|
||||
"session_id": "xxx",
|
||||
"type": "stt",
|
||||
"text": "用户说的话"
|
||||
}
|
||||
```
|
||||
|
||||
5. **服务器 → 设备端**(TTS开始)
|
||||
```json
|
||||
{
|
||||
"session_id": "xxx",
|
||||
"type": "tts",
|
||||
"state": "start"
|
||||
}
|
||||
```
|
||||
接着服务器发送二进制音频帧给设备端播放。
|
||||
|
||||
6. **服务器 → 设备端**(TTS结束)
|
||||
```json
|
||||
{
|
||||
"session_id": "xxx",
|
||||
"type": "tts",
|
||||
"state": "stop"
|
||||
}
|
||||
```
|
||||
设备端停止播放音频,若无更多指令,则回到空闲状态。
|
||||
|
||||
---
|
||||
|
||||
## 9. 总结
|
||||
|
||||
本协议通过在 WebSocket 上层传输 JSON 文本与二进制音频帧,完成功能包括音频流上传、TTS 音频播放、语音识别与状态管理、MCP 指令下发等。其核心特征:
|
||||
|
||||
- **握手阶段**:发送 `"type":"hello"`,等待服务器返回。
|
||||
- **音频通道**:采用 Opus 编码的二进制帧双向传输语音流。
|
||||
- **JSON 消息**:使用 `"type"` 为核心字段标识不同业务逻辑,包括 TTS、STT、MCP、WakeWord 等。
|
||||
- **扩展性**:可根据实际需求在 JSON 消息中添加字段,或在 headers 里进行额外鉴权。
|
||||
|
||||
服务器与设备端需提前约定各类消息的字段含义、时序逻辑以及错误处理规则,方能保证通信顺畅。上述信息可作为基础文档,便于后续对接、开发或扩展。
|
||||