
春节最容易被忽略的健康问题不是吃什么而是喝水。走亲访友、打牌聊天、暖气房里待一天很多人早饭之后就再没主动喝过水等到口干才想起来。今年我给自己做了一个AR喝水提醒助手基于Rokid眼镜把喝水的进度和提醒直接投在眼前。这篇文章把完整方案、实现细节和踩坑记录整理一遍适合已经有Rokid眼镜但不知道拿它干什么的人也适合想做AR日常应用、又想快速验证想法的开发者。先说结论AR提醒的价值不在“多一个提醒”而在让提醒出现在最不容易被漏掉的位置。1. 先理思路为什么喝水提醒要放到AR眼镜上1.1 传统提醒输在哪里手机闹钟、微信提醒、手环震动这些方案我都试过。问题不是提醒本身不响而是响完之后容易忽略。手机在口袋里摸出来看一眼屏幕亮起来的时候你已经滑走注意力了手环震动一下正在打牌或者陪亲戚聊天时手缩在袖子下面震动根本感觉不到。喝水跟吃药不一样它不需要精确到分钟但它需要一个“持续可见的进度感”。AR眼镜刚好是干这个的——提醒信息不是藏在口袋里而是在你的视野边缘挂着抬头就能看到。Rokid眼镜采用光波导方案之后这一点尤其明显镜片可以做到比较高透显示区域放在视野正中偏下的位置不会遮挡人脸也不影响走路看路。另外AR提醒和手机提醒还有一个本质区别手机是“拉你过去看”AR是“过来让你看”。前者需要你中断当前动作后者只是在你正在做的事情旁边补了一条信息。喝水是低优先级动作用AR承载这种低打断成本的信息比在手机上弹窗合理得多。1.2 这套系统的三个核心组成部分一个完整的AR喝水提醒助手拆开看就三块感知端负责知道“喝了多少水”。这一步可以复杂到智能水杯称重也可以简单到一个倒计时按钮关键是数据能进到系统里。中控端负责算“该不该提醒”。所有饮水记录汇总到这里判断当前进度是否落后落后了才发事件不落后就闭嘴。中控端我直接放在手机或者Rokid的Station上不需要云服务器。显示端负责把提醒呈现到眼前也就是Rokid眼镜屏幕上的一张小卡片。卡片内容不多当前喝水量、目标量、进度条外加一句不烦人的文案。通信链路也不复杂感知端通过蓝牙或手动按钮把数据交给中控端中控端统计完后通过局域网WebSocket把提醒事件推给眼镜端浏览器。整套系统可以完全离线运行不需要外网。为方便对照各层级的角色和要点我整理成一张表层级角色落地方案通信方式感知端获取喝水行为数据智能水杯/称重垫/手动确认按钮BLE、本地HTTP中控端统计进度、判断提醒时机手机或Station上的Node服务本地服务、WebSocket显示端呈现提醒卡片、历史图表Rokid眼镜端浏览器页面局域网WebSocket2. 设备与开发环境让Rokid眼镜先跑起来2.1 Rokid眼镜的定位差异Rokid的眼镜型号不少别一上来就搞混。Rokid Max、Rokid Max 2这类产品核心定位是“头戴显示器”BirdBath光学方案沉浸感强适合看电影、打游戏但拿来做日常提醒透明度和佩戴舒适度都不算理想。Rokid AR Lite这一类空间计算套装用的是光波导方案镜片更接近普通眼镜显示区域是半透明浮窗更适合“眼前一直有一行字”的使用方式。我这次主要用Rokid AR Lite这套来演示因为它自带Station主机整条开发链路最简单。眼镜本身不做计算它只是显示终端Station是一个Android盒子跑浏览器、跑应用都行眼镜端的页面本质就是Station浏览器里的一个普通网页。这样开发门槛一下低了很多会写HTML就能做AR提醒。如果你手里只有Rokid Max这类眼镜也不是不能做但需要额外解决信号源问题要么通过支持DP输出的手机投屏要么接一个Type-C转接器。逻辑一样只是绕了一层建议先用自带主机的套装跑通流程再考虑迁移。2.2 最小开发链路浏览器页面跑通提醒浮窗我推荐的开发路径不是一上来就上Unity、上Rokid UXR Lab而是先用浏览器跑一个最小闭环。这一步的目的不是炫技是验证“眼镜里能看到提醒卡片”这个核心体验是否成立。整个链路是这样在手机或者电脑上启动一个Node服务监听HTTP和WebSocket眼镜端Station打开浏览器访问同一局域网下的服务地址中控端触发一条提醒WebSocket推送到眼镜浏览器页面更新浮窗内容。开发环境我用了最朴素的组合Node.js 18、ws库WebSocket、一个HTML文件。之所以不用Flask写服务端是因为WebSocket在Node里的生态最成熟而且一个进程就能同时干HTTP和推送两件事不用折腾线程。Python方案也能做但Flask做WebSocket还要再挂gevent或者websockets库逻辑上多绕了一道。Station浏览器访问页面时页面直接以全屏方式显示。真正的AR效果是Station渲染完画面叠加到光波导镜片上的对于提醒卡片这种简单UI普通网页就足够了不需要去调WebXR底层。很多开发者卡在“必须用Unity”这一步其实是被不存在的门槛拦住了。先用浏览器把浮窗做出来再谈原生体验。下面是网关服务的最小代码适合直接抄const http require(http); const fs require(fs); const { WebSocketServer } require(ws); const server http.createServer((req, res) { if (req.url /) { res.writeHead(200, { Content-Type: text/html }); res.end(fs.readFileSync(index.html)); return; } res.end(ok); }); const wss new WebSocketServer({ server, path: /ws }); wss.on(connection, (ws) { console.log(眼镜端已连接); }); server.listen(8765, () { console.log(网关运行在 http://0.0.0.0:8765); }); // 触发提醒的接口方便手动测试 server.on(request, (req, res) { if (req.url.startsWith(/remind)) { wss.clients.forEach((client) { if (client.readyState 1) { client.send(JSON.stringify({ message: 该喝水啦, progress: 42 })); } }); } });注意Node的request事件和http.createServer回调会同时触发上面为了演示直接复用了server对象实际项目里应该把路由集中写否则会踩到重复响应的坑。2.3 调试USB时遇到的错误代码40开发过程中有一个插曲很值得单独写我第一次把Rokid眼镜通过USB-C线插到Windows笔记本上准备抓取设备日志时设备管理器里直接报“该设备无法启动错误代码40”。第一次遇到的人大概率会以为是眼镜坏了其实Windows下这个提示90%是驱动和USB电源策略问题。排查顺序很重要我按下面这个顺序一步步试最后在第三步解决换线。很多Type-C线只有充电功能没有数据线芯。Rokid眼镜原装线往往只给充电做过优化调试时换一根支持USB 3.0数据传输出来问题概率直接降一半。换接口。不要用前面的扩展坞或USB Hub直接插主板上的后置接口或者笔记本侧面的直连口避免供电不稳。卸载设备驱动再重装。设备管理器里找到出错设备右键卸载勾选“删除此设备的驱动程序软件”然后拔掉眼镜重新插。若仍然报错打开“设备管理器—查看—显示隐藏的设备”把残留的未知设备清理掉再扫描检测硬件改动。最后检查电源管理。在USB Root Hub的“电源管理”选项卡里取消勾选“允许计算机关闭此设备以节约电源”。这里再补充一个容易误导人的点错误代码40和网络工程师语境里的“AR设备”没有任何关系。华为的AR系列路由器AR也是两个字在搜索引擎里占掉了大量“AR设备”的词条但那是企业路由器的产品代号跟增强现实的AR设备是完全两回事。搜资料时如果看到华为AR路由器、华为ENSP模拟器之类的页面直接跳过节省时间。3. 提醒策略与交互别让健康助手变成打扰狂魔3.1 提醒时机怎么算进度曲线与静默窗口AR能让提醒被看到但如果提醒本身设计得糟糕它只会让你更快摘掉眼镜。这一块是实现中最容易做砸的地方我前两版就翻过车设置成每个小时固定提醒一次结果下午打牌时眼镜一直在眼前刷“喝水喝水”被全家人吐槽了一下午。后来我改成“进度曲线静默窗口”策略。先定一个目标值默认1500mL然后按日常作息把一天切成几个时段每个时段有建议完成比例时间段建议完成进度说明07:00-10:0015%起床后补水不需要太多10:00-14:0035%午餐前一般摄入较多14:00-18:0060%下午最容易被忽略18:00-22:0085%晚饭后可以慢慢补22:00后不硬追避免睡前频繁起夜提醒触发条件只有两个一是当前时间段截止时实际进度低于目标进度二是距离上次喝水记录超过90分钟且当前进度落后于曲线。延迟提醒也很重要——连续两条提醒之间至少间隔45分钟同一小时内最多提醒2次超过2次就只记录不打扰。这套逻辑放在手机APP里也会有人用但放在AR眼镜上感受完全不一样。手机定时弹窗99%会被划掉AR眼镜里的浮窗你即使不看余光也能感知到它的存在。后来我甚至故意把提醒卡片设置成“关不掉”只要喝水记录没更新卡片就保持半透明悬浮状态低透明度但持续可见。实测这个设计比弹几秒就消失的方式有效得多。3.2 三层交互设计看见、确认、回看交互我做了三层每一层解决一个问题。第一层是被动看见。卡片半透明悬浮在视野中间偏下位置不挡视线。默认十秒后自动降为20%透明度只保留一小条“当前40%”的文字确保长时间不会产生压迫感。第二层是快速确认。Rokid AR Lite套装支持语音和按键两种输入。我实测下来产品语音识别在安静房间很好用说“喝水了”就能记录一次但春节家里人多嘈杂语音误触发率特别高。所以我最后把主确认方式改成了实体触控碰一下Station或者手柄按钮就记录喝水安静场景再用语音。调试时记得在服务端把语音和触控事件分开打日志否则你根本不知道用户是真的喝了水还是随口说了句“喝什么水”。第三层是回顾。眼镜端打开一个“今日喝水”页面按小时展示饮水分布。这个页面不用频繁打开但必须有因为它解决的是“我到底喝了多少”的焦虑感。图表我用了两种当天进度环用SVG在浏览器里直接画跨天趋势图用matplotlib在服务端生成PNG再推给眼镜端。后面这一节单独讲坑。3.3 春节特供场景化参数调优春节场景和日常办公场景差异非常大参数不调体验就是灾难。我做了三个调整第一目标量下调。走亲戚那天很多人不方便频繁喝水目标从1500mL降到1000mL只要保持尿液颜色不过深就说明方向对。不要用“必须达标”的心态来设计用“别落下太多”的松弛感替代。第二免打扰时段增加。大年三十晚上聊天到凌晨梦里突然被AR眼镜震醒那种体验可以当场卸载应用。所以23:00到次日08:00之间强制不提醒只在眼镜端显示“今日未完成明天继续”。第三误触防护。聚会时按键次数会暴增因为聊天时手会随手碰到触控板。我给确认操作加了一个“2秒内再次确认才生效”的防呆逻辑类似于双击确认误触率明显下降。4. 端到端实现从一口水到眼镜屏上的完整链路4.1 水杯数据怎么拿三种感知方案对比喝水提醒最难的从来不是显示而是数据采集。我前后试过三种方案各有性格整理如下方案准确性成本体验适合场景电子秤/智能水杯称重高但需要滤波中全自动固定办公位、居家日常手机扫码NFC标签中低需手动刷一下初版验证倒计时手动确认低纯靠自觉零最省事第一天就能跑通我没在第一版做传感器原因是想先验证“提醒逻辑”是否成立。手动确认的方案虽然原始但能让我快速评估这个提醒频率、这个文案、这个显示位置值不值得继续做硬件。等你确认体验成立再往杯垫里塞称重模块完全来得及别一上来就给自己上难度。称重方案也确实有坑。智能杯垫用蓝牙BLE上报重量数据抖动比想象中大得多。装水瞬间有冲击力重量会先冲到一个峰值再回落放回杯垫时可能因为没放正一直读数不稳。我处理方式很简单取连续5次稳定读数的平均值且两次读数差小于30g时才判定为一次“喝水事件”。判断逻辑写成“读取稳定然后清零”比每次实时累加靠谱得多。4.2 网关侧实现一个Node服务同时管HTTP和WebSocket网关侧是整个系统的大脑输入是“喝水记录”输出是“是否提醒”。我用一个Node进程搞定所有事HTTP服务提供页面、接收传感器上报WebSocket服务负责给眼镜端推送实时提醒内存里维护一份当天的喝水记录。完整逻辑写成伪代码如下// 简化的核心控制流 let today { total: 0, target: 1500, events: [], lastRecordTime: null }; function recordWater(ml, source) { today.total ml; today.lastRecordTime Date.now(); today.events.push({ ml, source, time: Date.now() }); evaluateReminder(); } function evaluateReminder() { const now new Date(); const hour now.getHours(); const progress today.total / today.target; // 检查当前时段是否落后 if (schedule[hour] progress schedule[hour].threshold) { sendReminder(进度落后建议喝一杯); } else if (Date.now() - today.lastRecordTime 90 * 60 * 1000) { sendReminder(90分钟没喝水了); } }sendReminder内部会自动判断距上次提醒是否超过45分钟超时了才真正推送到眼镜端。这个去重逻辑一定要做否则同一时刻触发多条提醒眼镜端会瞬间弹出好几个浮窗那就不是健康助手是弹窗轰炸机。4.3 眼镜端页面超大字号与进度环眼镜端页面我尽量保持极简因为光波导显示区域有限信息越少越清晰。页面一屏只放三样大号数字显示当前已喝量、目标量、一个环形进度条外加一行不超过6个字的提示文案。关键的一个设计细节是字号。Rokid眼镜默认分辨率按1080p基准计算但实际视觉尺寸只有桌面显示器那么大字太小会糊成一团。我建议正文至少用36px数字和进度提示用72px以上。用CSS变量统一控制字号方便后续适配不同眼镜。页面监听WebSocket消息收到提醒事件后更新UI然后自动进入半透明状态script const ws new WebSocket(ws://${location.host}/ws); ws.onmessage (e) { const data JSON.parse(e.data); document.getElementById(amount).textContent data.total; document.getElementById(target).textContent data.target; document.getElementById(progressRing).style.strokeDashoffset 2 * Math.PI * 50 * (1 - data.progress); document.getElementById(hint).textContent data.message; showFloatingCard(); }; /script进度环用SVG绘制即可不需要引入图表库。核心逻辑是计算周长然后根据progress设置strokeDashoffset。这里有一个很容易错的地方strokeDashoffset的计算应该是(1 - progress)乘以周长不是progress乘以周长反了的话进度条会从满到空。4.4 图表可视化matplotlib画周报时的中文字体坑“今日喝水”页面用SVG直接画折线图就够但跨天数据我还是建议用图片方式因为Service端的图表库生态更成熟。我的做法是每周生成一张喝水周报图横轴是周一到周日纵轴是每天的累计量用matplotlib画柱状图。这里栽了个跟头必须提醒matplotlib默认字体不支持中文“喝水”两个字画出来全是方块。网上很多教程会让你改rcParams[font.sans-serif]但只改这个在macOS上照样无效因为系统字体名字不一定匹配。我实测有效的是直接指定FontPropertiesimport matplotlib import matplotlib.pyplot as plt from matplotlib.font_manager import FontProperties font FontProperties(fname/System/Library/Fonts/PingFang.ttc) fig, ax plt.subplots() ax.bar(days, amounts) ax.set_title(本周喝水情况, fontpropertiesfont) ax.set_xlabel(星期, fontpropertiesfont)Linux和Windows下字体路径不同Windows可以指向C:/Windows/Fonts/msyh.ttc。另外PyCharm里立即显示中文还有一个额外坑如果当前环境变量LANG不是UTF-8matplotlib输出的背景色会变成奇怪的灰色此时建议在代码开头加一句plt.rcParams[axes.unicode_minus] False否则坐标轴负号也会显示成方块。生成的PNG通过HTTP接口暴露眼镜端直接以图片元素加载不占内存。这个方案的好处是眼镜端哪怕WebView性能比较弱渲染图片的负担也远小于跑复杂图表库。5. 实测一周的踩坑记录与排查方法5.1 高频问题速查表运行一周多遇到的典型问题我整理成了表格方便你对号入座症状原因解决办法眼镜黑屏无显示Station休眠或USB松了长按Station电源键重启重新插拔Type-C线提醒浮窗看不清环境亮度过高或透明度太低提高卡片背景对比度降低到20%透明度前的停留时间浏览器打不开本地页面防火墙拦截、局域网隔离放行8765端口确认眼镜和网关在同一网段BLE称重数据跳动蓝牙丢包、采样未稳定采用多帧平均阈值过滤后再累加多次弹提醒没有做45分钟去重在服务端维护lastRemindTime字段语音识别乱触发聚会环境噪声大切换为按键确认语音仅作为可选模式5.2 三个真正让人崩溃的问题除了表格里的常规项有三个坑我觉得值得单独展开。第一是WebSocket在Station浏览器锁屏后自动断开。Station作为Android设备系统为了省电会冻结后台进程浏览器切后台再回来时WebSocket连接往往已经挂了。第一次联调时我还没意识到第二天演示前发现提醒全部不推送排查很久才发现是保活问题。解决办法是在Station开发者选项里关闭浏览器进程冻结同时把电量和性能模式调到“不限制”。如果你在真实产品里做还要加一个连接状态指示让使用者一眼看出“当前离线”。第二是浮窗千万别常驻。第一版我把提示卡片一直挂在视野中心看起来很有科技感结果半小时后眼睛累得不行。AR眼镜的显示区域虽然透明但它始终在视野里大脑会不自觉去处理它。后来我改成默认10秒后透明度降到10%几乎像不存在一样。需要看进度时做出一个抬头动作或者按一下按键才激活。这个体验差距非常大。第三是光波导显示效果最容易在白色背景下翻车。客厅白墙、白桌子、窗边环境光太强或背景特别白的时候浮窗文字的对比度会下降黑字看不清白字更惨。建议所有UI都使用“深色圆角底板白色文字”的组合背景板给80%以上不透明度不要用纯透明文字。这也是为什么我最终的提醒卡片都加了一块深色底。5.3 搜索资料时容易被带偏的几个方向不写这个专题我觉得对不起自己浪费的那些时间。AR开发对新手来说搜索陷阱比技术陷阱还多。搜“AR设备”会混入华为AR路由器。AR在华为企业网里是路由器系列的产品代号网络工程师圈子里聊的AR、ENSP模拟器跟增强现实没有半点关系。这类词条占比极高建议搜索时直接加“眼镜”或者“Rokid”来过滤。如果搜索AR开发环境还会看到不少关于ARCore的讨论它们大多围绕手机上的AR能力以及中间件如何配置ARCore服务。Rokid这类专用眼镜的设备能力和接口跟手机ARCore并不一致那些配置教程对当前项目基本没用别照抄。另外一个方向是云渲染、远程协作等企业级AR应用它们和“日常小工具”的诉求完全不在一个维度。做喝水提醒这种轻量应用最实用的资料其实是“光波导”“BirdBath”“WebXR”这三个关键词多关注光学方案差异了解哪些眼镜能做透明显示哪些只是头戴显示器就不会选错设备。6. 一些可以继续折腾的方向现在的版本核心链路已经通了但还有几个明显可以继续做的方向排一下优先级。第一个是喝水动作自动识别。现在必须按一下按钮来记录虽然用着还行但跟“AR助手”这个定位还是有差距。Rokid眼镜本身带了IMU传感器理论上可以检测“低头-抬手-仰头”这一串喝水动作识别出来就自动记录一次喝水。这个方案对误判的容忍度比较低得先收集几十条真实动作数据才能调好阈值。第二个是多人共用模式。春节家里人多一个杯子经常被好几个人拿来拿去杯垫数据完全分不清是谁喝的。可以给每个人配一个独立按键或者一个独立NFC标签喝水前先刷自己再喝水。这样系统就能区分“爷爷喝了一口”和“我喝了一口”数据各自累计。第三个是场景联动。现在提醒全靠自己电脑上的Node服务跑如果能接入家庭智能家居中枢就可以在室内开暖气时自动降低目标量、在空调房里提高提醒频率。室内湿度这个数据比所有算法都靠谱——空气干不干身体最诚实。最后说一个我自己的体会这个东西做完之后最大的收获不是“每天都喝够水”而是理解了AR设备适合做什么、不适合做什么。AR眼镜不适合长时间沉浸但它天然适合放信息不适合玩游戏但它适合在真实活动之上叠一条轻提示。想让AR走出“演示”阶段就该从喝水这种轻量、高频、真实场景里找机会。如果你也想动手试试建议先跑通“倒计时手动确认眼镜浮窗”的最小闭环别急着上传感器把提醒体验调到不烦人之后再一步步加硬件这条路最顺。