编辑 | blame | 历史 | 原始文档

SmartLearning 情境感知智能英语学习系统

基于 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

核心流程

1. 设备首次上线(自动初始化)

终端开机后通过 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
(同一设备不会重复触发,除非手动强制)

自动触发条件(须同时满足):

  1. 收到 snlp/v1/{device_id}/rpt/state
  2. device_id 不在本地已初始化记录中
  3. MQTT 已连接
  4. AUTO_INIT_ENABLED=true(默认开启)

不会自动触发的情况:

  • 已初始化设备再次上报心跳 → 跳过
  • MQTT 未连接 → 跳过(下次心跳可重试)
  • 可通过 DELETE /api/v1/devices/initialized/{device_id} 清除记录后重新触发

2. 基础课程资源同步说明

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 个视频占位路径)。

3. 单课程初始化(手动,可选)

若需单独对某一门课程执行完整任务(含播放引导),可调用:

POST /api/v1/devices/course-init
Content-Type: application/json

{
  "device_id": "SN-88293021",
  "course_id": "course01"
}

该接口读取 courseTask.json,下发 UPDATE_MANIFEST、DOWNLOAD、PLAY_MEDIA 等指令,适用于选课后增量同步或重新拉取。

4. 场景感知与对话

环境音/语音 → POST /api/v1/scene/analyze → 场景识别(置信度 < 0.7 走通用问候)
                    ↓
用户回复 → GET /api/v1/dialogue/stream (SSE) → 流式返回教学对话与反馈

5. 手动强制重新初始化

POST /api/v1/devices/init
Content-Type: application/json

{
  "device_id": "SN-88293021",
  "force": true
}

force: false 且设备已初始化时,返回 status: "already_initialized",不会重复下发。


MQTT 协议

Topic 规则

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 一览

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 延时重启

下发指令 Payload 格式

{
  "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 上报进度。


课程与任务清单

index.json — 基础课程索引

{
  "courses": [
    { "course_id": "course01", "config": "courses/course01/course.json" },
    { "course_id": "course02", "config": "courses/course02/course.json" }
  ]
}

新增课程:在 data/courses/ 下创建目录和 course.json,并登记到 index.json,设备重新初始化后自动同步。

course.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/ 的路径,须与实际文件位置一致。

tasklist.json — 设备开机任务

定义设备基础初始化步骤(时钟、音量、唤醒)。基础课程资源包由服务端在初始化完成后**自动**通过 cmd/task 下发,无需在此文件中配置。

courseTask.json — 单课程任务(可选)

定义某一门课程的扩展初始化流程(如下载 + 播放引导),通过 course-init API 手动触发。


HTTP 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
    }
  }
}

本地开发

环境要求

  • Python 3.10+
  • 可访问的 MQTT Broker(自动初始化依赖 MQTT)

安装与启动

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。


方式二:Docker 部署

在项目根目录创建 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

方式三:Windows 服务部署

使用 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,但没有收到初始化指令?

  1. 检查 /health 中 mqtt_connected 是否为 true
  2. 检查该设备是否已在 initialized_devices.json 中
  3. 确认 AUTO_INIT_ENABLED=true
  4. 用 GET /api/v1/devices/initialized 查看记录

Q: 终端下载资源失败?

  1. 确认 BASE_URL 是终端网络可达的地址,不是 localhost
  2. 确认视频文件已上传到 data/courses/ 对应路径
  3. 用浏览器或 curl 访问 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 仍正常运行,但自动初始化与指令下发不可用。


相关文档

  • 产品需求与 MQTT 详细规范:docs/smartlearing_prd_v1.01.txt
  • API 交互式文档:启动服务后访问 /docs

版本

  • 服务端版本:1.0.0
  • 协议版本:snlp/v1

http://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}'