Web of Things(WoT)脚本 API

W3C 小组说明

关于本文档的更多详细信息
此版本:
https://www.w3.org/TR/2023/NOTE-wot-scripting-api-20231003/
最新发布版本:
https://www.w3.org/TR/wot-scripting-api/
最新编辑草案:
https://w3c.github.io/wot-scripting-api/
历史:
https://www.w3.org/standards/history/wot-scripting-api/
提交 历史
编辑:
Zoltan Kis (Intel)
Daniel Peintner (Siemens AG)
Cristiano Aguzzi (受邀专家)
Johannes Hund (前编辑,当时任职于 Siemens AG)
Kazuaki Nimura (前编辑,任职于 Fujitsu Ltd.)
反馈:
GitHub w3c/wot-scripting-api拉取 请求 新问题未解决 问题
public-wot-wg@w3.org,主题行请写为 [wot-scripting-api] … 消息主题 …归档
仓库
在 GitHub 上
提交 bug
贡献者
GitHub 上的贡献者

摘要

万维物联网由实体()组成,这些实体可以在机器可解释的物 描述(TD)中描述其能力,并通过 WoT 接口公开这些能力,也就是建模为 属性(用于读取 和写入值)、动作(用于 执行带有或不带返回值的远程过程)和 事件(用于 发出 通知信号)的网络交互。

主要的 万维 物联网(WoT)概念在 Web of Things(WoT)架构 1.1 规范中描述。

脚本编写是 WoT 中一个可选的构建块,它通常用于能够运行 WoT 运行时 脚本管理的网关或浏览器中,提供一种便捷方式来将 WoT 支持扩展到新类型的端点,并实现诸如 TD 目录之类的 WoT 应用。

本规范描述一个应用程序编程 接口(API),该接口表示 WoT 接口,允许 脚本发现、操作,并公开由脚本指定的 ,这些本地定义的物 以 WoT 交互 为特征。

本文档中定义的 API 有意紧密遵循 Web of Things(WoT)物描述 1.1 规范。可以在它们之上实现更 抽象的 API,或直接实现面向 WoT 网络的接口(即 WoT 接口)。

编辑注

本规范至少已由 Eclipse Thingweb 项目实现,该项目也称为 node-wot, 目前被视为参考开源实现。请查看其源 代码,包括 示例

本文档状态

本节描述本文档在 发布时的状态。当前 W3C 出版物列表以及 本技术报告的最新修订版,可在 W3C 技术报告 索引 https://www.w3.org/TR/ 中找到。

实现者需要注意,本规范 被认为是不稳定的。有意在本规范最终达到候选 推荐阶段之前实现本 规范的厂商,应订阅该仓库并 参与讨论。

编辑注:W3C WoT WG 正在征求反馈

请使用 GitHub Issues 页面为本草案做出贡献,该页面属于 WoT 脚本 API 仓库。关于安全和隐私 考量的反馈,请使用 WoT 安全与 隐私 Issues。

本文档由 Web of Things 工作 组作为小组说明发布,使用 说明 轨道

本小组说明得到 Web of Things 工作 组认可,但未得到 W3C 本身或其 成员认可。

这是一份草案文档,可能随时被更新、替换或 废弃。不宜 将本文档作为非进行中工作的内容引用。

W3C 专利政策并 不对本文档施加任何许可要求或承诺。

本文档受 2023 年 6 月 12 日 W3C 流程 文档约束。

1. 引言

WoT 基于的使用方式提供分层互操作性:即“被使用”和 “被公开”,如 Web of Things(WoT)架构 1.1 术语中所定义。

通过使用 TD,客户端 会创建一个本地运行时资源模型,该模型允许访问远程设备上服务器 所公开的 属性动作事件

公开一个 需要: 本规范描述如何通过脚本公开和使用 。此外, 它还定义了一个用于发现的通用 API。

通常,脚本旨在用于桥接器 或网关,这些桥接器或网关将较简单的设备公开和控制为 WoT ,并且 具有处理(例如安装、卸载、更新等)和运行 脚本的手段。

本规范不对 WoT 运行时如何处理和运行脚本做出假设,包括单租户或 多租户、脚本部署和生命周期管理。 该 API 已经支持通用机制,使得 实现脚本管理成为可能,例如通过 公开一个管理器, 其动作 (动作处理器)实现脚本生命周期管理 操作。

2. 用例场景

本节为非规范性内容。

[WOT-USE-CASES] 文档中列出的业务用例可以使用此 API 实现,基础是此处描述的 脚本用例场景。

2.1 使用物

2.2 公开物

2.3 发现

3. 一致性

除标记为非规范性的章节外,本规范中的所有编写 指南、图表、示例和注释均为非规范性内容。除此之外,本规范中的所有内容都是 规范性的。

本文档中的关键词 MAYMUSTSHOULD 应按 BCP 14 [RFC2119] [RFC8174] 中的描述解释,但仅当它们以全大写形式出现时, 如此处所示。

编辑注

本规范曾是一个预期成为 W3C 推荐标准的工作草案。 然而,它现在是一个 WG 说明,其中仅包含资料性 陈述。因此,我们需要考虑如何处理 本一致性章节中的描述。

本规范描述以下类别的用户代理UA)的一致性标准。

由于小型嵌入式实现的要求, 需要拆分 WoT 客户端和服务器接口。随后, 发现是一个分布式应用,但典型场景 已通过本规范中的通用发现 API 覆盖。 这导致实现此 API 的 UA 使用 3 个一致性类别:一个用于 客户端,一个用于服务器,一个用于发现。使用此 API 的应用 可以内省 WoT API 对象上是否存在 consume()produce()discover() 方法,以便 确定该 UA 实现的是哪个一致性类别。

WoT Consumer UA

此一致性类别的实现 MUST 实现 ConsumedThing 接口,以及 WoT API 对象上的 consume() 方法。

WoT Producer UA

此一致性类别的实现 MUST 实现 ExposedThing 接口,以及 WoT API 对象上的 produce() 方法。

WoT Discovery UA

此一致性类别的实现 MUST 实现 ThingDiscoveryProcess 接口,以及 WoT API 对象上的 discover() 方法、 exploreDirectory() 方法和 requestThingDescription() 方法。

这些一致性类别 MAY 可以在单个 UA 中实现。

本规范可用于以多种编程语言实现 WoT 脚本 API。接口 定义在 [WEBIDL] 中指定。

UA 可以在 浏览器中实现,也可以在单独的运行时环境中实现,例如 Node.js,或在 小型嵌入式 运行时中实现。

使用在浏览器中执行的 ECMAScript 来 实现本文档中定义的 API 的实现,MUST 以与 Web IDL 规范中定义的 ECMAScript 绑定 [WEBIDL] 一致的方式实现它们。

使用运行时中的 TypeScript 或 ECMAScript 来 实现本文档中定义的 API 的实现, MUST 以与 TypeScript 规范中定义的 TypeScript 绑定 [TYPESCRIPT] 一致的方式实现它们。

4. 术语和约定

通用 WoT 术语定义于 [WOT-ARCHITECTURE]: ThingThing Description(简称 TD)、Partial TDWeb of Things(简称 WoT)、WoT InterfaceProtocol BindingsWoT RuntimeConsuming a Thing DescriptionTD DirectoryPropertyActionEvent DataSchema Form SecurityScheme NoSecurityScheme 等。

WoT Interaction Interaction Affordance 的同义词。Interaction Affordance(或简称 affordance)是 [WOT-TD] 在指称 能力时使用的术语,如 TD issue 282 中所解释。然而,该术语在 TD 语义 上下文之外并不易理解。因此,为提高可读性,本文档将 改用先前的术语 WoT interaction,或简称 interaction

WoT network interfaceWoT Interface 的同义词。

JSON Schema 定义于 这些 规范中。

PromiseErrorJSONJSON.stringifyJSON.parse internal method internal slot 定义于 [ECMASCRIPT]。

5. ThingDescription 类型

WebIDLtypedef object ThingDescription;

表示 [WOT-TD] 中 定义的一个物描述TD)。 它预期是一个 解析后的 JSON 对象,并使用 JSON Schema 验证进行验证。

5.1 获取物 描述

给定 URL 获取 TD 应通过外部方法完成,例如 Fetch API 或 HTTP 客户端库,这些方法已经提供了用于指定获取细节的标准化选项。

示例 1:获取 物描述
try {
  let res = await fetch('https://tds.mythings.biz/sensor11');
  // ... 可以对 res.headers 进行额外检查
  let td = await res.json();
  let thing = await WOT.consume(td);
  console.log("物名称:" + thing.getThingDescription().title);
} catch (err) {
  console.log("获取 TD 失败", err.message);
}

5.2 展开物 描述

请注意, Web of Things(WoT)物描述 1.1 规范允许借助 默认值使用简写的物 描述,并要求客户端使用 Web of Things(WoT)物描述 1.1 规范中为给定 TD 中未显式 定义的属性指定的默认值来展开它们。

要在给定 td 的情况下展开 TD, 运行以下步骤:
  1. 对于 [WOT-TD] 中 TD 默认值表中的每一项, 如果该术语未在 td 中定义,则使用 [WOT-TD] 中指定的默认值添加该术语 定义。

5.3 验证物 描述

[WOT-TD] 规范定义了应如何验证 TD。因此, 此 API 期望 ThingDescription 对象在作为参数使用之前已被验证。本 规范定义如下基本的 TD 验证。

要在给定 td 的情况下验证 TD,运行 以下步骤:
  1. 如果 JSON Schema 验证td 上失败,则抛出一个 “TypeError” 并停止。
编辑注: 处理默认值

可以添加其他步骤来填充 必填字段的默认值。

6. WOT 命名空间

WoT API 对象 定义为单例,并包含按 一致性类别分组的 API 方法。

WebIDL[SecureContext, Exposed=(Window,Worker)]
namespace WOT {
  // methods defined in UA conformance classes
};

6.1 consume() 方法

WebIDLpartial namespace WOT {
  Promise<ConsumedThing> consume(ThingDescription td);
};
属于 WoT Consumer 一致性 类别。期望一个 td 实参,并返回一个 Promise,该 Promise 会以一个 ConsumedThing 对象兑现,该对象表示用于操作 的客户端接口。 该方法 MUST 运行以下 步骤:
  1. 返回一个 Promise promise,并 并行执行 后续步骤。
  2. 如果出于安全原因,当前脚本上下文 不允许调用此方法,则用 SecurityError 拒绝 promise,并停止。
  3. thing 为一个 从 td 构造的新 ConsumedThing 对象。
  4. 基于对 td 的内省,按照 [WOT-TD] 和 [WOT-PROTOCOL-BINDINGS] 中的说明设置 WoT 交互。 向底层平台发出请求,以初始化 协议 绑定
    编辑注

    实现会封装如何使用 协议 绑定来实现 WoT 交互的 复杂性。 未来,其中一些元素可以被标准化。

  5. thing 兑现 promise
编辑注

请注意构造 ConsumedThing 与使用 consume() 方法之间的区别:后者 还会初始化协议绑定,而简单 构造的对象在其被调用之前,不会初始化 WoT 交互

6.2 produce() 方法

WebIDLtypedef object ExposedThingInit;

partial namespace WOT {
  Promise<ExposedThing> produce(ExposedThingInit init);
};
属于 WoT Producer 一致性 类别。期望一个 init 实参,并返回一个 Promise,该 Promise 会以一个 ExposedThing 对象兑现,该对象通过服务器接口扩展 ConsumedThing, 即定义请求 处理器的能力。init 对象是 ExposedThingInit 类型的一个实例。具体而言,一个 ExposedThingInit 值是用于初始化 ExposedThing 的字典,并且它表示一个 [WOT-ARCHITECTURE] 中描述的 Partial TD。 因此,它具有与物描述相同的结构, 但可以省略一些信息。该方法 MUST 运行以下步骤:
  1. 返回一个 Promise promise,并 并行执行 后续步骤。
  2. 如果出于安全原因,当前脚本上下文 不允许调用此方法,则用 SecurityError 拒绝 promise,并停止。
  3. thing 为一个 使用 init 构造的新 ExposedThing 对象。
  4. thing 兑现 promise

6.2.1 展开 ExposedThingInit

要在给定 init 并以有效的 td 作为结果的情况下展开 ExposedThingInit,运行 以下步骤:
  1. init 上运行 validate an ExposedThingInit。如果失败,则 抛出 SyntaxError 并停止。
  2. td 为给定 init 运行 clone 的结果。
  3. 对于 td.["securityDefinitions"] 中的每个 scheme, 向底层平台发出请求,检查它是否 至少受一个协议绑定支持。 如果不支持,则从 td 中移除 scheme
  4. 如果 td.["security"] 在 td.["securityDefinitions"] 中 不存在, 则从 td 中移除 security
  5. 对于 td.properties、td.actions 和 td.events 中的每个 affordance,运行以下 子步骤:
    1. 对于 affordance.forms 中的每个 form
      1. 如果运行时未将 form.contentType 识别为有效,则从 form 中移除 contentType
      2. 如果 form.href 具有未知 scheme,则从 form 中移除 href
      3. 如果 form.href 是绝对的,并且运行时未将其 authority 识别为有效,则从 form 中移除 href
      4. 如果 form.href 已被其他 ExposedThings 使用, 则从 form 中移除 href
  6. 按照 TD JSON Schematd 中搜索缺失的必需属性。
    编辑注

    编辑认为此步骤含糊。它将在 下一次迭代中得到改进或移除。

  7. 对于每个 missing 属性,运行这些 子步骤:
    1. 如果 missingtitle, 生成一个运行时唯一名称并赋给 title
    2. 如果 missing@context, 分配最新受支持的 Thing Description 上下文 URI。
    3. 如果 missinginstance, 分配字符串 1.0.0
    4. 如果 missingforms, 使用可用的 协议 绑定和内容类型编码器生成一个 Forms 列表。然后 将所得列表赋给 forms
    5. 如果 missingsecurity, 分配 securityDefinitions 字段中第一个受支持的 SecurityScheme 的标签。如果未找到 SecurityScheme, 则生成一个名为 nosecNoSecurityScheme, 并将字符串 nosec 赋给 security
      Issue 1

      关于如何适当地 为 security 生成值的讨论 仍处于开放状态。参见 issue #299

    6. 如果 missinghref,则将 formStub 定义为不具有 href 的部分 Form。使用第一个 满足 formStub 要求的 协议 绑定生成一个有效的 url。将 url 赋给 href。如果找不到 协议 绑定,则从 td 中移除 formStub
    7. missing 添加到 td,并以 value 作为 值
  8. td 上运行 validate a TD。如果 失败,则重新抛出 该错误并停止
  9. 返回 td

