基于 PRD(docs/smartlearing_prd_v1.01.txt)实现的云端服务端,负责场景识别、智能对话、课程资源管理与终端设备 MQTT 指令下发,支撑「场景感知 → 意图分析 → 智能对话 → 学习反馈」闭环。
┌─────────────┐ MQTT (snlp/v1/...) ┌──────────────────┐
│ 学习终端 │ ◄──────────────────────────► │ SmartLearning │
│ (设备) │ HTTP API / SSE │ Server (本仓库) │
└─────────────┘ ◄──────────────────────────► └────────┬─────────┘
│
┌────────▼─────────┐
│ data/ 课程资源 │
│ runtime/ 运行状态 │
└──────────────────┘
| 组件 | 技术栈 | 职责 |
|---|---|---|
| HTTP 服务 | FastAPI + Uvicorn | REST API、SSE 对话流、静态资源托管 |
| MQTT 客户端 | paho-mqtt | 订阅终端上报、下发控制/任务指令 |
| 课程数据 | JSON 文件 | 课程索引、章节视频清单、初始化任务 |
| 运行状态 | JSON 持久化 | 记录已完成初始化的设备 ID |
smartlearning/
├── docs/
│ └── smartlearing_prd_v1.01.txt # 产品需求与 MQTT 协议规范
├── data/ # 课程与任务数据(同时作为静态资源根目录)
│ ├── tasklist.json # 设备开机初始化任务清单
│ └── courses/
│ ├── index.json # 已发布课程索引(基础课程包)
│ ├── course01/
│ │ ├── course.json # 课程视频清单
│ │ ├── courseTask.json # 单课程初始化任务(可选扩展)
│ │ └── chapter01/resources/ # 视频文件目录
│ └── course02/
│ └── ...
├── server/
│ ├── app/
│ │ ├── main.py # 应用入口
│ │ ├── config.py # 配置(支持 .env)
│ │ ├── routes/ # API 路由
│ │ ├── services/ # 业务逻辑
│ │ └── mqtt/ # MQTT 与指令下发
│ ├── runtime/
│ │ └── initialized_devices.json # 已初始化设备记录(运行时生成)
│ ├── requirements.txt
│ └── run.py # 启动脚本
└── README.md
终端开机后通过 MQTT 上报心跳 rpt/state,服务端检测到该 device_id 尚未初始化时,**自动**执行两阶段流程:
终端上报 rpt/state(首次)
│
▼
┌───────────────────────────────────────┐
│ 阶段 1:设备基础初始化(cmd/ctl) │
│ · SET_CLOCK 同步系统时钟 │
│ · SET_VOLUME 设置默认音量(50) │
│ · SET_POWER 唤醒设备 │
└───────────────────────────────────────┘
│
▼
┌───────────────────────────────────────┐
│ 阶段 2:基础课程资源同步(cmd/task) │
│ · UPDATE_MANIFEST 下发完整资源清单 │
│ · DOWNLOAD × N 逐文件下载资源包 │
│ (index.json 中所有课程) │
└───────────────────────────────────────┘
│
▼
写入 server/runtime/initialized_devices.json
(同一设备不会重复触发,除非手动强制)
自动触发条件(须同时满足):
snlp/v1/{device_id}/rpt/statedevice_id 不在本地已初始化记录中AUTO_INIT_ENABLED=true(默认开启)不会自动触发的情况:
DELETE /api/v1/devices/initialized/{device_id} 清除记录后重新触发data/courses/index.json 中列出的课程均视为**基础课程**,在设备初始化完成后自动同步到终端存储卡。
终端存储路径规则:
设备路径 = {DEVICE_DATA_ROOT} + 服务端 data/ 下的相对路径
默认 DEVICE_DATA_ROOT=/data,示例:
| 服务端文件 | 终端目标路径 |
|---|---|
data/courses/index.json |
/data/courses/index.json |
data/courses/course01/course.json |
/data/courses/course01/course.json |
data/courses/course01/chapter01/resources/001.mp3 |
/data/courses/course01/chapter01/resources/001.mp3 |
当前 course01 + course02 共 23 个文件(索引 + 配置 + 任务清单 + 18 个视频占位路径)。
若需单独对某一门课程执行完整任务(含播放引导),可调用:
POST /api/v1/devices/course-init
Content-Type: application/json
{
"device_id": "SN-88293021",
"course_id": "course01"
}
该接口读取 courseTask.json,下发 UPDATE_MANIFEST、DOWNLOAD、PLAY_MEDIA 等指令,适用于选课后增量同步或重新拉取。
环境音/语音 → POST /api/v1/scene/analyze → 场景识别(置信度 < 0.7 走通用问候)
↓
用户回复 → GET /api/v1/dialogue/stream (SSE) → 流式返回教学对话与反馈
POST /api/v1/devices/init
Content-Type: application/json
{
"device_id": "SN-88293021",
"force": true
}
force: false 且设备已初始化时,返回 status: "already_initialized",不会重复下发。
snlp/v1/{device_id}/{direct}/{msg_type}
| 方向 | Topic | 说明 |
|---|---|---|
| 终端 → 云 | snlp/v1/{device_id}/rpt/state |
心跳/状态(cpu、电量、网络、模式) |
| 终端 → 云 | snlp/v1/{device_id}/rpt/event |
事件(唤醒、ASR 结果等) |
| 终端 → 云 | snlp/v1/{device_id}/rpt/ack |
指令执行回执 |
| 终端 → 云 | snlp/v1/{device_id}/rpt/progress |
异步任务进度(下载/OTA) |
| 云 → 终端 | snlp/v1/{device_id}/cmd/ctl |
实时控制指令 |
| 云 → 终端 | snlp/v1/{device_id}/cmd/task |
异步任务指令(下载资源包等) |
| Action | 通道 | 说明 |
|---|---|---|
SET_CLOCK |
ctl | 同步时间戳 |
SET_VOLUME |
ctl | 设置音量 0–100 |
SET_POWER |
ctl | 休眠/唤醒 |
PLAY_MEDIA |
ctl | 播放本地媒体 |
STOP_MEDIA |
ctl | 停止播放 |
DOWNLOAD |
task | 异步下载文件到 target_path |
UPDATE_MANIFEST |
task | 更新课程资源清单 |
OTA_UPDATE |
task | 固件升级 |
REBOOT |
ctl | 延时重启 |
{
"header": {
"version": "1.0",
"device_id": "SN-88293021",
"req_id": "uuid",
"timestamp": 1718365200,
"ttl": 3600
},
"action": "DOWNLOAD",
"payload": {
"task_id": "uuid",
"url": "http://your-server:9500/static/courses/course01/chapter01/resources/001.mp3",
"md5": "",
"target_path": "/data/courses/course01/chapter01/resources/001.mp3"
}
}
终端执行后须回 rpt/ack;下载过程中通过 rpt/progress 上报进度。
{
"courses": [
{ "course_id": "course01", "config": "courses/course01/course.json" },
{ "course_id": "course02", "config": "courses/course02/course.json" }
]
}
新增课程:在 data/courses/ 下创建目录和 course.json,并登记到 index.json,设备重新初始化后自动同步。
{
"course_id": "course01",
"course_name": "小智学习课程01",
"chapters": [
{
"chapter_id": "chapter01",
"chapter_name": "第一章",
"audios": [
{
"id": "001",
"title": "课程001",
"path": "courses/course01/chapter01/resources/001.mp3"
}
]
}
]
}
path 为相对于 data/ 的路径,须与实际文件位置一致。
定义设备基础初始化步骤(时钟、音量、唤醒)。基础课程资源包由服务端在初始化完成后**自动**通过 cmd/task 下发,无需在此文件中配置。
定义某一门课程的扩展初始化流程(如下载 + 播放引导),通过 course-init API 手动触发。
服务默认地址:http://localhost:9500
交互式文档:http://localhost:9500/docs
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /health |
服务状态与 MQTT 连接状态 |
| 方法 | 路径 | 说明 |
|---|---|---|
| POST | /api/v1/scene/analyze |
场景识别 |
| GET | /api/v1/dialogue/stream |
SSE 对话流(参数:dialogue_id、user_text、scene_prompt、difficulty) |
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /api/v1/courses |
课程索引 |
| GET | /api/v1/courses/base-manifest |
预览基础课程同步资源列表 |
| GET | /api/v1/courses/{course_id} |
课程详情 |
| GET | /api/v1/courses/{course_id}/manifest |
单课程资源清单 |
| GET | /api/v1/courses/{course_id}/tasks |
单课程任务清单 |
| GET | /api/v1/tasklist |
设备初始化任务清单 |
| POST | /api/v1/devices/init |
手动触发设备初始化(支持 force) |
| POST | /api/v1/devices/course-init |
单课程初始化 |
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /api/v1/devices |
在线设备列表(来自 MQTT 上报) |
| GET | /api/v1/devices/initialized |
已初始化设备记录 |
| GET | /api/v1/devices/{device_id} |
设备状态 |
| GET | /api/v1/devices/{device_id}/init-status |
初始化状态 |
| DELETE | /api/v1/devices/initialized/{device_id} |
清除初始化记录 |
| GET | /api/v1/devices/{device_id}/acks |
指令回执历史 |
| GET | /api/v1/devices/{device_id}/events |
事件历史 |
| POST | /api/v1/devices/{device_id}/cmd |
手动下发 ctl 指令 |
| POST | /api/v1/devices/{device_id}/task |
手动下发 task 指令 |
GET /static/{path}
示例:/static/courses/course01/chapter01/resources/001.mp3
终端 DOWNLOAD 指令中的 url 指向此地址,请确保 BASE_URL 配置为终端可访问的公网/内网地址。
场景识别:
curl -X POST http://localhost:9500/api/v1/scene/analyze \
-H "Content-Type: application/json" \
-d '{
"timestamp": 1718365200,
"audio_blob": "",
"device_id": "SN-88293021",
"context_hints": { "scene": "dining", "confidence": 0.9 }
}'
对话流(SSE):
curl -N "http://localhost:9500/api/v1/dialogue/stream?user_text=Good%20morning&difficulty=A2"
预览基础课程同步清单:
curl http://localhost:9500/api/v1/courses/base-manifest
在 server/ 目录下创建 .env 文件(推荐,避免将密钥写入代码):
# HTTP 服务
HOST=0.0.0.0
PORT=9500
BASE_URL=http://your-public-ip:9500
# 课程数据目录(默认指向项目根 data/)
# DATA_DIR=E:/smartlearning/data
# MQTT
MQTT_BROKER=your-mqtt-broker
MQTT_PORT=1883
MQTT_USERNAME=your-username
MQTT_PASSWORD=your-password
MQTT_CLIENT_ID=snlp-server
MQTT_ENABLED=true
# 初始化
AUTO_INIT_ENABLED=true
DEVICE_DATA_ROOT=/data
# 场景识别
SCENE_CONFIDENCE_THRESHOLD=0.7
| 变量 | 默认值 | 说明 |
|---|---|---|
HOST |
0.0.0.0 |
监听地址 |
PORT |
9500 |
监听端口 |
BASE_URL |
http://localhost:9500 |
资源下载 URL 前缀,**部署时必须改为终端可访问的地址** |
DATA_DIR |
../data |
课程数据目录 |
MQTT_BROKER |
— | MQTT Broker 地址 |
MQTT_PORT |
— | MQTT 端口 |
MQTT_ENABLED |
true |
设为 false 可仅运行 HTTP API |
AUTO_INIT_ENABLED |
true |
是否自动触发设备初始化 |
DEVICE_DATA_ROOT |
/data |
终端存储卡根路径 |
路径:server/runtime/initialized_devices.json
{
"devices": {
"SN-88293021": {
"initialized_at": 1781701381,
"source": "auto",
"mode": "idle",
"courses_synced": true,
"courses_sync_at": 1781701381,
"courses_download_count": 23
}
}
}
cd server
pip install -r requirements.txt
# 创建 .env 并填写配置(见上文)
# copy .env.example .env # 如有示例文件
python run.py
或使用 uvicorn 直接启动(支持热重载):
cd server
uvicorn app.main:app --host 0.0.0.0 --port 9500 --reload
# 健康检查
curl http://localhost:9500/health
# 课程索引
curl http://localhost:9500/api/v1/courses
# 基础课程同步清单
curl http://localhost:9500/api/v1/courses/base-manifest
访问 http://localhost:9500/docs 查看完整 API 文档。
1. 打包项目
在开发机上将项目目录打包(排除缓存与虚拟环境):
# Linux / macOS
cd /path/to
tar czvf smartlearning-server.tar.gz \
--exclude='server/__pycache__' \
--exclude='server/.venv' \
--exclude='server/runtime/*.json' \
smartlearning/
# Windows PowerShell
Compress-Archive -Path E:\smartlearning -DestinationPath smartlearning-server.zip
上传到服务器后解压:
tar xzvf smartlearning-server.tar.gz
cd smartlearning/server
2. 创建虚拟环境并安装依赖
python3 -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -r requirements.txt
3. 配置环境变量
cp .env.example .env # 或手动创建
vim .env # 填写 BASE_URL、MQTT 等
重要:
BASE_URL必须设置为终端能访问到的地址(公网 IP 或域名),否则DOWNLOAD指令中的资源 URL 无效。
4. 准备课程资源
将视频文件放入 data/courses/ 对应目录,确保与 course.json 中 path 一致。
5. 使用 systemd 守护进程(Linux)
创建 /etc/systemd/system/smartlearning.service:
[Unit]
Description=SmartLearning Server
After=network.target
[Service]
Type=simple
User=www-data
WorkingDirectory=/opt/smartlearning/server
EnvironmentFile=/opt/smartlearning/server/.env
ExecStart=/opt/smartlearning/server/.venv/bin/python run.py
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable smartlearning
sudo systemctl start smartlearning
sudo systemctl status smartlearning
6. Nginx 反向代理(可选)
server {
listen 80;
server_name your-domain.com;
location / {
proxy_pass http://127.0.0.1:9500;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_buffering off; # SSE 对话流需要关闭缓冲
}
}
若使用 HTTPS,配置 TLS 证书并将 BASE_URL 改为 https://your-domain.com。
在项目根目录创建 Dockerfile(如尚未存在,可参考以下内容):
FROM python:3.12-slim
WORKDIR /app
COPY server/requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY server/ ./server/
COPY data/ ./data/
WORKDIR /app/server
ENV HOST=0.0.0.0
ENV PORT=9500
ENV DATA_DIR=/app/data
EXPOSE 9500
CMD ["python", "run.py"]
docker-compose.yml 示例:
services:
smartlearning:
build: .
ports:
- "9500:9500"
env_file:
- server/.env
volumes:
- ./data:/app/data:ro
- ./server/runtime:/app/server/runtime
restart: unless-stopped
构建与启动:
docker compose build
docker compose up -d
docker compose logs -f
使用 NSSM 注册为 Windows 服务:
# 安装 NSSM 后执行
nssm install SmartLearning "E:\smartlearning\server\.venv\Scripts\python.exe" "E:\smartlearning\server\run.py"
nssm set SmartLearning AppDirectory "E:\smartlearning\server"
nssm set SmartLearning AppEnvironmentExtra "BASE_URL=http://your-ip:9500"
nssm start SmartLearning
| 检查项 | 说明 |
|---|---|
BASE_URL |
终端可通过此地址下载 /static/ 资源 |
| MQTT 连通 | /health 返回 "mqtt_connected": true |
| 防火墙 | 放行 HTTP 端口(9500 或 Nginx 80/443) |
| 课程文件 | data/courses/ 下视频文件已就位 |
| 持久化 | server/runtime/ 目录需可写(初始化记录) |
| 密钥安全 | MQTT 账号密码放在 .env,勿提交到版本库 |
| 进程守护 | 使用 systemd / Docker restart 策略 |
升级步骤:
# 1. 备份运行状态
cp server/runtime/initialized_devices.json /backup/
# 2. 更新代码
git pull # 或替换解压包
# 3. 更新依赖
cd server && pip install -r requirements.txt
# 4. 重启服务
sudo systemctl restart smartlearning
# 或 docker compose restart
新增课程后: 无需重启服务;已初始化设备需清除记录或 force: true 重新初始化方可同步新课程。
curl -X DELETE http://localhost:9500/api/v1/devices/initialized/SN-88293021
# 设备下次上报 state 时将自动重新初始化并同步全部基础课程
Q: 设备上报了 state,但没有收到初始化指令?
/health 中 mqtt_connected 是否为 trueinitialized_devices.json 中AUTO_INIT_ENABLED=trueGET /api/v1/devices/initialized 查看记录Q: 终端下载资源失败?
BASE_URL 是终端网络可达的地址,不是 localhostdata/courses/ 对应路径BASE_URL/static/courses/... 验证Q: 如何只同步基础课程,不重复做时钟/音量初始化?
当前设计为一体化流程。若仅需补发资源,可手动调用:
# 通过内部 API 下发(需自行扩展端点)或直接向 MQTT 发布 DOWNLOAD 指令
curl -X POST http://localhost:9500/api/v1/devices/SN-88293021/task \
-H "Content-Type: application/json" \
-d '{"header":{"device_id":"SN-88293021","timestamp":0},"action":"DOWNLOAD","payload":{"url":"...","target_path":"..."}}'
或清除初始化记录后让设备重新走完整流程。
Q: MQTT Broker 暂时不可用,HTTP API 还能用吗?
可以。设置 MQTT_ENABLED=false 或 Broker 连接失败时,HTTP API 仍正常运行,但自动初始化与指令下发不可用。
docs/smartlearing_prd_v1.01.txt/docshttp://localhost:9500/api/v1/courses/course01/manifest
http://localhost:9500/api/v1/courses/base-manifest
curl.exe -X DELETE http://124.222.109.197:2101/api/v1/devices/initialized/498e93f3-cdc9-4362-b32a-d2fc10e69c12
Invoke-WebRequest -Uri "http://124.222.109.197:2101/api/v1/devices/initialized/498e93f3-cdc9-4362-b32a-d2fc10e69c12" -Method Delete
Invoke-RestMethod -Uri "http://124.222.109.197:2101/api/v1/devices/init" -Method Post -ContentType "application/json" -Body '{"device_id": "498e93f3-cdc9-4362-b32a-d2fc10e69c12", "force": true}'