首页 / 资讯中心 / 文章详情

佳明Garmin数据大屏搭建:用Marvis开源服务打造本地健康看板

佳明Garmin数据大屏搭建:用Marvis开源服务打造本地健康看板 ★ FEATURED ARTICLE
佳明手表用了三年朋友圈截图发得不少但真正让我觉得数据有价值的是这周折腾出来的 Marvis 佳明数据大屏。Marvis 是一套把佳明 Garmin Connect 账号数据拉取到本地的开源服务配合自建 Web 看板我可以把心率、睡眠、训练负荷、步数这些指标做成一页随时刷新的电视大屏。跑步回来扫一眼昨晚睡眠恢复多少、今天训练该拼还是该缓不用再翻手机 App。这套东西特别适合喜欢自己搭数据面板的跑者、骑行爱好者以及想给家里长辈做健康看板的开发者。我知道很多人会觉得奇怪佳明官方 App 和网页版已经提供了图表为什么还要自己折腾一个 Marvis 大屏答案很简单——官方页面的指标很全但交互是手机逻辑不是墙面展示逻辑。你很难让官方数据页常驻大屏更没法把睡眠、压力、训练负荷、步数、一周趋势这些维度按自己的审美排布在一页里。Marvis 的核心价值就是把数据从 Garmin 的封闭生态里取出来放到你能完全控制的本地看板里。这篇文章我会从整体设计、安装、数据接入、前端渲染到问题排查把完整路径写出来你哪怕没接触过写代码也能照着做。1. 整体设计与思路拆解1.1 数据大屏到底需要什么先别急着装软件想清楚你要展示什么。佳明设备产生的数据维度非常多步数、心率、压力、睡眠阶段、血氧、训练负荷、恢复时间、运动记录、海拔、温度、续航电量。全部怼到一屏上结果就是什么都看不清。我自己的筛选原则是“一眼能指导行动”。起床前看睡眠跑步前看恢复状态训练中看实时心率晚上看全天活动量和压力。所以最终大屏只保留了四个主功能区左上是今日概览步数、活动卡路里、爬楼层数、强度分钟右上是心率与压力曲线左下是训练状态训练负荷、恢复时间、最大摄氧量趋势右下是一周睡眠结构。屏幕分辨率是 1080p字号大一点站在两米外也能看清。如果你是骑行用户可以加一栏“功率区间时长”和“爬升”如果只看重健康管理血氧和呼吸频率就比训练负荷更重要。关键是要先明确使用场景再决定字段否则后期改布局会让你疯掉。1.2 为什么选“本地服务 自研看板”而不是官方 API佳明官方并没有开放正式的数据 APIGarmin Connect 的数据读取主要靠两类方式一是官方生态内的分享链接二是社区逆向维护的非官方接口。硬要用官方能力你会发现能拿到的只有授权后的少量数据而且展示形式已经固定无法做自定义大屏。Marvis 这类工具的价值就体现在这里。它相当于一个中间层你只需要在本地部署 Marvis 服务给它配置 Garmin 账号令牌它就会定时把所有运动、健康数据同步到本地数据库。前端看板再直接读取本地数据库完全不需要每次展示都去请求 Garmin 服务器。这样风险也小即使哪天 Garmin 调整接口影响的只是同步层而不是整个大屏系统。这个架构还有两个隐藏优势第一离线可用大屏展示的是本地数据断网也照样显示最近一次同步结果第二可以叠加自己的计算逻辑比如把一周七天的训练负荷做成环比百分比或者计算“当前恢复时间是否足够应付明天的长距离”这些佳明客户端很难实现。1.3 为什么不直接用 Grafana看到这里熟悉监控面板的人会问Grafana 加上社区的数据源插件不就行了吗我也试过。Grafana 的图表类型丰富做时间序列很好看但问题是它面向的是监控系统单位往往是秒、字节、百分占比而健康数据需要的是“昨晚深睡 1 小时 52 分”“当前压力 23”“恢复时间 42 小时”这样的语义化表达。在 Grafana 里写这些转换语法比直接用前端框架渲染要绕得多。另外大屏如果长期挂机刷新频率不需要 Grafana 那套毫秒级监控。健康数据半小时同步一次就够了本质是一个“半实时展示”用内部自研看板反而更轻量不用维护一堆插件依赖。我把 Grafana 留给家里的智能家居监控佳明数据则交给 Marvis 自研前端方案各干各的活。2. 核心细节解析与实操要点2.1 Marvis 安装时的环境准备我选的 Marvis 版本是社区维护的 Docker 镜像安装前需要准备好三样东西一台可长期运行的机器我用的是家里那台 NUC 小主机8GB 内存绰绰有余、Docker 环境以及一个可联网的目录用来放配置。如果你没有 Docker也可以直接跑 Python 源码包但我更推荐 Docker因为后面升级版本、迁移数据都会方便很多。安装的过程并不复杂先把镜像拉下来然后启动容器并挂载两个目录一个存放 Garmin 同步凭证一个存放 SQLite 数据库文件。第一次启动后Marvis 会生成一个配置文件你需要填进去的是 Garmin 账号邮箱和密码以及同步间隔。我设置为每 30 分钟同步一次既不会太频繁触发 Garmin 的风控限流也能保证大屏数据基本新鲜。这里要特别提醒千万别用主力账号的强密码明文写到配置里。我的做法是单独注册一个佳明账号再通过佳明家庭账户功能把主力账号的数据共享给这个子账号Marvis 只读取子账号。这样即使配置泄露也不会影响你的主账号安全。初次接触这个工具的人很容易忽略这一层等真的出了问题再补救就晚了。2.2 Garmin 账号连接与令牌管理Marvis 之所以能拉到佳明数据是因为它模拟了 Garmin Connect 手机端的登录流程获取到有效的访问令牌后再通过内部接口请求 JSON 数据。这个令牌不是永久的通常几个小时到几天就会过期Marvis 会在同步时自动刷新但前提是登录凭证没有被风控限制。实际操作中最折腾我的就是登录环节。如果账号开启了二步验证Marvis 的纯命令行登录方式会卡住。解决办法是先临时关闭二步验证完成首次令牌获取后再重新开启。注意每台设备登录 Garmin 后会有一个设备信任列表Marvis 首次登录后建议在佳明官网把“未知设备”标记为信任设备否则后续容易出现同步间歇性失败。令牌管理还有一个反向经验不要手动删除 Marvis 的缓存目录。我刚开始折腾时遇到同步失败就想“重置一下”把整个 cache 文件夹删了结果令牌也一起清除下一次同步要重新走登录流程还更容易触发风控。正确的做法是只在日志提示 credential 失效时才去执行重新登录操作。2.3 数据清洗与单位换算的门道拉下来的 JSON 数据并不是开箱即用的。举个例子睡眠数据里深睡、浅睡、快速眼动和清醒阶段的时长单位有时是毫秒有时是秒心率数据的时间戳是 UTC但你是东八区直接展示会让曲线整体偏移八个时区。这些都在 Marvis 的同步层里做过一部分处理但并非每个字段都处理到位。我自己在写看板时总结了一个转换清单所有时长统一换算成分钟心率按“区间时长”重新分组为放松、燃脂、有氧、无氧、极限五档睡眠数据把入睡时间减去本地时区偏移确保“昨晚 23:00 入睡”不被显示成“今天 07:00 入睡”。如果你用的同步工具没有做这些转换一定要在数据入库前清洗不要等前端渲染时再补救否则后面写图表会写出一堆判断条件。另一个容易被忽略的是“活动类型字段”。Garmin 返回的 activityType 有时候是字符串比如 running有时候是 ID 数字比如 1 代表跑步。建议在数据库里建一张字典表把常见类型 ID 映射成中文名称统一转换后再存大屏展示表。否则你大屏上可能一会儿显示 “Running”一会儿显示 “跑步”看着很混乱。2.4 大屏前端的技术选型数据从 Garmin 到本地数据库后剩下的就是纯前端活了。我用的技术栈是 Vue 3 ECharts不复杂知识点也足够多。为什么不用 React没有特别原因纯粹是我 Vue 熟练而且 Vue 模板写法更适合快速搭信息面板。大屏和普通网页有一个隐藏区别它要在全屏模式下长时间运行所以需要用“暗色背景 光色图表”的配色方案降低 OLED 屏幕烧屏风险也减少夜间亮度刺激。我最终的背景色用的是深灰蓝 #101418主文字色是 #E5E7EB强调色是佳明的品牌蓝和跑步常用的亮橙。大屏上的每个卡片都保持固定尺寸不用响应式栅格系统而是用绝对定位 百分比混合确保任何一台屏幕上看都不会乱跳。3. 实操过程与核心环节实现3.1 数据接入层用 Python 脚本拉取数据虽然 Marvis 已经负责了主要同步但有些个性化指标它不会计算比如“本周训练负荷相比上周同期变化率”。所以我额外写了一个 Python 脚本每天凌晨定时执行一次把 Marvis 同步好的基础表进一步聚合成指标表。这里只说一下核心思路和片段完整代码你可以在自己的项目里按需扩展。脚本的关键是从本地数据库读取昨天的活动记录然后按“运动类型”分组计算总时长和平均心率。核心逻辑大致是这样from datetime import datetime, timedelta import sqlite3 import pandas as pd db_path /data/garmin/marvis.db conn sqlite3.connect(db_path) yesterday datetime.now().date() - timedelta(days1) sql SELECT activity_type, start_time, duration, avg_hr, training_load FROM activities WHERE date(start_time) ? df pd.read_sql_query(sql, conn, params(str(yesterday),)) # 按类型聚合 summary df.groupby(activity_type).agg( total_duration(duration, sum), avg_hr(avg_hr, mean), total_load(training_load, sum), ).reset_index()这就是一个典型的二次加工步骤。Marvis 已经把原始数据规整得很干净了但聚合口径各家需求不同所以最好自己再包一层。对于不懂 Python 的读者你可以跳过这段只理解前面“Marvis 同步 二次计算”的配合关系即可。3.2 指标计算训练负荷与恢复时间的估算训练负荷是佳明独有的一个指标官方的算法没有完全公开但大屏上总不能永远只显示官方给的数值。我参考常见的 TRIMP 算法用“运动时长 × 心率强度系数”来估算单次训练负荷。心率强度系数不是线性增长的心率处于 60% 最大心率附近时系数低到了 85% 以上的阈值区间系数会明显上升。恢复时间也可以用简化方式估算。我的参考公式是恢复小时 本次训练负荷 ÷ 基准强度值再根据历史平均负荷做一个平滑处理。这个算法在强度波动大的日子里偏差会比较大但对于大屏展示来说足够直观。我会在面板上标注“参考值”字样避免误认为自己写的是佳明官方算法。这里有个实操心得不要把原始 API 返回的训练负荷和自算的负荷同时显示在同一张趋势图上数值量级不一样会形成双线走势误导自己。要么全部用官方值做时间序列要么全部用自己的估算值口径一致才有对比价值。3.3 大屏渲染从数据库到 ECharts 图表到了前端这一步核心就是定时请求本地后端接口把数据填充到 ECharts 配置项里。后端我用的是一个简单 Flask 服务只开放几个 JSON 接口不承担业务逻辑。为什么这样做因为大屏展示和数据处理是两个生命周期数据处理出了问题不能影响大屏显示分开部署之后即使同步服务挂了大屏仍然能展示上一次的数据。前端每 60 秒轮询一次数据接口刷新图表。为了减少闪烁我用了 ECharts 的setOption方法而不是重新init一个实例这样整个大屏画面不会在刷新时闪一下。心率曲线部分我额外加了动画过渡效果让新数据点平滑地滑入观看体验会好很多。一个容易踩坑的地方ECharts 在数据为null的时候会断线所以对缺失数据点要手动补零或生成占位值。比如睡眠数据里某个阶段没有记录如果不处理图表会显示一条突然跌到 0 的裂缝。我的做法是在后端返回前先做一次填充用前一个非空值代替缺失点前端只负责展示不负责补数据。3.4 性能调优长时间挂机的注意事项大屏是要七天二十四小时挂着的时间久了会遇到两个问题一是浏览器内存占用越来越大二是 WebSocket 断线后自动重连机制不生效。解决内存问题的关键在于控制图表历史数据长度。比如心率曲线最多保留最近 6 小时数据点超出部分在入渲染队列前就裁剪掉而不是让 ECharts 无限堆积。我还在大屏页面里加了一个“夜间自动降级模式”凌晨 1:00 到 5:00 之间停止刷新只显示静态数据。这个模式省电也能让前端和后端在低负载时段喘口气。很多人做数据大屏只想着加特效不考虑长期运行成本这一步是我实测后觉得特别有价值的优化。4. 常见问题与排查技巧实录4.1 令牌失效与 429 限流第一次跑通后我遇到了最经典的问题同步几天后突然失败日志里出现HTTP 429。这是 Garmin 服务器对频繁请求的限流反馈。排查方法很简单先在 Marvis 日志里确认触发时间点然后看是不是手动点击过“立即同步”太多次。我建议同步间隔不要低于 15 分钟并且不要在大屏刷新之外再额外开脚本请求 Garmin 接口。如果真的被限流了最快的恢复办法是把定时任务暂时停掉 1 到 2 小时让服务器的限流窗口过去。不要反复试登录否则令牌可能被吊销紧接着就是二步验证重新走一遍。前面说的“主副账号分离”在这时候帮了我很大忙即使子账号被短期封禁也不影响我主力账号的使用。4.2 时区偏移导致睡眠曲线错位这个问题很隐蔽。睡眠数据的日期归属Garmin 后端是按 UTC 天数记录的但大屏展示要按本地时区显示。你本地已经凌晨 2 点UTC 还是前一天 18 点数据会被算到前一天。最终表现在大屏上就是睡眠周期里的“清醒阶段”莫名出现在早晨 8 点而后半夜的睡眠数据却少了一段。我的处理方法是在 Marvis 同步后的统一清洗脚本里把 sleep 记录里的start_time和end_time先转成带时区的时间对象再按本地时区重新计算日期。这样“睡眠属于哪一天”就完全由本地时区决定。处理完以后睡眠各阶段的分布与大屏上的一周趋势就都能对上了。4.3 大屏长时间运行后白屏大屏挂在电视上跑了一周之后偶尔会出现白屏。排查后主要原因是前端 WebSocket 断了以后没自动重连后续请求失败页面处于假死状态。解决方式是在前端代码里监听WebSocket.onclose事件结束之后 5 秒自动重建连接并附带上一次的心跳序号避免重连后重复推送近段时间的数据。另外还需要在操作系统层面做一件事把浏览器设置成“禁用自动休眠”关掉电视的节能模式否则大屏会在半夜自己熄屏。这个看似土办法的步骤反而比代码里的各种重连机制更容易被忽略。反正我踩过这个坑第二天起来发现大屏黑了一整晚。4.4 常见问题速查表把这段时间踩过的问题整理成速查表方便你排查。问题现象可能原因解决思路数据完全不更新令牌过期清除凭证重新登录部分指标为 0数据清洗阶段被过滤检查类型映射和单位换算睡眠曲线错位时区未转换统一转换为本地时区HTTP 429请求太频繁拉大同步间隔暂停 1~2 小时大屏白屏WebSocket 断开增加自动重连机制Docker 容器启动失败数据目录权限异常检查 UID/GID 挂载参数5. 让这套大屏进一步延展5.1 接入家庭场景从大屏到语音播报数据大屏做好以后我觉得只挂在客厅还是不够过瘾所以加了第二个输出端把每日训练的简报通过家庭语音助手播报出来。每天早上会自动播报“昨晚睡眠 7 小时 20 分其中深睡 1 小时 10 分今日恢复状态良好建议进行中等强度有氧训练”。这个功能本质上只是把大屏最上方的一行摘要文字转成语音但体验完全不同。做法也很简单本地 Python 服务在每天 7 点定时读取最新指标拼接成一句话调用一个 TTS 服务生成音频再推送给家里的智能音箱播放。做这个步骤时要注意别把指标说得太绝对前面加一句“基于当前数据预估”能避免一些误导。另外如果家里有孩子可以把这个播报当成早上的小闹钟比手机闹钟温柔很多。5.2 做一个小型的跑团排行榜如果你身边有一群用佳明手表的跑友Marvis 大屏还可以做成“跑团周榜”。原理非常简单每个账号部署一个 Marvis 服务然后把多个服务的数据库汇总到一个中心库前端排行榜只展示汇总结果。汇总指标不复杂无非就是周跑量、周训练次数、最大摄氧量变化。我实际跑了一次跑团测试大家的积极性明显高了不少因为每周大屏上能够清晰看到自己的排名变化形成了一种轻量的良性竞争。但这里也要注意隐私问题非公开数据需要经过每个成员明确授权。我建议只展示昵称或首字母缩写不展示全名和具体运动轨迹。5.3 赛前赛后的数据复盘除了日常展示大屏还可以在比赛周期切换到“赛前状态模式”。赛前一周大屏主视觉变成倒计时、训练减量提醒、当前恢复状态赛后一天变成完赛成绩、分段配速、心率区间占比、睡眠变化对比。这个模式切换不需要开发两套前端只需要在数据接口加一个mode参数前端根据参数更换顶栏横幅和重点指标的位置即可。实际操作时我把这些模式写成了配置文件。比如“马拉松模式”会在左上角显示倒计时天数和赛前 48 小时建议“恢复模式”会把恢复时间、睡眠质量和压力水平三大指标放大到屏幕中央。这个设计让我在比赛周不用刻意去翻佳明 App墙上的大屏已经替我把关键信息整理好了。关于这一周折腾的一点体会数据大屏做到最后真正消耗时间的不是写代码而是理解数据背后的口径和语义。比如同样是“训练负荷”佳明官方接口给的是一个综合数值Garmin 网页端显示的是另一个口径自己估算又是一套逻辑三者之间不能混用。我觉得做这类个人数据项目最重要的能力不是把图表做得多炫而是能清晰掌握数据的来源、清洗规则和展示边界。如果你也想搞一套自己的佳明数据大屏建议从最小闭环开始先让 Marvis 把步数和睡眠同步到本地用一个最简单的 HTML 页面展示这两个指标跑通以后再往里加心率、训练负荷和趋势分析。不要一开始就想着几十个指标、复杂图表那样大概率会卡在数据清洗里出不来。先用起来再慢慢打磨这个项目才能真正变成你日常会看的东西。
阅读完成 · 觉得有帮助?
咨询建站