6.2.2 验证 ExposedThingInit

要在给定 init 的情况下验证 ExposedThingInit,运行 以下步骤:
  1. 解析 TD JSON Schema,并将其加载到名为 exposedThingInitSchema 的对象中
  2. optional 为一个列表, 其中包含以下字符串:title@contextinstanceformssecurityhref
  3. 对于 exposedThingInitSchema 中等于 required 的每个属性和子属性 key,执行以下 步骤:
    1. 如果 keyvalue 是一个 Array,则移除其中所有等于 optional 中元素的元素
    2. 如果 keyvalue 是一个 string,则如果 value 等于 optional 中的某个元素,则从 exposedThingInitSchema 中移除 key
  4. 返回给定 initexposedThingInitSchema 运行 validating an object with JSON Schema 的结果。
    编辑注

    validating an object with JSON Schema 步骤仍在讨论中。目前,本 规范引用 JSONSchema 的验证过程。 在用 exposedThingInitSchema 验证 init 时,请遵循此 文档。请注意, 工作组正在评估另一种形式化 方法。

6.3 discover() 方法

WebIDLpartial namespace WOT {
  Promise<ThingDiscoveryProcess> discover(optional ThingFilter filter = {});
};
属于 WoT Discovery 一致性 类别。启动发现过程,该过程将提供 与类型为 ThingFilter 的可选 filter 实参匹配的物描述ThingDescription 对象。 该方法 MUST 运行以下 步骤:
  1. 返回一个 Promise promise,并 并行执行 后续步骤。
  2. 如果出于安全原因,当前脚本上下文 不允许调用此方法,则用 SecurityError 拒绝 promise,并停止。
  3. 如果实现不支持发现, 则用 NotSupportedError 拒绝 promise,并停止。
  4. discovery 为一个新的 ThingDiscoveryProcess 对象。
  5. discovery.[[filter]] 设置为 filter
  6. discovery.[[url]] 设置为 undefined
  7. 如果实现通常不支持过滤器,并且 filter 不是 undefinednull,则用 NotSupportedError 拒绝 promise,并停止。
  8. 如果底层平台无法启动发现, 则用 OperationError 拒绝 promise,并停止。
  9. 请求底层平台通过 WoT 运行时 中任何受支持并已预配、且脚本有权访问的方式来启动 发现过程, 并将 discovery 传递给它。
  10. discovery 兑现 promise

6.4 exploreDirectory() 方法

WebIDLpartial namespace WOT {
  Promise<ThingDiscoveryProcess> exploreDirectory(USVString url,
      optional ThingFilter filter = {});
};
属于 WoT Discovery 一致性 类别。启动发现过程,该过程在给定 TD 目录 URL 时, 将提供 与类型为 ThingFilter 的可选 filter 实参匹配的物描述ThingDescription 对象。 该方法 MUST 运行以下 步骤:
  1. 返回一个 Promise promise,并 并行执行 后续步骤。
  2. 如果出于安全原因,当前脚本上下文 不允许调用此方法,则用 SecurityError 拒绝 promise,并停止。
  3. 如果实现不支持目录发现, 则用 NotSupportedError 拒绝 promise,并停止。
  4. discovery 为一个新的 ThingDiscoveryProcess 对象。
  5. discovery.[[url]] 设置为 url
  6. discovery.[[filter]] 设置为 filter
  7. 请求底层平台启动 目录发现过程。

    这是发现算法中 更多细节的占位符。实现应 遵循 [WOT-DISCOVERY] 和 [WOT-PROTOCOL-BINDINGS] 规范中描述的过程。下面 指出了一些规范性步骤。

    1. 如果 url 不是一个 TD 目录,或底层 实现无法支持 url 指示的 协议 绑定,则用 NotSupportedError 拒绝 promise,并终止 这些步骤。
    2. 如果实现通常不支持过滤器,并且 filter 不是 undefinednull, 则用 NotSupportedError 拒绝 promise,并停止。
    3. 给定 discovery,运行 发现 过程

      从此时起,错误 只记录在 error 上,但不再影响 promise

  8. discovery 兑现 promise

6.5 requestThingDescription() 方法

WebIDLpartial namespace WOT {
  Promise<ThingDescription> requestThingDescription(USVString url);
};
属于 WoT Discovery 一致性 类别。从给定 URL 请求一个物描述。 该方法 MUST 运行以下步骤:
  1. 返回一个 Promise promise,并 并行执行 后续步骤。
  2. 如果出于安全原因,当前脚本上下文 不允许调用此方法,则用 SecurityError 拒绝 promise,并停止。
  3. 如果实现不支持获取物 描述, 则用 NotSupportedError 拒绝 promise,并停止。
  4. td 为向底层平台发出请求,使用 url 指定的 协议绑定 检索 物描述 的结果。如果检索 td 失败,则用 NotFoundError 拒绝 promise,并停止。
  5. td 兑现 promise

7. 处理交互数据

Web of Things(WoT)物描述 1.1 规范所指定,WoT 交互扩展 DataSchema,并包含 若干可能的表单,其中一个会被 选作该交互使用。 表单包含一个 contentType,用于描述 数据。对于某些内容类型,会定义一个基于 JSON SchemaDataSchema,从而 可以将这些内容表示为 JavaScript 类型,并 最终在数据上设置范围约束。

7.1 InteractionInput 类型

WebIDLtypedef any DataSchemaValue;
typedef (ReadableStream or DataSchemaValue) InteractionInput;

属于 WoT Consumer 一致性 类别,并表示由应用脚本提供给 UA 的 WoT 交互数据。

DataSchemaValue 是一个 ECMAScript 值,可被 [WoT-TD] 中定义的 DataSchema接受。 可能的值 MUST 属于 null boolean number stringarrayobject 类型。

ReadableStream 旨在用于那些在物 描述中没有 DataSchema,而只有 可以由流表示的FormcontentTypeWoT 交互

实践中,任何 ECMAScript 值都可用于那些在 物描述中定义了 DataSchemaWoT 交互,或用于那些可由实现映射到 物 描述中定义的 FormcontentType 的交互。

本文档中的算法指定了输入 数据在 WoT 交互中到底如何使用。

7.2 InteractionOutput 接口

属于 WoT Consumer 一致性 类别。InteractionOutput 对象始终由实现创建,并将从 WoT 交互返回的数据公开给 应用脚本。

此接口公开一个便利函数,该函数应 覆盖绝大多数 IoT 用例:value() 函数。其 实现会检查数据;如果数据符合 DataSchema,则解析它;否则会尽早失败, 让底层流保持未受扰动,以便 应用脚本可以尝试自行读取该流, 或将数据作为 ArrayBuffer 处理。

WebIDL[SecureContext, Exposed=(Window,Worker)]
interface InteractionOutput {
  readonly attribute ReadableStream? data;
  readonly attribute boolean dataUsed;
  readonly attribute Form? form;
  readonly attribute DataSchema? schema;
  Promise<ArrayBuffer> arrayBuffer();
  Promise<DataSchemaValue> value();
};

data 属性表示 WoT 交互中的原始 载荷,其形式为 ReadableStream, 初始为 null

dataUsed 属性说明 数据流是否已被 扰动。初始为 false

form 属性 表示从物描述中为 此 WoT 交互选择的表单, 初始为 null

schema 属性表示载荷的 DataSchema(定义于 [WoT-TD]), 其形式为一个 JSON 对象,初始为 null

[[value]] 内部槽表示 WoT 交互的已解析 值,初始为 undefined(注意 null 是有效值)。

7.2.1 value() 函数

解析由 WoT 交互返回的数据,并返回一个具有交互 DataSchema(如果存在)所描述类型的值, 或具有交互 表单contentType 所描述类型的值。该 方法 MUST 运行以下步骤:
  1. 返回一个 Promise promise,并 并行执行 后续步骤。
  2. 如果 this.[[value]] 不是 undefined,则以该值 兑现 promise 并停止。
  3. 如果 this.data 不是 ReadableStream,或 dataUsedtrue,或 form 不是 object, 或 schema 或其 typenullundefined,则用 NotReadableError 拒绝 promise 并停止。
  4. 如果 form.contentType 不是 application/json,并且 协议绑定中没有可用的映射 可将 form.contentType 映射到 [JSON-SCHEMA], 则用 NotSupportedError 拒绝 promise 并停止。
  5. reader 为从 data 获取 reader的结果。如果这 抛出异常,则用该 异常拒绝 promise 并停止。
  6. bytes 为使用 readerdata 读取所有字节的结果。
  7. dataUsed 设置为 true
  8. 如果 form.contentType 不是 application/json,并且 协议 绑定中存在可用映射,可将 form.contentType 映射到 [JSON-SCHEMA], 则使用该映射转换 bytes
  9. json 为在 bytes 上运行 parse JSON from bytes 的结果。如果这 抛出异常,则用该 异常拒绝 promise 并停止。
  10. [[value]] 设置为 在 jsonschema 上运行 check data schema 的结果。如果这抛出异常,则用该 异常拒绝 promise 并停止。
  11. [[value]] 兑现 promise

7.2.2 arrayBuffer() 函数

调用时,MUST 运行 以下步骤:
  1. 返回一个 Promise promise,并 并行执行 后续步骤。
  2. 如果 data 不是 ReadableStream,或 dataUsedtrue,则用 NotReadableError 拒绝 promise 并停止。
  3. reader 为从 data 获取 reader的结果。如果这 抛出异常,则用该 异常拒绝 promise 并停止。
  4. bytes 为使用 readerdata 读取所有字节的结果。
  5. dataUsed 设置为 true
  6. arrayBuffer 为一个新的 ArrayBuffer,其内容为 bytes。如果这抛出异常,则用该 异常拒绝 promise 并停止。
  7. arrayBuffer 兑现 promise

7.2.3 check data schema 算法

要在 payloadschema 上运行 check data schema 步骤,
  1. typeschema.type
  2. 如果 type"null",并且 payload 不是 null,则抛出 TypeError 并停止;否则返回 null
  3. 如果 type"boolean",且 payload 是 falsy 值或其字节长度为 0,则返回 false;否则返回 true
  4. 如果 type"integer""number"
    1. 如果 payload 不是数字,则抛出 TypeError 并停止。
    2. 如果 form.minimum 已定义 且 payload 更小,或 form.maximum 已定义且 payload 更大,则抛出 RangeError 并停止。
  5. 如果 type"string",则返回 payload
  6. 如果 type"array",则运行这些 子步骤:
    1. 如果 payload 不是数组,则抛出 TypeError 并停止。
    2. 如果 form.minItems 已定义 且 payload.length 小于 该值,或 form.maxItems 已定义且 payload.length 大于 该值,则抛出 RangeError 并停止。
    3. payload 为一个由以下方式获得的项数组: 对 payload 的每个元素 itemschema.items 运行 check data schema 步骤。如果这在任何阶段抛出异常,则重新抛出该异常并 停止。
  7. 如果 type"object",则运行 这些子步骤:
    1. 如果 payload schema.properties 不是 object, 则抛出 TypeError 并停止。
    2. 对于 payload 中的每个 key
      1. proppayload[key]。
      2. propSchemainteraction.properties[key]。
      3. prop 为在 proppropSchema 上运行 check data schema 步骤的结果。如果这抛出异常,则重新抛出 该异常并停止。
    3. 如果 schema.required 是数组,则令 required 为该数组;否则令其为 空数组。
    4. 对于 required 中的每个 key, 如果 key 未出现在 payload 中,则抛出 SyntaxError 并停止。
  8. 返回 payload

7.2.4 create interaction request 算法

对于给定的 ConsumedThing 对象 thing,为 在给定 sourceformschema 的情况下 创建 交互请求,运行这些步骤:
  1. idata 为一个新的 InteractionOutput 对象。
  2. idata.form 设置为 form,将 idata.schema 设置为 schema,将 |idata.data 设置为 null,并将 idata.[[value]] 设置为 undefined
  3. 如果 source 是 一个 ReadableStream 对象,则令 idata.datasource,返回 idata 并停止。
  4. 如果 schema 及其 type 已定义且不为 null,则运行 这些子步骤:
    1. 如果 type"null",并且 source 不是 "null",则抛出 TypeError 并停止。
    2. 如果 type"boolean",并且 source 是 falsy 值,则将 idata.[[value]] 设置为 false;否则将其设置为 true
    3. 如果 type"integer""number",且 source 不是 数字,或 form.minimum 已定义且 source 更小,或 form.maximum 已定义且 source 更大,则抛出 RangeError 并停止。
    4. 如果 type"string",且 source 不是 字符串,则令 idata.[[value]] 为 给定 source 运行 serialize JSON to bytes 的结果。如果该结果为 failure,则抛出 SyntaxError 并停止。
    5. 如果 type"array",则运行 这些子步骤:
      1. 如果 source 不是数组, 则抛出一个 TypeError 并停止。
      2. lengthsource 的长度。
      3. 如果 form.minItems 已定义 且 length 小于该值,或 form.maxItems 已定义 且 length 大于该值,则抛出 RangeError 并停止。
      4. 对于 source 中的每个 item,令 itemschema schema.items,并令 item 为给定 itemformitemschema 运行 create interaction request 步骤的结果。如果 这抛出异常,则重新抛出该异常并停止。
      5. data.[[value]] 设置为 source
    6. 如果 type"object",则运行 这些子步骤:
      1. 如果 source 不是对象, 则抛出 TypeError 并停止。
      2. 如果 schema.properties 不是对象,则抛出 TypeError 并停止。
      3. 对于 source 中的每个 key
        1. valuesource[key]。
        2. propschemaproperties.interactions[key]。
        3. value 为在 valueformpropschema 上运行 create interaction request 步骤的结果。 如果这抛出异常,则重新抛出该异常并 停止。
      4. 如果 schema.required 是 数组,则对于 required 中的每个 item,检查 item 是否是 source 中的属性名。如果在 source 中未找到某个 item,则抛出 SyntaxError 并停止。
      5. data.[[value]] 设置为 source
  5. idata.data 设置为一个新的 ReadableStream,该流由 idata.[[value]] 内部槽创建,并将其作为该流的底层 源
  6. 返回 idata

