资讯动态

中学生智能体测穿戴系统实战(一)三端协同的整体架构与技术选型

发布时间:2026/10/24 4:06:04 来源:尧图企业网站定制
中学生智能体测穿戴系统实战一一块手表如何联动 FastAPI 后端、Web 后台与 Android 老师端——三端协同的整体架构与技术选型本系列基于真实可跑的比赛仓库contest2026_248_0911我会一边讲架构一边贴代码不写空话。这是第 1 篇总览篇先把整张地图给你铺开后面 7 篇再逐端拆。图 1项目封面。一句话——让中学生戴上手表就能自动完成 8 项国标体测的采、评、报老师在 Web/Android 上实时看结果。一、一句话讲清楚这个项目是干嘛的我们做的是一套面向中学生体质健康测试场景的数据采集与评分系统。学生手腕上戴一块 OpenVela 智能手表跑完 50 米、跳完绳、做完仰卧起坐、量完坐位体前屈手表自动计时计数、记录心率数据回传到云端服务端按《国家学生体质健康标准》打分再调大模型生成一份中文运动周报体育老师在 Web 后台或 Android App 里直接看到全班成绩、趋势和 AI 报告彻底告别纸质登记和手工录分。为什么要费这么大劲做这件事我自己当年上中学体测老师拿着秒表喊预备——跑终点再派一个人掐表回办公室把成绩抄到 Excel 里一个班 45 人光录分就要一节课。更别说跳绳、仰卧起坐这种靠人眼数数的项目漏数、错数是常事。我们想做的就是把采集—计分—归档—分析这条链全部塞到一块手表和一台服务器里老师只负责看结果。仓库里同时存在两种手表端实现这也是本系列最有意思的地方原生 C 版本app/hello_app/fitness/跑在 OpenVela/NuttX 上用 LVGL 画 UI快应用版本quickapp/hello_quickapp/包名com.xsport.fitness基于.ux单文件组件开发manifest.json里声明了service.health、system.fetch等特性。这两种实现对应的是同一套业务8 项体测、心率、离线缓存、AI 报告只是技术栈不同。为什么要做两遍一方面是比赛要求同时覆盖手表应用创新和AI 硬件产品创新两个方向另一方面也是想验证同一份后端 API 能不能同时被 C 端和.ux端消费——答案是可以只要后端把/api/v1/fitness/upload这种契约定好手表端用 HTTP 还是 MQTT 发、用 C 还是 JS 写服务端根本不关心。服务端和管理端则是一套FastAPI 后端 原生 HTML/JS 前端 Kotlin 写的 Android 老师端。整个仓库根目录还有两份关键清单openvela.xml是 repo 清单里面能看到我们依赖了vendor/sifli思澈 SF32LB52、vendor/xiaomi/vela、apps/graphics/lvgl、apps/netutils/mqttc/MQTT-C、frameworks/runtimes/quickapp这些子工程contest2026_248_duobaanzhizaoju.xml则是比赛专用的另一份 manifest我们后面部署篇会提到。二、四端拓扑一张图看明白谁连谁先看一张我们对外汇报用的成品架构图这是整个系统的门面图 2三端协同总览。穿戴端负责采服务器端负责算终端负责看——这是我们对外讲方案时最先抛出来的一张图。下面这张 Mermaid 是我把 PPT 图按真实代码还原出来的版本每条边都能在仓库里 grep 到对应文件你可以直接拿去改成自己的项目管理端服务端Docker Compose 编排手表端OpenVelaHTTP POST /api/v1/fitness/uploadsystem.fetch 离线队列MQTT fitness/{device_id}/datamqtt.js 实时心率REST /api/v1/*Retrofit /api/v1/*原生 C/LVGL 应用app/hello_app/fitnessfitness_main.c快应用 Xsportcom.xsport.fitness.ux system.fetchFastAPI:80007 个 routerEMQX 5.4:1883 / :8083 / :18083PostgreSQL 16:5432Redis 7:6379AI 引擎ai_engine.pyOpenAI/DeepSeek/Zhipu/Qwen/Doubao/Kimi/MiMoWeb 后台index.html Chart.js 4.4Android 老师端Kotlin Retrofit 2.9图里每条线都能在代码里找到出处下面我会逐个对号入座。这里我想先强调一个设计上的取舍手表端故意做轻服务端故意做重。手表上 466×466 的圆屏、内存就那么点我们只让它干三件事——采数据、本地缓存、把结果画出来。复杂的评分阈值、心率区间占比、AI 报告生成、班级统计全部放到服务端做。这样做有两个直接好处一是以后国标评分标准改了只动scoring.py一个文件手表不用 OTA二是手表端 C 代码和.ux代码都能保持在几千行以内调试起来不痛苦。三、通信链路端口、路径、协议全是代码里真实出现的3.1 服务端到底起了哪些服务看fitness-server/docker-compose.yml我们一共编排了 4 个服务# fitness-server/docker-compose.yml节选 services: db: image: postgres:16-alpine ports: - 5432:5432 redis: image: redis:7-alpine ports: - 6379:6379 emqx: image: emqx/emqx:5.4 ports: - 1883:1883 # MQTT TCP - 8083:8083 # MQTT over WebSocket - 8084:8084 # MQTT over WSS - 18083:18083 # EMQX 控制台 server: build: . ports: - 8000:8000 depends_on: db: condition: service_healthy redis: condition: service_started emqx: condition: service_started也就是说一台机器上 5432 跑 PG、6379 跑 Redis、1883 跑 MQTT、8000 跑 FastAPI全部由docker-compose up一键拉起来。开发期本地不启容器也能跑——config.py里默认DB_TYPEsqlite直接用fitness.db文件就能调试。3.2 FastAPI 挂了哪些 router打开fitness-server/backend/main.py一眼就能看清我们的 API 边界# fitness-server/backend/main.py节选 app FastAPI(titlesettings.APP_NAME, versionsettings.APP_VERSION, lifespanlifespan) app.add_middleware( CORSMiddleware, allow_originssettings.CORS_ORIGINS, # [*] allow_credentialsTrue, allow_methods[*], allow_headers[*], ) app.include_router(auth.router) app.include_router(students.router) app.include_router(devices.router) app.include_router(fitness.router) app.include_router(statistics.router) app.include_router(teacher_class.router) app.include_router(ai_report.router)7 个 router 的 URL 前缀我用 grep 一把抓出来分别是Router前缀干什么auth.py/api/v1/auth老师/管理员登录发 JWTstudents.py/api/v1/students学生档案、按班级查devices.py/api/v1/devices手表设备注册与绑定fitness.py/api/v1/fitness体测数据上传、查询、手动录入statistics.py/api/v1/statistics班级/项目平均分、及格率teacher_class.py/api/v1/teacher-class老师-班级绑定ai_report.py/api/v1/aiAI 周报列表、生成、配置体测数据上传的两个核心接口就在fitness.py里POST /api/v1/fitness/upload单条和POST /api/v1/fitness/upload/batch批量对应手表断网补传。另外还有GET /api/v1/fitness/records、GET /api/v1/fitness/records/{student_id}、POST /api/v1/fitness/records/manual老师在 Web/Android 上手工补录。3.3 MQTT 这条支线除了 HTTP手表还能走 MQTT。backend/mqtt_handler.py的文件头注释直接写明了 topic 规则设备发布Topic: fitness/{device_id}/data → 体测数据 fitness/{device_id}/status → 设备心跳 服务器订阅: fitness//data → 接收所有设备数据后端用的是paho-mqtt在lifespan里跟 FastAPI 一起启动订阅到消息后直接调scoring.calculate_score打分再落库。手表原生 C 端里对应模块叫http_client.c但在快应用那一侧是src/common/mqtt.js通过 TCP/WebSocket 把实时心率推给 EMQX。3.4 配置文件里到底有哪些旋钮backend/config.py用的是pydantic_settings.BaseSettings环境变量一改就能换数据库、换 MQTT# fitness-server/backend/config.py节选 class Settings(BaseSettings): APP_NAME: str 中学生体测数据管理平台 ALGORITHM: str HS256 ACCESS_TOKEN_EXPIRE_MINUTES: int 1440 # 24h DB_TYPE: str os.getenv(DB_TYPE, sqlite) POSTGRES_PORT: int int(os.getenv(POSTGRES_PORT, 5432)) REDIS_HOST: str os.getenv(REDIS_HOST, localhost) REDIS_PORT: int int(os.getenv(REDIS_PORT, 6379)) MQTT_HOST: str os.getenv(MQTT_HOST, 127.0.0.1) MQTT_PORT: int int(os.getenv(MQTT_PORT, 1883)) CORS_ORIGINS: list [*]线上跑 Docker 时docker-compose.yml把POSTGRES_HOSTdb、REDIS_HOSTredis、MQTT_HOSTemqx这几个环境变量塞给server容器服务之间就靠 Compose 内置 DNS 互找不用写死 IP。四、技术选型表为什么是这一套端语言/框架关键依赖代码里真实出现为什么这么选手表原生端COpenVela/NuttX RTOSLVGL、自研sport_algo.c、data_collector.c、http_client.cRTOS 上资源紧C LVGL 画 466×466 圆屏最省内存CMakeLists 里DEPENDS lvgl一行声明依赖手表快应用端.uxhapjssystem.fetch、system.storage、service.health上层业务迭代快.ux热更新方便manifest.json直接声明心率/血氧特性后端Python 3 FastAPISQLAlchemy(async)、Pydantic、pydantic-settings、paho-mqtt、uvicorn异步、自动生成/docsSwagger、Pydantic 做请求校验写一个 router 很快数据库PostgreSQL 16开发期可切 SQLiteasyncpg / aiosqlite主库存学生、成绩、设备、AI 报告开发期用 sqlite 免装服务缓存/消息Redis 7 EMQX 5.4redis:7-alpine、emqx/emqx:5.4Redis 做热点查询缓存EMQX 扛 MQTT 设备接入Web 后台原生 HTML/CSS/JSChart.js 4.4CDN、无打包工具老师端页面不多挂在 FastAPI 的/static下就行省去构建链Android 老师端KotlincompileSdk 34, minSdk 26Retrofit 2.9、OkHttp 4.12、Gson、Coroutines、MPAndroidChart、Glide、ViewBinding原生 Activity 更贴近老师日常平板Retrofit 跟 FastAPI 的 REST 接口对得整整齐齐部署Docker Compose4 个 service 一键起比赛现场不用手动装 PG/Redis/MQTT举个例子Android 那侧的依赖在android app/app/build.gradle里写得明明白白// android app/app/build.gradle节选 dependencies { implementation com.squareup.retrofit2:retrofit:2.9.0 implementation com.squareup.retrofit2:converter-gson:2.9.0 implementation com.squareup.okhttp3:okhttp:4.12.0 implementation com.github.PhilJay:MPAndroidChart:v3.1.0 implementation androidx.lifecycle:lifecycle-viewmodel-ktx:2.7.0 implementation org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3 }对应的AndroidManifest.xml里注册了 13 个 ActivityLoginActivity是 launcher后面依次是TeacherHomeActivity、StudentListActivity、StudentDetailActivity、AIReportActivity、ClassStatisticsActivity、AdminHomeActivity、ClassManagementActivity、DeviceManagementActivity、TeacherManagementActivity、ReportExportActivity、ProfileActivity、MainActivity。一个老师从登录到看班级统计、导出报告全走这几个页面。Web 那侧我再多说一句。很多人第一反应是后台肯定得上 Vue/React但我们最后选了最朴素的原生index.html/static/css/style.css CDN 引入 Chart.js 4.4。原因很实在页面就 9 个数据总览、学生管理、体测记录、统计分析、设备管理、排行榜、教师绑定、AI 模型配置、AI 报告管理没有复杂交互用打包工具反而是负担。FastAPI 的StaticFiles一行就把前端挂到/static下根路径/直接返回index.html老师浏览器打开 IP 就能用连 Nginx 都省了。为什么要花这么大力气把这套组合搭起来我们在 PPT 里把它总结成 7 个创新点回头看每一条都能对应到上面某段代码图 4七大创新亮点。这张图基本就是后面 7 篇文章的标题大纲——每一篇都会把其中 1–2 点从PPT 口号还原成代码细节。五、一次完整闭环从学生戴上手表到老师看到报告我按真实代码路径走一遍你就明白数据是怎么流的了。整体上我们同时支持两种模式——体测进行时走实时链路BLE/HTTP/MQTT 5Hz 推心率体测结束后走批量链路WiFi 直连服务器补传完整记录图 3实时 批量双模数据流。实时链路保证老师在体育课上能看到全班进度批量链路保证下课后数据能完整归档到数据库。下面是 7 步具体代码路径学生戴表。手表启动后无论是 C 端的fitness_main.c还是快应用的app.ux第一件事都是读本地配置学号、设备 ID、服务器地址没配过就跳设置页。开始测试。快应用进入pages/test调用service.health持续采心率原生 C 端走data_collector.csport_algo.c自己算跳绳峰谷、跑步配速。结束本地计分。结果页pages/result或ui_result.c先按国标粗略显示得分同时把记录塞进本地队列——快应用里是sync.js的{ _id, _synced, _createdAt, _retries }结构。联网上传。网络恢复后syncPending()批量调POST /api/v1/fitness/upload/batchC 端走http_client.c的http_upload_fitness_record()。也可以选择走 MQTTtopic 是fitness/{device_id}/data。服务端评分。mqtt_handler.py或fitness.py收到数据后调scoring.calculate_score(test_type, gender, value)按项目阈值表打出 0–100 分和excellent/good/pass/fail等级落 PostgreSQL。AI 周报。服务端聚合学生近一周的体测记录 心率由ai_engine.py拼成 prompt调 OpenAI / DeepSeek / 智谱 / 通义 / 豆包 / Kimi / 小米 MiMo 七家之一默认 base URL 都在PROVIDER_BASES字典里生成一份 HTML 报告存进ai_reports表。老师看到。Web 后台index.html侧边栏点AI 报告管理或 Android 端打开AIReportActivity同一个/api/v1/ai/reports接口把报告拉出来展示。整个闭环没有任何一处需要人工录分——这就是我们相比传统纸质体测最直接的效率提升。这里我想多讲两句离线缓存这一步因为它最容易被忽略但最影响现场体验。中学操场里 WiFi 信号经常不稳定体育课一节课 40 分钟老师不可能等手表联网再让学生跑。所以我们在快应用sync.js里实现了一个本地优先队列每条记录先写system.storage标记_syncedfalse应用启动时和网络状态变化时统一扫一遍未同步记录批量推送。C 端那侧则是直接把 JSON 写到 LittleFS 文件里等http_client.c探测到网络再逐条发。这样哪怕手表在操场角落断网 20 分钟回到教学楼也能把数据补齐不会丢一条。另一个值得说的细节是身份怎么绑定。学生戴着同一块手表上不同课怎么知道这条跳绳成绩是张三的还是李四的我们的做法很简单在手表设置页录入学号比如STU20260001每次上传时把student_id一起塞进 JSON服务端students.py按学号关联到具体档案。设备本身device_id比如VELA-WATCH-0001则在devices.py里独立登记方便老师在 Web 后台看哪块表现在在线、最后一次心跳是什么时候。六、产品价值图 5产品价值对照。左边是我们中学时代经历过的体测现场右边是这套系统上线后老师真正能省下的时间——这也是我们愿意把它写成一整个系列的原因。七、后续 7 篇我会讲什么这是系列导航先给你目录感第 1 篇本文中学生智能体测穿戴系统实战一三端协同整体架构与技术选型第 2 篇手表原生 C/LVGL 端实战——拆fitness_main.c、LVGL 圆屏适配、sport_algo.c的跳绳峰谷计数。第 3 篇手表快应用 Xsport 实战——.ux单文件组件、system.fetch的 String 坑、离线同步队列sync.js。第 4 篇FastAPI 后端骨架——lifespan、7 个 router 怎么分、SQLAlchemy async 模型、JWT 鉴权。第 5 篇体测评分引擎与 MQTT 接入——scoring.py阈值表、mqtt_handler.py怎么把设备消息落库。第 6 篇AI 周报引擎——ai_engine.py怎么拼 prompt、七家大模型切换、HTML 报告渲染。第 7 篇Web 管理后台——原生 HTML 单页应用、Chart.js 4.4 画趋势图、/static挂载。第 8 篇Android 老师端——Kotlin Retrofit ViewBinding 从登录到班级统计。第 9 篇Docker Compose 一键部署与联调排坑——PG/Redis/EMQX/Server 四容器串联、常见网络与 CORS 问题。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