M1.1 收尾: - 24项 P0/P1/P2 bug 修复 (Rust 107 tests + Flutter 15 tests) - Flutter App v0.3: cupertino_icons 修复, 单元测试, 调试面板, APK 52.6MB - 示例插件完善: manifest.json + 请求/响应示范 + 7个测试 - API 文档重写 (以 routes.rs 为唯一权威) - MILESTONES.md 更新至 100% M1.2 启动: - P0: 插件管理 API 闭环 (handle_manager_message Custom 分支 + broadcast_plugin_states) - ServiceManager 集成测试 8/8 (tests/m1_2_service_manager.rs) - M1.2 测试计划 (docs/M1.2_TEST_PLAN.md, 18个E2E场景) - 动态插件系统: auto_rollback + version_manager GC + 路径穿越防护 总计: Rust 115/115 测试, Flutter 15/15 测试, 零 warning Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1366 lines
25 KiB
Markdown
1366 lines
25 KiB
Markdown
# ShowenV2 HTTP API
|
||
|
||
## 基础信息
|
||
|
||
- Base URL: `http://<device-ip>:8080`
|
||
- API 前缀: `/api`
|
||
- 编码: `UTF-8`
|
||
- 认证: 当前版本局域网无认证,后续版本预留
|
||
- 权威实现: `src/plugins/http/routes.rs`
|
||
|
||
## 响应约定
|
||
|
||
### 成功响应
|
||
|
||
写操作接口统一返回:
|
||
|
||
```json
|
||
{
|
||
"status": "ok",
|
||
"message": "开始播放"
|
||
}
|
||
```
|
||
|
||
### 错误响应
|
||
|
||
除文件下载接口外,错误统一返回:
|
||
|
||
```json
|
||
{
|
||
"status": "error",
|
||
"message": "错误描述"
|
||
}
|
||
```
|
||
|
||
查询接口直接返回业务 JSON,不包裹 `status` 字段。
|
||
|
||
### 典型错误码
|
||
|
||
- `400 Bad Request`: 参数、JSON、文件名或配置内容非法
|
||
- `403 Forbidden`: 文件管理路径越界或目标目录不合法
|
||
- `404 Not Found`: 文件、目录、插件或配置不存在
|
||
- `409 Conflict`: 文件移动目标已存在
|
||
- `413 Payload Too Large`: 单文件超过 100 MB
|
||
- `500 Internal Server Error`: 内部发送消息失败、文件操作失败
|
||
- `502 Bad Gateway`: WiFi 插件返回无效 JSON
|
||
- `504 Gateway Timeout`: 等待 WiFi 插件响应超时
|
||
|
||
## Web UI
|
||
|
||
### GET /
|
||
|
||
- Method: `GET`
|
||
- Path: `/`
|
||
- Request Body: 无
|
||
- Response Example:
|
||
|
||
```html
|
||
<!doctype html>
|
||
<html lang="zh-CN">
|
||
<head><title>Showen 控制台</title></head>
|
||
<body>...</body>
|
||
</html>
|
||
```
|
||
|
||
- Error Response: 标准 HTTP 错误页面,无 JSON 包装
|
||
|
||
### GET /index.html
|
||
|
||
- Method: `GET`
|
||
- Path: `/index.html`
|
||
- Request Body: 无
|
||
- Response Example:
|
||
|
||
```html
|
||
<!doctype html>
|
||
<html lang="zh-CN">
|
||
<head><title>Showen 控制台</title></head>
|
||
<body>...</body>
|
||
</html>
|
||
```
|
||
|
||
- Error Response: 标准 HTTP 错误页面,无 JSON 包装
|
||
|
||
## WebSocket
|
||
|
||
### GET /ws
|
||
|
||
- Method: `GET` (Upgrade to WebSocket)
|
||
- Path: `/ws`
|
||
- Request Body: 无
|
||
- Response Example: 连接建立后服务端会先推送当前快照,再持续推送事件
|
||
- Error Response: WebSocket 握手失败时返回标准 HTTP 错误
|
||
|
||
### 服务端事件
|
||
|
||
#### `status_update`
|
||
|
||
```json
|
||
{
|
||
"type": "status_update",
|
||
"data": {
|
||
"running": true,
|
||
"paused": false,
|
||
"in_transition": false,
|
||
"current_index": 0,
|
||
"playlist_length": 3,
|
||
"current_video": "intro"
|
||
}
|
||
}
|
||
```
|
||
|
||
#### `state_update`
|
||
|
||
```json
|
||
{
|
||
"type": "state_update",
|
||
"data": {
|
||
"old_state": "rest",
|
||
"new_state": "interact"
|
||
}
|
||
}
|
||
```
|
||
|
||
#### `config_update`
|
||
|
||
```json
|
||
{
|
||
"type": "config_update",
|
||
"data": {
|
||
"display": {
|
||
"fullscreen": true,
|
||
"window_title": "Hologram Player - Cat",
|
||
"rotation": 0,
|
||
"flip_horizontal": true,
|
||
"flip_vertical": true,
|
||
"offset_x": 0,
|
||
"offset_y": 0,
|
||
"prevent_screen_lock": true,
|
||
"render_width": 1280,
|
||
"render_height": 800,
|
||
"output_width": null,
|
||
"output_height": null,
|
||
"scale_mode": "stretch",
|
||
"allow_upscale": true,
|
||
"perspective_correction": {
|
||
"enabled": false,
|
||
"points": [[0, 0], [1280, 0], [1280, 800], [0, 800]]
|
||
},
|
||
"chroma_key": {
|
||
"enabled": false,
|
||
"hsv_min": [0, 0, 200],
|
||
"hsv_max": [180, 30, 255],
|
||
"invert": false,
|
||
"feather": 3
|
||
},
|
||
"brightness_adjust": {
|
||
"enabled": true,
|
||
"subject_boost": 1.5,
|
||
"background_suppress": 0.3,
|
||
"threshold": 30
|
||
}
|
||
},
|
||
"playlist": [
|
||
{
|
||
"id": "anim_0",
|
||
"path": "videos/intro.mp4",
|
||
"duration": null,
|
||
"loop_count": 1,
|
||
"random_loop_range": null
|
||
}
|
||
],
|
||
"transition": {
|
||
"enabled": true,
|
||
"type": "fade",
|
||
"duration": 0.5
|
||
},
|
||
"playback": {
|
||
"loop_playlist": true,
|
||
"auto_start": true
|
||
},
|
||
"scenes": {
|
||
"rest": [],
|
||
"active": [],
|
||
"sleep": [],
|
||
"interact": [],
|
||
"state_machine": null
|
||
},
|
||
"remote_control": {
|
||
"enabled": true,
|
||
"host": "0.0.0.0",
|
||
"port": 8080
|
||
},
|
||
"ble": {
|
||
"enabled": true,
|
||
"device_name": "Showen"
|
||
},
|
||
"source_path": "/home/showen/Showen/ShowenV2/configs/cat_state_machine.json",
|
||
"source_dir": "/home/showen/Showen/ShowenV2/configs"
|
||
}
|
||
}
|
||
```
|
||
|
||
#### `wifi_update`
|
||
|
||
```json
|
||
{
|
||
"type": "wifi_update",
|
||
"data": {
|
||
"ok": true,
|
||
"networks": [
|
||
{
|
||
"ssid": "OfficeWiFi",
|
||
"signal": 78,
|
||
"security": "WPA2"
|
||
}
|
||
]
|
||
}
|
||
}
|
||
```
|
||
|
||
说明:该事件直接转发 WiFi 插件返回的 JSON,字段随具体命令而变。
|
||
|
||
#### `ble_update`
|
||
|
||
```json
|
||
{
|
||
"type": "ble_update",
|
||
"data": {
|
||
"ready": true
|
||
}
|
||
}
|
||
```
|
||
|
||
### 客户端可发送命令
|
||
|
||
WebSocket 文本消息需为 JSON;成功响应格式不是 HTTP API 的 `{ "status": "ok" }`,而是 WebSocket 专用格式:
|
||
|
||
```json
|
||
{
|
||
"ok": true,
|
||
"cmd": "play"
|
||
}
|
||
```
|
||
|
||
错误示例:
|
||
|
||
```json
|
||
{
|
||
"ok": false,
|
||
"cmd": "goto",
|
||
"error": "missing index"
|
||
}
|
||
```
|
||
|
||
已支持命令示例:
|
||
|
||
```json
|
||
{"cmd":"play"}
|
||
{"cmd":"pause"}
|
||
{"cmd":"next"}
|
||
{"cmd":"previous"}
|
||
{"cmd":"goto","index":3}
|
||
{"cmd":"scene","name":"rest"}
|
||
{"cmd":"trigger","name":"voice","value":"name"}
|
||
{"cmd":"connect","ssid":"OfficeWiFi","password":"secret"}
|
||
{"cmd":"ap_start","ssid":"showen","password":"12345678"}
|
||
```
|
||
|
||
## 播放控制
|
||
|
||
### GET /api/status
|
||
|
||
- Method: `GET`
|
||
- Path: `/api/status`
|
||
- Request Body: 无
|
||
- Response Example:
|
||
|
||
```json
|
||
{
|
||
"running": true,
|
||
"paused": false,
|
||
"in_transition": false,
|
||
"current_index": 0,
|
||
"playlist_length": 3,
|
||
"current_video": "intro"
|
||
}
|
||
```
|
||
|
||
- Error Response: `404/405` 由路由层处理;无业务级错误 JSON
|
||
|
||
### POST /api/play
|
||
|
||
- Method: `POST`
|
||
- Path: `/api/play`
|
||
- Request Body: 无
|
||
- Response Example:
|
||
|
||
```json
|
||
{
|
||
"status": "ok",
|
||
"message": "开始播放"
|
||
}
|
||
```
|
||
|
||
- Error Response: `{ "status": "error", "message": "发送命令失败: ..." }`
|
||
|
||
### POST /api/pause
|
||
|
||
- Method: `POST`
|
||
- Path: `/api/pause`
|
||
- Request Body: 无
|
||
- Response Example:
|
||
|
||
```json
|
||
{
|
||
"status": "ok",
|
||
"message": "已暂停"
|
||
}
|
||
```
|
||
|
||
- Error Response: `{ "status": "error", "message": "发送命令失败: ..." }`
|
||
|
||
### POST /api/next
|
||
|
||
- Method: `POST`
|
||
- Path: `/api/next`
|
||
- Request Body: 无
|
||
- Response Example:
|
||
|
||
```json
|
||
{
|
||
"status": "ok",
|
||
"message": "切换到下一个视频"
|
||
}
|
||
```
|
||
|
||
- Error Response: `{ "status": "error", "message": "发送命令失败: ..." }`
|
||
|
||
### POST /api/previous
|
||
|
||
- Method: `POST`
|
||
- Path: `/api/previous`
|
||
- Request Body: 无
|
||
- Response Example:
|
||
|
||
```json
|
||
{
|
||
"status": "ok",
|
||
"message": "切换到上一个视频"
|
||
}
|
||
```
|
||
|
||
- Error Response: `{ "status": "error", "message": "发送命令失败: ..." }`
|
||
|
||
### POST /api/goto/:index
|
||
|
||
- Method: `POST`
|
||
- Path: `/api/goto/:index`
|
||
- Request Body: 无
|
||
- Response Example:
|
||
|
||
```json
|
||
{
|
||
"status": "ok",
|
||
"message": "跳转到视频 2"
|
||
}
|
||
```
|
||
|
||
- Error Response: `{ "status": "error", "message": "无效的视频索引" }` 或 `{ "status": "error", "message": "发送命令失败: ..." }`
|
||
|
||
### GET /api/playlist
|
||
|
||
- Method: `GET`
|
||
- Path: `/api/playlist`
|
||
- Request Body: 无
|
||
- Response Example:
|
||
|
||
```json
|
||
{
|
||
"playlist": [
|
||
{
|
||
"id": "anim_0",
|
||
"path": "videos/intro.mp4",
|
||
"duration": null,
|
||
"loop_count": 1,
|
||
"random_loop_range": null
|
||
},
|
||
{
|
||
"id": "anim_1",
|
||
"path": "videos/idle.mp4",
|
||
"duration": 12.5,
|
||
"loop_count": 2,
|
||
"random_loop_range": null
|
||
}
|
||
],
|
||
"current_index": 0
|
||
}
|
||
```
|
||
|
||
- Error Response: `404/405` 由路由层处理;无业务级错误 JSON
|
||
|
||
### POST /api/scene/:name
|
||
|
||
- Method: `POST`
|
||
- Path: `/api/scene/:name`
|
||
- Request Body: 无
|
||
- Response Example:
|
||
|
||
```json
|
||
{
|
||
"status": "ok",
|
||
"message": "切换到场景: rest"
|
||
}
|
||
```
|
||
|
||
- Error Response: `{ "status": "error", "message": "发送命令失败: ..." }`
|
||
|
||
### POST /api/trigger/:name
|
||
|
||
- Method: `POST`
|
||
- Path: `/api/trigger/:name`
|
||
- Request Body: 无
|
||
- Response Example:
|
||
|
||
```json
|
||
{
|
||
"status": "ok",
|
||
"message": "触发器 'voice' 已发送"
|
||
}
|
||
```
|
||
|
||
- Error Response: `{ "status": "error", "message": "发送命令失败: ..." }`
|
||
|
||
### POST /api/trigger/:name/:value
|
||
|
||
- Method: `POST`
|
||
- Path: `/api/trigger/:name/:value`
|
||
- Request Body: 无
|
||
- Response Example:
|
||
|
||
```json
|
||
{
|
||
"status": "ok",
|
||
"message": "触发器 'voice' 已发送,值: name"
|
||
}
|
||
```
|
||
|
||
- Error Response: `{ "status": "error", "message": "发送命令失败: ..." }`
|
||
|
||
## 配置管理
|
||
|
||
### GET /api/config
|
||
|
||
- Method: `GET`
|
||
- Path: `/api/config`
|
||
- Request Body: 无
|
||
- Response Example:
|
||
|
||
```json
|
||
{
|
||
"display": {
|
||
"fullscreen": true,
|
||
"window_title": "Hologram Player - Cat",
|
||
"rotation": 0,
|
||
"flip_horizontal": true,
|
||
"flip_vertical": true,
|
||
"offset_x": 0,
|
||
"offset_y": 0,
|
||
"prevent_screen_lock": true,
|
||
"render_width": 1280,
|
||
"render_height": 800,
|
||
"output_width": null,
|
||
"output_height": null,
|
||
"scale_mode": "stretch",
|
||
"allow_upscale": true,
|
||
"perspective_correction": {
|
||
"enabled": false,
|
||
"points": [[0, 0], [1280, 0], [1280, 800], [0, 800]]
|
||
},
|
||
"chroma_key": {
|
||
"enabled": false,
|
||
"hsv_min": [0, 0, 200],
|
||
"hsv_max": [180, 30, 255],
|
||
"invert": false,
|
||
"feather": 3
|
||
},
|
||
"brightness_adjust": {
|
||
"enabled": true,
|
||
"subject_boost": 1.5,
|
||
"background_suppress": 0.3,
|
||
"threshold": 30
|
||
}
|
||
},
|
||
"playlist": [
|
||
{
|
||
"id": "anim_0",
|
||
"path": "videos/intro.mp4",
|
||
"duration": null,
|
||
"loop_count": 1,
|
||
"random_loop_range": null
|
||
}
|
||
],
|
||
"transition": {
|
||
"enabled": true,
|
||
"type": "fade",
|
||
"duration": 0.5
|
||
},
|
||
"playback": {
|
||
"loop_playlist": true,
|
||
"auto_start": true
|
||
},
|
||
"scenes": {
|
||
"rest": [],
|
||
"active": [],
|
||
"sleep": [],
|
||
"interact": [],
|
||
"state_machine": null
|
||
},
|
||
"remote_control": {
|
||
"enabled": true,
|
||
"host": "0.0.0.0",
|
||
"port": 8080
|
||
},
|
||
"ble": {
|
||
"enabled": true,
|
||
"device_name": "Showen"
|
||
},
|
||
"source_path": "/home/showen/Showen/ShowenV2/configs/cat_state_machine.json",
|
||
"source_dir": "/home/showen/Showen/ShowenV2/configs"
|
||
}
|
||
```
|
||
|
||
- Error Response: `404/405` 由路由层处理;无业务级错误 JSON
|
||
|
||
### GET /api/config/display
|
||
|
||
- Method: `GET`
|
||
- Path: `/api/config/display`
|
||
- Request Body: 无
|
||
- Response Example:
|
||
|
||
```json
|
||
{
|
||
"fullscreen": true,
|
||
"window_title": "Hologram Player - Cat",
|
||
"rotation": 0,
|
||
"flip_horizontal": true,
|
||
"flip_vertical": true,
|
||
"offset_x": 0,
|
||
"offset_y": 0,
|
||
"prevent_screen_lock": true,
|
||
"render_width": 1280,
|
||
"render_height": 800,
|
||
"output_width": null,
|
||
"output_height": null,
|
||
"scale_mode": "stretch",
|
||
"allow_upscale": true,
|
||
"perspective_correction": {
|
||
"enabled": false,
|
||
"points": [[0, 0], [1280, 0], [1280, 800], [0, 800]]
|
||
},
|
||
"chroma_key": {
|
||
"enabled": false,
|
||
"hsv_min": [0, 0, 200],
|
||
"hsv_max": [180, 30, 255],
|
||
"invert": false,
|
||
"feather": 3
|
||
},
|
||
"brightness_adjust": {
|
||
"enabled": true,
|
||
"subject_boost": 1.5,
|
||
"background_suppress": 0.3,
|
||
"threshold": 30
|
||
}
|
||
}
|
||
```
|
||
|
||
- Error Response: `404/405` 由路由层处理;无业务级错误 JSON
|
||
|
||
### POST /api/config
|
||
|
||
- Method: `POST`
|
||
- Path: `/api/config`
|
||
- Request Body Example:
|
||
|
||
```json
|
||
{
|
||
"display": {
|
||
"fullscreen": true,
|
||
"window_title": "Showen",
|
||
"rotation": 0,
|
||
"flip_horizontal": false,
|
||
"flip_vertical": false,
|
||
"offset_x": 0,
|
||
"offset_y": 0,
|
||
"prevent_screen_lock": true,
|
||
"render_width": 1280,
|
||
"render_height": 800,
|
||
"output_width": null,
|
||
"output_height": null,
|
||
"scale_mode": "fit",
|
||
"allow_upscale": true,
|
||
"perspective_correction": {
|
||
"enabled": false,
|
||
"points": []
|
||
},
|
||
"chroma_key": {
|
||
"enabled": false,
|
||
"hsv_min": [0, 0, 200],
|
||
"hsv_max": [180, 30, 255],
|
||
"invert": false,
|
||
"feather": 0
|
||
},
|
||
"brightness_adjust": {
|
||
"enabled": false,
|
||
"subject_boost": 1.5,
|
||
"background_suppress": 0.3,
|
||
"threshold": 30
|
||
}
|
||
},
|
||
"playlist": [
|
||
{
|
||
"id": "intro",
|
||
"path": "videos/intro.mp4",
|
||
"duration": null,
|
||
"loop_count": 1,
|
||
"random_loop_range": null
|
||
}
|
||
],
|
||
"transition": {
|
||
"enabled": true,
|
||
"type": "fade",
|
||
"duration": 0.5
|
||
},
|
||
"playback": {
|
||
"loop_playlist": true,
|
||
"auto_start": true
|
||
},
|
||
"scenes": {
|
||
"rest": [],
|
||
"active": [],
|
||
"sleep": [],
|
||
"interact": [],
|
||
"state_machine": null
|
||
},
|
||
"remote_control": {
|
||
"enabled": true,
|
||
"host": "0.0.0.0",
|
||
"port": 8080
|
||
},
|
||
"ble": {
|
||
"enabled": true,
|
||
"device_name": "Showen"
|
||
}
|
||
}
|
||
```
|
||
|
||
- Response Example:
|
||
|
||
```json
|
||
{
|
||
"status": "ok",
|
||
"message": "配置已保存,热重载将自动生效"
|
||
}
|
||
```
|
||
|
||
- Error Response: `{ "status": "error", "message": "请求体不是有效的 UTF-8" }`、`{ "status": "error", "message": "配置验证失败: ..." }`、`{ "status": "error", "message": "写入配置文件失败: ..." }`
|
||
|
||
### GET /api/config/available
|
||
|
||
- Method: `GET`
|
||
- Path: `/api/config/available`
|
||
- Request Body: 无
|
||
- Response Example:
|
||
|
||
```json
|
||
{
|
||
"configs": [
|
||
"cat_state_machine.json",
|
||
"dog_state_machine.json"
|
||
],
|
||
"active": "cat_state_machine.json"
|
||
}
|
||
```
|
||
|
||
- Error Response: `404/405` 由路由层处理;无业务级错误 JSON
|
||
|
||
### POST /api/config/switch
|
||
|
||
- Method: `POST`
|
||
- Path: `/api/config/switch`
|
||
- Request Body Example:
|
||
|
||
```json
|
||
{
|
||
"filename": "dog_state_machine.json"
|
||
}
|
||
```
|
||
|
||
- Response Example:
|
||
|
||
```json
|
||
{
|
||
"status": "ok",
|
||
"message": "已切换到配置: dog_state_machine.json"
|
||
}
|
||
```
|
||
|
||
- Error Response: `{ "status": "error", "message": "文件名不合法" }`、`{ "status": "error", "message": "只支持 .json 配置文件" }`、`{ "status": "error", "message": "配置文件不存在" }`、`{ "status": "error", "message": "配置验证失败: ..." }`
|
||
|
||
## 视频管理
|
||
|
||
### GET /api/videos
|
||
|
||
- Method: `GET`
|
||
- Path: `/api/videos`
|
||
- Request Body: 无
|
||
- Response Example:
|
||
|
||
```json
|
||
[
|
||
{
|
||
"name": "intro.mp4",
|
||
"size": 1048576
|
||
},
|
||
{
|
||
"name": "subdir/idle.mp4",
|
||
"size": 2097152
|
||
}
|
||
]
|
||
```
|
||
|
||
- Error Response: `404/405` 由路由层处理;无业务级错误 JSON
|
||
|
||
### POST /api/videos/upload
|
||
|
||
- Method: `POST`
|
||
- Path: `/api/videos/upload`
|
||
- Request Body Example: `multipart/form-data`,一个或多个 `file` 字段
|
||
- Response Example:
|
||
|
||
```json
|
||
{
|
||
"status": "ok",
|
||
"message": "已上传 2 个文件: intro.mp4, idle.mp4"
|
||
}
|
||
```
|
||
|
||
- Error Response: `{ "status": "error", "message": "未找到上传文件" }`、`{ "status": "error", "message": "文件大小超过限制: 单文件最大 100 MB" }`、`{ "status": "error", "message": "保存文件失败: ..." }`
|
||
|
||
### DELETE /api/videos/:filename
|
||
|
||
- Method: `DELETE`
|
||
- Path: `/api/videos/:filename`
|
||
- Request Body: 无
|
||
- Response Example:
|
||
|
||
```json
|
||
{
|
||
"status": "ok",
|
||
"message": "已删除: intro.mp4"
|
||
}
|
||
```
|
||
|
||
- Error Response: `{ "status": "error", "message": "无效的文件名" }`、`{ "status": "error", "message": "文件不存在" }`、`{ "status": "error", "message": "删除失败: ..." }`
|
||
|
||
## WiFi
|
||
|
||
### GET /api/wifi/status
|
||
|
||
- Method: `GET`
|
||
- Path: `/api/wifi/status`
|
||
- Request Body: 无
|
||
- Response Example:
|
||
|
||
```json
|
||
{
|
||
"connected": true,
|
||
"ssid": "OfficeWiFi",
|
||
"ip": "192.168.1.23"
|
||
}
|
||
```
|
||
|
||
- Error Response: `{ "status": "error", "message": "等待 WiFi 响应超时" }`、`{ "status": "error", "message": "WiFi 返回了无效 JSON: ..." }`、`{ "status": "error", "message": "WiFi 操作失败" }`
|
||
|
||
### GET /api/wifi/scan
|
||
|
||
- Method: `GET`
|
||
- Path: `/api/wifi/scan`
|
||
- Request Body: 无
|
||
- Response Example:
|
||
|
||
```json
|
||
[
|
||
{
|
||
"ssid": "OfficeWiFi",
|
||
"signal": 78,
|
||
"security": "WPA2"
|
||
},
|
||
{
|
||
"ssid": "Guest",
|
||
"signal": 42,
|
||
"security": "open"
|
||
}
|
||
]
|
||
```
|
||
|
||
- Error Response: `{ "status": "error", "message": "等待 WiFi 响应超时" }`、`{ "status": "error", "message": "WiFi 返回了无效 JSON: ..." }`
|
||
|
||
### POST /api/wifi/scan
|
||
|
||
- Method: `POST`
|
||
- Path: `/api/wifi/scan`
|
||
- Request Body: 无
|
||
- Response Example:
|
||
|
||
```json
|
||
[
|
||
{
|
||
"ssid": "OfficeWiFi",
|
||
"signal": 78,
|
||
"security": "WPA2"
|
||
}
|
||
]
|
||
```
|
||
|
||
- Error Response: 与 `GET /api/wifi/scan` 相同
|
||
|
||
### POST /api/wifi/connect
|
||
|
||
- Method: `POST`
|
||
- Path: `/api/wifi/connect`
|
||
- Request Body Example:
|
||
|
||
```json
|
||
{
|
||
"ssid": "OfficeWiFi",
|
||
"password": "secret123"
|
||
}
|
||
```
|
||
|
||
- Response Example:
|
||
|
||
```json
|
||
{
|
||
"status": "ok",
|
||
"message": "WiFi 连接成功: OfficeWiFi"
|
||
}
|
||
```
|
||
|
||
- Error Response: `{ "status": "error", "message": "等待 WiFi 响应超时" }`、`{ "status": "error", "message": "WiFi 操作失败" }`
|
||
|
||
### POST /api/wifi/ap/start
|
||
|
||
- Method: `POST`
|
||
- Path: `/api/wifi/ap/start`
|
||
- Request Body Example:
|
||
|
||
```json
|
||
{
|
||
"ssid": "showen",
|
||
"password": "12345678"
|
||
}
|
||
```
|
||
|
||
- Response Example:
|
||
|
||
```json
|
||
{
|
||
"status": "ok",
|
||
"message": "AP 热点已启动: SSID=showen"
|
||
}
|
||
```
|
||
|
||
- Error Response: `{ "status": "error", "message": "JSON 格式错误: ..." }`、`{ "status": "error", "message": "等待 WiFi 响应超时" }`
|
||
|
||
说明:请求体可省略;默认 `ssid=showen`、`password=12345678`。
|
||
|
||
### POST /api/wifi/hotspot/start
|
||
|
||
- Method: `POST`
|
||
- Path: `/api/wifi/hotspot/start`
|
||
- Request Body Example:
|
||
|
||
```json
|
||
{
|
||
"ssid": "showen",
|
||
"password": "12345678"
|
||
}
|
||
```
|
||
|
||
- Response Example:
|
||
|
||
```json
|
||
{
|
||
"status": "ok",
|
||
"message": "AP 热点已启动: SSID=showen"
|
||
}
|
||
```
|
||
|
||
- Error Response: 与 `POST /api/wifi/ap/start` 相同
|
||
|
||
说明:这是 `POST /api/wifi/ap/start` 的兼容别名。
|
||
|
||
### POST /api/wifi/ap/stop
|
||
|
||
- Method: `POST`
|
||
- Path: `/api/wifi/ap/stop`
|
||
- Request Body: 无
|
||
- Response Example:
|
||
|
||
```json
|
||
{
|
||
"status": "ok",
|
||
"message": "AP 热点已关闭"
|
||
}
|
||
```
|
||
|
||
- Error Response: `{ "status": "error", "message": "等待 WiFi 响应超时" }`、`{ "status": "error", "message": "WiFi 操作失败" }`
|
||
|
||
### POST /api/wifi/hotspot/stop
|
||
|
||
- Method: `POST`
|
||
- Path: `/api/wifi/hotspot/stop`
|
||
- Request Body: 无
|
||
- Response Example:
|
||
|
||
```json
|
||
{
|
||
"status": "ok",
|
||
"message": "AP 热点已关闭"
|
||
}
|
||
```
|
||
|
||
- Error Response: 与 `POST /api/wifi/ap/stop` 相同
|
||
|
||
说明:这是 `POST /api/wifi/ap/stop` 的兼容别名。
|
||
|
||
## BLE
|
||
|
||
### POST /api/ble/start
|
||
|
||
- Method: `POST`
|
||
- Path: `/api/ble/start`
|
||
- Request Body Example:
|
||
|
||
```json
|
||
{
|
||
"device_name": "Showen"
|
||
}
|
||
```
|
||
|
||
- Response Example:
|
||
|
||
```json
|
||
{
|
||
"status": "ok",
|
||
"message": "BLE 配网服务已内嵌运行中,设备名: Showen"
|
||
}
|
||
```
|
||
|
||
- Error Response: `{ "status": "error", "message": "JSON 格式错误: ..." }`
|
||
|
||
说明:请求体可省略;未传时使用当前配置中的 `ble.device_name`。
|
||
|
||
### POST /api/ble/stop
|
||
|
||
- Method: `POST`
|
||
- Path: `/api/ble/stop`
|
||
- Request Body: 无
|
||
- Response Example:
|
||
|
||
```json
|
||
{
|
||
"status": "ok",
|
||
"message": "BLE 配网服务随主进程运行,无需手动停止"
|
||
}
|
||
```
|
||
|
||
- Error Response: 无业务级错误 JSON
|
||
|
||
### GET /api/ble/status
|
||
|
||
- Method: `GET`
|
||
- Path: `/api/ble/status`
|
||
- Request Body: 无
|
||
- Response Example:
|
||
|
||
```json
|
||
{
|
||
"running": true,
|
||
"embedded": true,
|
||
"device_name": "Showen"
|
||
}
|
||
```
|
||
|
||
- Error Response: `404/405` 由路由层处理;无业务级错误 JSON
|
||
|
||
## App 下载
|
||
|
||
### GET /api/app/info
|
||
|
||
- Method: `GET`
|
||
- Path: `/api/app/info`
|
||
- Request Body: 无
|
||
- Response Example:
|
||
|
||
```json
|
||
{
|
||
"version": "0.1.0",
|
||
"apk_available": true,
|
||
"apk_size": 18432000,
|
||
"download_url": "/download/showen-app.apk"
|
||
}
|
||
```
|
||
|
||
- Error Response: `404/405` 由路由层处理;无业务级错误 JSON
|
||
|
||
### GET /download/:filename
|
||
|
||
- Method: `GET`
|
||
- Path: `/download/:filename`
|
||
- Request Body: 无
|
||
- Response Example: 二进制附件下载,响应头包含:
|
||
|
||
```text
|
||
Content-Type: application/octet-stream
|
||
Content-Disposition: attachment; filename="showen-app.apk"
|
||
```
|
||
|
||
- Error Response Example:
|
||
|
||
```text
|
||
HTTP 400/404/500
|
||
text/html; charset=utf-8
|
||
无效的文件名
|
||
```
|
||
|
||
说明:该接口错误响应不是 JSON。
|
||
|
||
## 插件管理
|
||
|
||
### GET /api/plugins
|
||
|
||
- Method: `GET`
|
||
- Path: `/api/plugins`
|
||
- Request Body: 无
|
||
- Response Example:
|
||
|
||
```json
|
||
[
|
||
{
|
||
"id": "wifi",
|
||
"info": {
|
||
"name": "WiFi Plugin",
|
||
"version": "0.1.0",
|
||
"description": "Manage WiFi via nmcli",
|
||
"platform": "LinuxArm64"
|
||
},
|
||
"is_dynamic": false,
|
||
"error_policy": "auto_rollback",
|
||
"error_count": 0,
|
||
"max_errors": 5,
|
||
"enabled": true,
|
||
"test_results": [
|
||
{
|
||
"capability": "wifi_scan",
|
||
"passed": true,
|
||
"message": "no test defined"
|
||
}
|
||
],
|
||
"capabilities": ["wifi_scan", "wifi_connect"],
|
||
"needs_rollback": false
|
||
}
|
||
]
|
||
```
|
||
|
||
- Error Response: `404/405` 由路由层处理;无业务级错误 JSON
|
||
|
||
### GET /api/plugins/:id
|
||
|
||
- Method: `GET`
|
||
- Path: `/api/plugins/:id`
|
||
- Request Body: 无
|
||
- Response Example:
|
||
|
||
```json
|
||
{
|
||
"id": "wifi",
|
||
"info": {
|
||
"name": "WiFi Plugin",
|
||
"version": "0.1.0",
|
||
"description": "Manage WiFi via nmcli",
|
||
"platform": "LinuxArm64"
|
||
},
|
||
"is_dynamic": false,
|
||
"error_policy": "auto_rollback",
|
||
"error_count": 0,
|
||
"max_errors": 5,
|
||
"enabled": true,
|
||
"test_results": [],
|
||
"capabilities": ["wifi_scan", "wifi_connect"],
|
||
"needs_rollback": false
|
||
}
|
||
```
|
||
|
||
- Error Response: `{ "status": "error", "message": "plugin 'wifi' not found" }`
|
||
|
||
### POST /api/plugins/:id/enable
|
||
|
||
- Method: `POST`
|
||
- Path: `/api/plugins/:id/enable`
|
||
- Request Body: 无
|
||
- Response Example:
|
||
|
||
```json
|
||
{
|
||
"status": "ok",
|
||
"message": "plugin_enable 命令已发送"
|
||
}
|
||
```
|
||
|
||
- Error Response: `{ "status": "error", "message": "发送失败: ..." }`
|
||
|
||
### POST /api/plugins/:id/disable
|
||
|
||
- Method: `POST`
|
||
- Path: `/api/plugins/:id/disable`
|
||
- Request Body: 无
|
||
- Response Example:
|
||
|
||
```json
|
||
{
|
||
"status": "ok",
|
||
"message": "plugin_disable 命令已发送"
|
||
}
|
||
```
|
||
|
||
- Error Response: `{ "status": "error", "message": "发送失败: ..." }`
|
||
|
||
### POST /api/plugins/:id/rollback
|
||
|
||
- Method: `POST`
|
||
- Path: `/api/plugins/:id/rollback`
|
||
- Request Body: 无
|
||
- Response Example:
|
||
|
||
```json
|
||
{
|
||
"status": "ok",
|
||
"message": "plugin_rollback 命令已发送"
|
||
}
|
||
```
|
||
|
||
- Error Response: `{ "status": "error", "message": "发送失败: ..." }`
|
||
|
||
### POST /api/plugins/:id/switch
|
||
|
||
- Method: `POST`
|
||
- Path: `/api/plugins/:id/switch`
|
||
- Request Body Example:
|
||
|
||
```json
|
||
{
|
||
"version": "1.2.3"
|
||
}
|
||
```
|
||
|
||
- Response Example:
|
||
|
||
```json
|
||
{
|
||
"status": "ok",
|
||
"message": "版本切换请求已发送: wifi -> v1.2.3"
|
||
}
|
||
```
|
||
|
||
- Error Response: `{ "status": "error", "message": "发送失败: ..." }`
|
||
|
||
### POST /api/plugins/install
|
||
|
||
- Method: `POST`
|
||
- Path: `/api/plugins/install`
|
||
- Request Body Example:
|
||
|
||
```json
|
||
{
|
||
"id": "weather",
|
||
"version": "0.3.0"
|
||
}
|
||
```
|
||
|
||
- Response Example:
|
||
|
||
```json
|
||
{
|
||
"status": "ok",
|
||
"message": "安装请求已发送: weather"
|
||
}
|
||
```
|
||
|
||
- Error Response: `{ "status": "error", "message": "发送失败: ..." }`
|
||
|
||
说明:`version` 可省略,省略时请求体仍需提供 `id`。
|
||
|
||
### POST /api/plugins/check-updates
|
||
|
||
- Method: `POST`
|
||
- Path: `/api/plugins/check-updates`
|
||
- Request Body: 无
|
||
- Response Example:
|
||
|
||
```json
|
||
{
|
||
"status": "ok",
|
||
"message": "plugin_check_updates 命令已发送"
|
||
}
|
||
```
|
||
|
||
- Error Response: `{ "status": "error", "message": "发送失败: ..." }`
|
||
|
||
## 文件管理
|
||
|
||
目录参数 `:dir` 仅支持:`videos`、`configs`、`plugins`。
|
||
|
||
### GET /api/files/:dir
|
||
|
||
- Method: `GET`
|
||
- Path: `/api/files/:dir?path=<subdir>`
|
||
- Request Body: 无
|
||
- Response Example:
|
||
|
||
```json
|
||
[
|
||
{
|
||
"name": "subdir",
|
||
"is_dir": true,
|
||
"size": 0
|
||
},
|
||
{
|
||
"name": "intro.mp4",
|
||
"is_dir": false,
|
||
"size": 1048576
|
||
}
|
||
]
|
||
```
|
||
|
||
- Error Response: `{ "status": "error", "message": "不支持的目录: logs(仅支持 videos/configs/plugins)" }`、`{ "status": "error", "message": "路径不合法" }`、`{ "status": "error", "message": "目录不存在" }`
|
||
|
||
### POST /api/files/:dir/upload
|
||
|
||
- Method: `POST`
|
||
- Path: `/api/files/:dir/upload?path=<subdir>`
|
||
- Request Body Example: `multipart/form-data`,一个或多个 `file` 字段
|
||
- Response Example:
|
||
|
||
```json
|
||
{
|
||
"status": "ok",
|
||
"message": "已上传 1 个文件: intro.mp4"
|
||
}
|
||
```
|
||
|
||
- Error Response: `{ "status": "error", "message": "不支持的目录" }`、`{ "status": "error", "message": "目标目录不合法" }`、`{ "status": "error", "message": "未找到上传文件" }`、`{ "status": "error", "message": "文件大小超过限制: 单文件最大 100 MB" }`
|
||
|
||
### GET /api/files/:dir/download
|
||
|
||
- Method: `GET`
|
||
- Path: `/api/files/:dir/download?path=<file>`
|
||
- Request Body: 无
|
||
- Response Example: 二进制附件下载,响应头包含:
|
||
|
||
```text
|
||
Content-Type: application/octet-stream
|
||
Content-Disposition: attachment; filename="intro.mp4"
|
||
```
|
||
|
||
- Error Response Example:
|
||
|
||
```text
|
||
HTTP 400/403/404
|
||
text/html; charset=utf-8
|
||
缺少 path 参数
|
||
```
|
||
|
||
说明:该接口错误响应不是 JSON。
|
||
|
||
### POST /api/files/:dir/delete
|
||
|
||
- Method: `POST`
|
||
- Path: `/api/files/:dir/delete`
|
||
- Request Body Example:
|
||
|
||
```json
|
||
{
|
||
"path": "subdir/intro.mp4"
|
||
}
|
||
```
|
||
|
||
- Response Example:
|
||
|
||
```json
|
||
{
|
||
"status": "ok",
|
||
"message": "已删除: subdir/intro.mp4"
|
||
}
|
||
```
|
||
|
||
- Error Response: `{ "status": "error", "message": "不支持的目录" }`、`{ "status": "error", "message": "缺少 path" }`、`{ "status": "error", "message": "路径不合法" }`、`{ "status": "error", "message": "文件不存在" }`
|
||
|
||
### POST /api/files/move
|
||
|
||
- Method: `POST`
|
||
- Path: `/api/files/move`
|
||
- Request Body Example:
|
||
|
||
```json
|
||
{
|
||
"from_dir": "videos",
|
||
"from_path": "intro.mp4",
|
||
"to_dir": "videos",
|
||
"to_path": "archive/intro.mp4"
|
||
}
|
||
```
|
||
|
||
- Response Example:
|
||
|
||
```json
|
||
{
|
||
"status": "ok",
|
||
"message": "已移动: intro.mp4 → archive/intro.mp4"
|
||
}
|
||
```
|
||
|
||
- Error Response: `{ "status": "error", "message": "源目录不支持" }`、`{ "status": "error", "message": "目标目录不支持" }`、`{ "status": "error", "message": "源路径不合法" }`、`{ "status": "error", "message": "目标路径已存在" }`
|
||
|
||
### POST /api/files/:dir/mkdir
|
||
|
||
- Method: `POST`
|
||
- Path: `/api/files/:dir/mkdir`
|
||
- Request Body Example:
|
||
|
||
```json
|
||
{
|
||
"path": "archive"
|
||
}
|
||
```
|
||
|
||
- Response Example:
|
||
|
||
```json
|
||
{
|
||
"status": "ok",
|
||
"message": "已创建目录: archive"
|
||
}
|
||
```
|
||
|
||
- Error Response: `{ "status": "error", "message": "不支持的目录" }`、`{ "status": "error", "message": "缺少 path" }`、`{ "status": "error", "message": "路径不合法" }`、`{ "status": "error", "message": "创建失败: ..." }`
|
||
|
||
## 已移除的旧文档端点
|
||
|
||
以下端点当前实现中不存在,不应再使用:
|
||
|
||
- `/api/stop`
|
||
- `/api/wifi/disconnect`
|