7.2.5 parse interaction response 算法

对于给定的 ConsumedThing 对象 thing,为 在给定 responseformschema 的情况下 解析 交互响应,运行这些步骤:
  1. result 为一个新的 InteractionOutput 对象。
  2. result.schemaschema
  3. result.formform
  4. result.data 为一个新的 ReadableStream,其中 response 的载荷数据作为其底层 源
  5. result.dataUsedfalse
  6. 返回 result

如下图所示,每当实现向 脚本提供数据时,都会使用 InteractionOutput 接口;而当脚本向实现传递数据时, 使用 InteractionInput

1 读取 数据时使用的数据结构

ConsumedThing 读取数据时,它会从实现接收一个 InteractionOutput 对象。

ExposedThing 读取处理器InteractionInput 的形式向实现提供读取数据。

2 写入 数据时使用的数据结构

ConsumedThing 写入数据时,它会以 InteractionInput 的形式将数据提供给实现。

ExposedThing 写入 处理器会从实现接收一个 InteractionOutput 对象作为数据。

3 调用 动作时使用的数据结构

ConsumedThing 调用一个动作时, 它会以 InteractionInput 的形式提供参数,并以 InteractionOutput 对象的形式接收该动作的输出。

ExposedThing 动作处理器InteractionOutput 对象的形式从实现接收实参,并以 InteractionInput 的形式向实现提供动作输出。

7.4 错误处理

此 API 中的算法定义了要 报告给应用脚本的错误。

报告给另一通信端的错误由 协议 绑定进行映射和封装。

4 WoT 交互中的错误 处理
编辑注

此主题仍在 Issue #200 中讨论。为了确保将脚本错误映射到 协议错误以及反向映射的一致性,需要一个标准化的错误映射。 尤其是,当 算法提到“从协议绑定收到的错误”时, 这将被分解为一个显式的错误映射 算法。目前,它由 实现封装。

8. ConsumedThing 接口

表示用于操作一个的客户端 API。属于 WoT Consumer 一致性 类别。

WebIDL[SecureContext, Exposed=(Window,Worker)]
interface ConsumedThing {
  constructor(ThingDescription td);
  Promise<InteractionOutput> readProperty(DOMString propertyName,
                              optional InteractionOptions options = {});
  Promise<PropertyReadMap> readAllProperties(
                              optional InteractionOptions options = {});
  Promise<PropertyReadMap> readMultipleProperties(
                              sequence<DOMString> propertyNames,
                              optional InteractionOptions options = {});
  Promise<undefined> writeProperty(DOMString propertyName,
                              InteractionInput value,
                              optional InteractionOptions options = {});
  Promise<undefined> writeMultipleProperties(
                              PropertyWriteMap valueMap,
                              optional InteractionOptions options = {});
  /*Promise<undefined> writeAllProperties(
                              PropertyWriteMap valueMap,
                              optional InteractionOptions options = {});*/
  Promise<InteractionOutput> invokeAction(DOMString actionName,
                              optional InteractionInput params = {},
                              optional InteractionOptions options = {});
  Promise<Subscription> observeProperty(DOMString name,
                              InteractionListener listener,
                              optional ErrorListener onerror,
                              optional InteractionOptions options = {});
  Promise<Subscription> subscribeEvent(DOMString name,
                              InteractionListener listener,
                              optional ErrorListener onerror,
                              optional InteractionOptions options = {});
  ThingDescription getThingDescription();
};

dictionary InteractionOptions {
  unsigned long formIndex;
  object uriVariables;
  any data;
};

[SecureContext, Exposed=(Window,Worker)]
interface Subscription {
  readonly attribute boolean active;
  Promise<undefined> stop(optional InteractionOptions options = {});
};

[SecureContext, Exposed=(Window,Worker)]
interface PropertyReadMap {
  readonly maplike<DOMString, InteractionOutput>;
};

[SecureContext, Exposed=(Window,Worker)]
interface PropertyWriteMap {
  readonly maplike<DOMString, InteractionInput>;
};

callback InteractionListener = undefined(InteractionOutput data);
callback ErrorListener = undefined(Error error);
编辑注:writeAllProperties 方法在哪里?

writeAllProperties() 方法 仍在讨论中。与此同时,请改用 writeMultipleProperties() 方法。

8.1 ConsumedThing 的内部槽

ConsumedThing 对象具有以下 内部槽

