The developer documentation is currently available in Chinese only. English site

开发者 / 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 兼容接口。

目录

总览

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。

怎么读这套文档

运行模型

本文是所有接口共同遵守的运行规则。某个接口具体收发什么,在它自己的文档里。

三种消息

桥上有三种消息:

  • 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,connect 15s。具体默认值写在各接口文档里。
  • 超时报错 { code: 'TIMEOUT' }。

写语义与职责划分

window.rti v1 改设备状态的接口是 publishDps;ty.device.publishCommands 通过同一设备会话下发,继承具体驱动的语义,不扩大权限或保证。职责这样划分:

App 侧(行为固定,只做一件事):

  • publishDps(dps, { timeout }):一次调用编码为一个 typed SET_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 时更简单。

能力边界

  1. 没有网络可达端点:桥对象只能从本 WebView 的页面 JS 访问,机器上其他程序碰不到。因此每条消息不需要带一次性密钥来证明「我是合法页面」。
  2. 按会话绑定,不靠消息里的字段:App 为某台设备打开 Web App 时,这个 WebView 在 App 侧绑定到「这台设备 + 这个会话」。处理命令时只作用于绑定设备,第三方不能在消息里指定别的设备。越权访问直接返回 DEVICE_SCOPE_VIOLATION。
  3. 只能做白名单内的事:第三方只能对绑定设备使用桥提供的方法——不能扫描、不能连别的设备、不能直接碰 GATT。
  4. 生命周期: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 identityfresh AUTH 证明的 vendor/product、discriminator、device instance、binding ID 与 generationApp 内持久绑定、SavedDevice 去重、OwnerKey 选择是,App 内部使用
BLE locatorAndroid address 或 CoreBluetooth UUID当前平台上的扫描结果和定向重连提示否;可能变化,且不公开给 Web App
deviceIdApp 为当前 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_ERRORBLE 读写/通信失败(兜底错误)
BAD_REQUEST请求参数错误,或宿主未接入对应处理器
UNAUTHORIZED会话已失效(WebView 卸载 / 会话已释放后再调用)
DEVICE_SCOPE_VIOLATION越权访问绑定设备以外的设备
VERSION_UNSUPPORTED版本不支持(handshake 协商失败时返回)
NOT_PAIRED设备未完成配对 / SecureChannel 未建立
DP_UNKNOWNpublishDps 使用了当前认证产品契约不存在的 DP 名
DP_READ_ONLYpublishDps 尝试写只读 DP
DP_TYPE_MISMATCHDP 的值类型与产品契约不一致
DP_VALUE_INVALIDDP 值超出范围,或 byte string / tuple 长度不合法
NOT_PLANNED明确不计划在 HeimLink 实现的涂鸦平台能力,不代表临时故障
NOT_SUPPORTED有业务意义,但当前版本、驱动、参数或平台不支持
PERMISSION_DENIED所需系统权限未获准
NETWORK_ERRORHTTP 传输失败;收到 4xx/5xx 响应本身不属于传输失败
STORAGE_NOT_FOUND当前应用及设备范围内没有指定存储项
CANCELLED操作被取消,如页面销毁时撤销挂起的原生请求
INTERNAL宿主内部错误(兜底)

接口自有错误码

某些接口有自己特有的错误码,只写在该接口文档里,不在此重复;不得复用通用错误码表达冲突含义。

publishDps 的业务错误映射固定如下:

设备 / App 侧原因public code
未知 dataPoint 或 App 本地未知名称DP_UNKNOWN
只读 dataPointDP_READ_ONLY
wire dataType 与注册表不匹配DP_TYPE_MISMATCH
值域或长度不合法DP_VALUE_INVALID
空写或无法解析的请求 payloadBAD_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设备信息
navigationWeb 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 端行为
securePairingWeb App 用 rti.onSecurePairingChange + rti.startSecurePairing / cancel-secure-pairing 自己画 secure pairing UI不挂原生 PairingRequiredOverlay
connectionUiWeb App 用 rti.onConnectionStateChange + rti.getConnectionState / rti.connect 自己呈现连接过程不显示原生连接卡片(TopCard),失败时不暂停自动重试

未声明的能力默认 false,App 维持现有兜底行为。

自有错误码

  • VERSION_UNSUPPORTED(含义见 error-model,此处列出是因为本接口会主动返回它)

超时

  • 默认超时:待定(应较短,版本检查要快速失败)。
  • 非写接口,无写语义。

示例

待定(等「能力协商粒度」问题定稿后补)。

接口清单

