设备接入手册¶
面向盒子固件开发者。固件只需实现一个取数路径与少量辅助接口;本文给出完整调用时序、参数与错误处理建议。
前置依赖(重要):设备首次校时依赖 NTP(TLS 证书校验需要正确时钟,
server_time无法承担首次校时)。固件必须先通过 NTP 同步时间(如ntp.aliyun.com,MicroPython 内置ntptime可配),NTP 不可达时不得发起 HTTPS 请求。
1. 鉴权方式¶
所有设备接口(激活、公告除外)携带请求头:
X-Device-SN: XMBOX-2026-000001
X-Device-Token: <64位hex,激活时下发>
- Token 由服务端随机生成,库中仅存哈希;解绑/管理员重置/重复激活都会使旧 Token 立即失效;
- 收到
40102时,固件应回退到出厂密钥重新激活(§2)。
2. 接入时序¶
开机 → NTP 校时 → POST /device/activate(换 Token)
→ 循环:POST /device/heartbeat(每 next_interval 秒)
→ GET /data/sources(开机 + 每 30 分钟刷新菜单)
→ GET /data/{source_code}(按 poll_interval 轮询渲染)
→ GET /media/**(音频类源按 payload.audio_url 下载,见 §3.1)
2.1 激活 POST /api/v1/device/activate¶
首次联网或 Token 失效(40102)后调用,无需鉴权头。
{
"sn": "XMBOX-2026-000001",
"model": "xmbox_v1",
"fw_version": "1.0.3",
"activation_key": "<出厂预置型号密钥>"
}
响应 data:
{ "device_token": "9f86d081…", "heartbeat_interval": 600, "server_time": 1788000000 }
activation_key(出厂预置型号密钥):平台为该型号配置了密钥时必传且须完全一致;平台未配置型号密钥的部署(开放激活模式)可不传、平台不校验——不确定时随请求携带即可;- 错误:
40104(SN 不存在或密钥错误 → 提示联系售后)、40301(设备被禁用); - 合法重复激活 = Token 轮换,旧 Token 立即失效。
2.2 心跳 POST /api/v1/device/heartbeat¶
请求体:{"fw_version": "1.0.3"}。响应 data:
{
"server_time": 1788000000,
"next_interval": 600,
"pending_ops": [
{ "type": "bind_confirm", "code": "482913", "expire_at": "2026-08-31 14:25:00" }
]
}
next_interval为服务端建议的心跳间隔(默认 600 秒,可配置),固件应按其调度;pending_ops为待执行指令:当前仅bind_confirm(用户发起绑定后的 6 位确认码,设备应在屏幕显示并语音/提示用户回填网页);未来可能扩展新 type,固件必须忽略不认识的 type;- 离线重连后若错过心跳响应,可调
GET /api/v1/device/pending兜底拉取相同指令。
2.3 数据源清单 GET /api/v1/data/sources¶
固件渲染菜单/轮播的唯一数据来源,建议开机及每 30 分钟刷新:
{
"sources": [
{
"source": "lottery_ssq", "version": "v1", "name": "双色球",
"category": "lottery", "poll_interval": 600,
"updated_at": "2026-08-30 21:35:00",
"source_name": "数据来自中国福利彩票官网,仅供信息展示,不构成任何投注建议"
}
]
}
poll_interval 为建议轮询间隔(秒)=min(cache_ttl, 600);固件可自行节流,但不得高于限流约束(默认 10 次/分/设备/源)。
VIP 设备:出厂预导入入库的设备(库内 VIP),清单返回全部已上线数据源(含付费源),取数免订阅;非 VIP 设备仅返回已订阅或免订阅源(2026-09-19)。
3. 统一取数 GET /api/v1/data/{source_code} ★核心¶
GET /api/v1/data/lottery_ssq?version=v1
X-Device-SN: XMBOX-2026-000001
X-Device-Token: 9f86d0...
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
version |
string | 否(默认 v1) |
数据源版本 |
<业务参数> |
query 平铺 | 按源 schema | 如天气 &city=上海;复杂结构用 params=<urlencoded JSON> |
format |
string | 否 | 预留,恒为 json |
成功响应(data.stale=false 为新鲜数据):
{
"code": 0, "msg": "success",
"data": {
"source": "lottery_ssq", "version": "v1",
"updated_at": "2026-08-30 21:35:00", "stale": false,
"payload": { }
},
"server_time": 1788000000
}
固件建议展示“数据更新于 {updated_at}”;各源 payload 字段与独立接入指导见数据源目录。
限流响应:42901,data.retry_after 给出建议等待秒数,按其退避重试。
3.1 媒体文件下载 GET /media/**¶
音频等二进制产物放不进取数接口的 payload(<4KB 硬约束),由服务端落盘后以静态文件提供。设备端流程(如 news_hourly 整点新闻播报):
- 正常取数
GET /api/v1/data/news_hourly,payload中拿到audio_url(如/media/news_audio/latest.wav)、script_url(播报稿全文)、hour_label(档期); - 向同一主机拼接完整地址发起普通 GET 下载(无需鉴权头):
GET https://api.wwzu.com/media/news_audio/latest.wav → 音频(WAV 24kHz/16bit/单声道 PCM,可直接 I2S 播放,约 8MB)
GET https://api.wwzu.com/media/news_audio/latest.mp3 → 同一播报的 MP3(约 1MB,供网页端/调试,设备端无需使用)
GET https://api.wwzu.com/media/news_audio/latest.txt → 本档播报稿全文(UTF-8)
latest.*每个整点档滚动覆盖;按档归档news_YYYYMMDD_HH.wav/mp3/txt保留最近 24 份,回放历史档期可用;- 服务端以临时文件 + 原子替换落盘,任意时刻发起下载都能拿到完整文件;响应带
Content-Length,无 chunked / gzip / 3xx 跳转,固件按Content-Length校验完整性即可; - 播报档期为每日 9:00~22:00 整点(服务端每档提前 5 分钟生成),设备按整点读取取数接口后下载当档音频播放。
3.2 听老故事连续播放 comic_audio¶
三级浏览 + 整本连续播放。音频经服务端 /media 站内缓存下发(源站地址与真实文件名均不下发,文件名为页号顺序名;需固件 mp3dec/play_mp3());数据契约与订阅开通(邀请码兑换)见听老故事接入指导:
- 取标签:
GET /api/v1/data/comic_audio(默认action=tags),渲染标签菜单(name+count); - 取故事:
GET /api/v1/data/comic_audio?action=stories&tag=英雄模范&sort=hits(11 本/页),渲染书目(cover_url封面 /pages页数 /title),并记住每本的htmlname(书目对外标识,翻页/取清单都要用它); - 取清单:
GET /api/v1/data/comic_audio?action=playlist&story_id=<htmlname>—— 一次拿到整本播放清单:base_url(站内相对路径)+files[](从第 1 页正文语音开始,文件名即页号:001.mp3→ 第 1 页,无音页号自然缺号;顺序即播放顺序); - 连续播放(边播边预取,整本不中断):
- 下载第 N 条(同主机 +
base_url+ 文件名,§3.1 同主机规则),起播同时后台预取第 N+1 条(双缓冲); - 当前条播完(
ended)无缝切下一条;单条下载失败重试 1 次,仍失败跳过该条继续(不中断整本);个别条目404表示服务端仍在备货(长书快进场景),退避 1 秒重试; more=true的超长书:余量不足 10 条时提前带&page=next_page取下一批清单;- 末条播完即停;用户切换书目/页码时取消旧调度链再重建(避免新旧链条叠加)。
带屏设备可在浏览/播放时另取页图 GET /api/v1/data/comic_audio?action=pages&story_id=<htmlname>(10 张/页,no 对应页码,与语音清单互不干扰)。
固件升级提示(2026-09-19 契约改版):stories 列表不再返回数字
id(改htmlname,11 本/页);playlist/pages 的story_id传htmlname;清单不再有梗概语音前置条,文件名为页号顺序名(001.mp3…)而非源站真实文件名。旧固件按数字 ID 请求将得到 50401(故事不存在)。
4. 缓存与降级语义¶
- 服务端两级缓存 + 全设备共享:多台设备取同一源,命中缓存,
updated_at即数据时间; - 上游故障时,若缓存仍在降级窗口内,返回
stale=true的旧数据(updated_at不变)——固件应照常渲染并保留“更新于”提示; - 降级窗口也过期后返回
50401——固件显示“暂无数据”,保留上次画面,按轮询间隔继续重试。
5. 固件实现建议¶
- 所有请求走 HTTPS + 证书校验;请求失败按指数退避,不得高频重试;
code为唯一判断依据,msg仅用于展示/日志;- 心跳/取数均为幂等或可安全重试;绑定确认是一次性动作(确认码一次即焚);
- Token 只存 RAM,不上报日志;序列号 SN 可在设备 web 页面的【系统】页签中查看,供用户绑定。
6. 错误码总表¶
本表为对外摘录,与内部《API 接口规范》错误码母版同步维护,新增错误码先改母版再同步本表。
| code | HTTP | 含义 | 设备端建议表现 |
|---|---|---|---|
| 0 | 200 | 成功 | 正常渲染 |
| 40001 | 400 | 参数缺失/校验失败 | 提示参数错误并告警日志 |
| 40101 | 401 | 设备未绑定 | 显示绑定引导(二维码/SN) |
| 40102 | 401 | 设备 Token 无效/已吊销 | 用出厂密钥重新激活 |
| 40103 | 401 | 用户未登录/JWT 失效(Web) | 跳转登录 |
| 40104 | 401 | 激活失败:SN 不存在或密钥错误 | 显示联系售后 |
| 40301 | 403 | 设备被禁用 | 提示联系管理员 |
| 40302 | 403 | 未开通该数据源(未订阅) | 提示去后台开通 |
| 40303 | 403 | 无权限(越权/非管理员,Web) | 提示无权限 |
| 40304 | 403 | 设备已被他人绑定 | 提示联系绑定者解绑 |
| 40305 | 403 | 绑定设备数超上限(默认 5) | 提示先解绑 |
| 40306 | 403 | 确认码错误或已过期 | 允许重新获取 |
| 40308 | 403 | 邀请码无效(Web 兑换) | 设备侧不出现 |
| 40309 | 403 | 该数据源需邀请码开通订阅 | 提示需邀请码开通(用户在用户中心兑换) |
| 40401 | 404 | 数据源或版本不存在 | 从清单接口刷新后重试 |
| 42901 | 429 | 请求过于频繁 | 按 data.retry_after 退避 |
| 42902 | 429 | 登录失败次数过多 | 显示剩余锁定分钟 |
| 50301 | 503 | 数据源已停用/下线/维护 | 显示占位页 |
| 50302 | 503 | 数据源版本已下线(日落到期) | 升级请求版本 |
| 50401 | 504 | 上游获取失败且无可用缓存 | 显示“暂无数据”,保留上次画面 |
| 50000 | 500 | 服务器内部错误 | 指数退避重试 |