开发者 / RtiTek Bridge API(window.rti)
RtiTek Bridge API(window.rti)
App 向运行在 WebView 里的小程序(Web App)注入全局对象 window.rti。小程序通过它连接会话绑定的设备、读取和下发 DP、订阅连接与状态变化、在页面之间导航,不直接接触蓝牙。
文档对应 App 0.6.8(2026-10-07)。接口处于 v1 草稿期,多数标注「会调整」。另见 window.ty 兼容接口。
目录
接口
- 接口清单
- rti.getDeviceInfo
- rti.connect
- rti.disconnect
- rti.getConnectionState
- rti.getDeviceState
- rti.publishDps
- rti.onConnectionStateChange
- rti.onDeviceStateChange
- rti.onError
- rti.navigateTo
- rti.replace
- rti.goBack
- rti.exit
- rti.openDeviceSettings
- rti.openFirmwareUpgrade
- rti.onNavigateBack
- rti.onInsetsChange
- rti.onSystemChange
- rti.onDeviceRemoved
- rti.getSecurePairingState
- rti.startSecurePairing
- rti.cancelSecurePairing
- rti.onSecurePairingChange
- rti.getHistoryInfo
- rti.getHistory
其他
总览
RtiTek Bridge API 让运行在 WebView 里的 Web App 安全地控制一台 BLE 设备,而不直接接触 BLE。
Bridge 在哪
自 2026-09-13 起,宿主还提供 window.ty 兼容命名空间;其同步/回调/嵌套语义有独立约定。下文的 Promise 示例指 window.rti,不把这些约定强加给涂鸦接口。
App 把控制界面交给第三方的一个静态网页(Web App),网页跑在 WebView 里。App 向网页注入一个全局对象 window.rti,第三方通过它调用接口:
await rti.connect();
const dps = await rti.getDeviceState();
await rti.publishDps({ brightness: 80 });
通道模型
- 命令和事件都走同一条 WebView postMessage 桥,没有对外监听的 HTTP API;
ty.request经该桥委托宿主发起真实 HTTP 请求。 - 命令是请求-响应:第三方
await rti.xxx(),SDK 在内部完成请求 id 配对、超时、清理。 - 事件是 App 主动推送:第三方用
rti.onXxx(cb)订阅。 - 设备端 HTTP 服务只用来加载 Web App 的静态资源,不承载任何 API。
第三方使用 window.rti 或 window.ty,无需自行处理 postMessage、请求 id、BLE 这些底层细节。
第三方看到的是业务概念,不是传输细节
第三方只接触「连接」「DP(设备状态项)」这类业务概念,不接触 UUID、字节、GATT。底层 BLE 怎么变,第三方都不受影响。
能力边界(一句话)
一个会话绑定唯一一台设备。第三方不能扫描、不能连别的设备、不能直接碰 GATT。详见 security-model。
怎么读这套文档
- 运行模型、消息、SDK 约定、写语义、超时 → runtime-model
- 版本与兼容 → versioning
- 安全与设备身份 → security-model
- 错误处理 → error-model
- 数据类型 → data-types
- 逐接口契约 → 接口清单
运行模型
本文是所有接口共同遵守的运行规则。某个接口具体收发什么,在它自己的文档里。
三种消息
桥上有三种消息:
call:网页 → App,发起命令。带id、版本v、点分method、payload。result:App → 网页,命令应答。回带同一个id,带data或error。event:App → 网页,主动推送。带版本v、type、data。
具体形态见 data-types。payload / data 内部长什么样是各接口自己的事。
命令:请求-响应
每个命令带一个请求 id,App 按 id 把结果回送。SDK 在内部完成「配对 id、超时、清理」,第三方只写 await rti.xxx()。
事件:主动推送
事件和命令走同一条 postMessage 桥,由 App 主动推送(kind: "event",没有 id)。
- 事件源不用
setInterval轮询,全部来自 BLE 通知或状态订阅。 - 有哪些
type、每个data长什么样,在各事件文档里。
SDK 通用约定
本节扁平对象、全异步和 DP 名称约定针对 window.rti。window.ty 是显式兼容例外:支持嵌套命名空间、三个同步快照接口、回调及 Promise 调用,以及数字 DP ID/hex;见 ty 通用约定。它复用同一消息封套和认证设备会话,不改变 rti 数据形态,也不提供完整涂鸦小程序框架。
- 一个扁平全局对象:App 注入
window.rti,不嵌套。调用形如await rti.publishDps({ brightness: 80 })。 - 全异步:每个方法返回
Promise。 - 统一 options:每个方法接受
CallOptions { timeout?: number }。乐观显示与回滚由第三方负责(见下方写语义)。 - 统一订阅形态:每个
on*方法返回退订函数() => void。多个订阅者时,SDK 底层只挂一次。 - DP 模型是通用的:设备状态是
Dps = Record<string, unknown>。rti 使用已认证产品快照的dataPointName,ty 兼容层提供该产品的数字 DP ID;同品类不代表模型相同。App 只允许当前安装目录中的已认证且非eol产品,设备页运行该产品appletId指定的小程序,category 不参与选择。
超时
- 每个方法都能传
timeout(毫秒),覆盖默认值。 - 默认值:读/写类 8s,
connect15s。具体默认值写在各接口文档里。 - 超时报错
{ code: 'TIMEOUT' }。
写语义与职责划分
window.rti v1 改设备状态的接口是 publishDps;ty.device.publishCommands 通过同一设备会话下发,继承具体驱动的语义,不扩大权限或保证。职责这样划分:
App 侧(行为固定,只做一件事):
publishDps(dps, { timeout }):一次调用编码为一个 typedSET_STATE,其中所有 DP 原子验证和提交,不跨调用合并。- 成功(设备返回完整
STATE_RESULT)→ Promise resolve。 - 任一字段失败、通信失败或超时 → Promise reject;设备和 App 的可信状态缓存都不应用部分结果。
- 成功(设备返回完整
onDeviceStateChange:只推送当前连接 generation、fresh AUTH 后收到的加密EVENT增量;与任何写请求无关。onError:推送没有对应返回值的错误(如意外断连),与publishDps的 reject 是两条独立路径。
控制页只有在 fresh AUTH 后收到当前 generation 的完整 STATE_RESULT 才进入 live。断链或 generation 变化会立即把缓存降级为 last-known;last-known 可展示,但不冒充已认证实时状态。
第三方侧(业务自己定):
- 想点按即时反馈,自己先显示预测值。
- 失败怎么处理(回到上一次
onDeviceStateChange的真实值?保留并提示?重试?)由第三方按自己的 UX 决定。 - App 不提供也不做预测值存储 / 回滚。
一句话:App 保证「状态如实、成败明确」;乐观显示和回滚归第三方。
版本与兼容
版本号
- 协议版本是消息里的
v字段(一个数字),不在 URL 路径里。 - 一个版本内只增不改:可以加字段、加方法,不改已有行为。
- 不兼容的改动才升版本号。App 侧可以在一段时间内同时接受新旧两个版本号。
能力探测
- 客户端能做什么,通过
handshake协商:启动时 SDK 与 App 做一次检查,App 按结果返回它支持的方法/字段。详见 handshake。 - 不要在第三方代码里硬编码版本假设。要不要用某个方法/字段,依据
handshake的返回,而不是写死。
错误码兼容
通用错误码只增不改语义。已有错误码的含义永远不变。详见 error-model。
废弃规则
- 接口的稳定性标在 接口清单 和各接口文档里:稳定 / 会调整 / 可能推翻。
- 当前 v1 接口多为「会调整」:方向已定,细节还会变,可以依赖但要预期会动。
安全模型
WebView 跑的是第三方代码,所以要限制它能做什么。改用单条 postMessage 桥后,安全模型比之前用 HTTP API 时更简单。
能力边界
- 没有网络可达端点:桥对象只能从本 WebView 的页面 JS 访问,机器上其他程序碰不到。因此每条消息不需要带一次性密钥来证明「我是合法页面」。
- 按会话绑定,不靠消息里的字段:App 为某台设备打开 Web App 时,这个 WebView 在 App 侧绑定到「这台设备 + 这个会话」。处理命令时只作用于绑定设备,第三方不能在消息里指定别的设备。越权访问直接返回
DEVICE_SCOPE_VIOLATION。 - 只能做白名单内的事:第三方只能对绑定设备使用桥提供的方法——不能扫描、不能连别的设备、不能直接碰 GATT。
- 生命周期:Web App 卸载/离开 → App 解绑会话、停止订阅,注入的桥对象随 WebView 一起销毁。
设备身份
window.ty 的新增能力范围
window.ty 仍绑定当前设备:启动时发放本地不透明句柄,原生将其映射至当前会话,不能通过 payload、设备列表或 URL 查询参数改写作用域。该句柄不是涂鸦云身份,不能当作外部设备服务的身份凭据;本次不改变既有 rti 字段。
存储按应用和本地设备记录隔离,仅暴露页面自身写入的 JSON。页面初始化数据按挂载实例隔离并安全转义。设备状态与控制仍经已认证的设备会话;系统/存储/HTTP 不要求初始化 BLE。
HTTP 不启动入站端点,不自动附加宿主 token、密钥或账号信息;只发送调用者的参数和 headers,禁用默认凭据。生产限 HTTPS,development/preview 可用 HTTP;URL 不允许内嵌用户名密码。当前没有业务域名 allowlist,因此已授权运行的控制页面可以请求任意允许协议的地址,包括局域网地址;不应把此能力开放给未经审核的页面。
定位按系统权限获取真实手机位置,只请求前台授权;剪贴板只写不读。页面销毁清理事件、取消 HTTP 与位置采集。已出现的系统权限框和已经提交的设备写入不能通过销毁页面撤回。
NOT_PLANNED 不表示授权拒绝或临时故障。对当前不具备的地图选择及 OTA 状态查询返回 NOT_SUPPORTED,不能假装成功。详见 兼容层限制。
HeimLink vNext 明确区分三层标识,三者不能互相替代:
| 标识 | 来源 | 用途 | 可否作为授权身份 |
|---|---|---|---|
| BindingRecord identity | fresh AUTH 证明的 vendor/product、discriminator、device instance、binding ID 与 generation | App 内持久绑定、SavedDevice 去重、OwnerKey 选择 | 是,App 内部使用 |
| BLE locator | Android address 或 CoreBluetooth UUID | 当前平台上的扫描结果和定向重连提示 | 否;可能变化,且不公开给 Web App |
deviceId | App 为当前 SavedDevice/WebView session 分配的本地句柄 | Bridge 会话内路由调用 | 否;不是 locator,也不是跨安装全局身份 |
deviceId 是什么
deviceId 只在当前 App 安装和当前 WebView 会话内指代已绑定设备。它不再承诺等于原生 BLE identifier,也不允许第三方从它推导 MAC、CoreBluetooth UUID、BindingRecord 或 OwnerKey。
第三方必须接受以下约束:
- 不得把
deviceId当作可跨手机、跨重装或云端长期共享的设备唯一 ID。 - 不能枚举或改写 session 绑定的
deviceId。rti.connect()只表示连接 App 已绑定的设备。 - BLE locator 变化时,App 可在 fresh AUTH 后更新内部
lastLocator,而 Web App 的 session scope 不因此获得更大的权限。
App 侧保证
- 只有 fresh AUTH 证明的 BindingRecord identity 可以合并 SavedDevice 或更新 locator;ADV 名字、未认证
auth_info和平台 locator 都不能单独授权。 getDeviceInfo不返回平台原始 locator、OwnerKey 或完整 BindingRecord。- 断链、重新发现或 locator 变化不会把一个未认证候选自动并入已有设备。
以后的计划
以后会引入平台级唯一设备 ID(暂定名 globalDeviceId,最终名待定),用于跨平台/跨手机/重装后稳定指代同一台物理设备。它和这里的 deviceId 是两层,必须区分,不得混用或互相替代:
| 维度 | deviceId(现在) | globalDeviceId(以后) |
|---|---|---|
| 来源 | App 会话本地句柄 | RtiTek 分配/解析的稳定公开身份 |
| 范围 | 本 App、本次安装、本机 | 平台级,跨手机,跨重装 |
| 用途 | 本会话内找到并连接设备 | 长期存储、云端映射、跨设备同步 |
| 第三方可否长期存储 | 否 | 是(引入后) |
引入后,SDK 和消息格式会把两个值作为两个独立字段暴露,不会用新值覆盖 deviceId。在那之前,任何「设备唯一性」需求都不得建立在 deviceId 上。
错误模型
统一错误形态
window.rti 的错误和 bridge result.error 使用同一形态:
{ code: string; message: string }
code:机器可判的错误码(见下表),第三方按code分支处理。message:给人看的描述,不保证稳定,不要用它做逻辑判断。
错误有两条路径:
- 命令失败:
await rti.xxx()的 Promise reject,错误形态同上(对应result消息的error字段)。 - 带外错误:没有对应返回值的错误(如意外断连)走
rti.onError,详见 events/on-error。
window.ty 使用同一组错误码,但将命令失败转换为兼容对象:
{
success: false;
errorCode: string;
errorMsg: string;
innerError: { errorCode: string; errorMsg: string };
api: string;
errMsg: string;
}
errorCode / errorMsg 对应底层的 code / message,innerError 与外层相同(对齐涂鸦失败对象,HeimLink 没有外部依赖错误这一层),errorCode 不换算成涂鸦数字错误码,api 是完整方法名(如 ty.getUserInfo),errMsg 为 <api>:fail <errorMsg>。有回调时交给 fail、再交给 complete;无回调时作为 Promise 拒绝值。同步不计划实现入口的例外及完整约定见 window.ty 兼容层。不要把 NOT_PLANNED 或 HTTP 请求收到响应当作服务健康的证明。
通用错误码
所有接口共用一份错误码表。错误码只增,已有含义永不改。
| code | 含义 |
|---|---|
NOT_CONNECTED | 设备未连接 |
TIMEOUT | 调用超时(默认值见各接口文档) |
BLE_IO_ERROR | BLE 读写/通信失败(兜底错误) |
BAD_REQUEST | 请求参数错误,或宿主未接入对应处理器 |
UNAUTHORIZED | 会话已失效(WebView 卸载 / 会话已释放后再调用) |
DEVICE_SCOPE_VIOLATION | 越权访问绑定设备以外的设备 |
VERSION_UNSUPPORTED | 版本不支持(handshake 协商失败时返回) |
NOT_PAIRED | 设备未完成配对 / SecureChannel 未建立 |
DP_UNKNOWN | publishDps 使用了当前认证产品契约不存在的 DP 名 |
DP_READ_ONLY | publishDps 尝试写只读 DP |
DP_TYPE_MISMATCH | DP 的值类型与产品契约不一致 |
DP_VALUE_INVALID | DP 值超出范围,或 byte string / tuple 长度不合法 |
NOT_PLANNED | 明确不计划在 HeimLink 实现的涂鸦平台能力,不代表临时故障 |
NOT_SUPPORTED | 有业务意义,但当前版本、驱动、参数或平台不支持 |
PERMISSION_DENIED | 所需系统权限未获准 |
NETWORK_ERROR | HTTP 传输失败;收到 4xx/5xx 响应本身不属于传输失败 |
STORAGE_NOT_FOUND | 当前应用及设备范围内没有指定存储项 |
CANCELLED | 操作被取消,如页面销毁时撤销挂起的原生请求 |
INTERNAL | 宿主内部错误(兜底) |
接口自有错误码
某些接口有自己特有的错误码,只写在该接口文档里,不在此重复;不得复用通用错误码表达冲突含义。
publishDps 的业务错误映射固定如下:
| 设备 / App 侧原因 | public code |
|---|---|
| 未知 dataPoint 或 App 本地未知名称 | DP_UNKNOWN |
| 只读 dataPoint | DP_READ_ONLY |
| wire dataType 与注册表不匹配 | DP_TYPE_MISMATCH |
| 值域或长度不合法 | DP_VALUE_INVALID |
| 空写或无法解析的请求 payload | BAD_REQUEST |
| 设备内部错误或无法分类的 BLE 业务错误 | BLE_IO_ERROR |
connect 未找到绑定设备 / 不在范围内仍使用 TIMEOUT(扫描超时)。
数据类型
公共数据类型。各接口 payload / data 内部结构在各接口文档里。
消息封套
// 网页 → App(命令)
type CallMessage = {
kind: 'call';
id: string; // 网页生成,App 在 result 里回带
v: number; // 协议版本
method: string; // 如 'connection.connect' | 'dps.publish'
payload?: Record<string, unknown>;
};
// App → 网页(应答)
type ResultMessage = {
kind: 'result';
id: string;
ok: boolean;
data?: unknown;
error?: { code: string; message: string };
};
// App → 网页(推送)
type EventMessage = {
kind: 'event';
v: number;
type: 'connectionStateChange' | 'deviceStateChange' | 'error' | 'deviceRemoved'
| 'navigateBack' | 'insetsChange' | 'systemChange' | 'securePairingChange'
| 'ty.dpDataChange' | 'ty.deviceOnlineStatusUpdate' | 'ty.deviceRemoved'
| 'ty.keyboardHeightChange' | 'ty.systemInfoChange' | 'ty.storageChange';
data: unknown;
};
这是公开契约形态。SDK 在内部封装收发,第三方一般不直接构造这些消息。
route是 SDK 内部事件:App 用它维护rti.route数据属性,不对 WebApp 暴露,也没有rti.onRoute订阅入口,因此不在上面的公开type清单里。WebApp 能订阅的事件以接口清单为准。原生路由跳转由navigation.*命令触发(见接口清单),跳转后rti.route由 SDK 自动同步。
调用选项
ty.* RPC 采用同一个 call/result 封套,消息 method 与公开完整名字相同(例如 ty.request)。同步接口和事件订阅不发送 RPC。ty.systemInfoChange 更新同步系统快照;ty.storageChange 的 {revision, data} 更新整个隔离存储镜像,属于 SDK 内部维护事件,不是新增公开订阅方法。其他 ty 事件字段见逐接口文档。
ty 失败对象为 {success:false, errorCode:string, errorMsg:string, api:string, errMsg:string};errorCode 是封套 error.code,errorMsg 是 error.message。新增分类包括 NOT_PLANNED、NOT_SUPPORTED、PERMISSION_DENIED、NETWORK_ERROR、STORAGE_NOT_FOUND、CANCELLED,含义见 ty 通用约定。这些字段不改变 rti 的 {code,message} 拒绝形态。
ty 数字 DP 采用 Record<string, string | number | boolean>;键为实际契约数字编号字符串,RAW 为 hex、数值按十进制 schema.scale 编码。下文 DP 名称/物理单位/Base64 规则仍专指 rti。
type CallOptions = { timeout?: number }; // 毫秒;覆盖默认超时
连接状态
type ConnectionState = 'disconnected' | 'connecting' | 'connected' | 'disconnecting';
Secure pairing 状态
与 BLE 链路独立的一层。BLE connected 不等于 paired —— secure-only 设备(如温控器)在 paired 前 dps 读写会失败。
type SecurePairingState = 'not-required' | 'pairing' | 'paired' | 'failed';
not-required:当前没有适用的已绑定 vNext 设备(例如 session 尚未连接);不表示允许明文业务 fallback。pairing:PASE commissioning 或 OwnerKey AUTH 正在进行。paired:fresh SecureChannel 已建立,且当前 generation 的完整 STATE_RESULT 已验证,控制已就绪。failed:上次尝试失败或被取消;sticky 直到下次开始或断开。
运行历史点(device history)
温控器等品类经 rti.getHistory 返回的逻辑点(非线形 5 字节)。时间已是 UNIX 秒;温度已是 ℃ 浮点。
type HistoryKind = 'sample' | 'setpoint' | 'mode';
type HistoryPoint
= | { t: number; kind: 'sample'; tempC: number }
| { t: number; kind: 'setpoint'; setpointC: number }
| { t: number; kind: 'mode'; mode: 'off' | 'heat' | 'cool' | 'auto' };
type HistoryInfo = {
count: number;
tMin: number; // UNIX 秒;无数据为 0
tMax: number;
timeValid: boolean;
generation: number;
retentionSeconds: number; // 0 = 以物理环为准(开发阶段)
sampleIntervalMin: number;
};
详见 get-history / get-history-info。
设备状态(DP)
type Dps = Record<string, unknown>;
Web API 的 DP 名直接使用品类契约中的 dataPointName,不要求 Web App 接触数字编号或 wire data type。示例:
{ "mode": "heat", "setpoint": 22, "currentTemp": 20.5, "fan": "auto" }
App 会在可信品类注册表中把名称映射为永久 dataPoint:u16,并在 ApplicationFrame v6 内编码为 canonical typed CBOR。每个条目都显式携带 dataType:u8 和 value;Web App 不得自行构造这些编号。未知名称、只读点、类型不匹配或值域错误分别使用稳定的 DP_* 错误码。
一次 publishDps 可包含多个 DP。它们构成同一个原子事务:全部验证成功后一起提交,任一字段失败则设备状态零修改。
bytes(RAW)DP 的值形态
契约 payload.type === "bytes" 的字段在 WebView JSON 边界上是标准 base64 字符串(无 data: 前缀);App 内部与 SecureFrame CBOR 为原始字节。
// 下发 3 字节私有命令(thermostat 演示字段 echoBlob)
await rti.publishDps({ echoBlob: btoa(String.fromCharCode(0x01, 0x02, 0xff)) });
// → base64 "AQL/"
// app_features(dataPoint 101,同样 bytes / 仅 ApplicationFrame secure payload)
await rti.publishDps({ app_features: btoa(String.fromCharCode(0xaa, 0xbb)) });
const dps = await rti.getDeviceState();
// dps.echoBlob === "AQL/" (string)
// dps.app_features === "qrs=" (string)
长度必须落在契约 minLength..maxLength;非法 base64 或越界由 bridge 拒绝。
设备信息
// 固定字段来自 thermostat 的 Device Information Service;capabilities 来自可信品类契约
{
"model": "Smart Thermostat v1.0",
"manufacturer": "RtiTek",
"firmware": "0.1.0.0",
"hardware": "v2",
"capabilities": {
"mode": true,
"setpoint": true,
"currentTemp": true,
"fan": true,
"echoBlob": true,
"app_features": true
}
}
错误
type RtiTekError = { code: string; message: string };
错误码取值见 error-model。
handshake(SDK 内部 / 消息 handshake)
起始版本:v1
稳定性:会调整(P5 版本/能力协商,方向已定、细节会变)
继承的通用规则:消息格式见 data-types,通用错误码见 error-model,版本策略见 versioning。
功能
启动时 SDK 与 App 做一次版本和能力检查。第三方不直接调用,由 SDK 内部完成。检查之后才知道哪些方法/字段可用——不要在第三方代码里硬编码版本假设。
请求字段
{ "sdkVersion": "<string>", "acceptVersions": ["v1"] } // 字段待定
返回字段
{ "version": "v1", "capabilities": ["connection", "dps", "deviceInfo", "navigation", "securePairing", "deviceHistory"] }
| capability | 含义 |
|---|---|
connection | 连接/断开/连接状态 |
dps | 设备运行 DP 读写与推送 |
deviceInfo | 设备信息 |
navigation | Web App 内路由;离开设备页、打开设备设置页与固件升级页(2026-10-06 起),供自己画导航栏的 Web App 使用 |
securePairing | 安全配对状态与重试 |
deviceHistory | 运行历史元信息与点列(getHistoryInfo / getHistory) |
tyCompatibility | 宿主已接入 window.ty 兼容层;仅表示命名空间/分发能力,不表示 38 项全部完整实现,也不表示具备涂鸦云端或小程序框架 |
tyCompatibility 仅在宿主注册了兼容处理器时返回。本次不新增公开 rti.handshake(),也不增加 SDK 自动握手流程;页面不能依赖自动协商阻挡未支持调用。应按逐接口支持状态处理 NOT_PLANNED / NOT_SUPPORTED。
客户端能力(Web App 端)
Web App 在自己的 app.json 顶层 capabilities 字段里声明它能接管哪些原生兜底 UI。App 端读到后会让出对应区域,由 Web App 自己画。
// app.json 节选
{
"capabilities": {
"securePairing": true, // App 不再挂原生 PairingRequiredOverlay
"connectionUi": true // App 不再显示原生连接卡片(TopCard)
}
}
当前已识别的客户端能力:
| 名称 | 含义 | App 端行为 |
|---|---|---|
securePairing | Web App 用 rti.onSecurePairingChange + rti.startSecurePairing / cancel-secure-pairing 自己画 secure pairing UI | 不挂原生 PairingRequiredOverlay |
connectionUi | Web App 用 rti.onConnectionStateChange + rti.getConnectionState / rti.connect 自己呈现连接过程 | 不显示原生连接卡片(TopCard),失败时不暂停自动重试 |
未声明的能力默认 false,App 维持现有兜底行为。
自有错误码
VERSION_UNSUPPORTED(含义见 error-model,此处列出是因为本接口会主动返回它)
超时
- 默认超时:待定(应较短,版本检查要快速失败)。
- 非写接口,无写语义。
示例
待定(等「能力协商粒度」问题定稿后补)。
接口清单
| 接口 | rti 方法 / 事件 | 消息 method / 事件 type | 文档 | 起始版本 | 稳定性 |
|---|---|---|---|---|---|
| 协商能力/版本 | (SDK 内部,非公开方法) | handshake | 查看 | v1 | 会调整 |
| 读设备信息 | rti.getDeviceInfo | device.info | 查看 | v1 | 会调整 |
| 连接绑定设备 | rti.connect | connection.connect | 查看 | v1 | 会调整 |
| 断开连接 | rti.disconnect | connection.disconnect | 查看 | v1 | 会调整 |
| 查询连接状态 | rti.getConnectionState | connection.get | 查看 | v1 | 会调整 |
| 读设备运行状态 | rti.getDeviceState | dps.get | 查看 | v1 | 会调整;主动读设备并刷新缓存 |
| 下发 DP | rti.publishDps | dps.publish | 查看 | v1 | 会调整 |
| 订阅连接状态变化 | rti.onConnectionStateChange | 事件 connectionStateChange | 查看 | v1 | 会调整 |
| 订阅设备状态变化 | rti.onDeviceStateChange | 事件 deviceStateChange | 查看 | v1 | 会调整 |
| 订阅带外错误 | rti.onError | 事件 error | 查看 | v1 | 会调整 |
| Web App 内导航跳转 | rti.navigateTo | navigation.push | 查看 | v1 | 会调整 |
| 替换当前路由 | rti.replace | navigation.replace | 查看 | v1 | 会调整 |
| 返回上一路由 | rti.goBack | navigation.back | 查看 | v1 | 会调整 |
| 离开设备页 | rti.exit | navigation.exit | 查看 | v1(2026-10-06 新增) | 会调整 |
| 打开设备设置页 | rti.openDeviceSettings | navigation.openDeviceSettings | 查看 | v1(2026-10-06 新增) | 会调整 |
| 打开固件升级页 | rti.openFirmwareUpgrade | navigation.openFirmwareUpgrade | 查看 | v1(2026-10-06 新增) | 会调整 |
| 拦截用户返回 | rti.onNavigateBack | 事件 navigateBack | 查看 | v1 | 会调整 |
| 订阅安全区变化 | rti.onInsetsChange | 事件 insetsChange | 查看 | v1 | 会调整 |
| 订阅系统/语言变化 | rti.onSystemChange | 事件 systemChange | 查看 | v1 | 会调整 |
| 订阅设备被移除 | rti.onDeviceRemoved | 事件 deviceRemoved | 查看 | v1 | 会调整 |
| 查询 secure pairing 状态 | rti.getSecurePairingState | securePairing.get | 查看 | v1 | 会调整 |
| 触发 secure pairing 重试 | rti.startSecurePairing | securePairing.start | 查看 | v1 | 会调整 |
| 取消 secure pairing | rti.cancelSecurePairing | securePairing.cancel | 查看 | v1 | 会调整 |
| 订阅 secure pairing 变化 | rti.onSecurePairingChange | 事件 securePairingChange | 查看 | v1 | 会调整 |
| 查运行历史元信息 | rti.getHistoryInfo | history.info | 查看 | v1 | 会调整;需 secure pairing |
| 拉运行历史点列 | rti.getHistory | history.query | 查看 | v1 | 会调整;需 secure pairing;默认 60s 超时 |
rti.getDeviceInfo(消息 device.info)
起始版本:v1
稳定性:会调整
继承的通用规则:消息格式见 data-types,通用错误码见 error-model,设备身份见 security-model。
功能
读取本会话绑定设备的固定信息(型号、厂商、固件等)。第三方据此适配自己的 UI/功能。
请求字段
{} // 无输入;操作哪台设备由会话绑定的 deviceId 决定
返回字段
{
"model": "Smart Thermostat v1.0",
"manufacturer": "RtiTek",
"firmware": "0.1.0.0",
"hardware": "v2",
"capabilities": {
"mode": true,
"setpoint": true,
"currentTemp": true,
"fan": true,
"echoBlob": true,
"app_features": true
}
}
model、manufacturer、firmware、hardware 分别读取 thermostat 契约声明的标准 Device Information Service 特征;capabilities 不由设备明文上报,而由 App 的可信 thermostat 契约补全。
返回不含 deviceId 以外的任何平台原始标识;将来 globalDeviceId 是否进入本返回,见 security-model 设备身份。
自有错误码
无(只可能返回 error-model 的通用码,如 NOT_CONNECTED / BLE_IO_ERROR)。
超时
- 默认超时:8s(读类型,可覆盖)。
- 读类型,无写语义。
示例
const info = await rti.getDeviceInfo({ timeout: 5000 });
console.log(info.model, info.firmware);
rti.connect(消息 connection.connect)
起始版本:v1
稳定性:会调整
继承的通用规则:消息格式见 data-types,通用错误码见 error-model,设备身份/会话见 security-model。
功能
扫描并连接会话绑定的 deviceId:扫描直到找到这个 id → 连接 → 发现服务和特征。不依赖任何系统配对状态(当前没有 BLE bonding)。第三方不能指定别的设备。
在 app.json 里声明了 客户端能力 connectionUi 的页面,App 不显示原生连接卡片,页面需要自己呈现连接中、已连接、失败,并提供重试入口,重试时调用本方法。
请求字段
{} // 无输入;连哪台设备由会话绑定决定
返回字段
{ "connectionState": "connected" } // 结构待定(是否返回状态待定)
自有错误码
- 无专用码。未找到绑定设备 / 不在范围内目前归入
TIMEOUT(扫描超时);通用码见 error-model。是否新增DEVICE_NOT_FOUND仍在评估,未定稿前不属契约。
超时
- 默认超时:15s(连接类型);超时报
TIMEOUT。 - 连接类型,无写语义。
示例
await rti.connect({ timeout: 20000 });
rti.disconnect(消息 connection.disconnect)
起始版本:v1
稳定性:会调整
继承的通用规则:消息格式见 data-types,通用错误码见 error-model。
功能
断开与会话绑定设备的连接。幂等:未连接时调用也算成功(确切行为待定——是否返回明确状态待确认)。
请求字段
{}
返回字段
{ "connectionState": "disconnected" } // 结构待定
自有错误码
无(通用码见 error-model)。
超时
- 默认超时:8s(可覆盖)。
示例
await rti.disconnect();
rti.getConnectionState(消息 connection.get)
起始版本:v1
稳定性:会调整
继承的通用规则:消息格式见 data-types,通用错误码见 error-model。
功能
查询当前连接状态。要实时跟踪变化,用 rti.onConnectionStateChange 订阅(不轮询)。
请求字段
{}
返回字段
wire 层 connection.get 的 data:
{ "connectionState": "disconnected" | "connecting" | "connected" | "disconnecting" }
SDK 的 rti.getConnectionState() 会把它解包,resolve 出裸字符串(即上面 connectionState 的值),见下方示例。
自有错误码
无(通用码见 error-model)。
超时
- 默认超时:8s。
- 读类型,无写语义。
示例
const state = await rti.getConnectionState(); // SDK 解包后返回字符串
rti.getDeviceState(消息 dps.get)
起始版本:v1
稳定性:会调整
继承的通用规则:消息格式见 data-types,通用错误码见 error-model,DP 模型见 runtime-model。
功能
读取当前设备的完整、已认证 DP 快照。要实时跟踪后续变化,用 rti.onDeviceStateChange。
rti.getDeviceState() 不是本地 cache getter。它等待当前绑定设备完成 fresh AUTH,经 ApplicationFrame v6 发送 typed GET_STATE,严格验证完整 STATE_RESULT 后才返回并建立当前 connection generation 的 live snapshot。
BLE connected 不代表控制就绪。SecureChannel 尚未建立时,本调用在自己的超时预算内等待;仍不可用时以 NOT_PAIRED 或 TIMEOUT 失败。断链后保存的 last-known 值只供 App 原生列表展示,本接口不会把它冒充实时结果。
请求字段
{} // 无输入;v1 固定读取当前品类的完整快照
返回字段
// Dps = Record<string, unknown>;DP 名 = 当前认证品类 registry 的 dataPointName
{ "mode": "heat", "setpoint": 22, "currentTemp": 20.5, "fan": "auto" }
字段集按已认证 category 决定。Web API 只暴露名称和值;永久 dataPoint:u16、dataType:u8 和 canonical CBOR 由 App 的可信 registry 处理。未知/不受支持品类不会回退到其他品类或明文读取。
自有错误码
NOT_PAIRED:当前设备没有 fresh SecureChannel。- 其余通用码见 error-model。
超时
- 默认超时:8s(读类型)。
- 读类型,无写语义。
示例
const dps = await rti.getDeviceState();
rti.publishDps(消息 dps.publish)
起始版本:v1
稳定性:会调整
继承的通用规则:消息格式见 data-types,通用错误码见 error-model,DP 模型与写语义/职责划分见 runtime-model。
功能
作为控制方,向设备下发一组 DP。一次调用是一个原子事务:App 将全部字段编码到同一个 typed SET_STATE;设备先验证完整候选快照,再一次提交。任一字段失败时所有字段都保持原值。App 不存预测值、不回滚;乐观显示和回滚由第三方负责。
请求字段
{ "dps": { "brightness": 80 } } // 部分 DP;DP 名 = 契约特征键(powerState/brightness/rgbColor)
// bytes(RAW)DP:值为标准 base64 字符串,例如 thermostat 的 echoBlob / app_features
// { "dps": { "echoBlob": "AQL/" } } // 解码后 3 字节 01 02 FF
// { "dps": { "app_features": "qrs=" } } // 解码后 2 字节 AA BB
返回字段
{} // 成功(设备确认写入)无 body
自有错误码
| code | 含义 |
|---|---|
DP_UNKNOWN | DP 名不属于当前认证产品的锁定契约 |
DP_READ_ONLY | DP 在契约中不可写 |
DP_TYPE_MISMATCH | 值类型与契约不一致 |
DP_VALUE_INVALID | 值超范围或 byte string / tuple 长度不合法 |
空对象 / malformed payload 使用 BAD_REQUEST;设备内部或无法分类的业务错误使用 BLE_IO_ERROR。完整映射见 error-model。
超时
- 默认超时:8s(写类型)。
- 行为(本接口与通用写语义一致):
- 成功(设备返回完整
STATE_RESULT)→ resolve,无 body。 - 失败 / 超时(
TIMEOUT)→ reject;App 不应用部分结果、不改可信状态、不回滚预测 UI。
- 成功(设备返回完整
- 第三方若要点按即时反馈,自己先显示预测值;失败怎么回滚由第三方决定,依据 rti.onDeviceStateChange 的真实状态。
示例
try {
await rti.publishDps({ brightness: 80 }, { timeout: 8000 });
// 设备已确认
}
catch (e) {
// 失败:是否回滚 UI 由第三方决定(App 不做)
}
rti.onConnectionStateChange(事件 connectionStateChange)
起始版本:v1
稳定性:会调整
继承的通用规则:事件规则见 runtime-model。
功能
订阅连接状态变化。事件源是 BLE 状态订阅(不轮询)。
在 app.json 里声明了 客户端能力 connectionUi 的页面,App 不显示原生连接卡片,页面需要用本事件自己呈现连接中、已连接、失败,并提供重试入口(调用 rti.connect)。
订阅签名
rti.onConnectionStateChange(cb: (s: ConnectionState) => void): () => void
// 返回退订函数;多个订阅者时 SDK 底层只挂一次
事件字段
{ "type": "connectionStateChange", "v": 1, "data": { "state": "connected" } }
state 取值:disconnected / connecting / connected / disconnecting。
自有错误码
无(事件没有返回值;连接相关错误走 rti.onError)。
超时
无(订阅类型)。
示例
const off = rti.onConnectionStateChange(s => render(s));
// 不再需要时:off();
rti.onDeviceStateChange(事件 deviceStateChange)
起始版本:v1
稳定性:会调整
继承的通用规则:事件规则与 DP 模型见 runtime-model,写语义/职责划分见 runtime-model 写语义。
功能
订阅当前已认证设备状态的变化。生产 HeimLink vNext 只接受 ApplicationFrame v6 中的 typed 状态:fresh AUTH 后的完整 STATE_RESULT 建立 live snapshot,随后同一 connection generation 的加密 EVENT 才能增量更新它。
App 会把可信缓存中实际变化的字段作为本事件发给 Web App。成功的 GET_STATE / SET_STATE 完整结果或设备主动 EVENT 都可能使缓存变化;设备不会为同一个 SET_STATE 再发送重复 EVENT。第三方若做乐观 UI,由第三方用本事件或命令返回值对账,App 不保存预测值。
断链、SecureChannel 关闭或 generation 变化会撤销 live provenance。旧值只能作为 App 原生列表的 last-known 展示,不会作为本事件重放给控制 Web App。生产路径不接受明文 GATT notify 作为可信状态来源。
订阅签名
rti.onDeviceStateChange(cb: (s: Dps) => void): () => void // 返回退订函数
事件字段
{ "type": "deviceStateChange", "v": 1, "data": { "setpoint": 22 } }
data 永远是只含实际变化键的增量 Dps。监听器不重放当前快照;页面挂载时先调用 rti.getDeviceState,再把后续增量 merge 到本地 UI 状态。
自有错误码
无(错误走 rti.onError)。
超时
无(订阅类型)。
示例
const off = rti.onDeviceStateChange(dps => applyToUI(dps));
// 不再需要时:off();
rti.onError(事件 error)
起始版本:v1
稳定性:会调整
继承的通用规则:事件规则见 runtime-model,通用错误码见 error-model。
功能
订阅带外错误——没有对应返回值、没有 Promise 能报告的错误。主要是连接级问题(意外断连、BLE 栈错误等)。写请求的失败由 rti.publishDps 自己 reject,不走这个事件。
订阅签名
rti.onError(cb: (e: RtiTekError) => void): () => void // 返回退订函数
事件字段
{ "type": "error", "v": 1,
"data": { "code": "<error-model 的通用码或接口自有码>", "message": "string" } }
没有「rollback dps」字段——App 不回滚。
自有错误码
无新增;code 取值 = error-model 通用码 + 各接口自有码。
超时
无(订阅类型)。
示例
const off = rti.onError(e => toast(`${e.code}: ${e.message}`));
rti.replace(消息 navigation.replace)
起始版本:v1
稳定性:会调整
继承的通用规则:消息格式见 data-types,通用错误码见 error-model。
功能
与 rti.navigateTo 类似,但替换当前路由,而不是在其上压新页。返回后,返回手势/返回键会跳过调用 replace 的这一页,回到它下面的页。用于重定向式跳转——例如不该被返回重访的引导步骤。
请求字段
{
"route": "home", // 必填;Web App manifest 里的路由名
"params": {} // 可选;传给目标路由,默认 {}
}
返回字段
{}
自有错误码
BAD_REQUEST——route缺失或不是字符串,或宿主未接入导航处理器(通用码,见 error-model)。
超时
- 默认超时:8s(非连接类型)。
- App 派发导航时即 resolve,不是目标页加载完成时。
示例
await rti.replace('home');
rti.goBack(消息 navigation.back)
起始版本:v1
稳定性:会调整
继承的通用规则:消息格式见 data-types,通用错误码见 error-model。
功能
把当前路由弹出原生栈,回到上一页——等价于以编程方式按平台返回键。没有可返回的页时是 no-op(不会离开设备控制界面)。要在根路由上离开设备页、回到设备列表,用 rti.exit。
页面自己触发的 goBack() 不会触发该页自己的 onNavigateBack 监听——返回拦截只针对用户发起的返回(见 on-navigate-back)。
请求字段
{} // 无输入
返回字段
{}
自有错误码
BAD_REQUEST—— 宿主未接入导航处理器(通用码,见 error-model)。
超时
- 默认超时:8s(非连接类型)。
示例
rti.goBack();
rti.exit(消息 navigation.exit)
起始版本:v1(2026-10-06 新增)
稳定性:会调整
继承的通用规则:消息格式见 data-types,通用错误码见 error-model。
功能
离开设备页,回到设备列表。无论当前在 Web App 的哪个路由,都弹出整个设备页,效果与原生导航栏根路由上的「‹ Devices」相同。
自己画导航栏的 Web App(路由在 app.json 里设 headerShown: false)用它实现根路由上的返回按钮。rti.goBack 在根路由上什么也不做,不能代替它。
页面自己调用 exit() 不会触发 onNavigateBack 监听:返回拦截只针对用户发起的返回。
请求字段
{} // 无输入
返回字段
{}
自有错误码
NOT_SUPPORTED—— 设备页下面没有可以回去的页面。BAD_REQUEST—— 宿主未接入导航处理器(通用码,见 error-model)。
超时
- 默认超时:8s(非连接类型)。
示例
// 根路由页面(例如 control.html)上的返回按钮离开设备页;
// 其他路由页面上的返回按钮用 rti.goBack() 回到上一页。
backButton.addEventListener('click', () => rti.exit());
rti.openDeviceSettings(消息 navigation.openDeviceSettings)
起始版本:v1(2026-10-06 新增)
稳定性:会调整
继承的通用规则:消息格式见 data-types,通用错误码见 error-model。
功能
打开这台设备的原生设备设置页,压在当前页之上;用户从设置页返回后回到调用方页面。效果与原生导航栏右上角的设置按钮相同。
自己画导航栏的 Web App(路由在 app.json 里设 headerShown: false)用它实现设置按钮。只能打开会话绑定的这台设备的设置页。
请求字段
{} // 无输入
返回字段
{}
自有错误码
BAD_REQUEST—— 宿主未接入导航处理器(通用码,见 error-model)。
超时
- 默认超时:8s(非连接类型)。
示例
settingsButton.addEventListener('click', () => rti.openDeviceSettings());
rti.openFirmwareUpgrade(消息 navigation.openFirmwareUpgrade)
起始版本:v1(2026-10-06 新增)
稳定性:会调整
继承的通用规则:消息格式见 data-types,通用错误码见 error-model。
功能
打开这台设备的原生固件升级页,压在当前页之上;用户从升级页返回后回到调用方页面。只能打开会话绑定的这台设备的升级页。
本方法只负责打开页面:有没有可用固件、能不能升级、升级进度都由升级页显示,Web App 拿不到这些信息,也不能从页面里发起升级。
请求字段
{} // 无输入
返回字段
{}
自有错误码
BAD_REQUEST—— 宿主未接入导航处理器(通用码,见 error-model)。
超时
- 默认超时:8s(非连接类型)。
示例
upgradeRow.addEventListener('click', () => rti.openFirmwareUpgrade());
rti.onInsetsChange(事件 insetsChange)
起始版本:v1
稳定性:会调整
继承的通用规则:事件规则见 runtime-model。
功能
跟踪 WebView 所在原生屏幕的安全区内边距(safe-area inset)——页面必须避让状态栏/刘海、home indicator、圆角的留白。页面把这些值作为 CSS padding,内容就不会被系统 UI 遮住。全屏路由(无原生标题栏)最需要它——那里 inset 错或缺会立刻表现为内容滑到状态栏下面。
当前值也能通过只读属性 rti.insets 同步读取,便于首屏绘制时无需注册监听。
rti.insets 属性
rti.insets: { top: number; bottom: number; left: number; right: number } // 像素
页面加载时用 App 的初始值播种,之后由 insetsChange 事件保持最新。Android 首帧值可能短暂为 0;页面加载完、原生值稳定后 App 会重发 insetsChange,所以注册了 onInsetsChange 的页面总会收敛到正确的 padding。
订阅签名
rti.onInsetsChange(
cb: (insets: { top: number; bottom: number; left: number; right: number }) => void
): () => void // 返回退订函数
SDK 在调用监听之前先更新 rti.insets,所以回调里读 rti.insets 与事件 payload 一致。
事件字段
{ "type": "insetsChange", "v": 1, "data": { "top": 59, "bottom": 34, "left": 0, "right": 0 } }
App 在挂载时、WebView 加载结束时各发一次(加载结束的重发用于纠正 Android 首帧的 0);inset 真正变化(旋转等)时也可能重发。
自有错误码
无(订阅类型)。
超时
无(订阅类型)。
示例
function applyInsets(i) {
document.body.style.paddingTop = i.top + 'px';
document.body.style.paddingBottom = i.bottom + 'px';
}
applyInsets(rti.insets); // 首屏
const off = rti.onInsetsChange(applyInsets); // 加载后保持正确
rti.onSystemChange(事件 systemChange)
起始版本:v1
稳定性:会调整
继承的通用规则:事件规则见 runtime-model。
功能
跟踪设备的系统/区域设置,让 Web App 像系统其余部分那样渲染日期、数字和主题——语言、时区、浅色/深色模式、12/24 小时制偏好。
当前值也能通过只读属性 rti.system 同步读取。
rti.system 属性
rti.system: {
locale: string; // BCP-47 语言标签,如 "en-US"
timezone: string; // IANA 时区,如 "Asia/Shanghai"
colorScheme: 'light' | 'dark';
uses24HourClock: boolean;
}
页面加载时播种,之后由 systemChange 事件保持最新。
订阅签名
rti.onSystemChange(
cb: (system: {
locale: string;
timezone: string;
colorScheme: 'light' | 'dark';
uses24HourClock: boolean;
}) => void
): () => void // 返回退订函数
SDK 在调用监听之前先更新 rti.system,所以回调里读 rti.system 与事件 payload 一致。
事件字段
{
"type": "systemChange",
"v": 1,
"data": { "locale": "en-US", "timezone": "Asia/Shanghai", "colorScheme": "dark", "uses24HourClock": false }
}
App 在挂载时、WebView 加载结束时、以及回到前台时各发一次——用户离开期间可能改了语言、时区、24 小时开关或系统主题。想保持正确的页面应从这个事件重渲染,而不是只读一次 rti.system。
自有错误码
无(订阅类型)。
超时
无(订阅类型)。
示例
function applyTheme(s) {
document.documentElement.dataset.theme = s.colorScheme;
}
applyTheme(rti.system);
const off = rti.onSystemChange(applyTheme);
rti.onDeviceRemoved(事件 deviceRemoved)
起始版本:v1
稳定性:会调整
继承的通用规则:事件规则见 runtime-model。
功能
在用户从 App 的已保存设备列表里移除本设备(如设备设置 → 移除设备)时立即收到通知。Web App 在 WebView 被销毁前有最后一次机会反应——显示告别界面、把待处理状态持久化到自己的服务器、停止进行中的工作。
事件触发后,本 deviceId 的桥会话正在被释放:App 会弹出该设备的栈、把用户带回设备列表。此后再发起的任何 rti.* 调用,会在会话消失后以 UNAUTHORIZED 或 DEVICE_SCOPE_VIOLATION reject。
订阅签名
rti.onDeviceRemoved(cb: (data: { deviceId: string }) => void): () => void
// 返回退订函数
deviceId 与页面启动时拿到的 id 相同——提供它是为了让需要记录/上报的页面无需再读状态。
事件字段
{ "type": "deviceRemoved", "v": 1, "data": { "deviceId": "HeimLink-abc123" } }
每个会话至多触发一次(页面屏防重入)。
自有错误码
无(订阅类型)。
超时
无(订阅类型)。
示例
rti.onDeviceRemoved(({ deviceId }) => {
// 尽力而为:WebView 即将销毁。不要 await 任何依赖桥的操作——它的会话正在关闭。
document.body.classList.add('device-removed');
});
rti.getSecurePairingState(消息 securePairing.get)
起始版本:v1
稳定性:会调整
继承的通用规则:消息格式见 data-types,通用错误码见 error-model。
功能
查询当前设备的 secure pairing 状态快照。这是与 BLE 链路独立的一层:BLE 已 connected 不代表已 paired;secure-only 设备(如温控器)在 paired 之前 rti.publishDps / rti.getDeviceState 会失败。
要实时跟踪变化,用 rti.onSecurePairingChange 订阅(不轮询)。
请求字段
{}
返回字段
wire 层 securePairing.get 的 data:
{
"state": "not-required" | "pairing" | "paired" | "failed",
"deviceId": "<string>" | null
}
SDK 的 rti.getSecurePairingState() 会把它解包,resolve 出裸字符串(即上面 state 的值),并同步更新 rti.securePairing 数据属性。
state 语义:
not-required:当前没有适用的已绑定 vNext 设备;不授权明文业务 fallback。pairing:watcher 正在跑 PASE commissioning 或 OwnerKey AUTH。paired:fresh SecureChannel 与当前 generation 的完整 STATE_RESULT 均已就绪,dps 可读可写。failed:上一次尝试失败或被取消;sticky 直到下次开始或断开。
deviceId 与当前 session 不匹配时返回 null(防止跨设备信息泄露)。
自有错误码
无(通用码见 error-model)。
超时
- 默认超时:8s。
- 读类型,无写语义。
示例
const state = await rti.getSecurePairingState();
if (state === 'pairing') showSpinner();
else if (state === 'failed') showRetryButton();
rti.startSecurePairing(消息 securePairing.start)
起始版本:v1
稳定性:会调整
继承的通用规则:消息格式见 data-types,通用错误码见 error-model。
功能
让 App 端的 pair watcher 立即对当前已连接的设备重新尝试 secure pairing,不重连 BLE。给 Web App 在配对失败界面上挂"重试"按钮用。
行为:清空当前设备的失败计数 + 解锁 in-flight guard,立刻触发一次 AUTH 握手。状态变化通过 rti.onSecurePairingChange 推送,不在本响应里。
请求字段
{}
返回字段
{}
SDK 的 rti.startSecurePairing() resolve 出 void。
自有错误码
无(通用码见 error-model)。
行为细节
- 当前没有已连接设备:no-op,仍然 resolve(不抛错)。
- 当前设备
state已经是paired:no-op,仍然 resolve。 - 当前
state是pairing:仍然会再触发一次,但 in-flight guard 通常会让它合并。
超时
- 默认超时:8s。
- 写类型;返回后状态变化经事件流通知。
示例
async function onRetry() {
try {
await rti.startSecurePairing();
} catch (e) {
console.error('retry failed', e);
}
}
rti.cancelSecurePairing(消息 securePairing.cancel)
起始版本:v1
稳定性:会调整
继承的通用规则:消息格式见 data-types,通用错误码见 error-model。
功能
让用户从 Web App 的配对界面主动取消正在进行的 secure pairing。等价于 rti.disconnect(drops BLE 链路),但语义更清楚 —— 配对界面的"取消"按钮调用它而不是 disconnect,便于第三方阅读代码时知道这是配对流程的退出。
请求字段
{}
返回字段
{}
SDK 的 rti.cancelSecurePairing() resolve 出 void。
自有错误码
无(通用码见 error-model)。
行为细节
- 触发底层 BLE disconnect。
- 后续状态变化按正常 connection / securePairing 事件流通知。
超时
- 默认超时:8s。
- 写类型。
示例
async function onCancel() {
await rti.cancelSecurePairing();
}
rti.onSecurePairingChange(事件 securePairingChange)
起始版本:v1
稳定性:会调整
继承的通用规则:事件规则见 runtime-model。
功能
订阅 secure pairing 状态变化。事件源是 App 端的 secure-pairing 状态写入(不轮询)。每次状态变化时 SDK 同步更新 rti.securePairing 数据属性,再 fan out 给所有订阅者。
只有跟当前 session 的 deviceId 匹配的状态变化才会被推送(防止跨设备信息泄露)。
订阅签名
rti.onSecurePairingChange(cb: (state: SecurePairingState) => void): () => void
// 返回退订函数;多个订阅者时 SDK 底层只挂一次
事件字段
{ "type": "securePairingChange", "v": 1, "data": { "state": "pairing" } }
state 取值:not-required / pairing / paired / failed。语义见 data-types。
自有错误码
无(事件没有返回值)。
超时
无(订阅类型)。
重放
不重放。订阅时收到的不是当前快照,而是后续的变化。要拿到当前值,订阅前调用一次 rti.getSecurePairingState,或直接读 rti.securePairing 数据属性。
示例
rti.getSecurePairingState().then(render);
const off = rti.onSecurePairingChange(render);
// 不再需要时:off();
rti.getHistoryInfo(消息 history.info)
起始版本:v1
稳定性:会调整
继承的通用规则:消息格式见 data-types,通用错误码见 error-model。
功能
查询当前绑定设备上的运行历史元信息(条数、时间窗、是否已对时、generation 等)。不返回逐条记录;拉点用 rti.getHistory。
仅当前 WebView 会话绑定的设备可读;需要已完成 secure pairing(rti.securePairing === 'paired')。未配对时返回 NOT_PAIRED。
App 在配对成功后会自动下发设备墙钟(TIME_SET);Web App 不必自己对时。timeValid === false 时时间轴可能是相对启动的分钟语义,UI 应提示未校准。
请求字段
{} // 无输入
返回字段
{
"count": 1440, // 环内当前记录数
"tMin": 1767225600, // 窗内最早记录 UNIX 秒;无数据为 0
"tMax": 1767312000, // 窗内最晚记录 UNIX 秒
"timeValid": true, // 本 boot 是否已成功 TIME_SET
"generation": 3, // 环代数代数;出厂清空会 +1,缓存键依赖它
"retentionSeconds": 0, // 逻辑保留窗;开发阶段 0 = 以物理环为准
"sampleIntervalMin": 1 // 开发阶段室温采样间隔(分钟)
}
不返回:原始 5 字节线形、t_base、清空接口。
自有错误码
无(通用码见 error-model)。常见:NOT_CONNECTED、NOT_PAIRED、TIMEOUT、BLE_IO_ERROR。
超时
- 默认超时:15s。
示例
const info = await rti.getHistoryInfo();
if (!info.timeValid)
console.warn('device clock not calibrated');
rti.getHistory(消息 history.query)
起始版本:v1
稳定性:会调整
继承的通用规则:消息格式见 data-types,通用错误码见 error-model。
功能
按时间窗与类型拉取设备运行历史,Promise 在整窗成功(含 CRC)后 resolve。返回已解码的逻辑点(UNIX 秒、℃ 浮点、模式字符串),可直接画图。
Bridge 侧会:
- 校验会话设备身份(不可指定其他 deviceId)
- 经 SecureSession 拉
HISTORY_*分片并校验 CRC - 按
deviceId + generation做本地缓存,默认增量合并
Web App 看不到 5 字节原始记录、t_base、或清空命令。
请求字段
{
"from": 0, // 可选;含,UNIX 秒;0/省略 = 不限下界(或由 App 用缓存增量)
"to": 0, // 可选;不含,UNIX 秒;0/省略 = 设备 now
"kinds": ["sample", "setpoint", "mode"], // 可选;省略 = 全部
"maxRecords": 0, // 可选;0/省略 = 设备默认上限
"forceFull": false // 可选;true = 忽略本地增量,全量重拉
}
kinds 取值:"sample" | "setpoint" | "mode"。
返回字段
{
"points": [
{ "t": 1767225600, "kind": "sample", "tempC": 20.5 },
{ "t": 1767225660, "kind": "setpoint", "setpointC": 22 },
{ "t": 1767225720, "kind": "mode", "mode": "heat" }
],
"info": {
"count": 3,
"tMin": 1767225600,
"tMax": 1767225720,
"timeValid": true,
"generation": 3,
"retentionSeconds": 0,
"sampleIntervalMin": 1
}
}
kind | 额外字段 | 说明 |
|---|---|---|
sample | tempC: number | 室温 ℃(一位小数即可) |
setpoint | setpointC: number | 设温 ℃ |
mode | mode: "off"|"heat"|"cool"|"auto" | 与契约枚举一致 |
t为 UNIX 秒,且为 60 的倍数(分钟量化)info与 rti.getHistoryInfo 同形,便于一次拉完后刷新摘要- 空窗:
points: [],不视为错误
自有错误码
无。常见通用码:NOT_CONNECTED、NOT_PAIRED、TIMEOUT、BLE_IO_ERROR(含 CRC 失败 / 传输中断;失败时不得把半窗当成功)。
超时
- 默认超时:60s(全量环在低 MTU 下可能较长)。可用
CallOptions.timeout覆盖。
示例
// 全量(或 App 缓存合并后的完整序列)
const { points, info } = await rti.getHistory();
// 只要室温采样,最近一天
const dayAgo = Math.floor(Date.now() / 1000) - 86400;
const samples = await rti.getHistory({
from: dayAgo,
kinds: ['sample'],
});
// 折线:points.filter(p => p.kind === 'sample')
// 阶梯:setpoint;区间:mode
变更记录
RtiTek Bridge API 的接口变更记录。新增/调整接口时在此加一行。
| 日期 | 变更 |
|---|---|
| 2026-10-06 | 新增导航方法 rti.exit(navigation.exit,离开设备页回到设备列表)、rti.openDeviceSettings(navigation.openDeviceSettings,打开这台设备的设置页)、rti.openFirmwareUpgrade(navigation.openFirmwareUpgrade,打开这台设备的固件升级页),归入现有 navigation 能力。路由在 app.json 里设 headerShown: false 的 Web App 可以用它们自己画导航栏,做到原生导航栏能做的事。不破坏现有契约:rti.goBack 在根路由上仍然什么也不做;旧版 App 不认识这三个消息,调用返回 BAD_REQUEST。 |
| 2026-10-05 | 新增客户端能力 connectionUi:Web App 在 app.json 顶层 capabilities 里声明 "connectionUi": true 后,用 rti.onConnectionStateChange + rti.getConnectionState / rti.connect 自己呈现连接过程;App 不显示原生连接卡片(TopCard),连接失败时不暂停自动重试。默认 false,未声明时 App 照常显示 TopCard。不破坏现有契约:不认识这个字段的 App 版本忽略它,照常显示 TopCard。见 handshake 的「客户端能力」。 |
| 2026-10-03 | 新增 GET https://api.rti-tek.fr/api/heimlink/webapps/:appId/versions?channel=stable&scope=channel:scope=channel(缺省)列出曾经发布到所请求渠道的未删除版本,scope=all 列出全部未删除版本;响应含 current 与每个版本的 packageHashHex、totalSizeBytes、packageUrl、releaseNotes、publishedAt、uploadedAt。未知 scope 返回 400,没有版本时返回 200 与空数组;check 与包下载的端点和响应格式不变。App 保留所有下载过的小程序版本,设备页运行所选版本,没有所选版本时运行内置副本;下载只存入,不切换。自动更新开启时跟随渠道,check 的 current 改传所选版本。用户在版本页手动选择版本会关闭自动更新,重新打开后立即跟随渠道。设备设置页去掉「检查更新」按钮,改由「当前版本」进入版本页。见 小程序分发 API。 |
| 2026-10-03 | 小程序检查只把 check 的 404 记为「该渠道没有可用版本」;其他 HTTP 错误记为检查失败并显示状态码(「检查失败(HTTP 状态码)」),不计入 6 小时检查间隔,下一次回到前台时重试。端点与响应格式不变。见 小程序分发 API。 |
| 2026-10-02 | 原 Web App 包、控制页改称锐同小程序(RT Applet);公开路径 /api/heimlink/webapps/...、响应字段与包格式不变。产品定义新增可选字段 appletId,App 按它选择小程序,category 不再参与;没有 appletId 或指定的小程序不可用时,设备页显示“该产品暂无小程序”。App 跟随渠道:渠道版本与已下载副本不同就下载,可降级;没有已下载副本时下载渠道中的任何版本;内置副本只作兜底,不参与版本比较;不采用 updateAvailable,current 传已下载副本的版本,没有则不传。新增按小程序的自动检查(冷启动与回到前台,6 小时间隔)与设备设置页手动检查。见 小程序分发 API 与 产品契约目录 API。 |
| 2026-09-19 | Web App 包分发端点从 heimlink-console 迁至 https://api.rti-tek.fr/api/heimlink/webapps/...,由 extension-hub 只读提供;响应字段不变。App 基地址改为编译期常量(原环境变量从未被赋值,该更新器此前从未运行),并新增同源断言与响应体大小上限。Console 不再暴露设备面向的公开路由。 |
| 2026-09-18 | App 移除独立产品白名单;运行时完全以当前原子安装并校验通过的 snapshot catalog 为准。默认关闭远程目录同步,每个 APK 版本安装构建时锁定的快照。 |
| 2026-09-17 | 新增 GET https://api.rti-tek.fr/api/heimlink/product-contracts:extension-hub 从 HeimLink Console PostgreSQL 只读分发已生成不可变快照产品的最高 revision,支持强 ETag;未生成快照的新产品不进入目录。App 侧同步实现已完成但默认不启用。 |
| 2026-09-14 | 产品级契约:rti 方法签名不变,DP 解释改为按已认证产品锁定快照;同品类不同产品可有不同 DP。ty.getDeviceInfo 新增 HeimLink productId、heimlink.productId/contractRevision/contractDigest。未知身份不按品类回退,契约来源不匹配的离线业务缓存清除,绑定与用户信息保留。 |
| 2026-09-13 | 新增 window.ty 的 38 个 DOWT 星标 API 入口:32 项有实现(存在明确限制)、4 项 NOT_PLANNED、2 项 NOT_SUPPORTED;同步快照、回调/Promise、设备 DP 转换、隔离存储、真实 HTTP、定位/交互与导航。新增 tyCompatibility 能力及 ty.* 消息/事件,统一失败模型与 rti 保持分离。没有移植涂鸦 App/Page/JSSDK 或浇花器硬件协议。 |
| 2026-08-13 | HeimLink BLE vNext 硬切:window.rti 方法签名和成功 payload 不变;publishDps 的一次调用改为一个原子 typed dataPoint SET_STATE 事务,并新增稳定错误码 DP_UNKNOWN、DP_READ_ONLY、DP_TYPE_MISMATCH、DP_VALUE_INVALID。设备状态只在 fresh AUTH 后通过完整 STATE_RESULT 或加密增量 EVENT 升级为 live;断链后降级为 last-known。OTA uncertain-reboot 仍是 App 内部状态,没有新增公开 OTA 方法或错误码。见 publishDps、error model 与 runtime model。 |
| 2026-07-21 | 新增运行历史能力 deviceHistory:rti.getHistoryInfo(history.info)、rti.getHistory(history.query);handshake capabilities 增加 deviceHistory。返回 UNIX 秒 + ℃/模式字符串;不暴露 5B 线形 / t_base / CLEAR。需 secure pairing。见 get-history-info、get-history。 |
| 2026-07-20 | 历史 wire v5 记录:thermostat 新增 app_features(当时称 fieldId 101)与 bytes payload。vNext 沿用 Web 名称/base64 形态,但 wire 身份现为 typed dataPoint=101 / OCTET_STRING。 |
| 2026-07-20 | 历史 wire v5 记录:新增 echoBlob(当时称 fieldId 100)和 SecureFrame CBOR byte string。vNext 不重解释旧帧;当前形态见 data-types。 |
| 2026-05-18 | 建立接口清单 + 首批 10 个 v1 接口骨架 |
| 2026-05-19 | 改用平实语言重写,去掉黑话和比喻 |
| 2026-05-19 | 寻址从 HTTP 路径改为 postMessage method/type;清单列和模板更新 |
| 2026-05-19 | DP 命名定案:直接用契约特征键(powerState/brightness/rgbColor),不另设 DP 字典(契约即字典) |
| 2026-05-22 | 新增导航 + 安全区/系统接口(navigate-to、replace、go-back、on-navigate-back、on-insets-change、on-system-change) |
| 2026-06-10 | 新增 on-device-removed:用户从已保存列表移除设备时通知 Web App |
| 2026-06-15 | 新增 secure pairing 维度(4 个接口 + 状态字段 + 事件):rti.getSecurePairingState / rti.startSecurePairing / rti.cancelSecurePairing / rti.onSecurePairingChange / rti.securePairing;SecurePairingState 加入 data-types。handshake capabilities 加 securePairing。app.json 新增顶层 capabilities.securePairing 字段,声明后 App 不再挂原生 PairingRequiredOverlay。不破坏现有契约。 |
| 2026-06-15 | 历史 wire v5 记录:首次引入旧 SecureFrame EVENT 路径。命令号 0x3004 被 vNext only-add registry 保留,但当前 payload 与会话语义以 ApplicationFrame v6 为准。 |
| 2026-06-24 | 历史 wire v5 记录:旧 SecureFrame payload 从 JSON 改为无类型 {fieldId: value} CBOR。vNext 不接受该格式,也不按长度 fallback;当前 wire 是 typed {dataPoint: {1:dataType, 2:value}}。 |