下一代智能眼镜架构设计说明
00一句话愿景
眼镜端只做一个轻量的类HTML渲染器;所有应用以一套受限的类HTML格式运行在云端,通过手机把状态/diff传到眼镜渲染显示,用户操作再以 diff/事件回传——模仿网页的实现方式。目标是最大化节省眼镜算力与功耗,同时因为 AI 生成这套标记的效率足够高,天然兼容未来更 AI-native 的 OS。
整套架构能否落地,不取决于网络多快,而取决于有多大比例的交互能在眼镜本地闭环、根本不上行。这条贯穿全文。
01范式对齐:Server-Driven UI + 瘦客户端
这套想法在 web 世界已有成熟范式,不必从零发明,可直接借鉴其经过验证的设计:
- Phoenix LiveView / React Server Components —— 服务端持有全部状态与渲染,只把最小 diff 推到客户端;客户端极薄,只负责渲染与回传事件。与本方案几乎同构,是最近的参照。
- RDP / VNC / X11 —— 远程显示协议,但它们传像素/绘制指令,过重。我们要的是语义层 diff,比像素轻几个数量级。
- WAP/WML、Roku BrightScript —— 受限设备上的"轻量标记语言"先例,均未采用完整 HTML,而是裁剪过的子集。
不要用真 HTML,用一套"形似 HTML 的受限组件 DSL"。 单色、低分辨率、文本为主的 HUD 用不到 HTML 的表达力;而受限、封闭、可校验的 schema 反而让 AI 生成更可靠——这才是 AI-native 的关键。
02分层架构
03四个关键决策
① 标记层的抽象高度
从"像素帧"到"语义组件"是一道谱系:抽象越高 → diff 越小、眼镜算力/带宽越省、AI 越好生成,但眼镜需承担更多布局计算。
结论:偏语义组件端。 定义十几个受限原语(text / list / card / icon / progress / kv-row …)+ 布局槽位,而非开放的 HTML+CSS。受限词汇表让 AI 在小而稳定的 schema 内几乎不会跑偏。
② 交互的"光速问题"
网页快是因为浏览器在本地;本方案默认每次交互要 云→手机→眼镜 往返。必须设计本地乐观响应,并把状态分成两类:可本地预测的视图态 vs 必须云端裁决的应用态。(第 4 章展开。)
③ 真正的功耗瓶颈是射频,不是算力
眼镜上最耗电的往往是无线电(BLE/连接保持),不是渲染。省了渲染算力,但若每次交互都唤醒射频做小包往返,功耗账不一定划算。带宽与射频活跃度才是一等优化目标。
协议层应:批量/合并 diff、保持可预测的静默期、把高频交互留本地不上行。好消息是——这与延迟优化方向一致(见 4.5)。
④ 断网与降级
云端持有状态 = 断网即"白屏"。眼镜/手机侧需要:最近若干屏缓存、关键功能(时间、通知、上一帧导航)的本地兜底。手机这层做"会话快照"很合适。
04延迟胜负手:视图态 / 应用态划分
问题要重新表述为:怎么把尽可能多的"应用态交互"转化成"视图态交互",并保证本地预测与云端永不真正冲突?
4.1 用「闭包 + 确定性 + 可重放」判定本地化
一个交互能安全本地处理,当且仅当同时满足:
- 闭包性:算下一帧所需数据已在设备上(子树已下发)。
- 确定性:下一帧 = f(本地状态, 事件),不需云端逻辑/新数据。
- 可重放性:云端能用同一个 f 在镜像上算出同样结果。
视图态 · 本地闭环 <100ms
- 列表滚动 / 翻页
- 焦点 / 光标移动
- 展开/收起已下发的卡片
- 切换已下发的局部显隐
- 消除瞬态通知(本地)
应用态 · 必须上云裁决
- 选中项触发业务逻辑
- 提交、命令、状态变更 mutation
- 语音输入产生新内容
- 跳转到未预取的新界面
- 任何需要新数据的操作
这张表的左右边界不固定——它由"云端预取了多少"决定。预取就是把右列搬到左列的杠杆(见 4.4)。
4.2 协议:View Bundle 三件套
云端下发的不是裸渲染树,而是:
ViewBundle {
tree: // 渲染树(节点带稳定 key)
localTransitions: [ {event, target, patch} ] // 预授权的本地转移表
speculation: [ 预取的下一状态子树 ] // 把应用态变视图态的弹药
version: // 单调序列号
}
localTransitions 是核心创新:云端显式声明"这些事件你眼镜可自己处理,并告诉你怎么处理(patch)"。眼镜无需懂业务,只需查表执行。因此眼镜端只要一个极小的状态机:收到事件 → 查表 → 命中本地 apply 并渲染;未命中则上行。
4.3 确定性重放 → 视图态永不冲突
因为本地转移是云端预授权且确定性的:
- 眼镜本地 apply 的同时,把事件异步上行(不阻塞 UI)。
- 云端收到后,用同一个 f 在镜像上重放同一转移。
- 两边算出同一结果 → 天然收敛、无冲突(本质是确定性状态机复制,比 CRDT 更简单)。
对应用态则两种策略二选一:
- 保守:本地给轻微 pending 反馈(焦点变灰/小 spinner),等云端权威 diff。
- 激进:乐观预测 + 回滚——仅用于高置信、低风险操作,复杂度高,第一版不做。
版本对账:每个 diff 基于一个 version 打 patch;本地预测推进 predicted version;权威 diff 回来按 version rebase。视图态确定性收敛,rebase 几乎总是 no-op。
4.4 预取 = 把应用态搬进视图态的杠杆
"选中→看详情"本是应用态。但若云端在焦点停在该项时就预取其详情子树放进 speculation,则"选中"瞬间本地展开 → 0 往返。整套架构的"快"就来自此:云端预测下一步,提前把弹药打到设备。
预取走射频 = 耗电,因此是个预算问题:只预取置信度最高的 1–2 个下一状态,不是整棵树。长列表用虚拟窗口:只发可视窗口+缓冲,本地滚动,临近边缘再预取下一窗口。
4.5 延迟预算(硬数字)
| 链路 | 量级 | 备注 |
|---|---|---|
| 手机 ↔ 眼镜 BLE 单程 | ~30–100ms+ | 受 connection interval 影响;低延迟要短 interval,直接换功耗 |
| 蜂窝 → 云 RTT | ~50–150ms | — |
| 全往返 | ~150–350ms | 正落在人眼"明显卡顿"区间 |
| 人眼"即时"门槛 | ≈100ms | 视图态必须低于此值 |
把高频交互留本地,既满足 <100ms 的丝滑,又能让射频拉长 interval、空闲省电,只在应用态事件时"醒一下"。延迟优化与功耗优化在此方向一致。
05本地转移词汇表(v0 冻结候选)
词汇表里每条是一个转移类型(type);云端在 localTransitions 里把它实例化(填参数)。固件只硬编码这十来个类型的执行器。
另一层解耦:原始手势 ≠ 转移。镜腿轻触/滑动/长按、抬头/侧头、语音关键词,先经输入层映射成语义事件(next/prev/confirm/back),转移表只认语义事件——以后换手势映射,完全不动此表。
5.1 每条转移的统一结构
Transition {
type: // 词汇表枚举
scope: // 作用的节点/子树 key
params: // 类型相关参数(方向集、窗口范围…)
onEdge: clamp | wrap | bubble // 到边界怎么办 ← 关键旋钮
upstream: immediate | coalesced(ms) | none // 是否/如何回告云端 ← 关键旋钮
precond: // 本地必须已有的数据(子树、窗口范围)
}
5.2 词汇表
| # | type | 语义事件 | patch 语义 | 本地数据前提 | 典型 onEdge |
|---|---|---|---|---|---|
| 1 | FOCUS_MOVE | next / prev / dir | 焦点环移到 scope 内下一个可聚焦节点 | 可聚焦节点集已下发 | bubble |
| 2 | SCROLL | scroll-by / scroll-to | 视口在溢出内容上平移 delta 或到锚点 | 内容在当前窗口内 | bubble(预取下一窗口) |
| 3 | PAGE | page-next / page-prev | 多页视图离散切上/下一页 | 目标页已下发 | clamp / bubble |
| 4 | TOGGLE_EXPAND | confirm / expand | 展开或收起 disclosure 节点 | 子树必须已下发 | n/a |
| 5 | SET_LOCAL_STATE | select-tab / cycle | 把组件局部枚举/布尔态设到某值(tab、分段控件、纯视图开关) | 各状态子树已下发 | wrap |
| 6 | STEP | next / prev | 在兄弟条目带里前后移一格(carousel/stepper) | 相邻项已下发 | wrap / bubble |
| 7 | DISMISS | dismiss / back | 移除瞬态/浮层节点(通知、toast、临时卡) | 节点本就是 transient | n/a |
| 8 | ACTIVATE_SPECULATED | confirm | 用预取的目标子树即时打开新视图(消费 speculation 槽) | 目标在 speculation 里 | miss → bubble |
| 9 | BACK_LOCAL ·可选 | back | 弹回本地历史窗口里的上一视图 | 上一视图在本地历史缓存 | miss → bubble |
| 10 | CURSOR_MOVE ·可选 | next / prev | 在可复核文本里移动光标/朗读位 | 文本已下发 | clamp |
这 10 条(实际硬编码 8–9 条)就是眼镜固件的全部本地行为面——小、封闭、可冻结。
5.3 三个需要展开的硬骨头
#2 SCROLL 的窗口机制 —— 长列表不全量下发。实例带 window:[start,end] 与 prefetchWatermark(如滚到 80% 处)。窗口内滚动纯本地、0 上行;越过 watermark 触发异步预取下一窗口、当前滚动不阻塞;真滚到边界且下一窗口未到 → onEdge:bubble 短暂 pending。把"无限列表"也压进视图态。
#8 ACTIVATE_SPECULATED 是"快"的兑现点 —— confirm 落在某焦点项 → 查 speculation 有无该项预取子树 → 命中则本地直接换视图,同时上行通知云端"我进了这个详情";未命中 → bubble 退回正常往返。因此 #1 FOCUS_MOVE(焦点停留)与云端预取联动:焦点一停,云端就该预取该项的 #8 弹药。这是预取策略的接口点。
onEdge:bubble 是视图态↔应用态的法定边界 —— 任何本地转移撞到边界、本地数据不够,都不是"卡住",而是优雅降级为一个应用态事件上行。固件只要实现"撞边→打包成 app-event 发出→显示 pending",就自动接住所有本地处理不了的情况。
5.4 upstream(回告云端)策略
| 转移 | upstream | 原因 |
|---|---|---|
| FOCUS_MOVE | coalesced(~150ms) | 云端需知当前焦点(决定预取&下个 app 动作),可去抖 |
| SCROLL | coalesced / none | 云端多数时候不关心精确滚动位,靠 watermark 触发 |
| PAGE / SET_LOCAL_STATE / STEP / TOGGLE_EXPAND | coalesced | 镜像同步,确定性重放,去抖即可 |
| DISMISS | immediate | 关系瞬态生命周期,云端需准确知道 |
| ACTIVATE_SPECULATED | immediate | 视图已切换,云端镜像必须立刻跟上 |
回告是极小的 "view-state echo",去抖+合并,把射频活跃度压到最低——与功耗目标一致。
5.5 故意排除在外的(划清边界)
以下永远不进本地词汇表,撞到一律 bubble:
- 任何产生新数据 / 拉取未缓存数据的操作
- 任何业务逻辑 / mutation / 提交
- 语音转文本生成内容
- 跳转到未预取的新界面
- 涉及隐私裁决、权限、外发的动作
词汇表里每条转移,结果都必须能由"设备上已有的数据 + 这个事件"确定算出,且云端能重放出同一结果。算不出或重放不一致的,不属于这里。
06状态边界:眼镜持有什么
本方案让眼镜不再是"纯无状态瘦客户端",但边界可划得很干净:
眼镜持有视图态(滚动位置、焦点、展开标志、窗口缓存),但永不持有应用态(业务逻辑、数据模型)。
—— 即眼镜有"一个浏览器级别的状态",但没有"应用级别的状态",与真实浏览器定位一致。
07风险与代价小结
| 风险 | 缓解 |
|---|---|
| 每交互往返 → 卡顿(150–350ms) | 视图态本地化 + 确定性重放 + 预取,把高频交互压到 <100ms |
| 射频是真正功耗瓶颈 | diff 批量/合并、静默期、高频交互不上行、回告去抖 |
| 云端持有状态 → 断网白屏 | 手机会话快照 + 眼镜关键功能本地兜底 |
| 瘦客户端纯粹性被侵蚀 | 明确"视图态 yes / 应用态 no"的边界,固件只硬编码 8–9 个转移 |
| 隐私:全部数据流经云端 | (待设计)敏感裁决/外发归入应用态,需独立的权限与数据治理章节 |
08技术框架评估:基于哪些、怎么改、怎么拼
8.0 横切决策:一个 Rust core 横跨三层
协议编解码、tree-diff、patch apply、视图态状态机——三端(云/手机/眼镜)必须算出完全一致的结果(4.3 确定性重放靠它)。三端各写一遍(云 TS、手机 Swift/Kotlin、眼镜 C)会让重放一致性变成永久对账噩梦。
写一份 no_std-friendly 的 Rust core:云端直接用、手机经 uniffi 绑到 Swift/Kotlin、眼镜端编译进固件。"同一个 f"从架构上被保证,而非靠测试追平。
8.1 云端 App Runtime + Diff 引擎
基准:Phoenix LiveView(BEAM)作设计蓝本,不直接当运行时。
- 拿来(必抄两点):① 每会话一个有状态进程——BEAM 单机扛百万级有状态连接,正对应"每副眼镜一个云端会话";② static/dynamic 切分 diff——把模板拆成"静态骨架 + 动态槽位",只传变化的动态部分,对 BLE 带宽是金子。
- 改造:LiveView 的 diff 产物是 HTML 字符串片段,换成语义组件树的 keyed tree-diff(snabbdom / Million.js 的 block 思路),保留切分策略、替换渲染目标与序列化。
- 关键分叉:团队 Elixir 弱 → 用 Rust(tokio task per session)重建会话模型、diff 引擎落进 Rust core(倾向此条,兑现"one core");想立刻要 battle-tested 百万连接 → BEAM 当会话层、Rust core 当 NIF 嵌入(代价:diff 跨语言,重放回到测试对账)。
8.2 线协议 & 序列化
| 段 | 选型 | 理由 |
|---|---|---|
| 云 ↔ 手机 | CBOR / MessagePack | 自描述、调试友好、带宽不敏感 |
| 手机 ↔ 眼镜 | FlatBuffers / Cap'n Proto | 零拷贝:MCU 不反序列化、不分配,直接在 buffer 上访问字段 |
- 拿来:FlatBuffers 零拷贝模型,直接当眼镜端 ViewBundle/patch 容器。
- 改造:patch 语义不用 JSON Patch(太啰嗦),借 Yjs 的紧凑 update 编码(varint + run-length)编 tree-diff。只借编码,不借 CRDT 合并——我们是服务端权威 + 确定性重放,不需要并发合并语义。
- 拼接(强烈建议):手机↔眼镜段加 zstd 字典压缩,用 DSL 常见组件模式训练共享字典烧进固件;MCU 侧若 zstd 过重则退 heatshrink。
- 通道:高频 diff 流走 L2CAP CoC(吞吐远高于 GATT notification),控制/事件回告走 GATT。
8.3 手机 Bridge
基准:原生 BLE(CoreBluetooth / Android BLE)+ Rust core(uniffi 绑定)。
- 拿来:平台原生 BLE 栈做物理连接管理(后台保活、重连、配对)——别自己造。
- 改造/拼接:Bridge 的"大脑"(会话快照缓存、断网降级、diff apply、预取转发)全部落 Rust core,iOS/Android 只是薄壳,手机端与云端跑同一份逻辑、镜像天然一致。
- 缓存层借 Replicache / Rocicorp Zero 的"本地镜像 + 服务端权威"心智模型(只借模式不引依赖)。
8.4 眼镜端 Renderer(最该谨慎选的一层)
| 候选 | 定位 | 取舍 |
|---|---|---|
| LVGL | 嵌入式 GUI 库,C,原生支持 1-bpp 单色屏、字体引擎、flex/grid 子集,wearable 久经考验 | 最务实底座;retained-mode,我们要驱动它而非用其 API |
| Clay (clay.h) | 单头文件 C 布局库,immediate-mode,极小、MCU 友好,渲染后端可插 | 最轻最贴"瘦渲染器";只管布局,字体/显示驱动要自己补 |
| Slint | Rust/C++ 声明式 UI,可裸机跑,有 runtime interpreter | markup 与 DSL 概念最近、与 core 同语言;单色 + 解释器需 spike 验证 |
- 推荐组合:LVGL 当显示/字体/单色光栅底座 + 自研薄 DSL 解释器。链路:Rust core 解析 ViewBundle → 解释器把组件映射成 LVGL 对象 → 后续 patch 转成 LVGL 对象的增量 mutation(不重建整树)。
- 改造:只挑映射到第 5 章词汇表所需的原语(text/list/card/progress…),关掉其余 widget 以缩固件。
- CJK 字体:LVGL 字体引擎 + 点阵中文字(呼应 dot-matrix 调性;Fusion Pixel 12px 覆盖简体,OFL 可商用)。
- Rust 纯粹路线可对 Slint interpreter 做一次 spike——若单色 + 解释器在目标 SoC 跑得动,能省掉"C 渲染层 / Rust 逻辑层"的跨语言边界。
8.5 DSL 本身 + AI 生成层
- 不发明新语法:作者面/AI 面用 JSON + 正式 JSON Schema,线面用二进制 tree——同一棵组件树的两种编码。
- 生成可靠性靠语法约束解码:给 DSL 写形式语法,用 grammar-constrained decoding(llguidance / XGrammar / GBNF,或 Claude structured outputs / tool use)保证模型只能吐合法组件树——把"AI 生成高效"从"通常对"升级成"结构上不可能错"。
- 分层:LLM 吐意图 → Rust core 里的确定性编译器编成受限 DSL + 预授权
localTransitions(list→FOCUS_MOVE/SCROLL,disclosure→TOGGLE_EXPAND…)。不让 AI 直接决定本地转移,安全边界由确定性代码守。
8.6 汇总:栈与"自研 vs 拿来"
| 层 | 基准框架 | 拿来 | 自研/改造 |
|---|---|---|---|
| 云端会话+diff | LiveView 蓝本 → Rust | static/dynamic 切分、per-session 进程 | tree-diff 落 Rust core |
| 序列化 | FlatBuffers + CBOR | 零拷贝容器 | patch 编码借 Yjs,+zstd 字典 |
| 传输 | L2CAP CoC + 原生 BLE | 物理连接管理 | diff 流控/回告去抖 |
| 手机 Bridge | 原生 BLE + uniffi | 平台 BLE 保活 | 缓存/降级/预取入 core |
| 眼镜渲染 | LVGL(备选 Clay/Slint) | 单色光栅、字体、flex 子集 | DSL→LVGL 解释器 + patch 增量 mutation |
| DSL/AI | JSON Schema + 约束解码 | 语法约束生成 | 意图→DSL 确定性编译器 |
| 横切 | Rust core (no_std) | uniffi / wasm | 三端共享 diff/state/重放 |
① 眼镜 SoC 跑不跑得动 LVGL+解释器,单色屏刷新率能否撑 <100ms 本地转移——决定架构成不成立,第一个该做。
② no_std Rust core 能否真落地眼镜 MCU(内存/栈预算)——决定"one core"是真共享还是只共享云+手机。
③ L2CAP CoC 实际吞吐 + 功耗曲线——决定预取预算能开多大。
09待定决策 / 下一步
- 预取策略(优先):定义
FOCUS_MOVE停留 → 云端预取什么、打多少speculation弹药给ACTIVATE_SPECULATED。#1↔#8 的"缝"定义清楚,"快"就闭环。 - 转移字段级 schema:把 10 个转移的
params/patch写成可冻结的字段定义(体力活,可随时补)。 - 组件 DSL 原语集:冻结
text/list/card/icon/...的封闭词汇表与布局槽位。 - AI 分层:AI 吐"意图/组件意图",由确定性 runtime 编译成受限 DSL,享受生成效率 + schema 校验兜底。
- 隐私与数据治理:云端渲染全量数据的合规/加密/最小化设计(本稿尚缺)。