内部槽 初始值 描述(非规范性
[[td]] null ConsumedThing物 描述
[[activeSubscriptions]] {} 一个有序 映射,其为 表示事件字符串 名称,而 是 一个Subscription 对象。
[[activeObservations]] {} 一个有序 映射,其为 表示某个属性字符串 名称,而 是 一个Subscription 对象。

8.2 构造 ConsumedThing

在以 JSON 对象形式获取 一个物描述之后,可以创建一个 ConsumedThing 对象。

要使用 ThingDescription td 创建 ConsumedThing, 运行以下步骤:
  1. td 上运行 validate a TD 步骤。如果 失败,则抛出 SyntaxError 并停止。
  2. td 上运行 expand a TD 步骤。如果 失败,则重新抛出 该错误并停止。
  3. thing 为一个新的 ConsumedThing 对象。
  4. thing 内部槽 [[td]] 设置为 td
  5. 返回 thing

8.3 getThingDescription() 方法

返回 ConsumedThing 对象的 [[td]],该对象表示 ConsumedThing物描述。 应用可以查询存储在 [[td]] 中的元数据,以便在与其交互之前 内省其能力。

8.4 readProperty() 方法

读取一个属性值。接受 propertyName 以及可选的 options 作为实参。它返回一个 Promise,该 Promise 会以一个 InteractionOutput 对象形式表示的 属性值兑现, 或在错误时拒绝。该方法 MUST 运行以下步骤:
  1. 返回一个 Promise promise,并 并行执行 后续步骤。
  2. 如果出于安全原因,当前脚本上下文 不允许调用此方法,则用 SecurityError 拒绝 promise 并停止。
  3. interaction[[td]].properties.propertyName
  4. 如果 interactionundefined, 则用 NotFoundError 拒绝 promise 并停止。
  5. 如果 option.formIndex 已定义, 则令 forminteraction.forms 数组中与 formIndex 关联的表单;否则, 令 forminteraction.forms 中一个 opreadproperty表单,由 实现选择。
  6. 如果 form 是 failure,则用 SyntaxError 拒绝 promise 并停止。
  7. 向底层平台发出请求(通过 协议绑定), 使用 form 以及 options.uriVariables 中给定的可选 URI 模板, 检索 propertyName 属性的值。
  8. 如果请求失败,则用从协议 绑定收到的错误 拒绝 promise 并停止。
  9. response 为该请求收到的响应。
  10. data 为在 responseforminteraction 上运行 parse interaction response 的结果。如果这 失败,则用 SyntaxError 拒绝 promise 并停止。
  11. data 兑现 promise

8.5 readMultipleProperties() 方法

用一个请求读取多个属性值。接受 propertyNames 以及可选的 options 作为实参。它 返回一个 Promise,该 Promise 会以一个 PropertyReadMap 对象兑现,该对象将 propertyNames 中的键映射到 由此算法返回的值。该方法 MUST 运行以下步骤:
  1. 返回一个 Promise promise,并 并行执行 后续步骤。
  2. 如果出于安全原因,当前脚本上下文 不允许调用此方法,则用 SecurityError 拒绝 promise 并停止。
  3. 如果 option.formIndex 已定义, 则令 form[[td]].forms 数组中与 formIndex 关联的表单;否则, 令 form[[td]].forms 数组中 opreadmultipleproperties表单, 由实现选择。
  4. 如果 form 是 failure,则用 SyntaxError 拒绝 promise 并停止。
  5. result 为一个对象, 并且对于 propertyNames 中的每个字符串 name, 添加一个键为 name 且值为 null 的属性。
  6. 向底层平台发出请求(通过 协议绑定), 使用 form 以及 options uriVariables 中给定的可选 URI 模板,检索 propertyNames 给定的 属性值。
  7. 如果无法用 协议绑定通过单个请求完成此操作, 则用 NotSupportedError 拒绝 promise 并停止。
  8. 处理响应,并且对于 result 中的每个 key,运行以下 子步骤:
    1. valueresult[key]。
    2. schemathis.[[td]].properties[key]。
    3. property 为在 valueformschema 上运行 parse interaction response 的结果。
  9. 如果上述步骤在任何时刻抛出异常, 则用该 异常拒绝 promise 并停止。
  10. result 兑现 promise

8.6 readAllProperties() 方法

用一个请求读取的所有属性。接受 options 作为 可选实参。它返回一个 Promise,该 Promise 会以一个 PropertyReadMap 对象兑现,该对象将属性名称中的键映射到 由此算法返回的值。该方法 MUST 运行以下步骤:
  1. 返回一个 Promise promise,并 并行执行 后续步骤。
  2. 如果出于安全原因,当前脚本上下文 不允许调用此方法,则用 SecurityError 拒绝 promise 并停止。
  3. formssubscription.[[interaction]].forms
  4. 如果 formsundefined, 则用 SyntaxError 拒绝 promise 并停止。
  5. 如果 option.formIndex 不是 undefined 且小于 forms.length,则将 subscription.[[form]] 设置为 forms.[formIndex]。
  6. 否则,将 subscription.[[form]] 设置为 forms 中一个 op"readallproperties"表单, 由实现选择。
  7. 如果 subscription.[[form]] 是 failure,则用 SyntaxError 拒绝 promise 并停止。
  8. 使用 协议绑定向底层平台发出请求, 在给定 form 以及 options.uriVariables 中可选 URI 模板的情况下, 从 TD 检索所有属性定义。
  9. 如果无法使用该协议绑定通过单个请求完成此操作, 则用 NotSupportedError 拒绝 promise 并停止。
  10. 如果请求失败,则用从协议 绑定收到的错误 拒绝 promise 并停止。
  11. 处理回复,并令 result 为一个具有从回复中获得的键和值的对象。
  12. 处理响应,并且对于 result 中的每个 key,运行以下 子步骤:
    1. valueresult[key]。
    2. schemathis.[[td]].properties[key]。
    3. property 为在 valueformschema 上运行 parse interaction response 的结果。
  13. result 兑现 promise

8.7 writeProperty() 方法

写入单个属性。接受 propertyNamevalue 以及 可选的 options 作为实参。它返回一个 Promise,该 Promise 在成功时兑现, 并在失败时拒绝。该方法 MUST 运行以下步骤:
  1. 返回一个 Promise promise,并 并行执行 后续步骤。
  2. 如果出于安全原因,当前脚本上下文 不允许调用此方法,则用 SecurityError 拒绝 promise 并停止。
  3. interactionthis.[[td]].properties[propertyName]。
  4. 如果 interactionundefined, 则用 NotFoundError 拒绝 promise 并停止。
  5. 如果 option.formIndex 不是 undefined,则令 forminteraction.forms 数组中与 formIndex 关联的 表单; 否则,令 forminteraction.forms 中一个 opwriteproperty表单, 由实现选择。
  6. 如果 form 是 failure,则用 SyntaxError 拒绝 promise 并停止。
  7. data 为给定 valueform interaction 运行 create interaction request 步骤的结果。如果这抛出异常,则用该异常 拒绝 promise 并停止。
  8. 向底层平台发出请求(通过 协议绑定), 使用 data 以及 optionsuriVariables 中给定的可选 URI 模板, 写入由 propertyName 给定的 属性
  9. 如果请求失败,则用从协议 绑定收到的错误 拒绝 promise 并停止。
  10. 否则兑现 promise
编辑注

Issue #193 中所讨论,设计决定是写入交互 只返回成功或错误,而不返回写入的值 (可选)。TD 应 捕获属性值的模式,包括 精度和替代格式。当交互预期有返回值时, 应使用动作而不是 属性

8.8 writeMultipleProperties() 方法

用一个请求写入多个属性值。接受 properties 作为实参——它是一个对象,其中键为 属性名称,值为 属性值——并 可选接受 options。它返回一个 Promise,该 Promise 在成功时兑现, 并在失败时拒绝。该方法 MUST 运行以下步骤:
  1. 返回一个 Promise promise,并 并行执行 后续步骤。
  2. 如果出于安全原因,当前脚本上下文 不允许调用此方法,则用 SecurityError 拒绝 promise 并停止。
  3. 如果 option.formIndex 已定义, 则令 form[[td]].forms 数组中与 formIndex 关联的表单;否则, 令 form[[td]].forms 数组中 opwritemultipleproperties表单, 由实现选择。
  4. 如果 form 是 failure,则用 SyntaxError 拒绝 promise 并停止。
  5. propertyNames 为一个 string 数组,其元素为 properties 对象的键。
  6. 对于 propertyNames 中的每个 name,令 propertythis.[[td]].properties[name]。
  7. 如果 propertynull undefined,或者不是 writeable, 则用 NotSupportedError 拒绝 promise 并停止。
  8. result 为一个对象, 并且对于 propertyNames 中的每个字符串 name,添加一个键为 name 的属性,并令其值为 null
  9. schemas 为一个对象, 并且对于 propertyNames 中的每个 name,添加一个键为 name 的属性,并令其值为 this.[[td]].properties[name]。
  10. 对于 properties 中的每个键 key,给定 properties[key]、formschema[key] 的值,运行 create interaction request 步骤。 如果这对任何 name 抛出异常,则用该异常 拒绝 promise 并停止。
  11. 向底层平台发出单个请求(通过 协议 绑定), 使用 optionsuriVariables 中给定的可选 URI 模板,写入 properties 中提供的每个 属性
  12. 如果无法使用该协议绑定 通过单个请求完成此操作, 则用 NotSupportedError 拒绝 promise 并停止。
  13. 如果请求失败,则返回从 协议 绑定收到的错误并停止。
  14. 否则兑现 promise

8.9 observeProperty() 方法

发出对属性值变更 通知的请求。接受 propertyNamelistener 以及可选的 onerroroptions 作为实参。它 返回一个 Promise,该 Promise 在成功时兑现, 并在失败时拒绝。
编辑注

此算法每个 属性只允许一个活动的 Subscription。 如果在已有活动 Subscription 时创建新的 Subscription, 运行时将抛出 NotAllowedError

该方法 MUST 运行 以下步骤:
  1. thing 为此 ConsumedThing 对象的引用。
  2. 返回一个 Promise promise,并 并行执行 后续步骤。
  3. 如果出于安全原因,当前脚本上下文 不允许调用此方法,则用 SecurityError 拒绝 promise 并停止。
  4. 如果 listener 不是 Function, 则用 TypeError 拒绝 promise 并停止。
  5. 如果 onerror 不是 null,并且不是 Function, 则用 TypeError 拒绝 promise 并停止。
  6. 如果 thing.[[activeObservations]][propertyName] [=map/exists],则用 NotAllowedError 拒绝 promise 并停止。
  7. subscription 为一个新的 Subscription 对象,其 内部槽设置如下:
  8. 向底层平台发出请求,使用 form 以及 options uriVariables 中给定的可选 URI 模板,观察由 propertyName 标识的 属性
  9. 如果请求失败,则用从协议 绑定收到的错误 拒绝 promise 并停止。
  10. thing.[[activeObservations]][|propertyName] 设置subscription,并兑现 promise
  11. 每当底层平台检测到此 subscription 的通知,并且该通知以 propertyName, 带有新的属性value 时,运行以下子步骤:
  12. 每当底层平台检测到此订阅的错误时, 运行以下子步骤:
    • 如果该错误不可恢复并停止了 订阅,则将 subscription.active 设置为 false,并抑制后续 通知。
    • error 为一个新的 NetworkError,并将其 message 设置为反映底层错误 条件的内容。
    • 如果 onerror 是一个 Function, 则用 error 调用它。

8.10 invokeAction() 方法

发出调用一个动作并返回结果的请求。 接受 actionName、可选的 params 以及可选的 options 作为实参。它 返回一个 Promise,该 Promise 会以 动作的结果兑现, 该结果表示为一个 InteractionOutput 对象;或以错误拒绝。该方法 MUST 运行以下步骤:
  1. 返回一个 Promise promise,并 并行执行 后续步骤。
  2. 如果出于安全原因,当前脚本上下文 不允许调用此方法,则用 SecurityError 拒绝 promise 并停止。
  3. interactionthis.[[td]].actions[actionName]。
  4. 如果 interaction 不是一个 object, 则用 NotFoundError 拒绝 promise 并停止。
  5. formssubscription.[[interaction]].forms
  6. 如果 formsundefined, 则用 SyntaxError 拒绝 promise 并停止。
  7. 如果 option.formIndex 不是 undefined 且小于 forms.length,则将 subscription.[[form]] 设置为 forms.[formIndex]。
  8. 否则,将 subscription.[[form]] 设置为 forms 中一个 op"invokeaction"表单, 由实现选择。
  9. 如果 subscription.[[form]] 是 failure,则用 SyntaxError 拒绝 promise 并停止。
  10. args 为在 paramsforminteraction 上运行 create interaction request 步骤的结果。 如果这抛出异常,则用该异常 拒绝 promise 并停止。
  11. 向底层平台发出请求(通过 协议绑定), 在给定 argsoptions.uriVariables 的情况下, 调用由 actionName 标识的 动作
  12. 如果请求在本地失败或通过网络返回错误,则用从 协议 绑定收到的错误 拒绝 promise 并停止。
  13. value 为回复中返回的回复。
  14. result 为使用 valueforminteraction 运行 parse interaction response 的结果。如果这 抛出异常,则用该异常 拒绝 promise 并停止。
  15. result 兑现 promise

8.11 subscribeEvent() 方法

发出订阅事件通知的请求。接受 eventNamelistener 以及可选的 onerroroptions 作为实参。它 返回一个 Promise 来表示成功或 失败。
编辑注

此算法每个 事件只允许一个活动的 Subscription。 如果在已有活动 Subscription 时创建新的 Subscription, 运行时将抛出 NotAllowedError

该方法 MUST 运行 以下步骤:
  1. thing 为此 ConsumedThing 对象的引用。
  2. 返回一个 Promise promise,并 并行执行 后续步骤。
  3. 如果出于安全原因,当前脚本上下文 不允许调用此方法,则用 SecurityError 拒绝 promise 并停止。
  4. 如果 listener 不是 一个 Function, 则用 TypeError 拒绝 promise 并停止。
  5. 如果 onerror 不是 null,并且不是 Function, 则用 TypeError 拒绝 promise 并停止。
  6. 如果 thing.[[activeSubscriptions]][eventName] 不存在, 则用 NotAllowedError 拒绝 promise 并停止。
  7. subscription 为一个新的 Subscription 对象,其 内部槽设置如下:
  8. 通过 协议绑定 向底层平台发出请求,以使用 [[form]]options.uriVariables 中给定的可选 URI 模板,以及 options.data 中给定的可选订阅数据, 订阅由 eventName 标识的 事件
  9. 如果请求失败,则用从协议 绑定收到的错误 拒绝 promise 并停止。
  10. eventName 设置thing.[[activeSubscriptions]][eventName] 为 subscription
  11. 兑现 promise
  12. 每当底层平台检测到 以 eventName事件 subscription 通知时,运行以下子步骤:
    1. 使用在该 事件随附的数据、 subscription.[[form]]subscription.[[interaction]] 运行 parse interaction response 的结果, 调用 listener
  13. 每当底层平台检测到以 eventName事件 subscription 的错误时,运行 以下子步骤:
    • 如果该错误不可恢复并停止了 订阅,则将 subscription.active 设置为 false,并抑制后续 通知。
    • error 为一个新的 NetworkError,并将其 message 设置为反映底层错误 条件的内容。
    • 如果 onerror 是一个 Function, 则用 error 调用它。

8.12 InteractionOptions 字典

保存根据物 描述需要向 应用脚本公开的交互选项。

formIndex 属性如果已定义, 表示一个应用提示,指出对于给定的 WoT 交互,应使用 TD 中由该索引标识的哪个 Form 定义。实现 SHOULD 使用具有此索引的 Form 来进行 交互,但如果未找到该索引或索引无效, MAY 覆盖此 值。如果未定义, 实现 SHOULD 尝试 按照 TD 中列出的出现顺序,使用 给定 Wot 交互的 Form 定义。

uriVariables 属性如果已定义, 表示要与 WoT 交互一起使用的 URI 模板变量,这些变量表示为 [WOT-TD] 中定义的 解析后的 JSON 对象

编辑注

对 URI 变量的支持源于 Web of Things(WoT)物描述 1.1 规范所揭示的需求,即能够描述使用它们的现有 RESTful 端点。然而,应该可以 编写一个物描述,使用动作来表示这类 交互,并将 URI 变量建模为动作 参数。在这种情况下,实现可以将 参数序列化为 URI 变量,因此 options 参数可以被省略。

data 属性如果已定义,表示需要传递给交互的 额外不透明数据。

8.13 PropertyReadMap 类型

表示从属性名称到 InteractionOutput 对象的映射,该对象表示属性可以取的值。它用作涉及多个 属性同时进行的交互的 属性包。

8.14 PropertyWriteMap 类型

表示从属性名称到 InteractionInput 的映射,后者表示属性可以取的值。它用作涉及多个 属性同时进行的交互的 属性包。

8.15 InteractionListener 回调

用户提供的回调,会收到一个类型为 InteractionOutput 的实参,用于观察属性变化以及处理 事件 通知。 由于订阅事件是 WoT 交互,并且 可能接受选项甚至数据,因此它们不使用 软件事件建模。

8.16 ErrorListener 回调

用户提供的回调,会收到一个类型为 Error 的实参,用于将来自 协议绑定的 关键和非关键错误传递给 应用。

8.17 Subscription 接口

表示对属性变更和事件交互的订阅。

active 布尔属性表示该订阅是否处于活动状态,即 它未因错误或因调用 stop() 方法而 停止。

8.17.1 Subscription 的内部槽

Subscription 对象具有以下 内部槽
内部槽 初始值 描述(非规范性
[[type]] null 指示该 Subscription 指向哪个WoT 交互。该值可以是 "property""event"null
[[name]] null 属性事件名称。
[[interaction]] null 描述该 WoT 交互物 描述片段。
[[form]] null 与订阅关联的表单
[[thing]] null 与订阅关联的 ConsumedThing

8.17.2 stop() 方法

停止传递该订阅的通知。它 接受一个可选参数 options,并返回一个 Promise。调用时,该方法 MUST 执行以下 步骤:

  1. 返回一个 Promise promise,并 并行执行 后续步骤。
  2. 如果出于安全原因,当前脚本上下文 不允许调用此方法,则用 SecurityError 拒绝 promise 并停止。
  3. 如果 optionsformIndex 已定义,则令 unsubscribeForm[[interaction]]forms 数组中与 formIndex 关联的表单
  4. 否则,令 unsubscribeForm 为给定 [[form]] 运行 find a matching unsubscribe form 算法的 结果。
  5. 如果 unsubscribeForm 是 failure, 则用 SyntaxError 拒绝 promise 并停止。
  6. 如果 [[type]] "property",则通过 协议 绑定向底层 平台发出请求,使用 unsubscribeForm 以及 optionsuriVariables 中给定的可选 URI 模板,停止观察由 [[name]] 标识的 属性
  7. 否则,如果 [[type]]"event",则通过 协议 绑定向底层 平台发出请求,使用 unsubscribeFormoptionsuriVariables 中给定的可选 URI 模板,以及 options.data 中给定的可选取消订阅数据, 取消订阅由 [[name]] 标识的 事件
  8. 如果请求失败,则用从协议 绑定收到的错误 拒绝 promise 并停止。
  9. 否则:
  10. 如果底层平台收到该订阅的后续 通知,实现 SHOULD 静默抑制 它们。

8.17.3 查找 unsubscribe 表单

此算法正在开发中,并且是 非规范性内容。实现 MAY 选择另一种算法,为给定的 subscribe 表单查找匹配的 unsubscribe 表单

要在 Subscription 对象的上下文中,给定 subscribeForm查找匹配的 unsubscribe 表单,运行以下步骤:
  1. results 为一个空数组。
  2. 对于 [[interaction]].forms 中的每个 form
    1. form 上添加一个 内部槽 [[matchLevel]], 并将其值设置为 0
    2. 如果 [[type]]"property"form.op"unobserveproperty",或者如果 [[type]]"event"form.op"unsubscribeevent"
      1. form 上的 内部槽 [[matchLevel]] 设置为 1,并将 form 添加到 results
      2. 如果 form.href 和 [[subscribeForm]].href 同源域,则递增 form.[[matchLevel]]。
      3. 如果 form.contentType 等于 [[subscribeForm]] 的 contentType,并且 form.[[matchLevel]] 大于 2, 则递增 form.[[matchLevel]]。
  3. 如果 results 为空,则返回 null 并终止这些步骤。
  4. 返回 results 中第一个具有最高 [[matchLevel]] 值的 form

8.18 ConsumedThing 示例

下一个示例展示如何通过 URL 获取 TD,创建 ConsumedThing, 读取元数据(title)、读取属性值、订阅 属性变更、订阅 WoT 事件以及取消订阅。

示例 2:带数据值的物 客户端 API 示例
try {
  let res = await fetch("https://tds.mythings.org/sensor11");
  let td = res.json();
  let thing = new ConsumedThing(td);
  console.log("物 " + thing.getThingDescription().title + " 已被使用。");
} catch (e) {
  console.log("TD 获取错误:" + e.message);
};

try {
  // 订阅 “temperature” 的属性变更
  await thing.observeProperty("temperature", async (data) => {
    try {
      console.log("温度已变为:" + await data.value());
    } catch (error) {
      console.error("无法读取已观察的 temperature 属性");
      console.error(error);
    }
  });
  // 订阅 TD 中定义的 “ready” 事件
  await thing.subscribeEvent("ready", async (eventData) => {
    try {
      console.log("已就绪;索引:" + await eventData.value());
      // 运行 TD 定义的 “startMeasurement” 动作
      await thing.invokeAction("startMeasurement", { units: "Celsius" });
      console.log("测量已开始。");
    } catch (error) {
      console.error("无法读取 ready 事件,或 startMeasurement 失败");
      console.error(error)
    }
  });
} catch (e) {
  console.log("启动测量时出错。");
}

setTimeout(async () => {
  try {
    const temperatureData = await thing.readProperty("temperature")
    const temperature = await temperatureData.value();
    console.log("温度:" + temperature);

    await thing.unsubscribe("ready");
    console.log("已取消订阅 ‘ready’ 事件。");
  } catch (error) {
    console.log("清理函数中出错");
  }
}, 10000);

下面展示了 InteractionOutput 的高级用法,用于读取没有 DataSchema 的属性。

示例 3:带 arrayBuffer 的物 客户端 API 示例
/*
* takePicture affordance form:
* "form": {
*   "op": "invokeaction",
*   "href" : "http://camera.example.com:5683/takePicture",
*   "response": {
*     "contentType": "image/jpeg",
*     "contentCoding": "gzip"
*   }
*}
* 参见 https://www.w3.org/TR/wot-thing-description/#example-23
*/
let response;
let image;
try {
  response = await thing.invokeAction(“takePicture”));
  image = await response.value() // 抛出 NotReadableError --> 未定义 schema
} catch(ex) {
  image = await response.arrayBuffer();
  // image: ArrayBuffer [0x1 0x2 0x3 0x5 0x15 0x23 ...]
}

最后,接下来的两个示例展示了如何使用来自 InteractionOutputReadableStream

示例 4: 带可读流的物客户端 API 示例(例如 视频流)
/*{
"video": {
  "description" : "the video stream of this camera",
  "forms": [
    {
      "op": "readproperty",
      "href": "http://camera.example.com/live",
      "subprotocol": "hls"
      "contentType": "video/mp4"
    }
  ]
}}*/

const video = await thing.readProperty("video")
const reader = video.data.getReader()
reader.read().then(function processVideo({ done, value }) {
  if (done) {
    console.log("实时视频已停止");
    return;
  }
  const decoded = decode(value)
  UI.show(decoded)
  // 再读取一些内容,并再次调用此函数
  return reader.read().then(processText);
});

这里假设 JSON 对象太大,无法 整体读入内存。因此,我们使用流式处理 来获取远程 Web Thing 记录的事件 总数。

示例 5: 带可读流的物客户端 API 示例(例如 统计 json 对象)
/*
* "eventHistory":
* {
*   "description" : "A long list of the events recorded by this thing",
*   "type": "array",
*   "forms": [
*     {
*       "op": "readproperty",
*       "href": "http://recorder.example.com/eventHistory",
*     }
*   ]
* }
*/

// 流式处理示例:统计 json 对象
let objectCounter = 0
const parser = new Parser() // 用于 json 流式解析的用户库(即 https://github.com/uhop/stream-json/wiki/Parser)

parser.on('data', data => data.name === 'startObject' && ++objectCounter);
parser.on('end', () => console.log(`发现 ${objectCounter} 个对象。`));

const response = await thing.readProperty(“eventHistory”)
await response.data.pipeTo(parser);

// 发现 N 个对象

9. ExposedThing 接口

ExposedThing 接口是用于操作的服务器 API,它允许定义请求 处理器、属性动作事件交互。

WebIDL[SecureContext, Exposed=(Window,Worker)]
interface ExposedThing {
  ExposedThing setPropertyReadHandler(DOMString name,
          PropertyReadHandler handler);
  ExposedThing setPropertyWriteHandler(DOMString name,
          PropertyWriteHandler handler);
  ExposedThing setPropertyObserveHandler(DOMString name,
          PropertyReadHandler handler);
  ExposedThing setPropertyUnobserveHandler(DOMString name,
          PropertyReadHandler handler);
  Promise<undefined> emitPropertyChange(DOMString name,
          optional InteractionInput data);

  ExposedThing setActionHandler(DOMString name, ActionHandler action);

  ExposedThing setEventSubscribeHandler(DOMString name,
          EventSubscriptionHandler handler);
  ExposedThing setEventUnsubscribeHandler(DOMString name,
          EventSubscriptionHandler handler);
  Promise<undefined> emitEvent(DOMString name,
          optional InteractionInput data);

  Promise<undefined> expose();
  Promise<undefined> destroy();

  ThingDescription getThingDescription();
};

callback PropertyReadHandler = Promise<InteractionInput>(
        optional InteractionOptions options = {});

callback PropertyWriteHandler = Promise<undefined>(
        InteractionOutput value,
        optional InteractionOptions options = {});

callback ActionHandler = Promise<InteractionInput>(
        InteractionOutput params,
        optional InteractionOptions options = {});

callback EventSubscriptionHandler = Promise<undefined>(
        optional InteractionOptions options = {});

9.1 ExposedThing 的内部槽

ExposedThing 对象具有以下 内部槽

内部槽 初始值 描述(非规范性
[[td]] null ExposedThing物 描述
[[readHandlers]] {} 一个 Map,其键为属性名称, 值为 PropertyReadHandler
[[writeHandlers]] {} 一个 Map,其键为属性名称, 值为 PropertyWriteHandler
[[observeHandlers]] {} 一个 Map,其键为属性名称, 值为 PropertyReadHandler
[[unobserveHandlers]] {} 一个 Map,其键为属性名称, 值为 Function
[[actionHandlers]] {} 一个 Map,其键为动作名称, 值为 ActionHandler
[[subscribeHandlers]] {} 一个 Map,其键为事件名称, 值为 EventSubscriptionHandler
[[unsubscribeHandlers]] {} 一个 Map,其键为事件名称, 值为 EventSubscriptionHandler
[[propertyObservers]] {} 一个 Map,其键为属性名称, 值为监听器的 Array
[[eventListeners]] {} 一个 Map,其键为事件名称, 值为监听器的 Array

9.2 构造 ExposedThing

ExposedThing 接口扩展了 ConsumedThing。 它从一个完整或部分的 ThingDescription 对象构造。

注意,现有的 ThingDescription 对象可以被可选地修改(例如通过在其 propertiesactionsevents 内部 属性上添加或移除元素),并且所得对象可以用于 构造一个 ExposedThing 对象。这是当前添加和移除 属性动作事件定义的方式,如 示例中所示。

在调用 expose() 之前, ExposedThing 对象不会服务任何请求。这允许先 构造 ExposedThing, 然后在开始服务请求之前初始化其 属性和服务 处理器。

要使用 ExposedThingInit init 构造一个 ExposedThing, 运行以下步骤:
  1. 如果出于安全原因,当前脚本上下文 不允许调用此方法,则抛出 一个 SecurityError 并停止。
  2. init 上运行 expand an ExposedThingInit 步骤。如果失败,则重新抛出该 错误并停止。否则,存储获得的 td
  3. td 上运行 expand a TD 步骤。如果这 失败,则重新抛出 该错误并停止。
  4. thing 为一个新的 ExposedThing 对象。
  5. thing[[td]] 设置为 td
  6. 返回 thing

9.3 getThingDescription() 方法

返回 ExposedThing 对象的 [[td]],该对象表示 物描述。 应用可以 查询存储在 [[td]] 中的 元数据,以便在与其交互之前 内省其能力。

9.4 PropertyReadHandler 回调

当收到读取某个属性的外部请求时被调用的函数, 并定义如何处理此类请求。它返回一个 Promise,并以一个 ReadableStream 对象,或一个符合 DataSchema ECMAScript 值兑现,或者以 错误拒绝。

9.5 setPropertyReadHandler() 方法

接受 namehandler 作为实参。设置 服务处理器,该处理器定义当收到读取与 name 匹配的指定属性的请求时 应该做什么。错误时抛出异常。返回 对 this 对象的引用以支持 链式调用。

编辑注

注意,不需要注册用于处理读取多个或全部属性请求的处理器。 请求和回复在单个网络请求中传输,但 ExposedThing 可以通过多次调用单个读取 处理器来实现它们。

handler 回调函数应该实现读取一个属性,并且当从 底层平台收到读取一个属性的请求时, 实现 SHOULD 调用它。

对于任何给定的属性,最多 MUST 有一个处理器, 因此新添加的 处理器 MUST 替换先前的 处理器。如果没有为任何给定的属性初始化处理器,实现 SHOULD 基于 [[td]] 内部槽中提供的物描述 实现默认属性读取处理器。

当给定 namehandler 调用该方法时,实现 MUST 运行以下步骤:
  1. 如果出于安全原因,当前脚本上下文 不允许调用此方法,则抛出 一个 SecurityError 并停止。
  2. 如果 [[td]].properties[name] 不存在, 则抛出 NotFoundError 并停止。
  3. this.[[readHandlers]][name] 设置为 handler

9.6 处理读取 属性的请求

当实现收到读取属性 name 的网络请求,并带有 options 时,运行 以下步骤:
  1. 如果此操作不受支持,则按照协议 绑定发回一个 NotSupportedError 并停止。
  2. 如果此操作不被允许,则按照协议 绑定发回一个 NotAllowedError 并停止。
  3. value 为使用 nameoptions 运行以下 read server property 步骤的结果:
    1. interaction[[td]].properties.name
    2. 如果名称为 name属性不存在, 则抛出 NotFoundError 并停止。
    3. handlernull
    4. 如果在 [[readHandlers]] 内部槽中存在用于 interaction 的用户提供的 PropertyReadHandler, 则令 handler 为它。
    5. 否则,如果实现提供了默认读取处理器, 则令 handler 为它。
    6. 如果 handlernull,则抛出 NotSupportedError 并停止。
    7. value 为调用 handler 并给定 options 的结果。如果 这失败,则抛出该错误并停止。
    8. 返回 value

      这里返回的 value SHOULD 要么符合 DataSchema,要么 SHOULD 是一个由 handler 创建的 ReadableStream 对象。

  4. 如果上一步抛出了错误,则按照 协议绑定 创建回复,将该错误发回,并停止。
  5. 序列化并将返回的 value 添加到 按照 协议绑定创建的回复中。

9.7 处理读取 多个属性的请求

当收到读取在对象 propertyNames 中给定的多个属性的网络请求,并带有 options 时,对 propertyNamesoptions 运行以下 read multiple properties 步骤:
  1. 如果此操作不受支持,则按照协议 绑定发回一个 NotSupportedError 并停止。
  2. 如果此操作不被允许,则按照协议 绑定发回一个 NotAllowedError 并停止。
  3. 对于在 propertyNames 中定义的每个键为 name 的属性,
    1. value 为在 nameoptions 上运行 read server property 步骤的结果。如果 这抛出异常,则按照协议 绑定创建的回复中发回该错误 并停止。
    2. propertyNames.name 设置为 value
  4. 通过发送一个按照 协议绑定propertyNames 创建的单个回复来回复请求。

9.8 处理读取 所有属性的请求

当收到读取所有属性的网络请求, 并带有 options 时,运行 以下步骤:
  1. 如果此操作不受支持,则按照协议 绑定发回一个 NotSupportedError 并停止。
  2. 如果此操作不被允许,则按照协议 绑定发回一个 NotAllowedError 并停止。
  3. properties 为一个对象,该对象使用 中定义的 所有属性创建,其值设置为 null
  4. propertiesoptions 上运行 read multiple properties 步骤。

9.9 setPropertyObserveHandler() 方法

接受 namehandler 作为实参。设置 服务处理器,该处理器定义当收到观察与 name 匹配的指定属性的请求时 应该做什么。错误时抛出异常。返回 对 this 对象的引用以支持 链式调用。

handler 回调函数应该实现读取一个属性,并 以一个 InteractionOutput 对象兑现,或以 错误拒绝

对于任何给定的属性,最多 MUST 有一个处理器, 因此新添加的 处理器 MUST 替换先前的 处理器。如果没有为任何给定的属性初始化处理器,实现 SHOULD 基于物 描述实现默认属性 读取处理器。

当给定 namehandler 调用该方法时,实现 MUST 运行以下步骤:
  1. 如果出于安全原因,当前脚本上下文 不允许调用此方法,则抛出 一个 SecurityError 并停止。
  2. 如果 this.[[td]].properties[name] 不存在, 则抛出 NotFoundError 并停止。
  3. this[[observeHandlers]][name] 设置为 handler

9.10 处理属性观察 请求

当实现收到观察一个属性 name 的网络请求,并带有 options 时,运行 以下步骤:
  1. 如果此操作不受支持,则按照协议 绑定发回一个 NotSupportedError 并停止。
  2. 如果此操作不被允许,则按照协议 绑定发回一个 NotAllowedError 并停止。
  3. 如果 this.[[td]].properties[name] 不存在, 则在回复中发回一个 NotFoundError 并停止。
  4. 在内部保存请求发送者信息,以及 optionsthis.[[propertyObservers]][name], 以便能够通知属性值 变更。

每当 property 的值 发生变化时,应用 脚本都需要显式调用 emitPropertyChange()

9.11 setPropertyUnobserveHandler() 方法

接受 namehandler 作为实参。设置 服务处理器,该处理器定义当收到取消观察与 name 匹配的指定属性的请求时 应该做什么。错误时抛出异常。返回 对 this 对象的引用以支持 链式调用。

handler 回调函数应该实现当实现收到 取消观察请求时应做什么。

对于任何给定的属性,最多 MUST 有一个处理器, 因此新添加的 处理器 MUST 替换先前的 处理器。如果没有为任何给定的属性初始化处理器,实现 SHOULD 基于物 描述实现默认处理器。

当给定 namehandler 调用该方法时,实现 MUST 运行以下步骤:
  1. 如果出于安全原因,当前脚本上下文 不允许调用此方法,则抛出 一个 SecurityError 并停止。
  2. 如果 this.[[td]].properties[name] 不存在, 则抛出 NotFoundError 并停止。
  3. this.[[unobserveHandlers]][name] 设置为 handler

9.12 处理属性取消观察 请求

当实现收到对属性 name 取消观察的网络请求,并带有 options 时, 运行以下步骤:
  1. 如果此操作不受支持,则按照协议 绑定发回一个 NotSupportedError 并停止。
  2. 如果此操作不被允许,则按照协议 绑定发回一个 NotAllowedError 并停止。
  3. 如果 this.[[td]].properties[name] 不存在, 则在回复中发回一个 NotFoundError 并停止。
  4. handlerthis.[[unobserveHandlers]][name];
  5. 如果 handler 是一个 Function, 则使用 options 调用它,然后发回一个 遵循协议绑定的 回复并停止。
  6. 否则,如果 this.[[propertyObservers]][name] 存在, 则将其从 this.[[propertyObservers]] 中移除, 按照协议 绑定中定义的方式发回一个回复并停止。
  7. 否则,按照协议绑定 中定义的方式,在回复中发回一个 NotFoundError 并停止。

9.13 emitPropertyChange() 方法

接受 name 实参, 表示一个属性名称,以及 可选的 data 实参。触发向指定属性的所有观察者发出通知,其中数据 由 data 实参提供;如果未提供, 则由与该属性关联的观察处理器或 读取处理器获得。该方法 MUST 运行以下步骤:
  1. promise 为一个新的 Promise
  2. 返回 promise 并行执行后续步骤。
  3. 如果出于安全原因,当前脚本上下文 不允许调用此方法,则用 SecurityError 拒绝 promise 并停止。
  4. name 为第一个 实参。
  5. propertythis.[[td]].properties[name]。
  6. 如果 propertyundefined, 则用 NotFoundError 拒绝 promise 并停止。
  7. data 为 第二个实参。
  8. 如果 dataundefined,则运行以下子步骤:
    1. handlernull.
    2. 如果 name [[readHandlers]] 中不 存在, 则拒绝 promise 并停止。
    3. handler [[readHandlers]][name]。
    4. 如果 handlernullundefined, 则拒绝 promise 并停止。
    5. handled 为调用 handler 并给定 null 的结果。
    6. 如果 handled拒绝, 则 拒绝 promise 并停止。
    7. 否则,如果 handledvalue 兑现, 则令 datavalue
  9. 对于 [[propertyObservers]][name] 中的每个 observer, 运行以下子步骤:
    1. options 为与 observer 一起保存的交互选项。
    2. 请求底层平台按照协议 绑定,从 dataoptions 创建一个 reply
      编辑注

      此条款需要扩展,和/或 引用 [WOT-PROTOCOL-BINDINGS] 中的算法。

    3. reply 发送给 observer
  10. 兑现 promise

9.14 PropertyWriteHandler 回调

当收到写入某个属性的外部请求时被调用的函数, 并定义如何处理此类请求。接受 value 作为实参并返回一个 Promise,当 由设置处理器时提供的名称标识的 属性值已更新时兑现;或者 如果未找到该属性或无法更新该 值,则以错误拒绝。

编辑注

注意,如果需要,此回调函数中的代码 可以在更新属性之前读取该属性,以便 找出旧值。因此旧值 不会提供给此函数。

该值由实现以 InteractionOutput 对象形式提供,以便能够表示 未由 DataSchema 描述的值,例如 流。

9.15 setPropertyWriteHandler() 方法

接受 namehandler 作为实参。设置 服务处理器,该处理器定义当收到写入由设置 处理器时给定的 name 匹配的 属性的请求时 应该做什么。错误时抛出异常。返回 对 this 对象的引用以支持链式调用。

注意,即使对于 readonly 属性,也可以 指定写入处理器,如 Issue 199 中所解释。在这种情况下,写入处理器可以以 应用特定的方式定义使请求失败。

对于任何给定的属性,最多 MUST 有一个写入 处理器,因此新添加的 处理器 MUST 替换先前的 处理器。如果没有为任何给定 属性初始化写入处理器, 实现 SHOULD 在该 属性可写时实现 默认属性更新,并在该属性可观察时通知观察者 变化,这些均基于物 描述

当给定 namehandler 调用该方法时,实现 MUST 运行以下步骤:
  1. 如果出于安全原因,当前脚本上下文 不允许调用此方法,则抛出 一个 SecurityError 并停止。
  2. 如果 this.[[td]].properties[name] 不存在, 则抛出 NotFoundError 并停止。
  3. this.[[writeHandlers]][name] 设置为 handler

9.16 处理写入 属性的请求

当收到对属性 name 写入新值 value 的网络请求,并带有 options 时, 实现 MUST 运行以下 update property steps,给定 namevalueoptions,并将 mode 设置为 "single"
  1. 如果此操作不受支持,则按照协议 绑定发回一个 NotSupportedError 并停止。
  2. 如果此操作不被允许,则按照协议 绑定发回一个 NotAllowedError 并停止。
  3. interactionthis.[[td]].properties[name]。
  4. 如果 interactionundefined, 则在回复中返回一个 NotFoundError 并停止。
  5. handler this.[[writeHandlers]][name]。
  6. 如果 handlerundefined,并且存在由实现提供的默认写入 处理器,则令 handler 为它。
  7. 如果 handler undefined,则随回复发回一个 NotSupportedError 并停止。
  8. promise 为调用 handler 并给定 nameoptions 的结果。如果它 失败,则在回复中返回 该错误并停止。
  9. 如果 mode"single",则按照协议 绑定 回复请求并报告成功, 然后停止。

9.17 处理写入 多个属性的请求

当收到写入在对象 propertyNames 中给定的多个属性的网络请求,并带有 options 时,运行 以下步骤:
  1. 如果此操作不受支持,则按照协议 绑定发回一个 NotSupportedError 并停止。
  2. 如果此操作不被允许,则按照协议 绑定发回一个 NotAllowedError 并停止。
  3. 对于 propertyNames 中定义的每个键为 name、值为 value 的属性,使用 namevalueoptions 并将 mode 设置为 "multiple",运行 update property steps。如果这 失败,则用该错误回复请求并停止。
  4. 通过按照协议 绑定发送单个回复来回复请求。

9.18 ActionHandler 回调

当收到调用某个动作 的外部请求时被调用的函数,并定义如何处理此类请求。它会在给定 params 并可选给定一个 options 对象时被调用。它返回一个 Promise,该 Promise 以错误拒绝或 以动作返回的值兑现,该值作为 InteractionInput

应用脚本 MAYActionHandler 返回一个 ReadableStream 对象。实现随后将使用该流来构造 动作的响应。

9.19 setActionHandler() 方法

接受 nameaction 作为实参。设置处理器 函数,该函数定义当收到请求以调用与 name 匹配的动作 时应做什么。错误时抛出 异常。返回对 this 对象的引用以 支持链式调用。

action 回调 函数将实现一个动作,并且当从 底层平台收到调用该动作的 请求时,实现 SHOULD 调用它。

对于任何给定的动作, 最多 MUST 有一个处理器, 因此新添加的处理器 MUST 替换 先前的处理器。

当给定 nameaction 调用该方法时,运行以下步骤:
  1. 如果出于安全原因,当前脚本上下文 不允许调用此方法,则抛出 一个 SecurityError 并停止。
  2. interactionthis.[[td]].actions[name]。
  3. 如果 interactionundefined, 则抛出 一个 NotFoundError 并停止。
  4. this.[[actionHandlers]][name] 设置为 action

9.20 处理动作请求

当收到调用由 name 标识的动作的网络请求,并给定 inputs 以及可选的 options 时,运行以下 步骤:
  1. 如果此操作不受支持,则按照协议 绑定发回一个 NotSupportedError 并停止。
  2. 如果此操作不被允许,则按照协议 绑定发回一个 NotAllowedError 并停止。
  3. interactionthis.[[td]].properties[name]。
  4. 如果 interactionundefined, 则在回复中返回一个 NotFoundError 并停止。
  5. handler this.[[actionHandlers]][name]。
  6. 如果 handler undefined,则返回一个 NotSupportedError,并带有按照 协议 绑定创建的回复,然后停止。
  7. promise 为调用 handler 并给定 nameinputsoptions 的结果。
  8. 如果 promise 拒绝,则随 回复发送该错误并停止。
  9. promisedata 兑现时,使用 data 按照 协议 绑定创建并发送回复。

9.21 EventSubscriptionHandler 回调

当收到订阅某个事件的外部请求时被调用的函数, 并定义如何处理此类请求。它在给定由 实现提供并来自订阅者的 options 对象时被调用。它返回一个 Promise,该 Promise 以错误拒绝或 在订阅被接受时兑现。

9.22 setEventSubscribeHandler() 方法

接受 namehandler 作为实参。设置 处理器函数,该函数定义当收到针对由 name 匹配的指定事件的 订阅请求时应做什么。错误时抛出异常。返回对 this 对象的引用以支持链式调用。

handler 回调函数 SHOULD 实现当收到 订阅请求时应做什么,例如必要的 初始化。注意,用于发出事件的处理器是单独设置的。

对于任何给定的事件,最多 MUST 有一个事件 订阅处理器,因此新添加的处理器 MUST 替换先前的 处理器。

当给定 namehandler 调用该方法时,运行以下 步骤:
  1. 如果出于安全原因,当前脚本上下文 不允许调用此方法,则抛出 一个 SecurityError 并停止。
  2. interactionthis.[[td]].events[name]。
  3. 如果 interactionundefined, 则抛出 一个 NotFoundError 并停止。
  4. this.[[subscribeHandlers]][name] 设置为 handler
  5. 返回 this

9.23 处理事件订阅 请求

当底层平台收到针对 name事件 订阅请求,并带有可选的 options 时,运行以下 步骤:
  1. 如果此操作不受支持,则按照协议 绑定发回一个 NotSupportedError 并停止。
  2. 如果此操作不被允许,则按照协议 绑定发回一个 NotAllowedError 并停止。
  3. interactionthis.[[td]].events[name]。
  4. 如果 interactionundefined, 则发回一个 NotFoundError 并停止。
  5. 如果 this.[[subscribeHandlers]][name] 是一个 Function, 则用 options 调用它并停止。
  6. 否则,实现默认订阅者机制:
    1. subscriber 为一个元组,它由 options (可从其中使用 uriVariablesdata)以及创建事件通知 响应所需的订阅者信息组成。
    2. this.[[eventListeners]][name] 设置为 subscriber

9.24 setEventUnsubscribeHandler() 方法

接受 namehandler 作为实参。设置 处理器函数,该函数定义当由 name 匹配的指定事件 被取消订阅时应做什么。错误时抛出异常。返回对 this 对象的引用以支持链式调用。

handler 回调函数 SHOULD 实现当收到 取消订阅请求时应做什么。

对于任何给定的事件,最多 MUST 有一个处理器, 因此新添加的处理器 MUST 替换 先前的处理器。

当给定 namehandler 调用该方法时,运行以下 步骤:
  1. 如果出于安全原因,当前脚本上下文 不允许调用此方法,则抛出 一个 SecurityError 并停止。
  2. interactionthis.[[td]].events[name]。
  3. 如果 interactionundefined, 则抛出 一个 NotFoundError 并停止。
  4. this.[[unsubscribeHandlers]][name] 设置为 handler
  5. 返回 this

9.25 处理事件取消订阅 请求

当底层平台收到针对 name事件 取消订阅请求,并可选带有 options 时,运行以下 步骤:
  1. 如果此操作不受支持,则按照协议 绑定发回一个 NotSupportedError 并停止。
  2. 如果此操作不被允许,则按照协议 绑定发回一个 NotAllowedError 并停止。
  3. interactionthis.[[td]].events[name]。
  4. 如果 interactionundefined, 则发回一个 NotFoundError 并停止。
  5. 如果 this.[[unsubscribeHandlers]][name] 存在 并且是一个 Function, 则用 options 调用它并停止。
  6. 否则,如果 namethis.[[eventListeners]] 中 [=map/exists], 则移除 name
  7. 返回 this

9.26 处理事件

emitEvent() 方法发出一个名称为 name、带有 data事件时,运行 以下步骤:
  1. listeners[[eventListeners]].name
  2. 对于 listeners 中的每个 subscriber,运行以下子步骤:
    1. 按照协议 绑定,从 datasubscriber 创建一个事件通知 response,包括其 options
    2. 如果 dataundefined,则假定该 通知 response 将包含一个 空数据载荷,如协议 绑定所规定。
    3. 如果底层协议栈允许 传达事件错误,并且 UA 检测到错误条件, 则按照协议 绑定,使用 datasubscriber 及其 options,将 response 创建为错误通知。

      错误报告是协议 特定的,并由 实现封装。在客户端端,如果客户端 UA 检测到该错误, 则会调用随订阅传入的错误 监听器。

    4. response 发送给由 subscriber 标识的订阅者。

9.27 emitEvent() 方法

接受 name 作为实参, 表示一个事件 名称,并可选接受 data。触发发出带有 可选数据的 事件。该方法 MUST 运行 以下步骤:
  1. 返回一个 Promise promise,并 并行执行 后续步骤。
  2. 如果出于安全原因,当前脚本上下文 不允许调用此方法,则用 SecurityError 拒绝 promise 并停止。
  3. interaction[[td]].events.name
  4. 如果找不到名称为 name事件, 则用 NotFoundError 拒绝 promise 并停止。
  5. 向底层平台发出请求,以发出一个带有可选 data事件。调用 handling events 步骤。

9.28 expose() 方法

开始为该服务外部请求,使得使用属性动作事件WoT 交互成为可能。该 方法 MUST 运行以下 步骤:
  1. 返回一个 Promise promise,并 并行执行 后续步骤。
  2. 如果出于安全原因,当前脚本上下文 不允许调用此方法,则用 SecurityError 拒绝 promise 并停止。
  3. [[td]] 上运行 expand a TD 步骤。
  4. [[td]] 上运行 validate a TD。如果失败, 则用 TypeError 拒绝 promise 并停止。
  5. 对于 [[td]].properties 中的每个 key, 将 this.[[propertyObservers]].key 初始化为一个空 Array,以存储在值 变化时通知观察者所需的观察 请求数据。
  6. 对于 this.[[td]].events 中的每个 key, 将 this.[[eventListeners]].key 初始化为一个空 Array,以存储在事件 发出时通知订阅者所需的订阅 请求数据。
  7. 基于内省 [[td]] 来设置 WoT 交互, 如 [WOT-TD] 和 [WOT-PROTOCOL-BINDINGS] 中所解释。 向底层平台发出请求以初始化 协议 绑定,然后基于 协议 绑定,开始服务针对 WoT 交互 的外部请求(读取、写入和观察属性,调用 动作并管理 事件订阅)。实现 MAY 因任何原因拒绝此步骤 (例如,如果它们想对交互形式强制执行进一步检查和 约束)。
  8. 如果请求期间发生错误, 则用一个 Error 对象 error 拒绝 promise,其中 error.message 设置为 协议 绑定看到的错误 代码,然后停止。
  9. 否则兑现 promise 并停止。

9.29 destroy() 方法

停止为该服务外部请求并销毁对象。 注意,最终的注销应在 调用此方法之前完成。该方法 MUST 运行以下步骤:
  1. 返回一个 Promise promise,并 并行执行 后续步骤。
  2. 如果出于安全原因,当前脚本上下文 不允许调用此方法,则用 SecurityError 拒绝 promise 并停止。
  3. 向底层平台发出请求,以基于 协议 绑定, 停止服务针对WoT 交互的外部请求。
  4. 如果请求期间发生错误, 则用一个 Error 对象 error 拒绝 promise,其 message 设置为 协议 绑定看到的错误代码,然后停止。
  5. 否则兑现 promise 并停止。

9.30 ExposedThing 示例

下一个示例展示如何基于预先构造的部分 TD 对象创建一个 ExposedThing

示例 6:使用简单属性创建 ExposedThing
try {
  let temperaturePropertyDefinition = {
    type: "number",
    minimum: -50,
    maximum: 10000
  };
  let tdFragment = {
    properties: {
      temperature: temperaturePropertyDefinition
    },
    actions: {
      reset: {
        description: "重置温度传感器",
        input: {
          temperature: temperatureValueDefinition
        },
        output: null,
        forms: []
      },
    },
    events: {
      onchange: temperatureValueDefinition
    }
  };
  let thing1 = await WOT.produce(tdFragment);
  // 初始化属性
  await thing1.writeProperty("temperature", 0);
  // 添加服务处理器
  thing1.setPropertyReadHandler("temperature", () => {
     return readLocalTemperatureSensor();  // Promise
  });
  // 开始服务请求
  await thing1.expose();
} catch (err) {
   console.log("创建 ExposedThing 时出错:" + err);
}

下一个示例展示如何在现有的 ExposedThing 上添加或修改一个 属性定义: 取其 td 属性,添加或修改它,然后 用它创建另一个 ExposedThing

示例 7:添加一个 对象属性
try {
  // 创建 thing1 的 TD 的深拷贝
  let instance = JSON.parse(JSON.stringify(thing1.td));
  const statusValueDefinition = {
    type: "object",
    properties: {
      brightness: {
        type: "number",
        minimum: 0.0,
        maximum: 100.0,
        required: true
      },
      rgb: {
        type: "array",
        "minItems": 3,
        "maxItems": 3,
        items : {
            "type" : "number",
            "minimum": 0,
            "maximum": 255
        }
      }
  };
  instance["name"] = "mySensor";
  instance.properties["brightness"] = {
    type: "number",
    minimum: 0.0,
    maximum: 100.0,
    required: true,
  };
  instance.properties["status"] = statusValueDefinition;
  instance.actions["getStatus"] = {
    description: "获取状态对象",
    input: null,
    output: {
      status : statusValueDefinition;
    },
    forms: [...]
  };
  instance.events["onstatuschange"] = statusValueDefinition;
  instance.forms = [...];  // 更新
  var thing2 = new ExposedThing(instance);
  // TODO: 添加服务处理器
  await thing2.expose();
  });
} catch (err) {
   console.log("创建 ExposedThing 时出错:" + err);
}

以下内容将涵盖一组示例,说明如何使用 expand an ExposedThingInit 步骤,从 ExposedThingInit 生成一个物描述。 作为假设,运行时 支持 HTTP 和 COAP 协议绑定,并托管在 192.168.0.1。

下一个示例展示如何利用 ExposedThingInit 创建一个简单的物描述, 其中包含一个使用默认值的属性

编辑注

TODO:添加更多示例,其中 ExposedThingInit 包含由算法替换的建议值。

10. ThingDiscoveryProcess 接口

发现是一种分布式应用,需要参与的网络节点 (客户端、服务器、目录服务)进行配置和支持。此 API 对各种 IoT 部署所支持的典型发现方案的 客户端侧进行建模。

ThingDiscoveryProcess 对象提供用于控制 发现过程并返回结果的属性和方法。

WebIDL[SecureContext, Exposed=(Window,Worker)]
interface ThingDiscoveryProcess {
  constructor(optional ThingFilter filter = {});
  readonly attribute boolean done;
  readonly attribute Error? error;
  undefined stop();
  async iterable<ThingDescription>;
};

ThingDiscoveryProcess 对象具有以下 内部槽

内部槽 初始值 描述(非规范性
[[filter]] undefined 发现中使用的 ThingFilter 对象。
[[url]] undefined 一个表示发现中的 TD 目录URL

如果发现已停止或已完成且没有更多 结果需要报告,则 done 属性为 true

error 属性表示发现过程中最近发生的 错误。通常用于会停止发现的关键错误。

ThingDiscoveryProcess 对象实现异步 迭代器概念。

10.1 构造 ThingDiscoveryProcess

要使用 filter 创建 ThingDiscoveryProcess, 运行以下步骤:
  1. 如果 filter 不是 对象或 null,则抛出一个 TypeError 并停止。
  2. discovery 为一个新的 ThingDiscoveryProcess 对象。
  3. discovery.[[filter]] 设置为 filter
  4. discovery.done 设置为 false
  5. discovery.error 设置为 null
  6. 返回 discovery

10.2 ThingFilter 字典

表示一个包含用于发现事物的约束的对象, 这些约束以键值对形式表示。

WebIDLdictionary ThingFilter {
  object? fragment;
  
};

fragment 属性表示一个 模板对象,用于逐个属性地与发现的 事物进行匹配。

编者注

query 属性已暂时从 ThingFilter 中移除, 直到 WoT Discovery 任务组将其标准化。 它表示实现所接受的查询字符串, 例如 SPARQL 或 JSON 查询。 原计划在 WoT 运行时中本地实现支持,或在 TD 目录中作为服务远程实现。

编者注

url 属性已被移除。它过去用于表示为发现请求提供服务的目标 实体,例如 TD 目录的 URL,或直接作为目标的事物的 URL,但现在这些功能 由专用方法实现。

10.3 发现过程 算法

描述在已经启动的发现过程中应执行的操作。给定 discovery,该算法运行以下 步骤:
  1. 每当底层平台发现并提供一个指向事物描述的新 link 时,运行以下子步骤:
    1. 使用底层发现过程所使用的协议 绑定(由 link 指定),以 JSON 对象形式获取 td。对于 HTTP(S) 绑定,此过程可以使用 Fetch API 来获取 td
    2. 如果失败,则将 discovery.error 属性设置为 SyntaxError,丢弃 td 并继续 发现过程。
  2. 每当底层平台或前一步发现并提供一个事物描述 td 时,运行以下子步骤:

    此时,实现可以控制发现 过程的流程(例如根据内存限制, 对结果进行排队,或在队列变得过大时暂时停止 发现,或在队列被充分清空时 恢复发现)。对于每个 已发现/获取的 td,都会运行这些步骤。

    1. fragmentdiscovery.[[filter]].fragment
    2. 如果 fragment 是一个 object, 则对于其中定义的每个 key
      1. 检查该 key 是否 存在json 中,以及 json[key 是否等于 fragment.key
      2. 如果任何检查失败,则丢弃 td 并 继续发现过程。
    3. 使用 asyncIterator 产生 td
      编者注

      使用正确的 asyncIterator 术语改进此步骤。

  3. 每当发现过程中发生错误时, 运行以下子步骤:

    保留最近一次错误。 如果实现认为应该报告该错误, 则实现可以选择 停止发现过程。

    1. error 为一个新的 Error 对象。将 error.name 设置为 "DiscoveryError"
    2. 如果协议 绑定提供了错误代码或消息,则将 error.message 设置为该值的字符串形式。
    3. discovery.error 设置为 error
    4. 如果错误不可恢复且底层平台已停止发现, 或者实现决定停止发现过程 并报告错误,则将 discovery.done 设置为 true 并终止这些步骤。

10.4 stop() 方法

停止或抑制发现过程。并非所有发现方法和端点 都可能支持此操作;但是,任何后续的发现结果或错误都会被丢弃, 并将发现标记为完成。该方法必须运行以下步骤:
  1. 如果出于安全原因,当前脚本上下文 不允许调用此方法,则 抛出 一个 SecurityError 并停止。
  2. 请求底层平台停止发现 过程。如果此操作返回错误,或者无法停止, 例如发现基于开放式的 多播请求时,实现应该丢弃后续发现的 项。
  3. done 属性设置为 true

10.5 发现示例

以下示例查找由本地硬件公开的ThingDescription 对象,这些对象属于事物, 无论其运行了多少个 WoT 运行时实例。使用 Discovery 对象提供的 asyncIterator,我们可以异步迭代结果,并 对获得的 ThingDescription 对象执行操作。

示例 9:获取一个 事物的事物描述
let url = "https://mythings.com/thing1";
let td = await WOT.requestThingDescription(url);
console.log("找到以下事物描述:" + td.title);

下一个示例查找列在 TD 目录服务中的ThingDescription 对象,这些对象属于事物。 为了安全起见,我们设置一个超时。

示例 10:通过目录发现 事物
let discovery = await WOT.exploreDirectory("http://directory.wotservice.org");
setTimeout( () => {
    discovery.stop();
    console.log("超时后停止发现。");
  },
  3000);
for await (const td of discovery) {
  console.log("找到以下事物描述:" + td.title);
  let thing = new ConsumedThing(td);
  console.log("事物名称:" + thing.getThingDescription().title);
};
if (discovery.error) {
  console.log("发现因错误而停止:" + error.message);
}

下一个示例用于通用发现,可通过为 WOT 运行时 配置的任何方式进行,包括本地事物(如果 有可用的本地事物)。

示例 11:在网络中发现 事物
let discovery = await WOT.discover();
setTimeout( () => {
    discovery.stop();
    console.log("已停止开放式发现");
  },
  10000);
for await (const td of discovery) {
  console.log("找到以下事物描述:" + td.title);
};
if (discovery.error) {
  console.log("发现因错误而停止:" + error.message);
}

11. 安全和 隐私

有关 Web of Things 的安全和隐私考虑事项的详细讨论, 包括可适用于各种情形的威胁模型,见信息性文档 [WOT-SECURITY]。 本节仅讨论与脚本和 WoT Scripting API 直接相关的安全和隐私风险以及 可能的缓解措施。

为提高 WoT 设备和服务安全性而建议采用的一组 最佳实践已记录在 [WOT-SECURITY] 中。 随着安全措施的发展,该文档可能会更新。 遵循这些实践并不能保证安全,但它 可能有助于避免常见的已知漏洞。

WoT 安全风险和可能的缓解措施涉及 以下几类:

11.1 脚本运行时安全 和隐私风险

本节是规范性的,并包含与 WoT 脚本运行时 相关的特定风险。

11.1.1 损坏输入的安全 和隐私风险

破坏任何进程的一种典型方式,是通过其公开的某个接口 向其发送损坏的输入。这种方式也可以 通过脚本实例公开的 WoT 接口对该实例实施。

缓解措施:
此 API 的实现者应该验证所有脚本 输入。除输入验证外,还应该使用模糊测试 来验证输入处理是否 正确完成。已有许多工具和技术 可以执行此类验证。更多详细信息可见 [WOT-SECURITY]。

11.1.2 直接访问物理设备的 安全和隐私风险

如果脚本遭到入侵或行为异常,并且脚本可以直接使用 所公开的原生设备接口,则底层物理设备 (以及潜在的周边环境)可能遭到损坏。如果这些接口的输入 缺乏安全检查,它们可能会使 底层物理设备(或环境)进入不安全 状态(即设备过热并爆炸)。

缓解措施:
WoT 脚本运行时应该避免将原生 设备接口直接公开给脚本开发者。相反, WoT 脚本运行时应该提供一个用于访问原生设备 接口的硬件抽象层。该硬件抽象层应该拒绝 执行可能使设备(或 环境)进入不安全状态的命令。此外,为了 在脚本遭到入侵时减少对物理 WoT 设备的损害, 应根据特定脚本的功能,最大限度减少 向其公开或允许其访问的接口 数量。

11.1.3 配置和更新 安全风险

如果 WoT 脚本运行时支持制造后 配置或更新脚本、WoT 脚本运行时 或任何相关数据(包括安全凭据), 这可能成为主要攻击向量。攻击者可能尝试 在更新或配置过程中修改上述任何 元素,或者直接配置攻击者的代码 和数据。

缓解措施:
制造后配置或更新脚本、 WoT 脚本运行时或任何相关数据,都应该以 安全的方式进行。有关安全 更新和制造后配置的一组建议可见 [WOT-SECURITY]。

11.1.4 安全凭据 存储的安全和隐私风险

通常,WoT 脚本运行时需要存储 配置给 WoT 设备、用于在 WoT 网络中运行的 安全凭据。如果攻击者能够破坏 这些凭据的机密性或完整性,那么 它就可能访问 WoT 资产、冒充 WoT 事物或设备,或者发起拒绝服务(DoS) 攻击。

缓解措施:
WoT 脚本运行时应该安全地存储 已配置的安全凭据,保证其 完整性和机密性。如果单个支持 WoT 的设备上存在多个 租户,则 WoT 脚本运行时应该保证每个 租户已配置安全凭据之间的隔离。此外,为了 最大限度降低已配置安全 凭据遭到泄露的风险,WoT 脚本运行时 不应该向脚本公开任何用于查询 已配置安全凭据的 API。

11.2 脚本安全和隐私 风险

本节是非规范性的。

本节描述与脚本 开发者相关的特定风险。

11.2.1 损坏脚本输入的 安全和隐私风险

脚本实例可能接收由 TD 定义的数据格式,或由应用程序定义的数据格式。虽然 WoT 脚本运行时应该 对 TD 定义的所有输入字段执行验证, 但脚本仍可能被输入数据利用。

缓解措施:
脚本开发者应该验证所有 由应用程序定义的脚本输入。除输入 验证外,还可以使用模糊测试 来验证输入处理是否 正确完成。已有许多工具和技术 可以执行此类验证。更多详细信息可见 [WOT-SECURITY]。

11.2.2 拒绝服务 安全风险

如果脚本在请求通过身份验证之前, 就对收到的请求执行繁重的功能处理, 则会带来很大的拒绝服务(DOS) 攻击风险。

缓解措施:
在请求者成功通过身份验证之前, 脚本应该避免执行繁重的功能处理。推荐的 身份验证机制集合可见 [WOT-SECURITY]。

A. API 设计原理

API 原理通常属于单独的文档,但在 WoT 的情况下,上下文的复杂性足以说明 在此包含基本原理是合理的。

A.1 WoT 应用程序 开发方法

WoT 兴趣组和工作组已经探索了 多种 WoT 应用程序开发方法, 并且这些方法都已得到实现和测试。

A.1.1 无脚本 API

可以开发仅使用 WoT 网络 接口的 WoT 应用程序,该接口通常由 WoT 网关公开, 此网关向客户端提供 RESTful API,并实现 与所支持 IoT 部署通信的 IoT 协议插件。此类实现之一是 Mozilla WebThings 平台。

A.1.2 简单脚本 API

WoT 事物 与软件对象具有很好的协同性,因此一个事物可以表示为 软件对象,其中属性表示为 对象属性,动作表示为方法, 事件 表示为事件。此外,元数据存储在特殊属性中。 消费和公开通过工厂方法完成,这些方法 生成一个直接表示远程 事物 及其交互的软件对象。此类实现之一是 Arena Web Hub 项目。

在下一个示例中,一个表示 与锁进行交互的事物如下所示: status 属性和 open() 方法直接公开在对象上。

示例 12:使用简单 API 打开锁
let lock = await WoT.consume(‘https://td.my.com/lock-00123’);
console.log(lock.status);
lock.open('withThisKey');

A.1.3 此 API 与 Web of Things (WoT) 事物描述 1.1 规范保持一致

由于将事物直接映射到软件对象 存在一些挑战,因此本规范采用另一种 方法,公开软件对象以将 事物 元数据表示为数据属性,并将 WoT 交互表示为方法。一个 实现是 node-wot, 它属于 Eclipse ThingWeb 项目,也是本文档所规定 API 的当前参考 实现。

现在,同一个示例将如下所示: status 属性和 open() 方法以间接方式表示。

示例 13:打开 锁
let res = await fetch(‘https://td.my.com/lock-00123’);
let td = await res.json();
let lock = new ConsumedThing(td);
console.log(lock.readProperty(‘status’));
lock.invokeAction(‘open’, 'withThisKey');

总之,WoT WG 决定探索第三种 方案,该方案紧密遵循 Web of Things (WoT) 事物描述 1.1 规范。基于此,也可以实现简单 API。 由于脚本是 WoT 中的可选模块, 这为仅使用WoT 网络 接口的应用程序留下了空间。因此,上述三种方法都得到 Web of Things (WoT) 事物描述 1.1 规范的支持。

此外,WoT 网络 接口可以使用多种语言和 运行时实现。可以将此 API 视为设计 WoT 脚本 API 时 需要考虑哪些因素的一个示例。

A.2 获取和验证 TD

fetch(url) 方法曾是此 API 早期版本的一部分。但是,现在给定 URL 获取TD应该使用 外部方法完成,例如 Fetch API 或 HTTP 客户端库,它们已经提供了用于指定获取细节的标准化 选项。原因是,虽然简单的 获取操作(涵盖大多数用例)可以在 此 API 中完成,但当需要各种获取选项时, 没有必要重复现有工作,再次在此 API 中公开这些 选项。

由于获取TD 已被排除在 范围之外,并且TD 验证是在 Web of Things (WoT) 事物描述 1.1 规范中外部定义的,因此这也被排除在范围之外。本规范 期望接收一个作为 已解析 JSON 对象TD, 且该对象已经依据 Web of Things (WoT) 事物描述 1.1 规范进行了验证。

A.3 工厂方法与构造函数

用于消费和公开事物的工厂方法是异步的,并且会完整地 验证输入TD。此外,还可以通过提供已解析和验证的 TD 来构造 ConsumedThingExposedThing。 随后在 WoT 交互期间按需完成平台初始化。

A.4 观察器

早期草案使用 Observer 构造,但由于它尚未成为标准,因此需要一种 对嵌入式实现足够轻量的新设计。 因此,观察属性变化和 处理 WoT 事件 是通过回调注册完成的。

A.5 使用事件

最终,此 API 完全没有使用软件事件, 原因如下:
  • 订阅 WoT 事件可能不同于 处理软件事件(订阅可能需要 参数,也可能涉及安全令牌等)。
  • 大多数实现面向 Node.js,而浏览器 实现很可能是库(因为原生 实现可能存在依赖管理问题),因此使用 Events 一直具有挑战性。
  • 观察属性变化和 处理 WoT 事件通过上述 方案完成。

A.6 多态函数

使用 readProperty()readMultipleProperties() 等函数名称,而不是通用多态 read() 函数的原因是,当前这些名称与 Web of Things (WoT) 事物描述 1.1 规范中 Form 定义的 "op" 词汇完全对应。

B. 变更

以下是本文档主要变更的列表。 本规范的主要版本如下:

有关完整变更列表,请参阅 github 变更日志。还可以查看 最近关闭的问题

C. 完整 Web IDL

WebIDLtypedef object ThingDescription;

[SecureContext, Exposed=(Window,Worker)]
namespace WOT {
  // 在 UA 一致性类中定义的方法
};

partial namespace WOT {
  Promise<ConsumedThing> consume(ThingDescription td);
};

typedef object ExposedThingInit;

partial namespace WOT {
  Promise<ExposedThing> produce(ExposedThingInit init);
};

partial namespace WOT {
  Promise<ThingDiscoveryProcess> discover(optional ThingFilter filter = {});
};

partial namespace WOT {
  Promise<ThingDiscoveryProcess> exploreDirectory(USVString url,
      optional ThingFilter filter = {});
};

partial namespace WOT {
  Promise<ThingDescription> requestThingDescription(USVString url);
};

typedef any DataSchemaValue;
typedef (ReadableStream or DataSchemaValue) InteractionInput;

[SecureContext, Exposed=(Window,Worker)]
interface InteractionOutput {
  readonly attribute ReadableStream? data;
  readonly attribute boolean dataUsed;
  readonly attribute Form? form;
  readonly attribute DataSchema? schema;
  Promise<ArrayBuffer> arrayBuffer();
  Promise<DataSchemaValue> value();
};

[SecureContext, Exposed=(Window,Worker)]
interface ConsumedThing {
  constructor(ThingDescription td);
  Promise<InteractionOutput> readProperty(DOMString propertyName,
                              optional InteractionOptions options = {});
  Promise<PropertyReadMap> readAllProperties(
                              optional InteractionOptions options = {});
  Promise<PropertyReadMap> readMultipleProperties(
                              sequence<DOMString> propertyNames,
                              optional InteractionOptions options = {});
  Promise<undefined> writeProperty(DOMString propertyName,
                              InteractionInput value,
                              optional InteractionOptions options = {});
  Promise<undefined> writeMultipleProperties(
                              PropertyWriteMap valueMap,
                              optional InteractionOptions options = {});
  /*Promise<undefined> writeAllProperties(
                              PropertyWriteMap valueMap,
                              optional InteractionOptions options = {});*/
  Promise<InteractionOutput> invokeAction(DOMString actionName,
                              optional InteractionInput params = {},
                              optional InteractionOptions options = {});
  Promise<Subscription> observeProperty(DOMString name,
                              InteractionListener listener,
                              optional ErrorListener onerror,
                              optional InteractionOptions options = {});
  Promise<Subscription> subscribeEvent(DOMString name,
                              InteractionListener listener,
                              optional ErrorListener onerror,
                              optional InteractionOptions options = {});
  ThingDescription getThingDescription();
};

dictionary InteractionOptions {
  unsigned long formIndex;
  object uriVariables;
  any data;
};

[SecureContext, Exposed=(Window,Worker)]
interface Subscription {
  readonly attribute boolean active;
  Promise<undefined> stop(optional InteractionOptions options = {});
};

[SecureContext, Exposed=(Window,Worker)]
interface PropertyReadMap {
  readonly maplike<DOMString, InteractionOutput>;
};

[SecureContext, Exposed=(Window,Worker)]
interface PropertyWriteMap {
  readonly maplike<DOMString, InteractionInput>;
};

callback InteractionListener = undefined(InteractionOutput data);
callback ErrorListener = undefined(Error error);

[SecureContext, Exposed=(Window,Worker)]
interface ExposedThing {
  ExposedThing setPropertyReadHandler(DOMString name,
          PropertyReadHandler handler);
  ExposedThing setPropertyWriteHandler(DOMString name,
          PropertyWriteHandler handler);
  ExposedThing setPropertyObserveHandler(DOMString name,
          PropertyReadHandler handler);
  ExposedThing setPropertyUnobserveHandler(DOMString name,
          PropertyReadHandler handler);
  Promise<undefined> emitPropertyChange(DOMString name,
          optional InteractionInput data);

  ExposedThing setActionHandler(DOMString name, ActionHandler action);

  ExposedThing setEventSubscribeHandler(DOMString name,
          EventSubscriptionHandler handler);
  ExposedThing setEventUnsubscribeHandler(DOMString name,
          EventSubscriptionHandler handler);
  Promise<undefined> emitEvent(DOMString name,
          optional InteractionInput data);

  Promise<undefined> expose();
  Promise<undefined> destroy();

  ThingDescription getThingDescription();
};

callback PropertyReadHandler = Promise<InteractionInput>(
        optional InteractionOptions options = {});

callback PropertyWriteHandler = Promise<undefined>(
        InteractionOutput value,
        optional InteractionOptions options = {});

callback ActionHandler = Promise<InteractionInput>(
        InteractionOutput params,
        optional InteractionOptions options = {});

callback EventSubscriptionHandler = Promise<undefined>(
        optional InteractionOptions options = {});

[SecureContext, Exposed=(Window,Worker)]
interface ThingDiscoveryProcess {
  constructor(optional ThingFilter filter = {});
  readonly attribute boolean done;
  readonly attribute Error? error;
  undefined stop();
  async iterable<ThingDescription>;
};

dictionary ThingFilter {
  object? fragment;
  
};

D. 致谢

特别感谢前编辑 Johannes Hund(任职至 2017 年 8 月,当时就职于 Siemens AG)和 Kazuaki Nimura(任职至 2018 年 12 月)对本规范的开发工作。此外,编辑们 还要感谢 Dave Raggett、Matthias Kovatsch、Michael Koster、Elena Reshetova、Michael McCool 以及其他 WoT WG 成员所提供的意见、贡献和 指导。

E. 参考文献

E.1 规范性参考文献

[ECMASCRIPT]
ECMAScript 语言规范。Ecma International。 URL:https://tc39.es/ecma262/multipage/
[fetch]
Fetch 标准。Anne van Kesteren。WHATWG。现行 标准。URL:https://fetch.spec.whatwg.org/
[html]
HTML 标准。Anne van Kesteren;Domenic Denicola; Ian Hickson;Philip Jägenstedt;Simon Pieters。WHATWG。 现行标准。URL:https://html.spec.whatwg.org/multipage/
[infra]
Infra 标准。Anne van Kesteren;Domenic Denicola。 WHATWG。现行标准。URL:https://infra.spec.whatwg.org/
[JSON-SCHEMA]
JSON Schema:用于描述 JSON 文档的媒体类型。Austin Wright;Henry Andrews;Ben Hutton;Greg Dennis。互联网工程任务组 (IETF)。2020 年 12 月 8 日。互联网草案。URL: https://datatracker.ietf.org/doc/html/draft-bhutton-json-schema
[RFC2119]
用于 RFC 中指示要求 级别的关键词。S. Bradner。IETF。1997 年 3 月。最佳 当前实践。URL:https://www.rfc-editor.org/rfc/rfc2119
[RFC8174]
RFC 2119 关键词中 大写与小写的歧义。B. Leiba。IETF。2017 年 5 月。最佳当前 实践。URL:https://www.rfc-editor.org/rfc/rfc8174
[streams]
Streams 标准。Adam Rice;Domenic Denicola;Mattias Buelens;吉野剛史 (Takeshi Yoshino)。WHATWG。现行标准。 URL:https://streams.spec.whatwg.org/
[TYPESCRIPT]
TypeScript 语言规范。 Microsoft。2012 年 10 月 1 日。URL: https://www.typescriptlang.org/docs/handbook/intro.html
[url]
URL 标准。Anne van Kesteren。WHATWG。现行 标准。URL:https://url.spec.whatwg.org/
[WEBIDL]
Web IDL 标准。Edgar Chen;Timothy Gu。WHATWG。 现行标准。URL:https://webidl.spec.whatwg.org/
[WOT-ARCHITECTURE]
Web of Things (WoT) 架构 1.1。 W3C。2023 年 1 月 19 日。URL: https://www.w3.org/TR/2023/CR-wot-architecture11-20230119/
[WOT-PROTOCOL-BINDINGS]
Web of Things (WoT) 绑定模板。 W3C。2020 年 1 月 30 日。URL: https://www.w3.org/TR/2020/NOTE-wot-binding-templates-20200130/
[WOT-SECURITY]
Web of Things (WoT) 安全和隐私 指南。W3C。2019 年 11 月 6 日。URL: https://www.w3.org/TR/2019/NOTE-wot-security-20191106/
[WOT-TD]
Web of Things (WoT) 事物描述 1.1。W3C。2023 年 1 月 19 日。URL: https://www.w3.org/TR/2023/CR-wot-thing-description11-20230119/

E.2 信息性参考文献

[WOT-DISCOVERY]
Web of Things (WoT) 发现。W3C。2023 年 1 月 19 日。URL:https://www.w3.org/TR/2023/CR-wot-discovery-20230119/
[WOT-USE-CASES]
Web of Things (WoT):用例和 要求。W3C。2022 年 3 月 7 日。URL:https://www.w3.org/TR/2022/NOTE-wot-usecases-20220307/