目标是让 heimlink-apps 的现有浇花器应用成为 HeimLink 中可实际操作的设备控制页,同时为 monorepo 增加可复用的第三种宿主适配。本报告包括需求矩阵、现有 bridge 的差距、推荐架构、验收范围和优化后的执行提示词。
1. 结论与推荐范围
可行,但工作量不只是新增一个 API 包。 DualOutletWaterTimer 已是 Next.js 15 / React 19 的静态导出 Web 应用,可复用页面、业务状态和浇水协议解析。主要改造位于运行时识别、启动、设备上下文、状态适配、日志、持久化、导航和资源打包。
建议先完成 真实 HeimLink 宿主 + 真实 WebView bridge + 宿主内虚拟浇花器 的完整闭环。设备状态与日志由宿主模拟,Web 页面通过正式 window.rti 通道访问;手机系统信息与布局仍来自真实宿主。此方案覆盖你允许的“直接在 HeimLink 中模拟”,也避免当前没有浇花器协议品类和硬件时,提前承担固件移植工作。
建议首轮验收覆盖:首页、双路手动浇水、计划增删改与回显、常用设置、模拟灌溉/故障/电量历史、连接状态、时区与安全区、页面导航、重新打开后的本地配置恢复。天气服务或定位不可用时,应明确降级,且不阻塞这些功能。涂鸦账号、云端历史、云场景、地图选点和 OTA 不默认承诺等价移植。
这里的“真实 bridge”指 WebView 消息确实进入 RN 宿主并返回。它不等于真实 BLE、真实配对或真实阀门已验证;验收记录必须标注模拟来源。
2. 分析依据与当前状态
| 范围 | 检查基线 | 已确认的事实 |
|---|---|---|
小程序 monorepo |
heimlink-apps,提交 31e6e27a |
DOWT 为静态导出;共享层已有涂鸦小程序、RN 和 Web 模拟分支 |
HeimLink 宿主 |
rtk-app-experiment,提交 4dbda417 |
注入 window.rti;有设备会话、DP 读写、系统/安全区与导航事件 |
当前设备品类 |
shared/contracts/categories.vnext.json 与 release matrix |
已注册 thermostat、trv;尚无浇花器品类。部分说明文档仍写 thermostat-only,报告按当前注册表核对 |
本次验证深度 |
源码与契约静态检查 |
尚未构建 DOWT 的 HeimLink 产物,也未进行应用真机联调 |
本机测试入口 |
ADB 与 USB 串口枚举 |
本次 ADB 未发现在线 Android;存在 USB 串口,但尚未识别哪一个是 ESP32,未读写或刷写设备 |
本次没有改业务代码、固件或现有公开 API 文档,也没有调用生产云服务验证设备身份或写入数据。下文的新增接口都是待确认方案,不能作为已可调用的 SDK 文档。
3. API 需求与 bridge 差距矩阵
“已具备”表示宿主已有对应能力,仍需适配与联调;“部分具备”表示语义或数据不完整。P0 为推荐首轮验收必需,P1 为增强能力或需要真实服务/硬件才能完整验收的能力。
没有匹配的能力。
| 能力 / 优先级 | 浇花器真实需求与调用点 | HeimLink 现状 | 建议处理 |
|---|---|---|---|
运行时识别与 ready / P0 |
platform.ts、waitApiBridgeReady();当前按 hash 或注入对象判断平台 |
rti.version、rti.mock 已有;握手处理存在,但 SDK 未公开完整能力查询方法 |
新增独立 HeimLink 分支和明确 ready/能力获取;检测先于通用 ReactNativeWebView 分支,避免识别成涂鸦 RN |
启动上下文 / P0 |
getLaunchOptionsSync 给 deviceId;startup.ts 初始化共享状态 |
设备绑定于宿主会话; rti.route.params 可读,缺少完整可消费上下文 |
宿主返回会话内的不透明设备标识与产品上下文;URL 参数不能改变可操作设备 |
安全区与窗口 / P0 |
SystemInfo.safeArea 是坐标矩形;UI 使用 screenHeight 和 bottom 计算留白 |
rti.insets 与 onInsetsChange 已有,值为边距 |
适配为正确坐标系;优先让 HeimLink 启动分支直接填 UI insets。区分屏幕、WebView、可用窗口尺寸及原生 header,避免双重留白 |
系统设置 / P0 |
timezoneId、language、theme、is24Hour;还读取平台/窗口信息 |
rti.system 与 onSystemChange 已提供 locale、IANA timezone、colorScheme、uses24HourClock |
复用已有系统对象;补足真正被使用的平台/窗口字段,订阅前后台恢复后的更新,不伪造未提供的授权状态 |
设备信息与 schema / P0 |
getDeviceInfo 的 schema、dpCodes、productId、名称、固件、能力等 |
rti.getDeviceInfo 主要返回 DIS 固定信息和契约能力;缺 schema、绑定元数据 |
建议新增 getDeviceContext,保留固定设备信息接口职责;由可信产品配置提供运行时 schema |
蓝牙连接状态 / P0 |
Bluetooth 的 CONNECTING / CONNECTED / CONNECT_BREAK,以及设备在线提示 |
connect、disconnect、getConnectionState、onConnectionStateChange 已有 |
转换状态枚举;分开链路连接、已认证可控制、云在线和手机蓝牙开关,不能合成一个 online 布尔值解释全部 |
安全配对与可控制状态 / P0 |
非涂鸦设备需要额外的宿主配对准备;原有 UI 不理解 Owner AUTH |
getSecurePairingState、配对重试/取消、事件及原生兜底已有 |
首轮保留原生配对兜底;只有当前会话有可信状态才开放设备操作。模拟会话明确标注,不伪装真实认证 |
设备初始状态 / P0 |
CAL getDeviceState,形成业务状态并运行拦截器 |
rti.getDeviceState 主动读取设备 |
复用读取通道;建立初始快照与增量订阅的顺序,防止订阅窗口漏事件或旧快照覆盖新值 |
状态更新 / P0 |
CAL DEVICE_STATE_CHANGED 和另一条 onDpDataChange 更新路径 |
onDeviceStateChange 已有;事件泵按缓存差异发增量,不回放快照 |
统一 HeimLink 状态入口,按需同步 DeviceStateProvider 和 device$.info.dpCodes,避免两份状态分叉;退订、切设备、重复值都需验证 |
下发控制 / P0 |
CAL sendCommand、DP publishDps;手动、计划、时间、阈值等 |
rti.publishDps 已有,契约校验并一次原子提交;尚无浇花器 schema |
按 DP code 适配;复用已有业务编码器。HeimLink 禁用“纯 BLE 默认拆分多 DP”策略,保留调用的事务范围 |
RAW 载荷 / P0 |
实时手动/计划等拦截器消费或产出 hex;日志数据源中的 RAW 是 Base64 |
字节 DP 的 Web 形式为标准 Base64 |
在明确的字段边界转换 hex ↔ bytes ↔ Base64;日志路径独立核对,禁止对所有字符串做统一转换 |
写入确认、失败与超时 / P0 |
mutation 等待状态满足 confirm 谓词;部分操作是动作型 RAW |
write Promise 成功、可信状态缓存更新、独立增量事件三者有各自语义 |
订阅先建立;适配器必要时在写后主动读可信状态,再进入确认管线。同值回显也能完成确认,不能通过重播下发命令伪造成功 |
设备运行日志 / P0 模拟,P1 真机完整历史 |
getAnalyticsLogsStatusLog,灌溉记录、故障、电量和历史安全阈值 |
getHistory 仅有温控 sample/setpoint/mode 点列,无法满足 |
新增通用 DP 日志读取契约及 HeimLink 数据源;首轮由宿主模拟器提供真实生成的模拟记录,明确 source 和覆盖区间 |
应用诊断日志 / P0 本地,P1 远端 |
共享 log / remote-log,用于诊断启动与控制失败 |
宿主已有内部 logger;未提供对应公开 bridge 方法 |
与设备运行日志分开;先保留可追踪的本地诊断,远端上传需核对账号、产品身份与用户开关。只有确需原生日志汇聚时才增桥方法 |
添加、配对与重新绑定时间 / P0 |
activeTime 用来判断重新激活并重新初始化设备位置 |
SavedDevice 有 addedAt;binding record 有 createdAt;未通过 bridge 暴露 |
分别定义 addedAt、pairedAt、bindingRevision;设备上下文输出 nullable 时间与来源。不能把打开页面或重连时间当配对时间 |
设备时区、时间同步 / P0 |
devTimezoneId、unix_time;计划按设备时区解释 |
系统时区已有;缺浇花器设备时区与 unix_time 契约 |
手机时区与设备安装时区独立;缺失时明确回退并提示。校时是设备控制行为,按协议/模拟 profile 实现,不能混用秒和毫秒 |
设备位置与手机定位 / P0 降级,P1 原生定位完善 |
设备位置来自 KV、缓存、deviceInfo、系统定位/IP;另有地图选点 |
当前 bridge 无定位与授权方法 |
设备安装位置持久化;手机当前位置单独查询。原生定位需要权限查询/请求、取消/拒绝/超时与坐标系;不支持时返回明确不可用,不填虚构坐标 |
持久化与设备 KV / P0 本地,P1 云同步 |
通道名、单位、时区提醒、设备配置覆盖等使用 storage 和 /v1/device-kv |
宿主已有 MMKV,但 Web bridge 无通用存储接口 |
增加按应用+绑定设备隔离的本地存储;共享 deviceKv 层选择本地实现。声明仅本机持久化,不能冒充涂鸦/RTI 云同步 |
产品配置和设备档案 / P0 明确本地配置,P1 云端 |
productProfile、deviceProfile、远程 feature gate;缺配置可能隐藏页面入口 |
可信产品注册信息已有;缺业务配置映射 |
首轮提供版本化浇花器模拟 profile 和本地覆盖;区分硬件能力、宿主能力和产品功能开关,不能全部写 true |
网络、区域与业务服务 / P0 失败不阻塞,P1 真服务 |
RTI API、region 探测、天气;请求依赖 productId 与设备身份 |
Web 可尝试标准网络;没有通用 bridge HTTP 代理 |
先确认直接 fetch 的 CORS、file/local-origin 与目标服务支持;无法满足时再设计受限代理。新 productId 是否可被后端识别尚未验证 |
导航、返回和页面生命周期 / P0 |
Navigation.loadPage 拼涂鸦 /pages/view/index;getPageParams / page events / exit |
navigateTo、replace、goBack、route params、返回拦截已有 |
映射到 manifest 中的 HeimLink 路由;验证返回到已有页、参数、根页面退出、前后台刷新和键盘。半屏容器不要求首轮完全一致 |
静态资源与离线启动 / P0 |
App Router 静态导出、Next chunks、图片、字体、Rive/WASM、多级路径 |
宿主可加载静态控制页;现有温控打包器不完整支持这些资源 |
增加面向 DOWT 的打包/装载路径,使用结构化 HTML/CSS 工具;不得用空白图替换缺失资源。检查动态 chunk、字体和 WASM 的实际加载 |
设备设置、改名、OTA、解绑 / P0 常用设置,P1 特殊宿主页 |
renameDeviceName、openDeviceDetailPage、getOTAUpdateInfo、openOTAPage、exit |
部分原生内部操作已有,但没有上述完整 Web API |
常用本地设置正常工作;特殊入口按能力隐藏或引导到真正可用的原生页面。解绑/复位语义单独定义,不复用“退出”代替 |
主题、剪贴板、提示与键盘 / P0 基础交互 |
Web toast;少量 native toast/clipboard;键盘高度事件 |
系统主题已有;剪贴板、键盘未有同形公开方法 |
优先保留 Web UI;视平台支持选择 Web API 或宿主方法,避免为了兼容旧命名无限扩展 bridge |
4. 必须提前解决的语义差异
4.1 宿主识别与启动不能靠 RN 这个名字
当前 detectPlatform() 把任意 window.ReactNativeWebView 判为涂鸦 RN。HeimLink 同样使用 React Native WebView,但双方消息格式不同:现有 monorepo 发 api-bridge RPC,HeimLink 接收 kind: call 的 rti 消息。因此直接打开页面会走错协议。
DOWT 的 Providers 明确调用小程序 defaultStartupConfig();新平台需要独立且确定的启动选择。不能只改 platform 布尔值,也不能只检测 window.rti 存在,因为 SDK 本身还提供浏览器 mock。正式运行必须验证宿主 ready 和模式;原有 hash 平台声明与两种涂鸦运行时需要保留。
4.2 安全区 bottom 不是同一个数值
例如可用坐标系高度为 800、底部 inset 为 24 时,矩形的 safeArea.bottom 是 776,而不是 24。但只有两边采用相同坐标系,这个换算才成立。宿主顶部有原生标题栏时,WebView 的高度和屏幕高度也不相等。推荐 HeimLink 启动分支直接消费真实 insets,再为旧读取点生成一致的窗口视图,不把屏幕坐标盲目套入页面。
4.3 三条时间轴和两种 RAW 表示
手机系统时间/时区、设备安装时区/设备时钟、日志事件时间是不同来源。现有日志查询参数是毫秒字符串,而日志记录 timeStamp 被业务代码按秒消费;温控历史 t 也为秒,但记录类型并不相同。新的中性日志契约建议统一显式 *Ms 字段,再在旧数据源边界转换。
设备记录的发生时间与 App 接收时间要分开:离线补报时不能用接收时间冒充浇水发生时间。日志 RAW 的 Base64 不能沿用实时计划 DP 的 hex 假设。手动程序、计划程序、灌溉日志继续使用现有 @international-iot-association/dual-outlet-water-timer-protocol 编解码器,不重新手写协议。
4.4 指令成功不自动完成业务确认
共享 DeviceStateProvider 当前固定创建 BridgeAdapter();它没有直接接受外部 Adapter 的 prop。useDeviceStateMutation 监听业务状态回显判断完成,单纯让 sendCommand() resolve 不够。
HeimLink 的写入会刷新可信缓存,事件泵只发发生差异的值;同值写入未必产生事件。实施时要设计“已建立订阅 → 下发 → 可信回读/上报 → confirm → 完成”的流程,并测重复值、旧事件、断连和迟到返回。原生事件与公开文档描述的关系也要在变更时核对。页面预测值或原样复制命令都不能作为设备确认来源。
同时,现有 Provider 对纯 BLE 默认拆分多字段命令,而 HeimLink 的一次 publishDps 是一个原子事务。新运行时要明确禁用不必要的拆分,不能把多个独立调用再偷偷合并。
4.5 配对时间不等于涂鸦激活时间
activeTime 在天气位置逻辑中用于识别设备重新激活。HeimLink 的 addedAt 是本机添加记录时间,binding 的 createdAt 是某次绑定记录时间,二者并不自动等于“设备首次激活”。推荐用独立 bindingRevision 判断绑定代际,时间缺失时返回 null;若保留旧 activeTime 视图,必须记录具体来源及其有限语义。
4.6 打包要适配 App Router 和二进制资源
温控器的 build-html-external.mjs 依赖 __NEXT_DATA__ 放置脚本,只收集 JS/CSS/JSON,还把部分 CSS 二进制引用替换成透明 PNG。DOWT 使用 App Router、Rive 和大量图片;不能直接复制该脚本并假定产物正确。需核对 DOWT 实际导出文件与 hydration/chunk 加载方式,选择保留目录资源或可靠的重打包方案,并验证 WASM MIME、资源权限与离线加载。
5. 推荐适配层架构
推荐新增 packages/shared-business/src/lib/heimlink-api/ 承担实际适配;若需要供多个包独立导入 SDK 类型,再增加 packages/heimlink-api/。现有 packages/tuya-miniapp-api / packages/tuya-rn-api 主要承担 RPC 类型定义,不应照目录名字误以为复制一份类型就完成运行时。
- DualOutletWaterTimer页面、业务状态、浇水协议解析
- shared-business / heimlink-api启动、状态、日志、存储、导航适配
- window.rti → RN 宿主真实 WebView 请求、响应与事件
- 宿主侧虚拟浇花器模拟状态、计划与日志;后续接真实设备会话
通用宿主能力由 HeimLink 提供;浇水计划、单位换算、通道业务状态、日志解释仍在现有业务模块。适配层吸收实际重复的环境差异,不把每个页面都变成平台判断集合。
| 方案 | 优点 | 代价与判断 |
|---|---|---|
宿主完整仿制涂鸦 RPC |
旧调用点初期改得少 |
RN 宿主会承担大量涂鸦命名、默认值与无后端支撑的语义;不推荐 |
一次重写全仓为平台无关框架 |
长期类型可能更统一 |
超出首个浇花器验收所需,容易同时影响 Sensor 与涂鸦运行时;不推荐首轮采用 |
按现有共享入口增 HeimLink Adapter |
复用页面、协议和数据源层,改动可定位 |
推荐。给 Provider 一个实际可注入的 Adapter,启动/导航/KV 等分别在已有入口选择实现;少量旧 RPC 可在 Web 侧做明确白名单兼容 |
具体设计约束:
- 新增运行时能力描述,分别表达宿主能力、设备 DP 能力和产品 UI 功能。避免再给
ApiBridgeClient叠一个大交叉类型来暗示所有平台都有所有方法。 - 首轮只替换真实调用到的入口;设备状态至少覆盖 CAL 与
device$.info.dpCodes两个消费者,日志通过已有 DataSource 接口接入。 - 运行时 schema 源自绑定设备/可信产品配置。模拟 schema 是有版本的显式 fixture,不能在正式环境读取
defaultSchema充当真实设备契约,也不在业务层写死数字 DP ID。 - 分离 ready 前的宿主初始化方法和 ready 后的业务 API。后者继续遵守
waitUntilFinishLaunch(),避免启动函数反过来等待自身完成。 - 不支持的方法有明确能力标记和错误/降级结果;失败不返回伪造的
{ success: true },用户可见文案沿用 i18n。
6. 建议补充的公开接口范围
以下名称是用于评审的候选。确认实施后,先完成每个接口的具体类型、超时、错误码、权限、数据来源和版本说明,再同步实现与正式 API 文档。已有 system / insets / connection / DP / navigation 接口优先复用,不因本次接入新增同义接口。
| 候选接口组 | 建议契约内容 | 首轮定位 |
|---|---|---|
getRuntimeInfo() 或等价 SDK ready 信息 |
runtime、SDK/API 版本、宿主平台、可用 capabilities、真实/模拟模式;必要能力不匹配时可诊断失败 |
必需;复用握手结果,避免只读版本常量 |
getDeviceContext() |
会话设备不透明 ID、产品/品类、displayName、运行时 schema、DP 权限与约束、addedAtMs、pairedAtMs、bindingRevision、deviceTimezone;未知值 nullable |
必需;不暴露 OwnerKey、MAC 作为稳定身份或允许网页选择任意设备 |
getStorage / setStorage / removeStorage |
应用+绑定设备范围内的键与 JSON 值;明确不存在、容量限制、持久化失败与清理策略 |
必需;本机 MMKV。云 deviceKv 的批量操作仅在实际调用需要时补充 |
getDeviceLogs(query) |
dpCodes、fromMs/toMs、cursor/limit/order;records、nextCursor、source、coverage。记录包含稳定 ID、dpCode、值/编码、reportedAtMs 与 observedAtMs |
首轮支持模拟源;本地记录覆盖不完整时明确标注。不能把未知时间填成当前时间 |
getLocationPermission / requestLocationPermission / getLocation |
权限状态、系统定位开关、结果经纬度/坐标系/精度/采集时间、拒绝/取消/超时 |
P1 原生定位完善;P0 必须有明确不支持或拒绝时的可用降级 |
系统设置跳转、设备改名、剪贴板、应用生命周期 |
仅在已有 Web/原生页面无法满足具体调用时增加;不迁移无实际消费者的涂鸦接口 |
按调用证据补充,避免扩大首轮接口面 |
新日志接口应区分“查询成功但没有记录”“不支持这种日志”“有部分记录但覆盖不完整”。设备离线期间 App 只记录不到新通知,不能据此断言设备没有浇水。
建议时间区间采用 [fromMs, toMs),记录以稳定 ID 去重、分页有稳定排序。具体范围上限与保留策略在实现前定稿。把这些保证落实在 DataSource 和宿主契约中,比让页面逐个猜测数据完整性更可靠。
7. 模拟方案与兼容难点
推荐:宿主内虚拟设备
在 HeimLink 提供显式开发/演示入口,挂载真正的控制页与 bridge。为虚拟浇花器注入设备会话实现,模拟器位于 RN 宿主侧;Web 侧不能通过自己的 mock 绕过消息通道。
模拟 profile 复用已有浇水协议库,维护两路独立状态、手动启停、倒计时、计划记录增删改、查询回显、时间同步,以及灌溉/故障/电量/阈值日志。提供可控制时钟和错误场景,让计划到点、迟到上报、断连、超时等可重复验证。虚拟设备生命周期与 WebView 页面生命周期分离,切页或关闭页面不应清空设备计划。
虚拟 profile 不加入正式 BLE release support matrix,不假装成 thermostat 或 trv,也不临时占用未经协议确认的正式品类编号。正式产品选择仍保持认证身份与控制 App 一致。
ESP32:后续硬件通道验证
ESP32 可验证 BLE 数据往返、协议分片、重连与真实时序,但当前还需要浇花器品类、RAW DP、历史格式、产品身份和固件模拟实现。它仍不能验证阀门机械行为、水流计量或真实功耗。因此它不是首轮最短路径;需要硬件验证时,再检查板卡、已有固件与可恢复方案。
真实浇花器 on-wire 协议的唯一事实源在 heimlink-protocol/specs/。本报告不分配 wire ID 或复制协议正文;若后续要增加正式品类,应先在协议仓形成变更,再同步本仓 contracts、生成物、向量与测试。
| 难点 | 推荐首轮处理 | 明确不代表什么 |
|---|---|---|
无正式浇花器品类/硬件 |
开发入口内的显式虚拟会话,复用业务协议解析 |
不代表真实浇花器已支持 HeimLink 配对/认证 |
无涂鸦云历史 |
宿主生成并保存模拟设备日志,显示来源及覆盖范围 |
不代表可以读取任意设备过去的云端记录 |
RTI 云身份和产品配置 |
版本化本地产品配置、设备 KV;核对现有远端服务可接纳的身份后再接云 |
不用现有涂鸦 productId 冒充新产品;不承诺跨手机同步 |
天气与设备位置 |
保留业务层;无位置/无网络时可解释降级;拒绝权限不阻塞控制 |
IP/手机定位都不是天然的设备安装位置 |
语音、云场景、家庭、用户、OTA |
按真实能力关闭入口或使用已有可用宿主页 |
不返回假的用户、版本更新或云操作成功 |
半屏容器与原生键盘差异 |
首轮保证全屏路由、返回、表单和键盘可用 |
不承诺两个涂鸦宿主的容器行为逐像素相同 |
已有 monorepo 类型/架构约束 |
只在受影响的共享入口增加扩展点;列出必要类型变更 |
不在本轮全面改造所有共享模块 |
8. 实施顺序与验收
这是待确认的实施顺序;执行状态由 beads 跟踪,不把本节作为第二套任务清单。
阶段 A:运行时与真实宿主加载
固定 runtime 检测、ready、设备上下文和 manifest/静态资源路径,建立 HeimLink 专用启动和开发模拟入口。第一条验证必须从 HeimLink 打开实际 DOWT 构建产物,完成至少一次真实 bridge 请求响应,所有图片、字体、Next chunk、Rive/WASM 可加载。记录首屏错误和资源失败,不能让浏览器 mock 自动兜底为成功。
阶段 B:设备控制与页面闭环
接入状态 Adapter、RAW 转换、可信状态确认、导航、连接状态和模拟设备引擎。验证 A/B 通道独立手动启停、计划新增/编辑/删除/启用、控制失败和重连后的状态重新获取。通过同一个 bridge 下发和接收,保留原有业务解析和 i18n。
阶段 C:日志、设置与系统行为
完成模拟灌溉/故障/电量/安全阈值历史与日志 DataSource,本地设备 KV、产品配置、通道名和单位持久化。验证设备/手机时区不同、配对代际变化、主题与安全区变化、定位拒绝或云服务失败时的降级。原生定位或云服务如仍缺条件,在验收结果中逐项列明,不把它们计为已完成真实能力。
阶段 D:真实 BLE 与云服务完善(另行确认)
正式浇花器协议与 release matrix、ESP32/真硬件模拟、云产品身份、完整历史来源、OTA 和第三方服务集成。这些属于额外交付,不作为首轮虚拟设备验收的隐藏前置条件。
| 验收项 | 必须提供的证据 |
|---|---|
确实在 HeimLink 中运行 |
手机或原生模拟器录屏/截图、构建版本、页面入口、bridge 请求响应日志;纯浏览器截图不够 |
静态资源完整 |
应用进入与多页面导航截图;无必需资源 404、hydration 错误或空白 Rive;核心控制页可离线加载 |
双路手动控制 |
A/B 独立启动、停止、倒计时;设备模拟状态与 UI 相符。模拟器注明未验证真实出水 |
计划闭环 |
新增、修改、删除、启用/停用和重新打开后读取;相同值保存、迟到回显、只读/非法字段、超时和断连的结果明确 |
连接与状态真实性 |
链路连接和可控制状态分开;断连不能继续显示已成功控制;重连不把旧 generation 状态当实时状态 |
日志 |
控制行为生成可解释记录;过滤、分页/游标与去重可验证;秒/毫秒正确;不完整覆盖和无数据可区分 |
设置与时间 |
通道名、单位等重启/切页恢复;手机和设备时区不同仍正确;配对时间缺失不编造;DST 和设备时间失效有场景 |
交互布局 |
手机/平板尺寸、顶部/底部安全区、系统主题、页面返回、键盘与表单关闭均正常 |
平台回归 |
涂鸦 RN、涂鸦原生小程序及现有 Web Simulator 的相关构建与测试;无真机条件的部分明示验证限度 |
可复现交付 |
两仓对应提交、构建/启动命令、模拟 profile、验证证据和剩余缺口;代码按仓库流程提交推送 |
测试重点是 API/Adapter 契约测试、设备状态与日志集成测试、现有模拟器 E2E 回归,以及真正 HeimLink WebView 中的原生验收。DOWT 的 E2E 应使用动态端口入口 pnpm test:e2e;Android dev 使用 pnpm dev:<alias>。需要分发 APK 时只使用本地打包与匹配签名,不使用 EAS,不卸载现有 App 来绕过签名问题。
9. 文档沉淀与需要确认的变更范围
当前分析保存在本仓 dev-docs/research/heimlink-apps-runtime-analysis-2026-09-12.md,并发布到 Memory Dump。它是一份研究结论和提案,不修改当前公开接口的承诺。
实施后建议在 monorepo 增加 docs/HEIMLINK_RUNTIME.md,记录调用需求、适配矩阵、启动方式和验证说明;字段契约以 HeimLink 的 dev-docs/api/ 为准,其他仓库链接引用,避免维护互相漂移的两份规范。
按 HeimLink 的 AGENTS.md,公开 API 变化必须先说明文档变更内容并取得确认。预期范围如下:
| 文档 | 预计修改内容 |
|---|---|
dev-docs/api/README.md、changelog.md、handshake.md、data-types.md、error-model.md |
新增接口清单、能力组、设备上下文/日志/存储类型、明确不支持与权限/容量错误及版本说明 |
dev-docs/api/runtime-model.md、security-model.md |
ready、可信快照与确认规则、应用+设备存储隔离、模拟来源及数据覆盖语义 |
新增 runtime-info / device-context / storage / device-logs 方法文档 |
第 6 节选定的方法:完整参数、返回、错误、超时、能力要求与示例 |
定位、系统设置等方法文档(选做) |
仅在确认实现这些能力时新增;明确授权交互、坐标系和失败结果 |
dev-docs/implementation/modules/rtitek-bridge.md、dev-docs/system-design/app/webview-device-control.md |
新能力的实现职责、模拟会话入口、资源装载和生命周期;不复制 on-wire 规范 |
monorepo 的共享 SDK / 天气 / 日志相关说明 |
按实际触及范围更新 DEVICE_KV.md、产品/设备档案文档、天气说明和 app/logs/spec.md 的数据源/降级行为 |
本报告确认项为:采用宿主侧模拟完成阶段 A–C;通过共享层 HeimLink Adapter 复用现有 DOWT;接受第 7 节列明的能力差异;按上表同步必要文档。具体新增类型与方法签名须在动手修改相应契约前形成可审阅稿,不能把这份能力清单当成未定字段的无限授权。
10. 优化后的执行提示词
以下文本用于你确认报告后启动实施。
请为
/Users/qiaoanran/worktrees/heimlink-appsmonorepo 增加 HeimLink 运行时,使现有apps/DualOutletWaterTimer作为设备控制 Web App 在/Users/qiaoanran/worktrees/rtk-app-experiment的 HeimLink 宿主中可操作。保持涂鸦 React Native 面板、涂鸦原生小程序和现有 Web Simulator 的相关行为兼容。以已确认的运行时分析报告为范围依据。先核对两个仓库当前代码与报告基线,列出必要的类型/API 契约变化,遵守两个仓库的 AGENTS.md、文档同步及 beads 流程。将接口需求、实现映射、错误/降级和验收证据持续沉淀到对应仓库文档,不重复维护 on-wire 协议正文。
复用现有页面、业务状态、日志 DataSource 和浇花器协议库。优先在
packages/shared-business/src/lib/heimlink-api/增加宿主适配,按需增加独立类型包;围绕启动、DeviceStateProvider、导航、日志与存储的现有入口增加有限扩展。不要在 HeimLink 宿主完整复刻涂鸦 RPC,也不要为本任务全面重写 monorepo。宿主能力至少覆盖正确的 runtime/ready、设备上下文和运行时 schema、安全区/窗口/系统设置、连接与可控制状态、状态快照及增量、DP 下发和可信确认、模拟设备日志、本地设备配置持久化、产品能力配置、导航与错误降级。优先复用 window.rti 已有接口,缺失能力按经确认的契约补齐。公开 SDK 文档的具体差异先展示并按仓库要求取得确认,再同步实现。
特别核对 DP code 与编号、hex/Base64、数据范围和权限、秒/毫秒、设备时区与手机时区、添加/绑定/激活时间、一次原子写与 BLE 多字段拆分、同值回显与业务确认、初始快照和订阅竞态。不得硬编码正式设备的 DP ID,不得用默认 schema、旧缓存、回放命令或虚假成功掩盖不兼容。
首轮通过显式 HeimLink 开发入口使用宿主侧虚拟浇花器。实际 DOWT 构建产物运行在真实 HeimLink WebView,所有设备请求与事件经过真实 rti bridge。复用协议库实现双路手动启停/倒计时、计划增删改及回显、时间同步、持久配置、灌溉/故障/电量/阈值日志与可重复错误场景。明确标注模拟来源;不依赖 Web 页面自行伪造 rti 来完成验收,不把虚拟品类加入正式 BLE 发布矩阵。
构建链路必须正确支持 Next App Router、动态 chunk、图片、字体和 Rive/WASM,不照搬只适合温控器的文本打包器或替换资源为空白图。保证多页面导航、参数、返回、键盘、安全区和核心离线加载正确。
天气、定位拒绝、云端配置或历史不可用时,采用报告中明确的降级并保证核心控制可用。涂鸦云历史、账号/家庭/场景、地图选点、OTA、正式浇花器 BLE 品类与固件作为明确额外范围;遇到不自然的兼容方案时列出代价和替代方案,不伪造能力。
验收必须包含:真正 HeimLink 内打开 DOWT 的证据;A/B 通道独立控制;计划保存与再次读取;连接/断连/重连;设备日志及时间语义;本地设置恢复;主题/安全区/时区/键盘/导航;两种涂鸦运行时与 Web Simulator 的相关回归。用契约/集成测试及真实 WebView 验证,不以“浏览器 mock 页面能打开”代替宿主验收。说明所有未能运行的检查和模拟不能证明的硬件能力。
完成后提供两仓提交、构建/启动命令、模拟入口、测试结果、截图或录屏、剩余缺口,并按仓库约定提交推送。Android dev 仅走
pnpm dev:<alias>;APK 只本地构建,不用 EAS,不卸载 App 或清数据绕过签名。未经额外确认,不修改/刷写现有 ESP32 固件。
11. 源码索引
路径相对于各自仓库。行号对应第 2 节的检查基线;作为后续复核入口,不是对未读取代码的推断。
| 仓库 | 位置 | 支持的分析结论 |
|---|---|---|
heimlink-apps |
apps/DualOutletWaterTimer/next.config.mjs:19、package.json |
App Router / 静态导出、依赖与构建入口 |
heimlink-apps |
packages/shared-business/src/lib/platform.ts:19、apiBridge.ts:143 |
平台识别、现有 RPC 分支与协议差异 |
heimlink-apps |
apps/DualOutletWaterTimer/components/providers.tsx:32、packages/shared-business/src/startup.ts:43 |
固定启动入口、系统/设备/UI 依赖 |
heimlink-apps |
packages/shared-business/src/lib/DeviceStateProvider/Context.tsx:83、:194 |
BLE splitCommands 默认值、固定 BridgeAdapter 创建 |
heimlink-apps |
packages/shared-business/src/lib/DeviceStateProvider/hooks/useDeviceStateMutation.ts:83、apps/DualOutletWaterTimer/hooks/useScheduleMutation.ts:137 |
状态回显与计划 confirm 谓词 |
heimlink-apps |
apps/DualOutletWaterTimer/utils/interceptors/manualWateringParseInterceptor.ts:17、scheduleCommandParseInterceptor.ts:102 |
实时 RAW 的业务编码/解析与程序记录语义 |
heimlink-apps |
apps/DualOutletWaterTimer/modules/irrigationLog/dataSources/APIBridgeDataSource.ts:11、repositories/IrrigationLogRepository.ts:317 |
日志查询、时间单位与 Base64 解码 |
heimlink-apps |
packages/shared-business/src/lib/weather/locationInit.ts:517、state/timezone.state.ts:61 |
activeTime、设备位置初始化及设备/手机时区回退 |
heimlink-apps |
packages/shared-business/src/lib/api/api.ts:38、lib/deviceKv/、lib/productProfile/ |
云产品上下文、设备 KV 与功能配置依赖 |
heimlink-apps |
packages/shared-business/src/lib/navigation.ts:42、src/startup.ts:277 |
涂鸦路由与安全区坐标依赖 |
HeimLink |
src/lib/rtitek-bridge/sdk-source.ts:313、host.ts:397 |
当前 SDK 方法、宿主分发与握手能力 |
HeimLink |
src/lib/rtitek-bridge/event-pump.ts:91、host.ts:184 |
缓存差异事件和原子写的可信状态更新 |
HeimLink |
shared/contracts/dps-bytes.ts:1、categories.vnext.json、release-support.vnext.json |
Base64 字节边界、品类及发布范围 |
HeimLink |
dev-docs/api/methods/get-history.md:16 |
当前历史仅温控逻辑点,不是浇水/通用 DP 日志 |
HeimLink |
src/features/devices/use-device-store.tsx:220、src/lib/commissioning/vnext-binding-store.ts:30 |
添加时间与绑定记录元数据 |
HeimLink |
web-apps/heimlink-thermostat/scripts/build-html-external.mjs:92、:181 |
文本资源限制、占位图片与 NEXT_DATA 依赖 |
HeimLink |
src/features/devices/built-in-web-apps/types.ts:9、index.ts:168 |
静态资源包与文件装载能力 |
待用户通过 Memory Dump 报告确认后,再开始阶段 A–C 的代码实施。