接口rti 方法 / 事件消息 method / 事件 type文档起始版本稳定性
协商能力/版本(SDK 内部,非公开方法)handshake查看v1会调整
读设备信息rti.getDeviceInfodevice.info查看v1会调整
连接绑定设备rti.connectconnection.connect查看v1会调整
断开连接rti.disconnectconnection.disconnect查看v1会调整
查询连接状态rti.getConnectionStateconnection.get查看v1会调整
读设备运行状态rti.getDeviceStatedps.get查看v1会调整;主动读设备并刷新缓存
下发 DPrti.publishDpsdps.publish查看v1会调整
订阅连接状态变化rti.onConnectionStateChange事件 connectionStateChange查看v1会调整
订阅设备状态变化rti.onDeviceStateChange事件 deviceStateChange查看v1会调整
订阅带外错误rti.onError事件 error查看v1会调整
Web App 内导航跳转rti.navigateTonavigation.push查看v1会调整
替换当前路由rti.replacenavigation.replace查看v1会调整
返回上一路由rti.goBacknavigation.back查看v1会调整
离开设备页rti.exitnavigation.exit查看v1(2026-10-06 新增)会调整
打开设备设置页rti.openDeviceSettingsnavigation.openDeviceSettings查看v1(2026-10-06 新增)会调整
打开固件升级页rti.openFirmwareUpgradenavigation.openFirmwareUpgrade查看v1(2026-10-06 新增)会调整
拦截用户返回rti.onNavigateBack事件 navigateBack查看v1会调整
订阅安全区变化rti.onInsetsChange事件 insetsChange查看v1会调整
订阅系统/语言变化rti.onSystemChange事件 systemChange查看v1会调整
订阅设备被移除rti.onDeviceRemoved事件 deviceRemoved查看v1会调整
查询 secure pairing 状态rti.getSecurePairingStatesecurePairing.get查看v1会调整
触发 secure pairing 重试rti.startSecurePairingsecurePairing.start查看v1会调整
取消 secure pairingrti.cancelSecurePairingsecurePairing.cancel查看v1会调整
订阅 secure pairing 变化rti.onSecurePairingChange事件 securePairingChange查看v1会调整
查运行历史元信息rti.getHistoryInfohistory.info查看v1会调整;需 secure pairing
拉运行历史点列rti.getHistoryhistory.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_UNKNOWNDP 名不属于当前认证产品的锁定契约
DP_READ_ONLYDP 在契约中不可写
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.onNavigateBack(事件 navigateBack)

起始版本:v1
稳定性:会调整
继承的通用规则:事件规则见 runtime-model。

功能

拦截用户发起的返回——硬件返回键(Android)、标题栏返回键、iOS 侧滑返回——让 Web App 在页面被弹出前先执行代码(确认未保存改动、关闭页内弹层而不是离开等)。

只要页面注册了至少一个 onNavigateBack 监听,App 就暂停每次用户返回,通知页面,等页面裁决后再离开或停留。没有监听时,返回不拦截,按原生行为走,没有 JS 往返、没有延迟。

只覆盖用户发起的返回。页面自己用 rti.goBack() 触发的返回不被拦截。

订阅签名

rti.onNavigateBack(
  cb: (e: { defaultPrevented: boolean; preventDefault: () => void }) => void
): () => void  // 返回退订函数

回调里调 e.preventDefault() 取消这次返回(页面停留)。什么都不做就放行。

事件字段

{ "type": "navigateBack", "v": 1, "data": { "id": "back-..." } }

data.id 是内部关联 id——SDK 处理,页面不读不回带。所有 onNavigateBack 回调跑完后,SDK 自动回复 App 是停留还是离开。

往返怎么走(SDK 处理,不是页面)

  • 第一个 onNavigateBack 订阅时,SDK 发 navigation.setBackInterception { enabled: true };最后一个退订时发 { enabled: false }。页面从不调这两个。
  • 用户触发返回时,App 发 navigateBack;SDK 跑监听,再发 navigation.backAck { id, defaultPrevented }。
  • defaultPrevented: true → App 保留页面。false → App 完成返回。

自有错误码

无(订阅类型;ack 往返不带错误)。

超时

  • App 最多等 30s 收 backAck。页面卡死/崩溃一直不回时,App 继续执行返回(视为未拦截)——这是安全兜底,不是正常路径。
  • 订阅类型,本身没有单次调用超时。

示例

let dirty = false;

const off = rti.onNavigateBack((e) => {
  if (dirty && !window.confirm('放弃修改?'))
    e.preventDefault();   // 停留在本页
});

// 页面不再需要守护返回时:
off();

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额外字段说明
sampletempC: number室温 ℃(一位小数即可)
setpointsetpointC: number设温 ℃
modemode: "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-19Web App 包分发端点从 heimlink-console 迁至 https://api.rti-tek.fr/api/heimlink/webapps/...,由 extension-hub 只读提供;响应字段不变。App 基地址改为编译期常量(原环境变量从未被赋值,该更新器此前从未运行),并新增同源断言与响应体大小上限。Console 不再暴露设备面向的公开路由。
2026-09-18App 移除独立产品白名单;运行时完全以当前原子安装并校验通过的 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-13HeimLink 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-19DP 命名定案:直接用契约特征键(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}}。