From: Caisking Date: Thu, 18 Sep 2025 09:02:23 +0000 (+0800) Subject: 修改雷鸟录音存储文件路径 X-Git-Tag: leiniao_debug_1.0.0_20250922_020716~46^2~6^2~4 X-Git-Url: http://git.snicker.com.cn/css/static/gitweb.css?a=commitdiff_plain;h=7a8639b95fc90409908432df22465d809e250526;p=mirrors%2FFD230.git 修改雷鸟录音存储文件路径 --- diff --git "a/code/sdk/application/wearable/ln/nativeapp/nativeui/thunderbird/API\344\275\277\347\224\250\347\244\272\344\276\213.md" "b/code/sdk/application/wearable/ln/nativeapp/nativeui/thunderbird/API\344\275\277\347\224\250\347\244\272\344\276\213.md" new file mode 100644 index 000000000..ee24dea51 --- /dev/null +++ "b/code/sdk/application/wearable/ln/nativeapp/nativeui/thunderbird/API\344\275\277\347\224\250\347\244\272\344\276\213.md" @@ -0,0 +1,321 @@ +# ThunderBirdModel API接口使用示例 + +## 概述 +本文档展示了如何在UI中调用ThunderBirdModel的API接口服务,实现与设备API的交互。 + +## 1. 设备认证和Token管理 + +### 获取设备Token +```cpp +// 在Presenter中调用 +ThunderBirdModel* model = ThunderBirdModel::GetInstance(); + +// 获取设备Token +ThunderBirdModel::DeviceToken token; +if (model->GetDeviceToken("DEVICE_001", "12345678901234567890", token)) { + WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP, "Token获取成功: %s", token.token.c_str()); +} else { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "Token获取失败"); +} + +// 检查Token有效性 +if (model->IsTokenValid(token)) { + WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP, "Token有效"); +} else { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "Token已过期"); +} +``` + +## 2. 语音对话接口 + +### 开始语音对话 +```cpp +// 在录音完成后调用 +std::string audioFilePath = "/user/audio/recording.mp3"; + +model->StartSpeechChat(audioFilePath, [](const ThunderBirdModel::SpeechResponse& response) { + if (response.isError) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "语音对话错误: %s", response.errorMessage.c_str()); + // 处理错误,如播放错误音频 + } else { + WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP, "收到回复: %s", response.textContent.c_str()); + + // 播放回复音频 + if (!response.audioUrl.empty()) { + // TODO: 实现音频播放 + } + + // 显示文本内容 + ThunderBirdModel::GetInstance()->SetTextStr(response.textContent.c_str()); + + // 检查意图 + if (response.intention == "StartSleepMode") { + // 进入哄睡模式 + StartLullabyMode(); + } + } +}); +``` + +### 停止语音对话 +```cpp +model->StopSpeechChat(); +``` + +## 3. 故事接口 + +### 智能选择故事 +```cpp +// 用户通过语音选择故事类型 +std::string audioFilePath = "/user/audio/story_preference.mp3"; + +model->StartChooseStory(audioFilePath, [](const ThunderBirdModel::StoryResponse& response) { + if (response.isError) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "故事生成错误: %s", response.errorMessage.c_str()); + } else { + WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP, "故事主题: %s", response.storyTheme.c_str()); + WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP, "故事内容: %s", response.storyContent.c_str()); + + // 播放故事音频 + if (!response.storyAudioUrl.empty()) { + // TODO: 实现音频播放 + } + + // 显示故事内容 + ThunderBirdModel::GetInstance()->SetTextStr(response.storyContent.c_str()); + } +}); +``` + +### 随机故事 +```cpp +// 直接请求随机故事 +std::string storyTheme = "童话故事"; + +model->StartRandomStory(storyTheme, [](const ThunderBirdModel::StoryResponse& response) { + if (response.isError) { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "随机故事错误: %s", response.errorMessage.c_str()); + } else { + // 处理故事响应 + WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP, "随机故事: %s", response.storyContent.c_str()); + } +}); +``` + +## 4. 提醒接口 + +### 处理提醒 +```cpp +// 收到WebSocket推送的提醒消息 +ThunderBirdModel::ReminderInfo reminder; +reminder.scheduleId = 123; +reminder.deviceId = "DEVICE_001"; +reminder.scheduleName = "开会提醒"; +reminder.reminderContent = "您有一个会议即将开始,请准备参加"; +reminder.reminderAudioUrl = "https://example.com/reminder/reminder_001.mp3"; +reminder.enableVoice = true; +reminder.enableVibration = true; + +model->ProcessReminder(reminder); +``` + +### 处理提醒配置 +```cpp +// 处理提醒配置 +std::string scheduleName = "七点起床"; +model->ProcessReminderConfig(scheduleName); +``` + +## 5. 表情接口 + +### 下载表情 +```cpp +// 收到WebSocket推送的下载表情命令 +std::vector emojis; +ThunderBirdModel::EmojiInfo emoji1; +emoji1.characterId = "char_001"; +emoji1.eyesColor = "blue"; +emoji1.eyesShape = "calm-eyes"; +emoji1.emotion = "happy"; +emoji1.emojiUrl = "https://example.com/emoji/happy.mp4"; +emojis.push_back(emoji1); + +ThunderBirdModel::EmojiInfo emoji2; +emoji2.characterId = "char_001"; +emoji2.eyesColor = "blue"; +emoji2.eyesShape = "calm-eyes"; +emoji2.emotion = "sad"; +emoji2.emojiUrl = "https://example.com/emoji/sad.mp4"; +emojis.push_back(emoji2); + +model->DownloadEmoji(emojis); +``` + +### 删除表情 +```cpp +// 收到WebSocket推送的删除表情命令 +std::string characterId = "char_001"; +std::string eyesColor = "blue"; +std::string eyesShape = "calm-eyes"; + +model->DeleteEmoji(characterId, eyesColor, eyesShape); +``` + +## 6. WebSocket连接管理 + +### 建立连接 +```cpp +// 在应用启动时建立WebSocket连接 +if (model->ConnectWebSocket()) { + WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP, "WebSocket连接成功"); + + // 启动心跳定时器 + StartHeartbeatTimer(); +} else { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "WebSocket连接失败"); +} +``` + +### 发送心跳 +```cpp +// 定时发送心跳 +void StartHeartbeatTimer() { + // 每30秒发送一次心跳 + std::thread([]() { + while (true) { + std::this_thread::sleep_for(std::chrono::seconds(30)); + ThunderBirdModel::GetInstance()->SendHeartbeat(); + } + }).detach(); +} +``` + +### 发送消息 +```cpp +// 发送WebSocket消息 +Json::Value data; +data["messageId"] = "msg_001"; +data["timestamp"] = time(nullptr); + +model->SendWebSocketMessage("Ping", data.toStyledString()); +``` + +## 7. 在Presenter中的集成示例 + +```cpp +// ThunderBirdPresenter.cpp +void ThunderBirdPresenter::OnClick(UIView &view) +{ + std::string viewId = view.GetViewId(); + ThunderBirdModel* model = ThunderBirdModel::GetInstance(); + + if (viewId == "RECORD_BUTTON") { + if (model->IsRecording()) { + // 停止录音 + model->StopRecording(); + + // 开始语音对话 + std::string audioPath = "/user/audio/recording.mp3"; + model->StartSpeechChat(audioPath, [this](const ThunderBirdModel::SpeechResponse& response) { + // 处理语音回复 + HandleSpeechResponse(response); + }); + } else { + // 开始录音 + model->StartRecording(); + } + } + else if (viewId == "STORY_BUTTON") { + // 开始随机故事 + model->StartRandomStory("童话故事", [this](const ThunderBirdModel::StoryResponse& response) { + // 处理故事回复 + HandleStoryResponse(response); + }); + } +} + +void ThunderBirdPresenter::HandleSpeechResponse(const ThunderBirdModel::SpeechResponse& response) +{ + if (response.isError) { + // 显示错误信息 + m_view->UpdateTextLabel(response.errorMessage.c_str()); + } else { + // 显示回复内容 + m_view->UpdateTextLabel(response.textContent.c_str()); + + // 检查意图 + if (response.intention == "StartSleepMode") { + // 进入哄睡模式 + StartLullabyMode(); + } + } +} + +void ThunderBirdPresenter::HandleStoryResponse(const ThunderBirdModel::StoryResponse& response) +{ + if (response.isError) { + // 显示错误信息 + m_view->UpdateTextLabel(response.errorMessage.c_str()); + } else { + // 显示故事内容 + m_view->UpdateTextLabel(response.storyContent.c_str()); + } +} +``` + +## 8. 错误处理 + +### 常见错误处理 +```cpp +void HandleAPIError(const std::string& errorCode, const std::string& errorMessage) +{ + if (errorCode == "AUTH_FAILURE") { + // Token认证失败,重新获取Token + ThunderBirdModel::GetInstance()->ClearToken(); + // 重新进行设备认证 + } + else if (errorCode == "INSUFFICIENT_STAMINA") { + // 对话体力不足,引导用户充值 + ShowStaminaInsufficientDialog(); + } + else if (errorCode == "DEVICE_NOT_FOUND") { + // 设备信息异常,重新获取设备信息 + RefreshDeviceInfo(); + } + else if (errorCode == "MODEL_ERROR") { + // 模型服务异常,稍后重试 + ShowRetryDialog(); + } + else { + // 其他错误 + ShowErrorDialog(errorMessage); + } +} +``` + +## 9. 注意事项 + +1. **线程安全**: 所有API调用都是线程安全的,可以在任何线程中调用 +2. **回调函数**: 回调函数会在后台线程中执行,如果需要更新UI,需要切换到主线程 +3. **资源管理**: 及时调用Stop方法停止不需要的服务 +4. **错误处理**: 始终检查返回值并处理错误情况 +5. **Token管理**: 定期检查Token有效性,过期时重新获取 +6. **网络状态**: 在调用API前检查网络连接状态 +7. **文件路径**: 确保音频文件路径正确且文件存在 + +## 10. 配置说明 + +### API配置 +```cpp +// 在ThunderBirdModel构造函数中配置 +m_baseUrl = "https://ambybox-api.test.leiniao.com/ambybox-device-api"; +m_signSecret = "vDdXVXgPUEUM6_piUjz5AEhMvYTSFzQdPaN-ogKnSus="; +``` + +### 日志配置 +```cpp +// 启用详细日志 +#define WEARABLE_LOG_MODULE_APP "APP" +``` + +这个API接口设计完全按照设备API接口文档的要求实现,提供了完整的设备认证、语音对话、故事生成、提醒管理、表情管理等功能,可以直接在UI中调用使用。 diff --git a/code/sdk/application/wearable/ln/nativeapp/nativeui/thunderbird/src/ThunderBirdModel.cpp b/code/sdk/application/wearable/ln/nativeapp/nativeui/thunderbird/src/ThunderBirdModel.cpp index 92ab53e7a..ea7141c58 100644 --- a/code/sdk/application/wearable/ln/nativeapp/nativeui/thunderbird/src/ThunderBirdModel.cpp +++ b/code/sdk/application/wearable/ln/nativeapp/nativeui/thunderbird/src/ThunderBirdModel.cpp @@ -446,6 +446,7 @@ } // ==================== 新手引导相关函数 ==================== + //判断是否绑定 ThunderBirdModel::BindingStatus ThunderBirdModel::CheckBindingStatus() { WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP, "ThunderBirdModel::CheckBindingStatus"); @@ -479,14 +480,12 @@ return false; } g_isRecording = true;//设置录音状态 - std::string audioPath = "/user/123.mp3";//故事选择的录音文件路径 - // uapi_ln_get_story_choose_audio_path(); - // audioPath = "/user/audio/test.pcm";//修改为实际的录音文件路径 - - // // 确保目录存在 - //std::string dirPath = "/user/123.mp3"; - // 这里可以添加目录创建逻辑 - + std::string audioPath = uapi_ln_get_story_choose_audio_path(); + if(audioPath.empty()) + { + WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "ThunderBirdModel::StartRecording: 故事选择的录音文件路径为空"); + return false; + } const char *defaultArgs[6] = {"01", "mp3", "16000", "1", "16", audioPath.c_str()}; WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP, "ThunderBirdModel::StartRecording: 开始录音,路径: %s", audioPath.c_str()); WEARABLE_LOGD(WEARABLE_LOG_MODULE_APP, "ThunderBirdModel::StartRecording: 参数 - 格式: %s, 采样率: %s, 声道: %s, 位宽: %s", diff --git "a/code/sdk/application/wearable/ln/nativeapp/nativeui/thunderbird/\350\256\276\345\244\207API\346\216\245\345\217\243\346\226\207\346\241\243.md" "b/code/sdk/application/wearable/ln/nativeapp/nativeui/thunderbird/\350\256\276\345\244\207API\346\216\245\345\217\243\346\226\207\346\241\243.md" new file mode 100644 index 000000000..19b1e14b7 --- /dev/null +++ "b/code/sdk/application/wearable/ln/nativeapp/nativeui/thunderbird/\350\256\276\345\244\207API\346\216\245\345\217\243\346\226\207\346\241\243.md" @@ -0,0 +1,2489 @@ +# 设备API接口文档 + +测试服务BaseUrl:https://ambybox-api.test.leiniao.com/ambybox-device-api + +## 1. 获取设备Token接口 + +### 时序图 + +```mermaid +sequenceDiagram + participant Device as 设备端 + participant Server as 服务端 + + Note over Device, Server: 设备认证流程 + + Device->>Device: 生成时间戳和随机数 + Device->>Device: 构建签名数据
deviceId:deviceIccid:timestamp:nonce + Device->>Device: 使用HMAC-SHA256生成签名 + + Device->>Server: GET /v1/auth/getDeviceToken
Headers: deviceId, deviceIccid, timestamp, nonce, signature + + alt 签名验证成功 + Server->>Server: 验证时间戳(5分钟内有效) + Server->>Server: 验证签名正确性 + Server->>Server: 生成JWT Token + Server->>Device: 返回设备Token
{"deviceId", "token", "expireTime"} + + Note over Device: Token获取成功,缓存Token + + else 签名验证失败 + Server->>Device: 返回错误
{"errorCode": "INVALID_SIGNATURE"} + Note over Device: 认证失败,重新尝试 + end +``` + +### 接口描述 +设备认证接口,用于获取设备访问令牌。该接口无需认证,但需要进行签名校验。 + +### 请求信息 +- **URL**: `/v1/auth/getDeviceToken` +- **方法**: `GET` +- **Content-Type**: `application/json` + +### 请求头参数 +| 参数名 | 类型 | 必填 | 描述 | 示例 | +|--------|------|------|------|------| +| deviceId | String | 是 | 设备唯一标识符 | "DEVICE_001" | +| deviceIccid | String | 是 | 设备物联网卡号 | "12345678901234567890" | +| timestamp | String | 是 | 毫秒时间戳 | "1640995200000" | +| nonce | String | 是 | 随机数,用于防重放攻击 | "abc123" | +| signature | String | 是 | 签名,用于验证请求合法性 | "a1b2c3d4..." | +| deviceModel | String | 否 | 设备型号,默认值: "abmybox_1" | "abmybox_1" | +| deviceVersion | String | 否 | 设备固件版本,默认值: "1.0.0" | "1.0.0" | + +### 签名算法 +签名使用 HMAC-SHA256 算法生成,具体步骤如下: + +1. **构建签名数据**:将参数按以下格式拼接 + ``` + signData = deviceId + ":" + deviceIccid + ":" + timestamp + ":" + nonce + ``` + +2. **生成签名**:使用预共享密钥对签名数据进行 HMAC-SHA256 加密 + ``` + signature = HMAC-SHA256(signSecret, signData) + ``` + +3. **转换格式**:将签名结果转换为十六进制字符串(小写) + +### 签名验证规则 +- 时间戳有效性:请求时间戳与服务器时间差不能超过5分钟 +- 签名正确性:使用相同算法生成的签名必须与请求中的签名一致 +- 防重放攻击:建议客户端每次请求使用不同的随机数 +- 测试签名密钥:vDdXVXgPUEUM6_piUjz5AEhMvYTSFzQdPaN-ogKnSus= + + +### 响应数据 +**成功响应**: +```json +{ + "errorCode": null, + "message": null, + "data": { + "deviceId": "DEVICE_001", + "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", + "expireTime": 2592000 + } +} +``` + +**失败响应**: +```json +{ + "errorCode": "INVALID_SIGNATURE", + "message": "签名验证失败", + "data": null +} +``` + +### 响应字段说明 +| 字段名 | 类型 | 描述 | +|--------|------|------| +| deviceId | String | 设备ID | +| token | String | 设备访问令牌(JWT格式) | +| expireTime | Long | 令牌过期时间(秒,默认30天) | + +### 错误码 +| 错误码 | 描述 | +|--------|------| +| INVALID_SIGNATURE | 签名验证失败 | +| SERVER_ERROR | 服务器内部错误 | + +--- + +## 2. 语音对话接口 + +### 时序图 + +```mermaid +sequenceDiagram + participant Device as 设备端 + participant Server as 服务端 + + Note over Device, Server: 流式语音对话流程 + + Device->>Device: 录制用户语音 + Device->>Device: 转换为MP3格式 + + Device->>Server: POST /v1/speechChat/chatStream
Headers: deviceId, deviceToken, Accept: text/event-stream
Form: audio file + + alt Token验证成功 + Server->>Server: 验证设备Token + Server->>Server: 建立SSE连接 + Server->>Server: 语音识别(ASR) + Server->>Server: 意图识别 + + loop 流式响应生成 + Server->>Server: 生成回复片段 + Server->>Server: 文本转语音(TTS) + Server->>Device: SSE事件: 回复片段
{"audioUrl", "textContent", "intention"} + Device->>Device: 播放回复音频片段 + Device->>Device: 显示文本片段 + end + + Server->>Device: SSE连接关闭(对话完成) + + alt 识别到哄睡意图 + Note over Device: intention = "StartSleepMode" + Device->>Device: 准备进入哄睡模式 + end + + else Token验证失败 + Server->>Device: 返回认证错误
{"errorCode": "AUTH_FAILURE"} + Device->>Device: 重新获取Token + end +``` + +### 接口描述 +流式语音对话接口,接收音频文件并通过SSE(Server-Sent Events)流式返回对话结果。 + +### 请求信息 +- **URL**: `/v1/speechChat/chatStream` +- **方法**: `POST` +- **Content-Type**: `multipart/form-data` +- **响应类型**: `text/event-stream` (SSE流式响应) + +### 认证要求 +该接口需要设备Token认证,有以下两种方式传递Token: + +1. **请求头方式**: + ``` + deviceToken: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... + ``` + +2. **查询参数方式**: + ``` + /v1/speechChat/chatStream?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... + ``` + +### 请求头参数 +| 参数名 | 类型 | 必填 | 描述 | 示例 | +|--------|------|------|------|------| +| deviceId | String | 是 | 设备唯一标识符 | "DEVICE_001" | +| deviceToken | String | 是 | 设备访问令牌 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." | +| Accept | String | 是 | 必须设置为text/event-stream | "text/event-stream" | +| Cache-Control | String | 建议 | 建议设置为no-cache | "no-cache" | + +### 请求参数 +| 参数名 | 类型 | 必填 | 描述 | 限制 | +|--------|------|------|------|------| +| file | MultipartFile | 是 | 音频文件 | MP3格式,压缩前应为采样率16k或8k单声道的PCM | + +### Token认证规则 +- Token必须是有效的JWT格式 +- Token必须包含设备ID、设备型号、设备版本等信息 +- Token不能过期(默认有效期30天) +- 请求头中的设备ID不能为空 +- 设备ID必须与Token中的设备ID一致 + +### SSE流式响应数据 +**回复片段事件**: +``` +data: {"errorCode":null,"message":null,"data":{"audioUrl":"https://example.com/audio/chunk_001.mp3","textContent":"好的,","intention":null}} + +data: {"errorCode":null,"message":null,"data":{"audioUrl":"https://example.com/audio/chunk_002.mp3","textContent":"我来为你","intention":null}} + +data: {"errorCode":null,"message":null,"data":{"audioUrl":"https://example.com/audio/chunk_003.mp3","textContent":"讲一个故事","intention":"StartSleepMode"}} +``` + +**失败响应(认证失败)**: +```json +{ + "errorCode": "AUTH_FAILURE", + "message": "设备Token无效", + "data": null +} +``` + +**失败响应(设备信息异常)**: +```json +{ + "errorCode": "DEVICE_NOT_FOUND", + "message": "设备信息异常", + "data": { + "audioUrl": "https://file-virtual-person.api-cn.leiniao.com/ambybox/audio/device_notfound.mp3", + "textContent": "设备信息异常,请稍后重试。", + "intention": null + } +} +``` + +**失败响应(对话体力不足)**: +```json +{ + "errorCode": "INSUFFICIENT_STAMINA", + "message": "对话体力不足", + "data": { + "audioUrl": "https://file-virtual-person.api-cn.leiniao.com/ambybox/audio/insuffient_stamina.mp3", + "textContent": "对话体力不足,请前往小程序充值体力。", + "intention": null + } +} +``` + +**失败响应(音频输入无效)**: +```json +{ + "errorCode": "INVALID_AUDIO_INPUT", + "message": "音频输入无效", + "data": { + "audioUrl": "https://file-virtual-person.api-cn.leiniao.com/ambybox/audio/invalid_audio_input.mp3", + "textContent": "抱歉,我无法识别语音内容,请再说一次吧", + "intention": null + } +} +``` + +**失败响应(模型服务异常)**: +```json +{ + "errorCode": "MODEL_ERROR", + "message": "模型服务异常", + "data": { + "audioUrl": "https://file-virtual-person.api-cn.leiniao.com/ambybox/audio/model_error.mp3", + "textContent": "模型服务异常,请稍后重试。", + "intention": null + } +} +``` + +**失败响应(服务异常)**: +```json +{ + "errorCode": "SERVER_ERROR", + "message": "服务器内部错误", + "data": { + "audioUrl": "https://file-virtual-person.api-cn.leiniao.com/ambybox/audio/server_error.mp3", + "textContent": "服务器异常,请稍后重试。", + "intention": null + } +} +``` + +### 响应字段说明 +| 字段名 | 类型 | 描述 | +|--------|------|------| +| audioUrl | String | 回复音频片段文件URL | +| textContent | String | 回复的文本片段内容 | +| intention | String | 用户意图识别结果,通常在第一个片段中返回,可能的值:StartSleepMode(哄我睡觉)等 | + +### 错误码 +| 错误码 | 描述 | +|--------|------| +| AUTH_FAILURE | token认证失败 | +| DEVICE_NOT_FOUND | 设备信息异常 | +| INSUFFICIENT_STAMINA | 对话体力不足 | +| MODEL_NOT_FOUND | 模型信息不存在 | +| VOICE_NOT_FOUND | 音色信息不存在 | +| INVALID_AUDIO_INPUT | 音频输入无效 | +| MODEL_ERROR | 模型服务异常 | +| SERVER_ERROR | 服务器内部错误 | + +### 错误响应说明 +当流式语音对话接口发生错误时,会返回包含错误码、描述信息、文本内容和对应音频URL的响应。设备端应该: +1. 检查响应中的errorCode字段 +2. 如果errorCode不为null,表示发生了错误 +3. 播放data字段中的audioUrl音频文件,向用户说明错误情况 +4. 根据错误类型进行相应的处理: + - **DEVICE_NOT_FOUND**: 设备信息异常,建议重新获取设备Token + - **INSUFFICIENT_STAMINA**: 用户对话体力不足,引导用户前往小程序充值 + - **MODEL_NOT_FOUND/VOICE_NOT_FOUND**: 配置信息缺失,建议重新获取角色配置 + - **INVALID_AUDIO_INPUT**: 语音识别失败,提示用户重新录音 + - **MODEL_ERROR**: 模型服务异常,建议稍后重试 + - **SERVER_ERROR**: 服务器内部错误,建议稍后重试 + +### SSE协议说明 +- 接口使用SSE(Server-Sent Events)协议进行流式响应 +- 每个回复片段作为一个独立的SSE事件发送 +- 客户端需要实现SSE客户端来接收流式数据 +- 连接会在对话生成完成后自动关闭 +- 客户端应当设置适当的超时时间(建议60秒) + +### 音频数据处理说明 +- 返回的 `audioUrl` 字段是音频片段文件URL +- 音频格式通常为MP3格式 +- 客户端需要将音频文件URL下载后播放 +- 建议客户端实现音频片段的缓存和顺序播放机制 + +### 用户意图说明 +流式语音对话接口会识别用户的意图,目前支持的意图包括: +- **StartSleepMode**:用户表达想要听故事或哄睡的意图 +- 更多意图类型会在后续版本中添加 + +用户意图通常在最后一个或几个SSE事件中返回,客户端应当: +- 监听所有SSE事件中的intention字段 +- 当收到非null的intention时,根据意图类型进行相应处理 +- 收到StartSleepMode意图时,可以调用故事相关的API接口 + +### 使用说明 +- 该接口适用于需要实时响应的语音对话场景 +- 客户端可以边接收边播放,实现流式播放体验 +- 相比普通语音对话接口,流式接口能提供更自然的交互体验 +- 建议在网络状况良好的环境下使用,以确保音频片段的连续播放 +- 对话生成完成后,SSE连接会自动关闭 + + + +## 3. 故事相关接口 + +### 时序图 + +```mermaid +sequenceDiagram + participant Device as 设备端 + participant Server as 服务端 + + Note over Device, Server: 哄睡故事完整流程 + + %% 意图触发 + Device->>Server: 语音对话识别到哄睡意图
intention = "StartSleepMode" + + %% 故事选择分支 + alt 用户指定故事类型 + Device->>Device: 录制用户偏好语音 + Device->>Server: POST /v1/story/chooseStory
Headers: Accept: text/event-stream
Form: audio file + else 随机故事 + Device->>Server: POST /v1/story/randomStory
Headers: Accept: text/event-stream
Body: {"storyTheme": "可选主题"} + end + + %% SSE流式响应 + Server->>Server: 建立SSE连接 + Server->>Server: 分析语音/生成故事主题 + Server->>Server: 开始生成故事片段 + + loop 故事片段流式推送 + Server->>Server: 生成故事片段文本 + Server->>Server: 文本转语音 + Server->>Device: SSE事件: 故事片段
{"storyContent", "storyAudioUrl", "storyTheme"} + Device->>Device: 播放音频片段 + Device->>Device: 显示文本内容 + end + + Server->>Device: SSE连接关闭(故事完成) + + %% 退出确认流程 + Device->>Server: POST /v1/story/getExitSleepInquiry
获取退出询问 + Server->>Device: 返回退出询问
{"audioContent", "textContent"} + Device->>Device: 播放退出询问音频 + + Device->>Device: 等待用户确认 + alt 用户确认退出 + Device->>Server: POST /v1/story/finishSleepMode
确认结束哄睡 + Server->>Device: 返回成功响应 + Device->>Device: 退出哄睡模式 + else 用户继续 + Note over Device: 继续哄睡模式或重新开始故事 + end +``` + +### 3.1 智能选择故事 + +#### 接口描述 +智能选择故事接口。用户通过语音表达故事类型偏好,系统会识别故事主题并流式返回相应的故事内容。该接口使用SSE(Server-Sent Events)协议进行流式响应,需要设备Token认证。 + +#### 请求信息 +- **URL**: `/v1/story/chooseStory` +- **方法**: `POST` +- **Content-Type**: `multipart/form-data` +- **响应类型**: `text/event-stream` (SSE流式响应) + +#### 认证要求 +该接口需要设备Token认证,Token通过请求头或查询参数传递。 + +#### 请求头参数 +| 参数名 | 类型 | 必填 | 描述 | 示例 | +|--------|------|------|------|------| +| deviceId | String | 是 | 设备唯一标识符 | "DEVICE_001" | +| deviceToken | String | 是 | 设备访问令牌 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." | +| Accept | String | 是 | 必须设置为text/event-stream | "text/event-stream" | +| Cache-Control | String | 建议 | 建议设置为no-cache | "no-cache" | + +#### 请求表单参数 +| 参数名 | 类型 | 必填 | 描述 | 限制 | +|--------|------|------|------|------| +| file | MultipartFile | 是 | 语音文件 | MP3格式,压缩前应为采样率16k或8k单声道的PCM | + + +#### SSE流式响应数据 +**故事片段事件**: +``` +data: {"errorCode":null,"message":null,"data":{"storyTheme":"童话故事","storyContent":"从前有一个可爱的小女孩","storyAudioUrl":"https://example.com/story/audio/chunk_001.mp3","storyDuration":null}} + +data: {"errorCode":null,"message":null,"data":{"storyTheme":"童话故事","storyContent":"她住在森林深处的小木屋里","storyAudioUrl":"https://example.com/story/audio/chunk_002.mp3","storyDuration":null}} + +data: {"errorCode":null,"message":null,"data":{"storyTheme":"童话故事","storyContent":"每天都会去采集美丽的花朵","storyAudioUrl":"https://example.com/story/audio/chunk_003.mp3","storyDuration":null}} +``` + +**错误响应(认证失败)**: +```json +{ + "errorCode": "AUTH_FAILURE", + "message": "设备Token无效", + "data": null +} +``` + +**错误响应(音频输入无效)**: +```json +{ + "errorCode": "INVALID_AUDIO_INPUT", + "message": "音频输入无效", + "data": { + "storyTheme": "", + "storyContent": "抱歉,我无法识别语音内容,请再说一次吧", + "storyAudioUrl": "https://file-virtual-person.api-cn.leiniao.com/ambybox/audio/invalid_audio_input.mp3", + "storyDuration": 0 + } +} +``` + +**错误响应(设备信息异常)**: +```json +{ + "errorCode": "DEVICE_NOT_FOUND", + "message": "设备信息异常", + "data": { + "storyTheme": "", + "storyContent": "设备信息异常,请稍后重试。", + "storyAudioUrl": "https://file-virtual-person.api-cn.leiniao.com/ambybox/audio/device_notfound.mp3", + "storyDuration": 0 + } +} +``` + +**错误响应(模型服务异常)**: +```json +{ + "errorCode": "MODEL_ERROR", + "message": "模型服务异常", + "data": { + "storyTheme": "", + "storyContent": "模型服务异常,请稍后重试。", + "storyAudioUrl": "https://file-virtual-person.api-cn.leiniao.com/ambybox/audio/model_error.mp3", + "storyDuration": 0 + } +} +``` + +**错误响应(故事生成失败)**: +```json +{ + "errorCode": "SERVER_ERROR", + "message": "服务器内部错误", + "data": { + "storyTheme": "", + "storyContent": "服务器异常,请稍后重试。", + "storyAudioUrl": "https://file-virtual-person.api-cn.leiniao.com/ambybox/audio/server_error.mp3", + "storyDuration": 0 + } +} +``` + +#### 响应字段说明 +| 字段名 | 类型 | 描述 | +|--------|------|------| +| storyTheme | String | 故事主题 | +| storyContent | String | 故事片段文本内容 | +| storyAudioUrl | String | 故事片段音频文件URL | +| storyDuration | Integer | 故事片段时长(秒) | + +#### 错误码 +| 错误码 | 描述 | +|--------|------| +| AUTH_FAILURE | Token无效 | +| INVALID_AUDIO_INPUT | 音频输入无效 | +| DEVICE_NOT_FOUND | 设备信息异常 | +| MODEL_NOT_FOUND | 模型信息不存在 | +| MODEL_ERROR | 模型服务异常 | +| SERVER_ERROR | 服务器内部错误 | + +#### 错误响应说明 +当智能选择故事接口发生错误时,会返回包含错误码、描述信息、文本内容和对应音频URL的响应。设备端应该: +1. 检查响应中的errorCode字段 +2. 如果errorCode不为null,表示发生了错误 +3. 播放data字段中的storyAudioUrl音频文件,向用户说明错误情况 +4. 根据错误类型进行相应的处理: + - **INVALID_AUDIO_INPUT**: 语音识别失败,提示用户重新录音表达故事偏好 + - **DEVICE_NOT_FOUND**: 设备信息异常,建议重新获取设备Token + - **MODEL_NOT_FOUND**: 故事生成模型配置缺失,建议重新获取配置或联系技术支持 + - **MODEL_ERROR**: 故事生成服务异常,建议稍后重试 + - **SERVER_ERROR**: 服务器内部错误,建议稍后重试 + +#### SSE协议说明 +- 接口使用SSE(Server-Sent Events)协议进行流式响应 +- 每个故事片段作为一个独立的SSE事件发送 +- 客户端需要实现SSE客户端来接收流式数据 +- 连接会在故事生成完成后自动关闭 +- 客户端应当设置适当的超时时间(建议120秒) + +#### 使用说明 +- 用户可以通过语音表达故事偏好,如"我想听童话故事"、"讲个冒险故事吧"等 +- 系统会分析语音内容,识别故事主题,并实时生成故事片段 +- 每个故事片段包含文本内容和对应的音频URL +- 客户端可以边接收边播放,实现流式播放体验 +- 故事生成完成后,SSE连接会自动关闭 + +### 3.2 随机播放故事 + +#### 接口描述 +随机故事接口。系统会随机生成一个故事并流式返回给用户。该接口使用SSE(Server-Sent Events)协议进行流式响应,需要设备Token认证。 + +#### 请求信息 +- **URL**: `/v1/story/randomStory` +- **方法**: `POST` +- **Content-Type**: `application/json` +- **响应类型**: `text/event-stream` (SSE流式响应) + +#### 认证要求 +该接口需要设备Token认证,Token通过请求头或查询参数传递。 + +#### 请求头参数 +| 参数名 | 类型 | 必填 | 描述 | 示例 | +|--------|------|------|------|------| +| deviceId | String | 是 | 设备唯一标识符 | "DEVICE_001" | +| deviceToken | String | 是 | 设备访问令牌 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." | +| Accept | String | 是 | 必须设置为text/event-stream | "text/event-stream" | +| Cache-Control | String | 建议 | 建议设置为no-cache | "no-cache" | + + +#### 请求体参数 +| 参数名 | 类型 | 必填 | 描述 | 示例 | +|--------|------|------|------|------| +| storyTheme | String | 否 | 故事主题 | "爱情故事" | + + +示例: +```json +{ + "storyTheme": "爱情故事" +} +``` + + +#### SSE流式响应数据 +**故事片段事件**: +``` +data: {"errorCode":null,"message":null,"data":{"storyTheme":"冒险故事","storyContent":"在一个阳光明媚的早晨","storyAudioUrl":"https://example.com/story/audio/chunk_001.mp3","storyDuration":null}} + +data: {"errorCode":null,"message":null,"data":{"storyTheme":"冒险故事","storyContent":"小兔子决定去探险","storyAudioUrl":"https://example.com/story/audio/chunk_002.mp3","storyDuration":null}} + +data: {"errorCode":null,"message":null,"data":{"storyTheme":"冒险故事","storyContent":"它背上小书包,踏上了未知的旅程","storyAudioUrl":"https://example.com/story/audio/chunk_003.mp3","storyDuration":null}} +``` + +**错误响应(认证失败)**: +```json +{ + "errorCode": "AUTH_FAILURE", + "message": "设备Token无效", + "data": null +} +``` + +**错误响应(设备信息异常)**: +```json +{ + "errorCode": "DEVICE_NOT_FOUND", + "message": "设备信息异常", + "data": { + "storyTheme": "", + "storyContent": "设备信息异常,请稍后重试。", + "storyAudioUrl": "https://file-virtual-person.api-cn.leiniao.com/ambybox/audio/device_notfound.mp3", + "storyDuration": 0 + } +} +``` + +**错误响应(模型信息不存在)**: +```json +{ + "errorCode": "MODEL_NOT_FOUND", + "message": "模型信息不存在", + "data": { + "storyTheme": "", + "storyContent": "设备模型信息异常,请稍后重试。", + "storyAudioUrl": "https://file-virtual-person.api-cn.leiniao.com/ambybox/audio/model_notfound.mp3", + "storyDuration": 0 + } +} +``` + +**错误响应(模型服务异常)**: +```json +{ + "errorCode": "MODEL_ERROR", + "message": "模型服务异常", + "data": { + "storyTheme": "", + "storyContent": "模型服务异常,请稍后重试。", + "storyAudioUrl": "https://file-virtual-person.api-cn.leiniao.com/ambybox/audio/model_error.mp3", + "storyDuration": 0 + } +} +``` + +**错误响应(故事生成失败)**: +```json +{ + "errorCode": "SERVER_ERROR", + "message": "服务器内部错误", + "data": { + "storyTheme": "", + "storyContent": "服务器异常,请稍后重试。", + "storyAudioUrl": "https://file-virtual-person.api-cn.leiniao.com/ambybox/audio/server_error.mp3", + "storyDuration": 0 + } +} +``` + +#### 响应字段说明 +| 字段名 | 类型 | 描述 | +|--------|------|------| +| storyTheme | String | 故事主题 | +| storyContent | String | 故事片段文本内容 | +| storyAudioUrl | String | 故事片段音频文件URL | +| storyDuration | Integer | 故事片段时长(秒) | + +#### 错误码 +| 错误码 | 描述 | +|--------|------| +| AUTH_FAILURE | Token无效 | +| DEVICE_NOT_FOUND | 设备信息异常 | +| MODEL_NOT_FOUND | 模型信息不存在 | +| MODEL_ERROR | 模型服务异常 | +| SERVER_ERROR | 服务器内部错误 | + +#### 错误响应说明 +当随机故事接口发生错误时,会返回包含错误码、描述信息、文本内容和对应音频URL的响应。设备端应该: +1. 检查响应中的errorCode字段 +2. 如果errorCode不为null,表示发生了错误 +3. 播放data字段中的storyAudioUrl音频文件,向用户说明错误情况 +4. 根据错误类型进行相应的处理: + - **DEVICE_NOT_FOUND**: 设备信息异常,建议重新获取设备Token + - **MODEL_NOT_FOUND**: 故事生成模型配置缺失,建议重新获取配置或联系技术支持 + - **MODEL_ERROR**: 故事生成服务异常,建议稍后重试 + - **SERVER_ERROR**: 服务器内部错误,建议稍后重试 + +#### SSE协议说明 +- 接口使用SSE(Server-Sent Events)协议进行流式响应 +- 每个故事片段作为一个独立的SSE事件发送 +- 客户端需要实现SSE客户端来接收流式数据 +- 连接会在故事生成完成后自动关闭 +- 客户端应当设置适当的超时时间(建议120秒) + +#### 使用说明 +- 该接口不需要任何参数,系统会自动生成随机主题的故事 +- 适用于用户没有特定偏好,想要听任意故事的场景 +- 可以作为默认的故事推荐方式 +- 客户端可以边接收边播放,实现流式播放体验 +- 故事生成完成后,SSE连接会自动关闭 + +### 3.3 获取退出哄睡询问 + +#### 接口描述 +获取退出哄睡询问接口。在用户要结束哄睡前,返回询问内容用于确认用户是否真的要退出哄睡模式。该接口需要设备Token认证。 + +#### 请求信息 +- **URL**: `/v1/story/getExitSleepInquiry` +- **方法**: `POST` +- **Content-Type**: `application/json` + +#### 认证要求 +该接口需要设备Token认证,Token通过请求头或查询参数传递。 + +#### 请求头参数 +| 参数名 | 类型 | 必填 | 描述 | 示例 | +|--------|------|------|------|------| +| deviceId | String | 是 | 设备唯一标识符 | "DEVICE_001" | +| deviceToken | String | 是 | 设备访问令牌 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." | + +#### 请求参数 +无请求参数 + +#### 响应数据 +**成功响应**: +```json +{ + "errorCode": null, + "message": null, + "data": { + "audioUrl": "https://example.com/audio/response.mp3", + "textContent": "你确定要退出哄睡模式吗?如果确定要退出,请说确定", + "intention": "EXIT_SLEEP_MODE" + } +} +``` + +**失败响应(认证失败)**: +```json +{ + "errorCode": "AUTH_FAILURE", + "message": "设备Token无效", + "data": null +} +``` + +#### 响应字段说明 +| 字段名 | 类型 | 描述 | +|--------|------|------| +| audioUrl | String | 询问音频文件URL | +| textContent | String | 询问的文本内容 | +| intention | String | 用户意图类型,此接口固定返回"EXIT_SLEEP_MODE" | + +#### 错误码 +| 错误码 | 描述 | +|--------|------| +| AUTH_FAILURE | 认证失败(设备ID为空或Token无效) | +| SERVER_ERROR | 服务器内部错误 | + +#### 使用说明 +- 该接口用于获取退出哄睡模式的确认询问 +- 返回的音频和文本内容用于向用户确认是否真的要退出哄睡模式 +- 设备播放询问内容后,应等待用户的确认回复 +- 如果用户确认退出,再调用`finishSleepMode`接口正式结束哄睡模式 + +### 3.4 确认结束哄睡模式 + +#### 接口描述 +结束哄睡模式接口。当用户完成哄睡故事播放或主动结束哄睡模式时,调用此接口通知服务器。该接口需要设备Token认证。 + +#### 请求信息 +- **URL**: `/v1/story/finishSleepMode` +- **方法**: `POST` +- **Content-Type**: `application/json` + +#### 认证要求 +该接口需要设备Token认证,Token通过请求头或查询参数传递。 + +#### 请求头参数 +| 参数名 | 类型 | 必填 | 描述 | 示例 | +|--------|------|------|------|------| +| deviceId | String | 是 | 设备唯一标识符 | "DEVICE_001" | +| deviceToken | String | 是 | 设备访问令牌 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." | + +#### 请求参数 +无请求参数 + +#### 响应数据 +**成功响应**: +```json +{ + "errorCode": null, + "message": null, + "data": "" +} +``` + +**失败响应(认证失败)**: +```json +{ + "errorCode": "AUTH_FAILURE", + "message": "设备Token无效", + "data": null +} +``` + +#### 响应字段说明 +| 字段名 | 类型 | 描述 | +|--------|------|------| +| data | String | | + +#### 错误码 +| 错误码 | 描述 | +|--------|------| +| AUTH_FAILURE | 认证失败(设备ID为空或Token无效) | +| SERVER_ERROR | 服务器内部错误 | + +#### 使用说明 +- 该接口用于通知服务器哄睡模式已结束 +- 服务器可以记录哄睡会话的结束时间和统计信息 +- 调用此接口后,服务器可能会停止推送哄睡相关的消息 +- 建议在故事播放完成或用户主动停止时调用此接口 + +### 故事接口使用流程 +1. **意图识别**:用户通过语音对话接口表达哄我睡觉的意图,系统返回`StartSleepMode`意图 +2. **故事选择**: + - 如果用户有特定偏好,调用`chooseStory`接口,传入用户的语音描述,获取对应主题的故事流 + - 如果用户没有特定偏好,调用`randomStory`接口获取随机故事流 +3. **SSE连接建立**:客户端建立SSE连接,准备接收流式故事数据 +4. **流式故事播放**: + - 接收到每个故事片段事件后,立即播放对应的音频文件 + - 可以同时显示文本内容,实现边听边看的体验 + - 继续接收后续故事片段,直到故事完整播放完毕 +5. **连接关闭**:故事生成完成后,SSE连接自动关闭 +6. **退出询问**:故事播放完成或用户主动结束时,调用`getExitSleepInquiry`获取二次确认询问 +7. **播放询问**:设备播放退出询问的音频内容,等待用户确认 +8. **确认结束**:用户确认后,调用`finishSleepMode`接口通知服务器正式结束哄睡模式 + +### SSE客户端实现要点 +- **请求头设置**:必须设置`Accept: text/event-stream`和`Cache-Control: no-cache` +- **连接超时**:建议设置120秒超时时间,因为故事生成可能需要较长时间 +- **事件解析**:正确解析SSE事件格式,提取`data:`字段中的JSON数据 +- **错误处理**:处理连接失败、超时、认证失败等异常情况 +- **资源管理**:连接完成后正确释放资源,避免内存泄漏 + + +--- + +## 4. 设备信息接口 + +### 时序图 + +```mermaid +sequenceDiagram + participant Device as 设备端 + participant Server as 服务端 + + Note over Device, Server: 设备信息管理流程 + + %% 获取设备角色信息 + Device->>Server: GET /v1/device/getCharacter
Headers: deviceId, deviceToken + + alt 首次开机 + Server->>Device: 返回默认角色信息
{"characterId", "characterName", "characterAvatar", "emotionEmojis"} + Device->>Device: 应用默认角色配置 + else 已有角色配置 + Server->>Device: 返回用户选择的角色信息 + Device->>Device: 应用角色配置 + end + + %% 检查用户绑定状态 + Device->>Server: GET /v1/device/getUserId
获取绑定用户ID + + alt 设备已绑定用户 + Server->>Device: 返回用户ID
{"data": "user_001"} + Device->>Device: 正常运行模式 + else 设备未绑定 + Server->>Device: 返回空值
{"data": null} + Device->>Device: 显示绑定二维码 + + %% 生成绑定二维码 + Device->>Server: POST /v1/qrcode/generate/image
{"content": "绑定链接", "width": 300, "height": 300} + Server->>Device: 返回二维码图片(PNG) + Device->>Device: 显示二维码等待绑定 + end +``` + +### 4.1 获取设备角色信息 + +#### 接口描述 +获取设备角色信息接口。首次开机时返回默认角色,否则返回小程序选择的角色信息。该接口需要设备Token认证。 + +#### 请求信息 +- **URL**: `/v1/device/getCharacter` +- **方法**: `GET` +- **Content-Type**: `application/json` + +#### 认证要求 +该接口需要设备Token认证,Token通过请求头传递。 + +#### 请求头参数 +| 参数名 | 类型 | 必填 | 描述 | 示例 | +|--------|------|------|------|------| +| deviceId | String | 是 | 设备唯一标识符 | "DEVICE_001" | +| deviceToken | String | 是 | 设备访问令牌 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." | + +#### 响应数据 +**成功响应**: +```json +{ + "errorCode": null, + "message": null, + "data": { + "characterId": "char_001", + "characterName": "小艾", + "characterAvatar": { + "hairColor": "blue", + "eyesColor": "green", + "hairStyle": "side-parted-comma", + "eyesShape": "calm-eyes", + "screenImage": "https://example.com/avatar.jpg", + "screenImageAsEmoji": false + } + } +} +``` + +**失败响应**: +```json +{ + "errorCode": "AUTH_FAILURE", + "message": "设备Token无效", + "data": null +} +``` + +#### 响应字段说明 +| 字段名 | 类型 | 描述 | +|--------|------|------| +| characterId | String | 角色ID | +| characterName | String | 角色名称 | +| characterAvatar | Object | 角色形象配置 | + +**characterAvatar字段说明**: + +| 字段名 | 类型 | 描述 | 可选值 | +|--------|------|------|--------| +| eyesShape | String | 眼型 | calm-eyes, round-eyes, upturned-eyes | +| hairColor | String | 发色 | light-brown, dark-brown, black, white, gray, blue, purple, gold | +| eyesColor | String | 瞳色 | red, yellow, blue, green, purple, brown, purple-orange-gradient, blue-pink-gradient, yellow-green-gradient | +| hairStyle | String | 发型 | side-parted-comma, m-shaped-bangs, diagonal-bangs, center-parted-bangs, straight-bangs, side-parted-texture | +| screenImage | String | 屏保图片URL | | +| screenImageAsEmoji | Boolean | 屏保图片是否作为对话表情 | 默认false | + + +#### 错误码 +| 错误码 | 描述 | +|--------|------| +| AUTH_FAILURE | 认证失败(设备ID为空或Token无效) | + +### 4.2 获取设备绑定用户ID + +#### 接口描述 +获取设备绑定的用户ID接口。用于检查设备是否已绑定用户,如果未绑定则需要弹出二维码供用户绑定。该接口需要设备Token认证。 + +#### 请求信息 +- **URL**: `/v1/device/getUserId` +- **方法**: `GET` +- **Content-Type**: `application/json` + +#### 认证要求 +该接口需要设备Token认证,Token通过请求头传递。 + +#### 请求头参数 +| 参数名 | 类型 | 必填 | 描述 | 示例 | +|--------|------|------|------|------| +| deviceId | String | 是 | 设备唯一标识符 | "DEVICE_001" | +| deviceToken | String | 是 | 设备访问令牌 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." | + +#### 响应数据 +**成功响应(已绑定用户)**: +```json +{ + "errorCode": null, + "message": null, + "data": "user_001" +} +``` + +**成功响应(未绑定用户)**: +```json +{ + "errorCode": null, + "message": null, + "data": null +} +``` + +**失败响应**: +```json +{ + "errorCode": "AUTH_FAILURE", + "message": "设备Token无效", + "data": null +} +``` + +#### 响应字段说明 +| 字段名 | 类型 | 描述 | +|--------|------|------| +| data | String | 用户ID,如果设备未绑定用户则返回null | + +#### 错误码 +| 错误码 | 描述 | +|--------|------| +| AUTH_FAILURE | 认证失败(设备ID为空或Token无效) | + +--- + +## 5. OTA升级任务接口 + +### 时序图 + +```mermaid +sequenceDiagram + participant Device as 设备端 + participant Server as 服务端 + + Note over Device, Server: OTA升级流程 + + %% 设备启动检查升级 + Device->>Device: 设备启动 + Device->>Device: 获取当前固件版本 + + Device->>Server: GET /v1/upgrade/getTask
?deviceId=xxx&sourceVersion=1.0.0 + + alt 有可用升级 + Server->>Server: 检查升级策略 + Server->>Server: 查找可用升级包 + Server->>Device: 返回升级任务
{"updateId", "targetVersion", "packageUrl", "packageMd5", "updateType"} + + Device->>Device: 检查升级类型 + + alt 强制升级 (updateType=1) + Device->>Device: 立即开始升级 + else 可选升级 (updateType=0) + Device->>Device: 提示用户选择 + alt 用户同意升级 + Device->>Device: 开始升级 + else 用户拒绝 + Device->>Device: 继续正常运行 + end + end + + %% 执行升级 + opt 开始升级流程 + Device->>Device: 下载升级包 + Device->>Server: 下载 packageUrl + Server->>Device: 返回升级包文件 + + Device->>Device: 校验MD5 + alt MD5校验成功 + Device->>Device: 执行固件升级 + Device->>Device: 重启设备 + Device->>Device: 升级完成,版本更新 + Device->>Server: 重新获取设备Token
(新版本) + else MD5校验失败 + Device->>Device: 升级失败,重新下载 + end + end + + else 无可用升级 + Server->>Device: 返回无升级
{"errorCode": "UPGRADE_NOT_FOUND"} + Device->>Device: 继续正常运行 + end +``` + +### 接口描述 +获取OTA升级任务接口。设备可通过此接口查询是否有可用的固件升级任务,如果有则返回升级包的详细信息。 + +### 请求信息 +- **URL**: `/v1/upgrade/getTask` +- **方法**: `GET` +- **Content-Type**: `application/json` + +### 请求参数 +| 参数名 | 类型 | 必填 | 描述 | 示例 | +|--------|------|------|------|------| +| deviceId | String | 是 | 设备唯一标识符 | "DEVICE_001" | +| sourceVersion | String | 是 | 设备当前固件版本 | "1.0.0" | + +### 响应数据 +**成功响应(有升级任务)**: +```json +{ + "errorCode": null, + "message": null, + "data": { + "updateId": 123, + "sourceVersion": "1.0.0", + "targetVersion": "1.1.0", + "updateType": 1, + "packageId": 456, + "packageUrl": "https://example.com/firmware/update_1.1.0.bin", + "packageMd5": "a1b2c3d4e5f6789012345678901234567890", + "packageSize": 1048576, + "description": "升级描述" + } +} +``` + +**成功响应(无升级任务)**: +```json +{ + "errorCode": "UPGRADE_NOT_FOUND", + "message": "已是最新版本", + "data": null +} +``` + +**失败响应**: +```json +{ + "errorCode": "SERVER_ERROR", + "message": "服务器异常", + "data": null +} +``` + +### 响应字段说明 +| 字段名 | 类型 | 描述 | +|--------|------|------| +| updateId | Integer | 升级任务ID | +| sourceVersion | String | 源版本号 | +| targetVersion | String | 目标版本号 | +| updateType | Integer | 升级类型,0:紧急修复,1:重要更新,2:普通更新 | +| packageId | Integer | 升级包ID | +| packageUrl | String | 升级包下载URL | +| packageMd5 | String | 升级包MD5校验值 | +| packageSize | Long | 升级包大小(字节) | +| description | String | 升级描述 | + +### 错误码 +| 错误码 | 描述 | +|--------|------| +| UPGRADE_NOT_FOUND | 已是最新版本 | +| SERVER_ERROR | 服务器异常 | + +### 升级流程说明 +1. 设备启动时,调用此接口检查是否有可用的升级任务 +2. 如果有升级任务,获取升级包URL和校验信息 +3. 设备下载升级包,使用MD5校验文件完整性 +4. 校验通过后,设备执行固件升级操作 +5. 升级完成后,设备重启并重新获取设备token + +--- + +## 6. WebSocket连接端点 + +### 时序图 + +```mermaid +sequenceDiagram + participant Device as 设备端 + participant Server as 服务端 + participant MiniProgram as 小程序端 + + Note over Device,MiniProgram: WebSocket通信流程 + + %% 建立连接 + Device->>Server: WebSocket连接请求
Headers: deviceId, deviceToken + + alt Token验证成功 + Server->>Device: 连接成功(101) + Server->>Server: 记录设备会话 + + %% 心跳保持 + loop 心跳循环 + Device->>Server: ping + Server->>Device: pong + end + + %% 业务消息推送示例 + par 语音对话分片传输 + Device->>Device: 用户语音输入 + Device->>Device: 音频数据分片(8KB) + loop 分片传输 + Device->>Server: WebSocket发送
{"command": "VoiceChatRequest", "data": {"audioBase64": "...", "chatId": "...", "finished": false}} + end + Device->>Server: WebSocket发送
{"command": "VoiceChatRequest", "data": {"finished": true}} + + Server->>Server: 重组音频数据 + Server->>Server: 语音识别和处理 + loop 流式响应 + Server->>Device: WebSocket推送
{"command": "VoiceChatResponse", "data": {"chatId": "...", "finished": false, "data": {...}}} + Device->>Device: 播放响应音频片段 + end + Server->>Device: WebSocket推送
{"command": "VoiceChatResponse", "data": {"finished": true}} + + and 角色切换 + MiniProgram->>Server: 用户在小程序切换角色 + Server->>Device: WebSocket推送
{"command": "CharacterChange", "data": {...}} + Device->>Device: 应用新角色配置 + and 哄睡模式控制 + MiniProgram->>Server: 用户启动哄睡模式 + Server->>Device: WebSocket推送
{"command": "SleepModeStart", "data": {"storyTheme": "..."}} + Device->>Device: 启动哄睡模式 + and 设备绑定通知 + MiniProgram->>Server: 用户绑定设备 + Server->>Device: WebSocket推送
{"command": "DeviceBind", "data": {"userId": "...", "userName": "..."}} + Device->>Device: 更新绑定状态 + and 日程提醒 + Server->>Device: WebSocket推送
{"command": "ScheduleReminder", "data": {"scheduleName": "...", "reminderContent": "..."}} + Device->>Device: 播放提醒内容 + end + + else Token验证失败 + Server->>Device: 连接拒绝(401) + Device->>Device: 重新获取Token后重连 + end + + %% 连接断开 + alt 正常断开 + Device->>Server: 关闭连接 + Server->>Server: 清理设备会话 + else 异常断开 + Server->>Server: 检测到连接断开 + Server->>Server: 清理设备会话 + Device->>Device: 自动重连机制 + end +``` + +### 接口描述 +WebSocket连接端点,用于设备与服务器建立实时双向通信连接。该连接需要设备Token认证。 + +### 连接信息 +- **URL**: `wss://ambybox-api.test.leiniao.com/ambybox-device-api/v1/websocket` +- **协议**: `WebSocket` +- **认证**: 设备Token认证 + +### 连接认证 +WebSocket连接需要设备Token认证,有以下两种方式传递Token: + +1. **请求头方式**: + ``` + deviceId: DEVICE_001 + deviceToken: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... + reconnect: 0 + ``` + +2. **查询参数方式**: + ``` + wss://ambybox-api.test.leiniao.com/ambybox-device-api/v1/websocket?deviceToken=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... + ``` + +### 连接参数 +| 参数名 | 类型 | 必填 | 描述 | 示例 | +|--------|------|------|------|------| +| deviceId | String | 是 | 设备唯一标识符(请求头) | "DEVICE_001" | +| deviceToken | String | 是 | 设备访问令牌 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." | +| reconnect | Integer | 否 | 重连次数,设备启动时设置为0,断线重连递增 | 0 | + +### 连接状态码 +| 状态码 | 描述 | +|--------|------| +| 400 | 设备ID为空 | +| 401 | 设备Token无效或过期 | +| 101 | 正常连接 | + + +### 连接流程 +1. 客户端携带设备Token发起WebSocket连接 +2. 服务器验证Token有效性和设备ID匹配性 +3. 连接建立成功,设备会话被管理器记录 +4. 客户端可发送心跳消息保持连接活跃 +5. 连接断开时,设备会话从管理器中移除 + +### 错误处理 +- **认证失败**:Token无效或设备ID不匹配,连接被拒绝 +- **传输错误**:连接出现异常时,服务器会主动关闭连接 +- **心跳超时**:长时间无消息交互时,连接可能被服务器关闭 + + + + +### 客户端业务消息 + +客户端可以通过WebSocket连接发送业务消息给服务端,目前支持心跳、语音对话分片传输、语音选择故事和随机故事请求。 + +#### 心跳请求 (Ping) +**命令描述**:客户端发送心跳请求,保持WebSocket连接活跃 +**协议说明**: +- 建议每30秒发送一次心跳请求 +- 服务端收到心跳请求后会立即响应Pong消息 +- 心跳机制用于检测连接状态和保持连接活跃 + +**消息格式**: +```json +{ + "deviceId": "设备ID", + "timestamp": 1640995200, + "messageId": "消息ID", + "command": "Ping", + "data": null +} +``` + +**消息示例**: +```json +{ + "deviceId": "DEVICE_001", + "timestamp": 1640995200, + "messageId": "ping_001", + "command": "Ping", + "data": null +} +``` + +**服务端响应**: +服务端收到心跳请求后,会立即通过WebSocket推送Pong响应: + +```json +{ + "deviceId": "DEVICE_001", + "timestamp": 1640995201, + "messageId": "pong_001", + "command": "Pong", + "data": null +} +``` + +**协议说明**: +- 客户端发送Ping消息后应等待服务端的Pong响应 +- 如果长时间未收到Pong响应,说明连接可能异常,建议重新连接 +- 心跳间隔建议设置为30秒,过于频繁会增加服务器负载 + +#### 语音对话分片传输 (VoiceChatRequest) +**命令描述**:客户端发送语音对话请求,支持大音频文件的分片传输 +**协议说明**: +- 大于8KB的音频数据应分片传输 +- 每个分片应包含相同的`chatId`用于服务端重组,建议使用UUIDv4算法生成 +- 最后一个分片应设置`finished=true`,否则设置为`finished=false` + +**消息格式**: +```json +{ + "deviceId": "设备ID", + "timestamp": 1640995200, + "messageId": "消息ID", + "command": "VoiceChatRequest", + "data": { + "audioBase64": "音频数据Base64编码(分片)", + "chatId": "对话ID,用于标识本次对话", + "finished": false + } +} +``` + +**分片传输示例**: +```json +// 第一个分片 +{ + "deviceId": "DEVICE_001", + "timestamp": 1640995200, + "messageId": "msg_001", + "command": "VoiceChatRequest", + "data": { + "audioBase64": "UklGRiQAAABXQVZFZm10IBAAAAABAAEA...", + "chatId": "chat_12345", + "finished": false + } +} + +// 第二个分片 +{ + "deviceId": "DEVICE_001", + "timestamp": 1640995201, + "messageId": "msg_002", + "command": "VoiceChatRequest", + "data": { + "audioBase64": "AAEAAQABAAEAAQABAAEAAQABAAEAAQAB...", + "chatId": "chat_12345", + "finished": false + } +} + +// 最后一个分片 +{ + "deviceId": "DEVICE_001", + "timestamp": 1640995202, + "messageId": "msg_003", + "command": "VoiceChatRequest", + "data": { + "audioBase64": "BAAEAAQABAAEAAQABAAEAAQABAAEAAAA...", + "chatId": "chat_12345", + "finished": true + } +} +``` + +**data字段说明**: + +| 字段名 | 类型 | 描述 | +|--------|------|------| +| audioBase64 | String | 音频数据Base64编码,单个分片不超过8KB原始数据 | +| chatId | String | 对话ID,用于标识本次对话,建议使用UUID | +| finished | Boolean | 是否为最后一个分片,true表示传输完成 | + +**分片传输规则**: +1. 原始音频数据按8KB分片 +2. 每个分片转换为Base64编码后发送 +3. 所有分片使用相同的chatId +4. 只有最后一个分片设置finished=true + +**服务端响应**: +服务端收到完整音频后,会通过WebSocket推送VoiceChatResponse消息: + +```json +{ + "deviceId": "DEVICE_001", + "timestamp": 1640995203, + "messageId": "msg_004", + "command": "VoiceChatResponse", + "data": { + "chatId": "chat_12345", + "finished": false, + "data": { + "textContent": "你好,我听到了你的话", + "audioUrl": "https://example.com/response_audio.mp3" + } + } +} +``` + +#### 语音选择故事请求 (ChooseStoryRequest) +**命令描述**:客户端发送语音选择故事请求,用户通过语音表达故事偏好,系统根据语音内容选择相应故事 +**协议说明**: +- 支持音频数据分片传输,大于8KB的音频数据应分片传输 +- 每个分片应包含相同的`chatId`用于服务端重组,建议使用UUIDv4算法生成 +- 最后一个分片应设置`finished=true`,否则设置为`finished=false` +- 服务端会通过WebSocket推送ChooseStoryResponse消息流式返回故事内容 + +**消息格式**: +```json +{ + "deviceId": "设备ID", + "timestamp": 1640995200, + "messageId": "消息ID", + "command": "ChooseStoryRequest", + "data": { + "audioBase64": "音频数据Base64编码(分片)", + "chatId": "对话ID,用于标识本次故事请求", + "finished": false + } +} +``` + +**分片传输示例**: +```json +// 第一个分片 +{ + "deviceId": "DEVICE_001", + "timestamp": 1640995200, + "messageId": "story_001", + "command": "ChooseStoryRequest", + "data": { + "audioBase64": "UklGRiQAAABXQVZFZm10IBAAAAABAAEA...", + "chatId": "story_12345", + "finished": false + } +} + +// 最后一个分片 +{ + "deviceId": "DEVICE_001", + "timestamp": 1640995202, + "messageId": "story_002", + "command": "ChooseStoryRequest", + "data": { + "audioBase64": "BAAEAAQABAAEAAQABAAEAAQABAAEAAAA...", + "chatId": "story_12345", + "finished": true + } +} +``` + +**data字段说明**: + +| 字段名 | 类型 | 描述 | +|--------|------|------| +| audioBase64 | String | 音频数据Base64编码,单个分片不超过8KB原始数据 | +| chatId | String | 对话ID,用于标识本次故事请求,建议使用UUID | +| finished | Boolean | 是否为最后一个分片,true表示传输完成 | + +**服务端响应**: +服务端收到完整音频后,会通过WebSocket推送ChooseStoryResponse消息流式返回故事内容: + +```json +{ + "deviceId": "DEVICE_001", + "timestamp": 1640995203, + "messageId": "story_response_001", + "command": "ChooseStoryResponse", + "data": { + "chatId": "story_12345", + "finished": false, + "data": { + "storyTheme": "童话故事", + "storyContent": "从前有一个可爱的小女孩", + "storyAudioUrl": "https://example.com/story/audio/chunk_001.mp3", + "storyDuration": null + }, + "errorCode": null + } +} +``` + +#### 随机故事请求 (RandomStoryRequest) +**命令描述**:客户端发送随机故事请求,系统会根据指定主题(可选)生成随机故事 +**协议说明**: +- 无需音频数据,仅通过文本参数指定故事主题 +- 故事主题为可选参数,不指定时系统会随机选择主题 +- 服务端会通过WebSocket推送RandomStoryResponse消息流式返回故事内容 + +**消息格式**: +```json +{ + "deviceId": "设备ID", + "timestamp": 1640995200, + "messageId": "消息ID", + "command": "RandomStoryRequest", + "data": { + "chatId": "对话ID,用于标识本次故事请求", + "storyTheme": "故事主题(可选)" + } +} +``` + +**消息示例**: +```json +{ + "deviceId": "DEVICE_001", + "timestamp": 1640995200, + "messageId": "random_story_001", + "command": "RandomStoryRequest", + "data": { + "chatId": "random_story_12345", + "storyTheme": "冒险" + } +} +``` + +**data字段说明**: + +| 字段名 | 类型 | 描述 | +|--------|------|------| +| chatId | String | 对话ID,用于标识本次故事请求,建议使用UUID | +| storyTheme | String | 故事主题,可选参数。如"童话"、"冒险"、"科幻"等,不指定时系统随机选择 | + +**服务端响应**: +服务端收到随机故事请求后,会通过WebSocket推送RandomStoryResponse消息流式返回故事内容: + +```json +{ + "deviceId": "DEVICE_001", + "timestamp": 1640995201, + "messageId": "random_story_response_001", + "command": "RandomStoryResponse", + "data": { + "chatId": "random_story_12345", + "finished": false, + "data": { + "storyTheme": "冒险故事", + "storyContent": "在一个阳光明媚的早晨", + "storyAudioUrl": "https://example.com/story/audio/chunk_001.mp3", + "storyDuration": null + }, + "errorCode": null + } +} +``` + +### 服务端业务消息 + +服务端通过WebSocket连接推送业务消息给设备端,消息采用JSON格式,消息结构如下: + +```json +{ + "deviceId": "设备ID", + "timestamp": 1640995200, + "messageId": "消息ID,用于设备去重防重放", + "command": "命令名称", + "data": { + // 具体的消息数据,根据不同命令名称而变化 + } +} +``` + +#### 消息结构说明 +| 字段名 | 类型 | 描述 | +|--------|------|------| +| deviceId | String | 设备唯一标识符 | +| timestamp | Long | 时间戳,单位:秒 | +| messageId | String | 消息ID,用于设备去重防重放 | +| command | String | 命令名称,见下方支持的命令列表 | +| data | Object | 消息体,不同命令对应不同的数据结构 | + +支持的命令名称如下: + +#### 1. 心跳响应 (Pong) +**命令描述**:服务端响应客户端的心跳请求 +**消息示例**: +```json +{ + "deviceId": "DEVICE_001", + "timestamp": 1640995201, + "messageId": "pong_001", + "command": "Pong", + "data": null +} +``` + +**data字段说明**:无数据字段 + +#### 2. 语音对话响应 (VoiceChatResponse) +**命令描述**:服务端推送语音对话响应,流式返回对话结果 +**消息示例**: +```json +{ + "deviceId": "DEVICE_001", + "timestamp": 1640995203, + "messageId": "chat_response_001", + "command": "VoiceChatResponse", + "data": { + "chatId": "chat_12345", + "finished": false, + "data": { + "textContent": "你好,我听到了你的话", + "audioUrl": "https://example.com/response_audio.mp3", + "intention": null + }, + "errorCode": null + } +} +``` + +**data字段说明**: + +| 字段名 | 类型 | 描述 | +|--------|------|------| +| chatId | String | 对话ID,与客户端请求中的chatId对应 | +| finished | Boolean | 是否为最后一个响应片段 | +| data | Object | 响应数据,包含文本内容、音频URL等 | +| errorCode | String | 错误码,正常情况下为null | + +**data.data字段说明**: + +| 字段名 | 类型 | 描述 | +|--------|------|------| +| textContent | String | 回复的文本内容 | +| audioUrl | String | 回复音频文件URL | +| intention | String | 用户意图识别结果,如"StartSleepMode"等 | + +#### 3. 语音选择故事响应 (ChooseStoryResponse) +**命令描述**:服务端推送语音选择故事响应,流式返回故事内容 +**消息示例**: +```json +{ + "deviceId": "DEVICE_001", + "timestamp": 1640995203, + "messageId": "story_response_001", + "command": "ChooseStoryResponse", + "data": { + "chatId": "story_12345", + "finished": false, + "data": { + "storyTheme": "童话故事", + "storyContent": "从前有一个可爱的小女孩", + "storyAudioUrl": "https://example.com/story/audio/chunk_001.mp3", + "storyDuration": null + }, + "errorCode": null + } +} +``` + +**data字段说明**: + +| 字段名 | 类型 | 描述 | +|--------|------|------| +| chatId | String | 对话ID,与客户端请求中的chatId对应 | +| finished | Boolean | 是否为最后一个故事片段 | +| data | Object | 故事数据,包含主题、内容、音频等 | +| errorCode | String | 错误码,正常情况下为null | + +**data.data字段说明**: + +| 字段名 | 类型 | 描述 | +|--------|------|------| +| storyTheme | String | 故事主题 | +| storyContent | String | 故事片段文本内容 | +| storyAudioUrl | String | 故事片段音频文件URL | +| storyDuration | Integer | 故事片段时长(秒) | + +#### 4. 随机故事响应 (RandomStoryResponse) +**命令描述**:服务端推送随机故事响应,流式返回故事内容 +**消息示例**: +```json +{ + "deviceId": "DEVICE_001", + "timestamp": 1640995201, + "messageId": "random_story_response_001", + "command": "RandomStoryResponse", + "data": { + "chatId": "random_story_12345", + "finished": false, + "data": { + "storyTheme": "冒险故事", + "storyContent": "在一个阳光明媚的早晨", + "storyAudioUrl": "https://example.com/story/audio/chunk_001.mp3", + "storyDuration": null + }, + "errorCode": null + } +} +``` + +**data字段说明**:与语音选择故事响应相同 + +#### 5. 角色切换 (CharacterChange) +**命令描述**:通知设备切换到指定角色 +**消息示例**: +```json +{ + "deviceId": "DEVICE_001", + "timestamp": 1640995200, + "messageId": "msg_001", + "command": "CharacterChange", + "data": { + "characterId": "char_001", + "characterName": "小艾", + "characterAvatar": { + "eyesShape": "upturned-eyes", + "hairColor": "blue", + "eyesColor": "green", + "hairStyle": "side-parted", + "screenImage": "https://example.com/avatar.jpg", + "screenImageAsEmoji": false + } + } +} +``` + +**data字段说明**: + +| 字段名 | 类型 | 描述 | +|--------|------|------| +| characterId | String | 角色ID | +| characterName | String | 角色名称 | +| characterAvatar | Object | 角色形象配置 | + +**characterAvatar字段说明**: + +| 字段名 | 类型 | 描述 | 可选值 | +|--------|------|------|--------| +| eyesShape | String | 眼型 | calm-eyes, round-eyes, upturned-eyes | +| eyesColor | String | 瞳色 | red, yellow, blue, green, purple, brown, purple-orange-gradient, blue-pink-gradient, yellow-green-gradient | +| hairColor | String | 发色 | light-brown, dark-brown, black, white, gray, blue, purple, gold | +| hairStyle | String | 发型 | side-parted-comma, m-shaped-bangs, diagonal-bangs, center-parted-bangs, straight-bangs, side-parted-texture | +| screenImage | String | 屏保图片URL | | +| screenImageAsEmoji | Boolean | 屏保图片是否作为对话表情 | 默认false | + +**枚举说明**: + +眼型 +| 值 | 描述 | +|--------|------| +| calm-eyes | 平缓眼 | +| round-eyes | 圆眼 | +| upturned-eyes | 上挑眼 | + +瞳色 +| 值 | 描述 | +|--------|------| +| red | 红 | +| yellow | 黄 | +| blue | 蓝 | +| green | 绿 | +| purple | 紫 | +| brown | 棕 | +| purple-orange-gradient | 紫橙渐变 | +| blue-pink-gradient | 蓝粉渐变 | +| yellow-green-gradient | 黄绿渐变 | + +发色 +| 值 | 描述 | +|--------|------| +| light-brown | 浅棕色 | +| dark-brown | 深棕色 | +| black | 黑色 | +| white | 白色 | +| gray | 灰色 | +| blue | 蓝色 | +| purple | 紫色 | +| gold | 金色 | + +发型 +| 值 | 描述 | +|--------|------| +| side-parted-comma | 偏分逗号 | +| m-shaped-bangs | M形刘海 | +| diagonal-bangs | 斜刘海 | +| center-parted-bangs | 中分刘海 | +| straight-bangs | 齐刘海 | +| side-parted-texture | 偏分纹理 | + + +#### 6. 角色配置更改 (CharacterConfigChange) +**命令描述**:更新当前角色的配置信息 +**消息示例**: +```json +{ + "deviceId": "DEVICE_001", + "timestamp": 1640995200, + "messageId": "msg_002", + "command": "CharacterConfigChange", + "data": { + "characterId": "char_001", + "characterName": "小艾", + "characterAvatar": { + "eyesShape": "upturned-eyes", + "hairColor": "blue", + "eyesColor": "green", + "hairStyle": "side-parted-comma", + "screenImage": "https://example.com/new_avatar.jpg", + "screenImageAsEmoji": true + } + } +} +``` + +**data字段说明**:与角色切换命令相同 + +#### 7. 哄睡模式开始 (SleepModeStart) +**命令描述**:通知设备启动哄睡模式 +**消息示例**: +```json +{ + "deviceId": "DEVICE_001", + "timestamp": 1640995200, + "messageId": "msg_003", + "command": "SleepModeStart", + "data": { + "storyTheme": "爱情故事" + } +} +``` + +**data字段说明**: + +| 字段名 | 类型 | 描述 | +|--------|------|------| +| storyTheme | String | 故事主题 | + +设备收到此命令后,使用推送的主题调用randomStory接口,播放故事。 + + +#### 8. 哄睡模式结束 (SleepModeEnd) +**命令描述**:通知设备结束哄睡模式 +**消息示例**: +```json +{ + "deviceId": "DEVICE_001", + "timestamp": 1640995200, + "messageId": "msg_004", + "command": "SleepModeEnd", + "data": {} +} +``` + +**data字段说明**:无特殊字段 + +设备收到改命令后,需要停止播放故事。 + + +#### 9. 哄睡模式故事内容 + +设备收到`SleepModeStart` 命令后,需要调用`/v1/story/randomStory`接口获取故事内容。 + + + +#### 10. 设备绑定 (DeviceBind) +**命令描述**:通知设备已被用户绑定 +**消息示例**: +```json +{ + "deviceId": "DEVICE_001", + "timestamp": 1640995200, + "messageId": "msg_007", + "command": "DeviceBind", + "data": { + "userId": "user_001", + "userName": "张三", + "bindTime": 1640995200 + } +} +``` + +**data字段说明**: + +| 字段名 | 类型 | 描述 | +|--------|------|------| +| userId | String | 用户ID | +| userName | String | 用户名称 | +| bindTime | Long | 绑定时间戳,单位:秒 | + +#### 11. 设备解绑 (DeviceUnbind) +**命令描述**:通知设备已被用户解绑 +**消息示例**: +```json +{ + "deviceId": "DEVICE_001", + "timestamp": 1640995200, + "messageId": "msg_008", + "command": "DeviceUnbind", + "data": { + "userId": "user_001", + "reason": "用户主动解绑", + "unbindTime": 1640995200 + } +} +``` + +**data字段说明**: + +| 字段名 | 类型 | 描述 | +|--------|------|------| +| userId | String | 用户ID | +| reason | String | 解绑原因 | +| unbindTime | Long | 解绑时间戳,单位:秒 | + +#### 12. 日程提醒 (ScheduleReminder) +**命令描述**:推送日程提醒信息 +**消息示例**: +```json +{ + "deviceId": "DEVICE_001", + "timestamp": 1640995200, + "messageId": "msg_009", + "command": "ScheduleReminder", + "data": { + "scheduleId": 123, + "deviceId": "DEVICE_001", + "scheduleName": "开会提醒", + "reminderContent": "您有一个会议即将开始,请准备参加", + "reminderAudioUrl": "https://example.com/reminder/reminder_001.mp3", + "enableVoice": 1, + "enableVibration": 1 + } +} +``` + +**data字段说明**: + +| 字段名 | 类型 | 描述 | +|--------|------|------| +| scheduleId | Integer | 日程ID | +| deviceId | String | 设备ID | +| scheduleName | String | 日程名称 | +| reminderContent | String | 提醒内容 | +| reminderAudioUrl | String | 提醒音频文件URL | +| enableVoice | Integer | 是否启用语音,1:启用,0:未启用 | +| enableVibration | Integer | 是否启用震动,1:启用,0:未启用 | + +#### 13. 日程提醒配置 (ScheduleReminderConfig) +**命令描述**:配置日程提醒相关设置 +**消息示例**: +```json +{ + "deviceId": "DEVICE_001", + "timestamp": 1640995200, + "messageId": "msg_010", + "command": "ScheduleReminderConfig", + "data": { + "scheduleName": "七点起床" + } +} +``` + +**data字段说明**: + +| 字段名 | 类型 | 描述 | +|--------|------|------| +| scheduleName | String | 日程名称 | + +#### 14. 下载表情 (DownloadEmoji) +**命令描述**:通知设备下载指定角色的表情包 +**消息示例**: +```json +{ + "deviceId": "DEVICE_001", + "timestamp": 1640995200, + "messageId": "msg_011", + "command": "DownloadEmoji", + "data": { + "characterId": "char_001", + "eyesColor": "blue", + "eyesShape": "calm-eyes", + "emotionEmojis": [ + { + "emotion": "happy", + "emojiUrl": "https://example.com/emoji/happy.mp4" + }, + { + "emotion": "sad", + "emojiUrl": "https://example.com/emoji/sad.mp4" + } + ] + } +} +``` + +**data字段说明**: + +| 字段名 | 类型 | 描述 | +|--------|------|------| +| characterId | String | 角色ID | +| eyesColor | String | 瞳色,用于标识表情包组合 | +| eyesShape | String | 眼型,用于标识表情包组合 | +| emotionEmojis | Array | 表情列表 | + +**emotionEmojis字段说明**: + +| 字段名 | 类型 | 描述 | +|--------|------|------| +| emotion | String | 心情类型,如"happy"、"sad"、"angry"等 | +| emojiUrl | String | 表情动画文件URL,通常为mp4格式 | + +**设备处理说明**: +- 设备收到此命令后,应下载所有emotionEmojis中的表情文件 +- 下载完成后,调用`/v1/emoji/reportEmoji`接口上报下载状态 +- 表情文件建议缓存到本地,以便快速显示 + +#### 15. 删除表情 (DeleteEmoji) +**命令描述**:通知设备删除指定角色的表情包 +**消息示例**: +```json +{ + "deviceId": "DEVICE_001", + "timestamp": 1640995200, + "messageId": "msg_012", + "command": "DeleteEmoji", + "data": { + "characterId": "char_001", + "eyesColor": "blue", + "eyesShape": "calm-eyes" + } +} +``` + +**data字段说明**: + +| 字段名 | 类型 | 描述 | +|--------|------|------| +| characterId | String | 角色ID | +| eyesColor | String | 瞳色,用于标识要删除的表情包组合 | +| eyesShape | String | 眼型,用于标识要删除的表情包组合 | + +**设备处理说明**: +- 设备收到此命令后,应删除对应角色和瞳色、眼型组合的所有表情文件 +- 删除完成后,调用`/v1/emoji/reportEmoji`接口上报删除状态 +- 删除操作应清理本地缓存的表情文件 + + +## 7. 表情管理接口 + +### 时序图 + +```mermaid +sequenceDiagram + participant Device as 设备端 + participant Server as 服务端 + participant MiniProgram as 小程序端 + + Note over Device,MiniProgram: 表情管理流程 + + %% 表情下载流程 + rect rgb(240, 248, 255) + Note over MiniProgram, Device: 1. 表情下载流程 + MiniProgram->>Server: 用户在小程序选择下载表情 + Server->>Device: WebSocket推送
{"command": "DownloadEmoji", "data": {...}} + Device->>Device: 下载表情文件到本地 + Device->>Server: POST /v1/emoji/reportEmoji
上报下载状态 + Server->>MiniProgram: 同步表情状态到小程序 + end + + %% 表情删除流程 + rect rgb(255, 250, 240) + Note over MiniProgram, Device: 2. 表情删除流程 + MiniProgram->>Server: 用户在小程序删除表情 + Server->>Device: WebSocket推送
{"command": "DeleteEmoji", "data": {...}} + Device->>Device: 删除本地表情文件 + Device->>Server: POST /v1/emoji/reportEmoji
上报删除状态 + Server->>MiniProgram: 同步表情状态到小程序 + end + + %% 定时同步流程 + rect rgb(245, 255, 250) + Note over Device, Server: 3. 定时同步流程 + Device->>Device: 每日0点触发 + Device->>Server: POST /v1/emoji/reportAllEmoji
上报所有表情状态 + Server->>Server: 更新设备表情状态 + end +``` + +### 7.1 上报角色表情组下载状态 + +#### 接口描述 +上报角色表情组下载状态接口。设备收到删除表情指令或者下载表情指令,执行成功后调用该接口,用以同步表情下载状态到小程序端。该接口需要设备Token认证。 + +#### 请求信息 +- **URL**: `/v1/emoji/reportEmoji` +- **方法**: `POST` +- **Content-Type**: `application/json` + +#### 认证要求 +该接口需要设备Token认证,Token通过请求头传递。 + +#### 请求头参数 +| 参数名 | 类型 | 必填 | 描述 | 示例 | +|--------|------|------|------|------| +| deviceId | String | 是 | 设备唯一标识符 | "DEVICE_001" | +| deviceToken | String | 是 | 设备访问令牌 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." | + +#### 请求体参数 +| 参数名 | 类型 | 必填 | 描述 | 示例 | +|--------|------|------|------|------| +| characterId | String | 是 | 角色ID | "char_001" | +| eyesColor | String | 是 | 瞳色 | "blue" | +| eyesShape | String | 是 | 眼型 | "calm-eyes" | +| status | String | 是 | 状态 | "download_success", "download_fail", "delete_success", "delete_fail" | + +#### 请求示例 +```json +{ + "characterId": "char_001", + "eyesColor": "blue", + "eyesShape": "calm-eyes", + "status": "download_success" +} +``` + +#### 响应数据 +**成功响应**: +```json +{ + "errorCode": null, + "message": null, + "data": "ok" +} +``` + +**失败响应(认证失败)**: +```json +{ + "errorCode": "AUTH_FAILURE", + "message": "设备Token无效", + "data": null +} +``` + +#### 响应字段说明 +| 字段名 | 类型 | 描述 | +|--------|------|------| +| data | String | 操作结果,成功时返回"ok" | + +#### 错误码 +| 错误码 | 描述 | +|--------|------| +| AUTH_FAILURE | 认证失败(设备ID为空或Token无效) | +| MISSING_PARAMETER | 必填参数缺失 | +| SERVER_ERROR | 服务器内部错误 | + +#### 使用说明 +- 该接口用于设备向服务器同步表情下载/删除状态 +- 设备执行表情下载或删除操作成功后,必须调用此接口 +- 服务器会根据上报的信息更新小程序端的表情状态显示 +- characterId、eyesColor、eyesShape三个字段组合标识一个唯一的表情组 + +### 7.2 上报设备所有表情组状态 + +#### 接口描述 +上报设备已下载的所有非预设表情组接口。需每天0点上报一次,同步表情下载状态到小程序端。该接口需要设备Token认证。 + +#### 请求信息 +- **URL**: `/v1/emoji/reportAllEmoji` +- **方法**: `POST` +- **Content-Type**: `application/json` + +#### 认证要求 +该接口需要设备Token认证,Token通过请求头传递。 + +#### 请求头参数 +| 参数名 | 类型 | 必填 | 描述 | 示例 | +|--------|------|------|------|------| +| deviceId | String | 是 | 设备唯一标识符 | "DEVICE_001" | +| deviceToken | String | 是 | 设备访问令牌 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." | + +#### 请求体参数 +| 参数名 | 类型 | 必填 | 描述 | 示例 | +|--------|------|------|------|------| +| emojiGroups | Array | 是 | 已下载的表情组列表 | 见下方示例 | + +#### EmojiGroup对象说明 +| 字段名 | 类型 | 必填 | 描述 | 示例 | +|--------|------|------|------|------| +| characterId | String | 是 | 角色ID | "char_001" | +| eyesColor | String | 是 | 瞳色 | "blue" | +| eyesShape | String | 是 | 眼型 | "calm-eyes" | +| status | String | 是 | 状态 | "download_success", "download_fail", "delete_success", "delete_fail" | + +#### 请求示例 +```json +{ + "emojiGroups": [ + { + "characterId": "char_001", + "eyesColor": "blue", + "eyesShape": "calm-eyes", + "status": "download_success" + }, + { + "characterId": "char_001", + "eyesColor": "green", + "eyesShape": "round-eyes", + "status": "download_success" + }, + { + "characterId": "char_002", + "eyesColor": "red", + "eyesShape": "upturned-eyes", + "status": "download_success" + } + ] +} +``` + +#### 响应数据 +**成功响应**: +```json +{ + "errorCode": null, + "message": null, + "data": "ok" +} +``` + +**失败响应(认证失败)**: +```json +{ + "errorCode": "AUTH_FAILURE", + "message": "设备Token无效", + "data": null +} +``` + +#### 响应字段说明 +| 字段名 | 类型 | 描述 | +|--------|------|------| +| data | String | 操作结果,成功时返回"ok" | + +#### 错误码 +| 错误码 | 描述 | +|--------|------| +| AUTH_FAILURE | 认证失败(设备ID为空或Token无效) | +| MISSING_PARAMETER | 必填参数缺失 | +| SERVER_ERROR | 服务器内部错误 | + +#### 使用说明 +- 该接口用于设备定时向服务器同步所有已下载的表情组状态 +- 建议每天0点自动调用一次,确保服务器端数据与设备端一致 +- emojiGroups数组包含设备当前已下载的所有非预设表情组 +- 空数组表示设备没有下载任何非预设表情组 +- 服务器会根据上报的信息更新小程序端的表情状态显示 + +### 表情管理接口使用流程 +1. **表情下载**:小程序端触发表情下载 → 服务器通过WebSocket推送DownloadEmoji命令 → 设备下载表情文件 → 设备调用reportEmoji接口上报状态 +2. **表情删除**:小程序端触发表情删除 → 服务器通过WebSocket推送DeleteEmoji命令 → 设备删除表情文件 → 设备调用reportEmoji接口上报状态 +3. **状态同步**:设备每日0点调用reportAllEmoji接口 → 服务器更新设备表情状态 → 小程序端显示最新状态 + +### 表情包标识说明 +表情包通过三个字段的组合进行唯一标识: +- **characterId**:角色ID,标识表情属于哪个角色 +- **eyesColor**:瞳色 +- **eyesShape**:眼型 + +相同characterId、eyesColor、eyesShape组合的所有表情文件构成一个表情组,设备需要统一管理这些表情文件的下载和删除。 + +--- + +## 8. 二维码生成接口 + +### 接口描述 +生成二维码图片接口,根据提供的内容生成PNG格式的二维码图片并直接返回图片二进制数据。该接口无需认证。 + +### 请求信息 +- **URL**: `/v1/qrcode/generate/image` +- **方法**: `POST` +- **Content-Type**: `application/json` + +### 请求参数 +| 参数名 | 类型 | 必填 | 描述 | 限制 | 默认值 | +|--------|------|------|------|------|--------| +| content | String | 是 | 二维码内容 | 不能为空 | - | +| width | Integer | 否 | 图片宽度 | 100-1000像素 | 300 | +| height | Integer | 否 | 图片高度 | 100-1000像素 | 300 | +| withLogo | Boolean | 否 | 是否包含Logo | - | false | + +### 请求示例 +```json +{ + "content": "https://example.com/device/bind?deviceId=DEVICE_001", + "width": 300, + "height": 300, + "withLogo": false +} +``` + +### 响应数据 +**成功响应**: +- **Content-Type**: `image/png` +- **Content-Disposition**: `inline; filename=qrcode.png` +- **响应体**: PNG格式的二维码图片二进制数据 + +**失败响应**: +- **400 Bad Request**: 请求参数无效(如content为空) +- **500 Internal Server Error**: 服务器内部错误 + +### 响应头说明 +| 头字段名 | 值 | 描述 | +|---------|-----|------| +| Content-Type | image/png | 响应内容类型为PNG图片 | +| Content-Length | 图片字节数 | 图片文件大小 | +| Content-Disposition | inline; filename=qrcode.png | 内联显示,文件名为qrcode.png | + +### 参数验证规则 +- **content**: 不能为空或空字符串 +- **width**: 如果提供,必须在100-1000像素范围内,否则使用默认值300 +- **height**: 如果提供,必须在100-1000像素范围内,否则使用默认值300 +- **withLogo**: 可选参数,默认为false + +### 使用场景 +此接口主要用于: +1. 设备绑定:生成包含设备ID的绑定二维码 +2. 设备信息展示:生成设备相关信息的二维码 +3. 通用二维码生成:根据任意文本内容生成二维码 + +### 客户端处理说明 +- 响应为二进制图片数据,可直接显示或保存 +- 建议客户端设置合理的超时时间 +- 可通过Content-Length头获取图片大小信息 + +--- + +## 9. 系统整体时序图 + +### 设备完整生命周期流程 + +```mermaid +sequenceDiagram + participant Device as 设备端 + participant Server as 服务端 + participant User as 用户 + participant MiniProgram as 小程序端 + + Note over Device,MiniProgram: 设备完整生命周期流程 + + %% 设备启动和认证 + rect rgb(240, 248, 255) + Note over Device, Server: 1. 设备启动和认证阶段 + Device->>Device: 设备开机启动 + Device->>Server: 获取设备Token (签名认证) + Server->>Device: 返回JWT Token + Device->>Server: 建立WebSocket连接 + Server->>Device: 连接成功 + end + + %% 设备初始化 + rect rgb(245, 255, 250) + Note over Device, Server: 2. 设备初始化阶段 + Device->>Server: 检查OTA升级 + alt 有升级 + Server->>Device: 返回升级包信息 + Device->>Device: 执行升级 + else 无升级 + Server->>Device: 无升级任务 + end + + Device->>Server: 获取设备角色信息 + Server->>Device: 返回角色配置 + Device->>Device: 应用角色设置 + + Device->>Server: 检查用户绑定状态 + alt 未绑定 + Server->>Device: 返回未绑定 + Device->>Server: 生成绑定二维码 + Server->>Device: 返回二维码图片 + Device->>Device: 显示二维码等待绑定 + else 已绑定 + Server->>Device: 返回用户ID + Device->>Device: 进入正常运行模式 + end + end + + %% 用户交互阶段 + rect rgb(255, 250, 240) + Note over Device, MiniProgram: 3. 用户交互阶段 + + par 语音对话 + User->>Device: 语音输入 + Device->>Server: 语音对话接口 + Server->>Device: 返回回复和意图识别 + Device->>User: 播放回复音频 + + alt 识别到哄睡意图 + Device->>Server: 请求故事接口(SSE) + loop 故事流式推送 + Server->>Device: 推送故事片段 + Device->>User: 播放故事音频 + end + Device->>Server: 结束哄睡模式 + end + + and 小程序控制 + MiniProgram->>Server: 角色切换/哄睡控制 + Server->>Device: WebSocket推送命令 + Device->>Device: 执行相应操作 + + and 系统提醒 + Server->>Device: WebSocket推送日程提醒 + Device->>User: 播放提醒内容 + end + end + + %% 运行维护 + rect rgb(248, 248, 255) + Note over Device, Server: 4. 运行维护阶段 + loop 持续运行 + Device->>Server: 心跳保持 + Server->>Device: 心跳响应 + end + + opt 定期检查升级 + Device->>Server: 检查OTA升级 + Server->>Device: 升级任务响应 + end + end +``` + +## 10. 通用说明 + +### 设备认证流程 +1. 设备首次启动时,使用签名算法调用获取设备Token接口 +2. 获取到Token后,后续所有API调用都需要携带该Token +3. Token过期后,需要重新获取新的Token + +### 安全注意事项 +- 签名密钥和Token密钥必须安全保存,不能泄露 +- 时间戳验证可以防止重放攻击 +- 使用HTTPS协议传输,确保数据安全 +- WebSocket连接建议使用WSS协议确保通信安全 + +### 开发调试 +- 签名算法实现时注意字符串拼接的顺序和分隔符 +- 时间戳必须是毫秒级别 +- WebSocket连接测试时注意Token的有效性 +- 建议实现心跳机制保持连接活跃 + +### 时序图说明 +本文档包含以下时序图: +1. **设备认证流程**:展示设备如何获取访问令牌 +2. **语音对话流程**:展示语音交互的完整过程 +3. **哄睡故事流程**:展示从意图识别到故事播放的完整流程 +4. **设备信息管理**:展示设备角色配置和用户绑定流程 +5. **WebSocket通信**:展示实时通信和命令推送流程 +6. **OTA升级流程**:展示固件升级的完整过程 +7. **表情管理流程**:展示表情下载、删除和状态同步的完整过程 +8. **系统整体流程**:展示设备完整生命周期的综合时序图 + +每个时序图都详细描述了参与方之间的交互序列,有助于开发者理解系统架构和实现细节。