# 体育课训练(班级统一运动模式)设计与执行方案 本文描述体育课训练场景中,教师通过界面为“本班级所有学生”统一开启运动模式,并在课堂期间实时查看每位学生手环上传的生物数据与状态的完整方案(UI + MQTT/后端 + 异常处理)。 ## 目标与范围 - 教师在上课前一键开启“运动模式(Training Mode)”,下发至本班全部手环。 - 确认所有手环已进入运动模式(有应答/状态回报),对未响应的设备进行重试或标注异常。 - 上课期间,教师端实时查看本班所有学生的关键指标(心率、步数、血氧、体温、电量、在线状态等),并支持异常提醒。 - 下课时,教师一键结束“运动模式”,保存训练记录与统计。 ## 角色与前置条件 - 角色:教师(Teacher)。 - 前置条件: - 学生-手环已完成绑定关系; - 教师端具备班级管理权限(可获取班级学生清单); - MQTT 网关已接入并可对设备下发命令与接收上报; - 教师端可以通过 WebSocket 或 MQTT 查看数据(建议 WebSocket 由网关聚合推送)。 ## 教师端 UI 方案(页面:体育课训练) 页面入口建议:`/pages/sport/class-training/index`(名称可在 `pages.json` 注册)。 布局: - 顶部:班级选择(默认当前班级)+ 当前训练状态(未开始/进行中/已结束)+ 开始/结束按钮。 - 中部:学生网格/列表卡片:头像/姓名/学号/设备号/在线状态/电量/实时心率/步数/告警标记。 - 侧栏/下拉:未响应设备清单与操作(重试、标记缺席/设备故障)。 - 流程提示条:显示“已应答/未应答”的汇总进度条。 因为要显示很多学生信息,一般训练时一个班级大概是50-60人左右,所以,我想把每个人的信息包装成小的card类型的component方式,同时每个card里面展示的信息以颜色,阈值为区分,如果超过警示的要把卡片排到最前面,应该设置多个阈值,而且可调。 交互流程: 1) 开始训练 - 教师点击“开始训练”→ 弹出确认对话框(本班X人,预计时长,是否立即开始)。 - 确认后:发起“班级训练会话”并向本班全部设备下发“进入运动模式”命令; - 页面进入“等待应答”态,显示进度(已应答/未应答),60秒未应答弹出“重试/忽略/标记异常”选项。 2) 训练进行中 - 学生卡片实时显示指标: - 在线/离线(离线>30s标红/提醒) - 心率(区间/颜色阈值) - 步数(累计) - 血氧/体温(可选) - 电量(低电<20%提醒) - 支持筛选:仅看异常、仅看未在线、仅看低电、仅看某个阈值以上心率。 - 支持点开某个学生查看最近1-5分钟趋势小图(sparklines)。 3) 结束训练 - 教师点击“结束训练”→ 确认后向本班设备下发“退出运动模式”命令; - 聚合保存本次训练会话(时长、参与人数、异常数、平均/峰值心率等),并生成简要报表。 ## 执行方案(MQTT 主题/消息 + 网关) 为保证简单可靠,采用“教师端→网关→MQTT”的下发通道与“设备→MQTT→网关→教师端”的上报通道: 主题约定(示例): - 设备上行(遥测/状态) - `iot/devices/{device_id}/telemetry`(QoS0/1):心率、步数、血氧、体温、电量等 - `iot/devices/{device_id}/status`(QoS1,保留):在线/离线、固件版本、运动模式开关 - `iot/devices/{device_id}/ack`(QoS1):对命令的应答 - 设备下行(命令) - `iot/devices/{device_id}/cmd`(QoS1):单设备命令 - 网关聚合通知(推给教师端 WebSocket 或 MQTT 客户端) - `iot/classes/{class_id}/stream`(WebSocket通道ID或服务端推送频道,供教师端消费) 命令与应答(JSON 负载格式建议): ```json // 下行:进入运动模式 { "type": "training.start", "tenant_id": "t-001", "class_id": "c-0901", "training_id": "trn_20250925_001", "issued_at": 1695619200000, "expire_secs": 60, "sampling_interval_secs": 5, "metrics": ["hr","steps","spo2","temp","battery"] } ``` ```json // 上行应答: { "type": "ack", "cmd": "training.start", "training_id": "trn_20250925_001", "device_id": "dev_abc123", "student_id": "stu_10001", "ok": true, "at": 1695619203500, "fw": "1.4.2", "note": "ready" } ``` ```json // 遥测: { "type": "telemetry", "training_id": "trn_20250925_001", "device_id": "dev_abc123", "student_id": "stu_10001", "ts": 1695619210000, "hr": 112, "steps": 350, "spo2": 98, "temp": 36.5, "battery": 72 } ``` 会话与关联: - 启动时由网关生成 `training_id` 并持久化(班级、发起教师、开始时间、目标人数)。 - 每条应答/遥测均携带 `training_id` 用于快速聚合与展示。 - 设备也可通过 `status` 保留消息标注 `training_mode: true/false`,便于后续“断线重连”后立即恢复状态。 QoS/保留/超时建议: - 命令:QoS1(可选保留=false);应答:QoS1;遥测:QoS0/1(按带宽权衡)。 - 保留消息:`status` 使用保留,保证新订阅可立即获知设备当前模式与在线状态。 - 超时:下发后 60s 内未应答则判定“未响应”,提供一键重试(最多2-3次)。 安全与权限: - 教师端不直接向 MQTT 发命令,统一走网关(鉴权、配额、风控)。 - 网关校验教师对 `class_id` 的管理权限,并仅向该班学生设备(或绑定设备)下发命令。 ## 网关(server/gateway-mqtt-node)对接 建议在网关提供两类接口: 1) REST 操作(供教师端触发) - `POST /api/classes/{class_id}/training/start` - 入参:`sampling_interval_secs`、`metrics`(可选)、`expected_count` - 出参:`training_id`、`sent_count` - `POST /api/classes/{class_id}/training/stop` - 入参:`training_id` - 出参:`stopped: true` 2) WebSocket 推送(供教师端订阅显示) - 频道:`/ws/classes/{class_id}/training/{training_id}` - 消息:三类事件 - `ack`(应答进度) - `telemetry`(实时数据) - `summary`(阶段统计/结束汇总) > 教师端也可直接使用 MQTT 订阅聚合主题,但 WebSocket 方案对移动端/小程序网络环境更友好。 ## 数据存储与报表(可选增强) - 表:`training_sessions`(会话元数据)、`training_participants`(参与学生/设备)、`training_metrics_live`(近实时缓存,用于课堂展示)、`training_metrics_hist`(历史明细/聚合,课后报表)。 - 课后可生成统计:参与率、平均/峰值心率、总步数、异常次数(离线、低电、心率异常区间)。 ### Supabase 实时事件流表设计 - 表:`public.training_stream_events` - 主键 `id` (`uuid`),默认 `gen_random_uuid()`。 - 必填 `training_id` (`uuid`) 对应课堂训练会话;后续 `training_sessions` 表建好后可添加外键。 - `event_type` (`varchar(32)`),值域:`ack` / `telemetry` / `metrics` / `state` / `summary` 等。 - `class_id` (`uuid`,非空) 引用 `ak_classes`,RLS 主要按照班级授权。 - `student_id` (`uuid`,可空) 引用 `ak_users`,若为空表示班级级别事件。 - `device_id` (`uuid`,可空) 引用 `ak_devices`,为空表示未绑定手环。 - `status` (`varchar(64)`)、`ack` (`bool`)、`metrics` (`jsonb`)、`payload` (`jsonb`) 存储结构化指标和原始载荷。 - `ingest_source` (`varchar(32)`,默认 `gateway`)、`ingest_note` (`text`) 记录事件来源或模拟批次说明。 - `recorded_at` / `ingested_at` (`timestamptz`) 记录设备事件发生时间与落库时间。 - 索引:分别在 `(training_id, recorded_at desc)`、`(class_id, recorded_at desc)`、`(student_id, recorded_at desc)`、`(device_id, recorded_at desc)` 与 `event_type` 上建立,方便实时排序和过滤。 - Realtime:脚本自动将表加入 `supabase_realtime` publication。 - 视图:`public.training_stream_latest_metrics` 使用 `DISTINCT ON` 获取每名学生最新的 telemetry/metrics 事件,页面可直接订阅该视图聚合结果。 - 行级安全(RLS): - `service_role full access`:Edge Function / Gateway 可以全量读写(`auth.role() = 'service_role'`)。 - `teacher read class training events`:教师凭 `ak_teacher_roles` 与 `current_user_has_permission` 读取自己负责班级的事件。 - `student read own training events`:学生仅能读取 `student_id` 等于自身的记录。 - `teacher insert training events`:允许具备班级权限的教师在课堂模拟工具里写入事件,或由服务角色写入。 - `service role delete training events`:仅服务角色可执行删除用于清理。 - 脚本位置:`create_training_stream_events.sql` ## 异常与边界处理 - 设备离线:`status` 超过 30s 未更新 → 标记“离线”,教师端置顶并提示。 - 未应答:首次下发后 60s 未应答 → 可重试;连续2次失败→ 标记为“未上线/设备异常/未佩戴”。 - 低电:`battery < 20` 高亮提示;`<10` 触发告警。 - 断点续传:设备重连后,读取 `status` 保留消息恢复 `training_mode`;教师端无需手动干预。 - 结束异常:若下课 stop 未成功(网关/设备异常),设备端可设定超时自动退出训练模式(例如 3 小时)。 ## 教师端页面验收清单 - [ ] 可选择班级并显示班级学生清单与绑定设备号 - [ ] 点击“开始训练”后,出现应答进度条与“未响应列表” - [ ] 训练中:实时刷新所有学生关键指标,异常醒目 - [ ] 可筛选/排序(异常优先) - [ ] “结束训练”后生成本次训练的小结 - [ ] 断线/重连后页面自动恢复到当前训练会话 ## 附:命令/应答/遥测数据示例 ```json // 结束训练命令 { "type": "training.stop", "tenant_id": "t-001", "class_id": "c-0901", "training_id": "trn_20250925_001", "issued_at": 1695619800000 } ``` ```json // 设备状态(保留) { "type": "status", "device_id": "dev_abc123", "student_id": "stu_10001", "online": true, "training_mode": true, "fw": "1.4.2", "battery": 72, "ts": 1695619210000 } ``` --- 如需,我可以: - 创建 `pages/sport/class-training/index.uvue` 的页面骨架(UTS 友好)、注册路由、接上网关 WebSocket; - 在 `server/gateway-mqtt-node` 内补充上述 REST 与 WS 接口示例,并提供一个内置 MQTT 模拟器用于联调。