Copyright © 2017-2023 World Wide Web Consortium. W3C® liability, trademark and permissive document license rules apply.
万维物联网由实体(物)组成,这些实体可以在机器可解释的物 描述(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/ 中找到。
实现者需要注意,本规范 被认为是不稳定的。有意在本规范最终达到候选 推荐阶段之前实现本 规范的厂商,应订阅该仓库并 参与讨论。
请使用 GitHub Issues 页面为本草案做出贡献,该页面属于 WoT 脚本 API 仓库。关于安全和隐私 考量的反馈,请使用 WoT 安全与 隐私 Issues。
本文档由 Web of Things 工作 组作为小组说明发布,使用 说明 轨道。
本小组说明得到 Web of Things 工作 组认可,但未得到 W3C 本身或其 成员认可。
这是一份草案文档,可能随时被更新、替换或 废弃。不宜 将本文档作为非进行中工作的内容引用。
W3C 专利政策并 不对本文档施加任何许可要求或承诺。
本文档受 2023 年 6 月 12 日 W3C 流程 文档约束。
WoT 基于物的使用方式提供分层互操作性:即“被使用”和 “被公开”,如 Web of Things(WoT)架构 1.1 术语中所定义。
通过使用 TD,客户端物 会创建一个本地运行时资源模型,该模型允许访问远程设备上服务器 物所公开的 属性、动作和事件。
通常,脚本旨在用于桥接器 或网关,这些桥接器或网关将较简单的设备公开和控制为 WoT 物,并且 具有处理(例如安装、卸载、更新等)和运行 脚本的手段。
本节为非规范性内容。
[WOT-USE-CASES] 文档中列出的业务用例可以使用此 API 实现,基础是此处描述的 脚本用例场景。
除标记为非规范性的章节外,本规范中的所有编写 指南、图表、示例和注释均为非规范性内容。除此之外,本规范中的所有内容都是 规范性的。
本文档中的关键词 MAY、MUST 和 SHOULD 应按 BCP 14 [RFC2119] [RFC8174] 中的描述解释,但仅当它们以全大写形式出现时, 如此处所示。
本规范曾是一个预期成为 W3C 推荐标准的工作草案。 然而,它现在是一个 WG 说明,其中仅包含资料性 陈述。因此,我们需要考虑如何处理 本一致性章节中的描述。
本规范描述以下类别的用户代理 (UA)的一致性标准。
由于小型嵌入式实现的要求,
需要拆分 WoT 客户端和服务器接口。随后,
发现是一个分布式应用,但典型场景
已通过本规范中的通用发现 API 覆盖。
这导致实现此 API 的
UA 使用 3 个一致性类别:一个用于
客户端,一个用于服务器,一个用于发现。使用此 API 的应用
可以内省 WoT API 对象上是否存在
consume()、produce() 和
discover() 方法,以便
确定该 UA 实现的是哪个一致性类别。
此一致性类别的实现 MUST 实现
接口,以及
WoT
API 对象上的 ConsumedThingconsume() 方法。
此一致性类别的实现 MUST 实现
接口,以及
WoT
API 对象上的 ExposedThingproduce() 方法。
此一致性类别的实现 MUST 实现
接口,以及
WoT
API 对象上的 ThingDiscoveryProcessdiscover() 方法、
exploreDirectory() 方法和
requestThingDescription() 方法。
这些一致性类别 MAY 可以在单个 UA 中实现。
本规范可用于以多种编程语言实现 WoT 脚本 API。接口 定义在 [WEBIDL] 中指定。
UA 可以在 浏览器中实现,也可以在单独的运行时环境中实现,例如 Node.js,或在 小型嵌入式 运行时中实现。
使用在浏览器中执行的 ECMAScript 来 实现本文档中定义的 API 的实现,MUST 以与 Web IDL 规范中定义的 ECMAScript 绑定 [WEBIDL] 一致的方式实现它们。
使用运行时中的 TypeScript 或 ECMAScript 来 实现本文档中定义的 API 的实现, MUST 以与 TypeScript 规范中定义的 TypeScript 绑定 [TYPESCRIPT] 一致的方式实现它们。
通用 WoT 术语定义于 [WOT-ARCHITECTURE]: Thing、 Thing Description(简称 TD)、Partial TD、Web of Things(简称 WoT)、WoT Interface、 Protocol Bindings、WoT Runtime、Consuming a Thing Description、 TD Directory、Property、Action、Event、 DataSchema、 Form、 SecurityScheme、 NoSecurityScheme 等。
WoT Interaction 是 Interaction Affordance 的同义词。Interaction Affordance(或简称 affordance)是 [WOT-TD] 在指称物 能力时使用的术语,如 TD issue 282 中所解释。然而,该术语在 TD 语义 上下文之外并不易理解。因此,为提高可读性,本文档将 改用先前的术语 WoT interaction,或简称 interaction。
WoT network interface 是 WoT Interface 的同义词。
JSON Schema 定义于 这些 规范中。
Promise、
Error、
JSON、
JSON.stringify、
JSON.parse、
internal method 和
internal slot 定义于 [ECMASCRIPT]。
WebIDLtypedef object ThingDescription;
表示 [WOT-TD] 中 定义的一个物描述 (TD)。 它预期是一个 解析后的 JSON 对象,并使用 JSON Schema 验证进行验证。
给定 URL 获取 TD 应通过外部方法完成,例如 Fetch API 或 HTTP 客户端库,这些方法已经提供了用于指定获取细节的标准化选项。
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);
}
请注意, Web of Things(WoT)物描述 1.1 规范允许借助 默认值使用简写的物 描述,并要求客户端使用 Web of Things(WoT)物描述 1.1 规范中为给定 TD 中未显式 定义的属性指定的默认值来展开它们。
[WOT-TD]
规范定义了应如何验证 TD。因此,
此 API 期望 ThingDescription
对象在作为参数使用之前已被验证。本
规范定义如下基本的 TD 验证。
TypeError”
并停止。
可以添加其他步骤来填充 必填字段的默认值。
将 WoT API 对象 定义为单例,并包含按 一致性类别分组的 API 方法。
WebIDL[SecureContext, Exposed=(Window,Worker)]
namespace WOT {
// methods defined in UA conformance classes
};
WebIDLpartial namespace WOT {
Promise<ConsumedThing> consume(ThingDescription td);
};
Promise,该 Promise 会以一个
ConsumedThing
对象兑现,该对象表示用于操作
物的客户端接口。
该方法 MUST 运行以下
步骤:
Promise promise,并
并行执行
后续步骤。
SecurityError
拒绝
promise,并停止。
ConsumedThing
对象。
请注意构造
ConsumedThing
与使用 consume() 方法之间的区别:后者
还会初始化协议绑定,而简单
构造的对象在其被调用之前,不会初始化
WoT 交互。
WebIDLtypedef object ExposedThingInit;
partial namespace WOT {
Promise<ExposedThing> produce(ExposedThingInit init);
};
Promise,该 Promise 会以一个
ExposedThing
对象兑现,该对象通过服务器接口扩展 ConsumedThing,
即定义请求
处理器的能力。init
对象是 ExposedThingInit
类型的一个实例。具体而言,一个 ExposedThingInit
值是用于初始化
ExposedThing
的字典,并且它表示一个
[WOT-ARCHITECTURE] 中描述的
Partial
TD。
因此,它具有与物描述相同的结构,
但可以省略一些信息。该方法 MUST 运行以下步骤:
Promise promise,并
并行执行
后续步骤。
SecurityError
拒绝
promise,并停止。
ExposedThing
对象。
SyntaxError
并停止。
"securityDefinitions"]
中的每个 scheme,
向底层平台发出请求,检查它是否
至少受一个协议绑定支持。
如果不支持,则从 td 中移除 scheme。
"security"]
在 td.["securityDefinitions"] 中
不存在,
则从 td 中移除 security。
authority
识别为有效,则从
form 中移除
href。
编辑认为此步骤含糊。它将在 下一次迭代中得到改进或移除。
title,
生成一个运行时唯一名称并赋给
title。
@context,
分配最新受支持的 Thing Description 上下文
URI。instance,
分配字符串 1.0.0。forms,
使用可用的
协议
绑定和内容类型编码器生成一个 Forms 列表。然后
将所得列表赋给 forms。
security,
分配 securityDefinitions 字段中第一个受支持的
SecurityScheme 的标签。如果未找到
SecurityScheme,
则生成一个名为 nosec 的
NoSecurityScheme,
并将字符串 nosec 赋给 security。
关于如何适当地
为 security 生成值的讨论
仍处于开放状态。参见 issue
#299
href,则将
formStub 定义为不具有
href 的部分 Form。使用第一个
满足 formStub 要求的
协议
绑定生成一个有效的
url。将 url 赋给
href。如果找不到
协议
绑定,则从 td 中移除 formStub。
title、
@context、instance、
forms、security 和
href。
required 的每个属性和子属性 key,执行以下
步骤:
Array,则移除其中所有等于
optional 中元素的元素
string,则如果 value 等于
optional 中的某个元素,则从
exposedThingInitSchema 中移除 key
validating an object with JSON Schema 步骤仍在讨论中。目前,本 规范引用 JSONSchema 的验证过程。 在用 exposedThingInitSchema 验证 init 时,请遵循此 文档。请注意, 工作组正在评估另一种形式化 方法。
WebIDLpartial namespace WOT {
Promise<ThingDiscoveryProcess> discover(optional ThingFilter filter = {});
};
ThingFilter
的可选 filter 实参匹配的物描述的
ThingDescription
对象。
该方法 MUST 运行以下
步骤:
Promise promise,并
并行执行
后续步骤。
SecurityError
拒绝
promise,并停止。
NotSupportedError
拒绝
promise,并停止。
ThingDiscoveryProcess
对象。
[[filter]] 设置为
filter。
[[url]] 设置为
undefined。
undefined
或 null,则用
NotSupportedError
拒绝
promise,并停止。
OperationError
拒绝
promise,并停止。
WebIDLpartial namespace WOT {
Promise<ThingDiscoveryProcess> exploreDirectory(USVString url,
optional ThingFilter filter = {});
};
ThingFilter
的可选 filter 实参匹配的物描述的
ThingDescription
对象。
该方法 MUST 运行以下
步骤:
Promise promise,并
并行执行
后续步骤。
SecurityError
拒绝
promise,并停止。
NotSupportedError
拒绝
promise,并停止。
ThingDiscoveryProcess
对象。
[[url]] 设置为
url。
[[filter]] 设置为
filter。
这是发现算法中 更多细节的占位符。实现应 遵循 [WOT-DISCOVERY] 和 [WOT-PROTOCOL-BINDINGS] 规范中描述的过程。下面 指出了一些规范性步骤。
NotSupportedError
拒绝
promise,并终止
这些步骤。
undefined 或 null,
则用
NotSupportedError
拒绝
promise,并停止。
从此时起,错误
只记录在
error 上,但不再影响
promise。
WebIDLpartial namespace WOT {
Promise<ThingDescription> requestThingDescription(USVString url);
};
Promise promise,并
并行执行
后续步骤。
SecurityError
拒绝
promise,并停止。
NotSupportedError
拒绝
promise,并停止。
NotFoundError
拒绝
promise,并停止。
如
Web of Things(WoT)物描述 1.1
规范所指定,WoT 交互扩展
DataSchema,并包含
若干可能的表单,其中一个会被
选作该交互使用。
表单包含一个 contentType,用于描述
数据。对于某些内容类型,会定义一个基于
JSON
Schema 的 DataSchema,从而
可以将这些内容表示为 JavaScript 类型,并
最终在数据上设置范围约束。
WebIDLtypedef any DataSchemaValue;
typedef (ReadableStream or DataSchemaValue) InteractionInput;
属于 WoT Consumer 一致性 类别,并表示由应用脚本提供给 UA 的 WoT 交互数据。
DataSchemaValue 是一个
ECMAScript 值,可被 [WoT-TD] 中定义的
DataSchema接受。
可能的值 MUST 属于
null、
boolean、
number、
string、array
或 object 类型。
ReadableStream
旨在用于那些在物
描述中没有
DataSchema,而只有
可以由流表示的Form 的
contentType 的 WoT 交互。
实践中,任何
ECMAScript 值都可用于那些在
物描述中定义了
DataSchema 的 WoT
交互,或用于那些可由实现映射到
物
描述中定义的 Form 的
contentType 的交互。
本文档中的算法指定了输入 数据在 WoT 交互中到底如何使用。
属于 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
是有效值)。
contentType 所描述类型的值。该
方法
MUST 运行以下步骤:
Promise promise,并
并行执行
后续步骤。
undefined,则以该值
兑现
promise
并停止。
ReadableStream,或 dataUsed 为
true,或 form 不是
object,
或 schema 或其 type 为
null 或 undefined,则用
NotReadableError
拒绝
promise 并停止。
application/json,并且
协议绑定中没有可用的映射
可将 form.contentType 映射到
[JSON-SCHEMA],
则用
NotSupportedError
拒绝
promise 并停止。
true。application/json,并且
协议
绑定中存在可用映射,可将
form.contentType 映射到
[JSON-SCHEMA],
则使用该映射转换 bytes。
Promise promise,并
并行执行
后续步骤。
ReadableStream,或 dataUsed 为
true,则用
NotReadableError
拒绝
promise 并停止。
true。
ArrayBuffer,其内容为
bytes。如果这抛出异常,则用该
异常拒绝
promise 并停止。
"null",并且
payload 不是 null,则抛出
TypeError 并停止;否则返回
null。
"boolean",且
payload 是 falsy 值或其字节长度为
0,则返回 false;否则返回
true。
"integer" 或
"number",
TypeError 并停止。
RangeError 并停止。
"string",则返回
payload。
"array",则运行这些
子步骤:
TypeError 并停止。
RangeError 并停止。
"object",则运行
这些子步骤:
object,
则抛出
TypeError 并停止。
SyntaxError 并停止。
ConsumedThing
对象 thing,为
在给定 source、form 和 schema 的情况下
创建
交互请求,运行这些步骤:
InteractionOutput
对象。
null,并将
idata.[[value]] 设置为
undefined。
ReadableStream 对象,则令
idata.data 为 source,返回
idata 并停止。
null,则运行
这些子步骤:
"null",并且
source 不是
"null",则抛出
TypeError 并停止。
"boolean",并且
source 是
falsy 值,则将
idata.[[value]] 设置为
false;否则将其设置为
true。
"integer" 或
"number",且 source 不是
数字,或
form.minimum 已定义且
source
更小,或 form.maximum 已定义且
source
更大,则抛出
RangeError 并停止。
"string",且
source 不是
字符串,则令 idata.[[value]] 为
给定 source 运行
serialize JSON to bytes 的结果。如果该结果为
failure,则抛出
SyntaxError 并停止。
"array",则运行
这些子步骤:
TypeError 并停止。
RangeError 并停止。
[[value]]
设置为 source。
"object",则运行
这些子步骤:
TypeError 并停止。
TypeError 并停止。
SyntaxError 并停止。
[[value]]
设置为 source。
ReadableStream,该流由
idata.[[value]]
内部槽创建,并将其作为该流的底层
源。
ConsumedThing
对象 thing,为
在给定 response、
form 和 schema 的情况下
解析
交互响应,运行这些步骤:
InteractionOutput
对象。
ReadableStream,其中
response 的载荷数据作为其底层
源。
false。
InteractionInput
和 InteractionOutput
如下图所示,每当实现向
脚本提供数据时,都会使用
InteractionOutput
接口;而当脚本向实现传递数据时,
使用 InteractionInput。
当 ConsumedThing
读取数据时,它会从实现接收一个
InteractionOutput
对象。
ExposedThing
读取处理器
以 InteractionInput
的形式向实现提供读取数据。
当 ConsumedThing
写入数据时,它会以
InteractionInput
的形式将数据提供给实现。
ExposedThing
写入
处理器会从实现接收一个
InteractionOutput
对象作为数据。
当 ConsumedThing
调用一个动作时,
它会以 InteractionInput
的形式提供参数,并以 InteractionOutput
对象的形式接收该动作的输出。
ExposedThing
动作处理器
以
InteractionOutput
对象的形式从实现接收实参,并以
InteractionInput
的形式向实现提供动作输出。
此 API 中的算法定义了要 报告给应用脚本的错误。
报告给另一通信端的错误由 协议 绑定进行映射和封装。
此主题仍在 Issue #200 中讨论。为了确保将脚本错误映射到 协议错误以及反向映射的一致性,需要一个标准化的错误映射。 尤其是,当 算法提到“从协议绑定收到的错误”时, 这将被分解为一个显式的错误映射 算法。目前,它由 实现封装。
表示用于操作一个物的客户端 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() 方法
仍在讨论中。与此同时,请改用
writeMultipleProperties() 方法。
ConsumedThing 的内部槽ConsumedThing
对象具有以下
内部槽:
| 内部槽 | 初始值 | 描述(非规范性) |
|---|---|---|
| [[td]] | null |
ConsumedThing 的物
描述。
|
| [[activeSubscriptions]] | {} |
一个有序
映射,其键为
表示事件的
字符串
名称,而
值是
一个Subscription
对象。
|
| [[activeObservations]] | {} |
一个有序
映射,其键为
表示某个属性的
字符串
名称,而
值是
一个Subscription
对象。
|
在以 JSON 对象形式获取
一个物描述之后,可以创建一个
ConsumedThing
对象。
ThingDescription
td 创建 ConsumedThing,
运行以下步骤:
SyntaxError
并停止。
ConsumedThing
对象。
[[td]] 设置为
td。
返回
ConsumedThing
对象的 [[td]],该对象表示
ConsumedThing 的物描述。
应用可以查询存储在
[[td]] 中的物元数据,以便在与其交互之前
内省其能力。
Promise,该 Promise 会以一个
InteractionOutput
对象形式表示的
属性值兑现,
或在错误时拒绝。该方法 MUST 运行以下步骤:
Promise promise,并
并行执行
后续步骤。
SecurityError
拒绝
promise
并停止。
[[td]].properties.propertyName。
undefined,
则用
NotFoundError
拒绝
promise
并停止。
readproperty 的
表单,由
实现选择。
SyntaxError
拒绝
promise
并停止。
SyntaxError
拒绝
promise
并停止。
Promise,该 Promise 会以一个
PropertyReadMap
对象兑现,该对象将 propertyNames 中的键映射到
由此算法返回的值。该方法 MUST
运行以下步骤:
Promise promise,并
并行执行
后续步骤。
SecurityError
拒绝
promise
并停止。
[[td]].forms
数组中与 formIndex 关联的表单;否则,
令 form 为
[[td]].forms
数组中 op 为
readmultipleproperties 的表单,
由实现选择。
SyntaxError
拒绝
promise
并停止。
null 的属性。
NotSupportedError
拒绝
promise 并停止。
[[td]].properties[key]。
Promise,该 Promise 会以一个
PropertyReadMap
对象兑现,该对象将属性名称中的键映射到
由此算法返回的值。该方法 MUST 运行以下步骤:
Promise promise,并
并行执行
后续步骤。
SecurityError
拒绝
promise
并停止。
[[interaction]].forms。
undefined,
则用
SyntaxError
拒绝
promise
并停止。
undefined 且小于
forms.length,则将
subscription.[[form]] 设置为
forms.[formIndex]。
[[form]] 设置为
forms 中一个
op 为
"readallproperties" 的
表单,
由实现选择。
[[form]] 是
failure,则用
SyntaxError
拒绝
promise
并停止。
NotSupportedError
拒绝
promise 并停止。
[[td]].properties[key]。
Promise,该 Promise 在成功时兑现,
并在失败时拒绝。该方法 MUST 运行以下步骤:
Promise promise,并
并行执行
后续步骤。
SecurityError
拒绝
promise
并停止。
[[td]].properties[propertyName]。
undefined,
则用
NotFoundError
拒绝
promise
并停止。
undefined,则令 form 为
interaction.forms 数组中与
formIndex 关联的
表单;
否则,令 form 为
interaction.forms 中一个
op 为 writeproperty 的
表单,
由实现选择。
SyntaxError
拒绝
promise
并停止。
promise 并停止。
如 Issue #193 中所讨论,设计决定是写入交互 只返回成功或错误,而不返回写入的值 (可选)。TD 应 捕获属性值的模式,包括 精度和替代格式。当交互预期有返回值时, 应使用动作而不是 属性。
Promise,该 Promise 在成功时兑现,
并在失败时拒绝。该方法 MUST 运行以下步骤:
Promise promise,并
并行执行
后续步骤。
SecurityError
拒绝
promise
并停止。
[[td]].forms
数组中与 formIndex 关联的表单;否则,
令 form 为
[[td]].forms
数组中 op 为
writemultipleproperties 的表单,
由实现选择。
SyntaxError
拒绝
promise
并停止。
[[td]].properties[name]。
null 或
undefined,或者不是 writeable,
则用
NotSupportedError
拒绝
promise 并停止。
null。
[[td]].properties[name]。
promise 并停止。
NotSupportedError
拒绝
promise 并停止。
Promise,该 Promise 在成功时兑现,
并在失败时拒绝。
此算法每个
属性只允许一个活动的
Subscription。
如果在已有活动
Subscription
时创建新的
Subscription,
运行时将抛出
NotAllowedError。
ConsumedThing
对象的引用。
Promise promise,并
并行执行
后续步骤。
SecurityError
拒绝
promise
并停止。
Function,
则用
TypeError
拒绝
promise 并停止。
null,并且不是 Function,
则用
TypeError
拒绝
promise 并停止。
[[activeObservations]][propertyName]
[=map/exists],则用
NotAllowedError
拒绝
promise 并停止。
Subscription
对象,其
内部槽设置如下:
[[type]] 为
"property"。
[[name]] 为
propertyName。
[[interaction]]
为 [[td]].properties[propertyName]。
[[thing]] 为
thing。
[[interaction]].forms。
undefined,
则用
SyntaxError
拒绝
promise 并停止。
undefined 且小于
forms.length,则将
subscription.[[form]] 设置为
forms.[formIndex]。
[[form]] 设置为
forms 中一个
op 为
"observeproperty" 的
表单,
由实现选择。
[[form]] 是
failure,则用
SyntaxError
拒绝
promise 并停止。
[[interaction]]
为 undefined,则用
NotFoundError
拒绝
promise 并停止。
[[activeObservations]][|propertyName]
设置
为 subscription,并兑现
promise。
[[form]] 和
subscription.[[interaction]]
运行
parse
interaction response 的结果。
如果这抛出异常,则用该异常
拒绝
promise 并停止。
false,并抑制后续
通知。
NetworkError,并将其
message 设置为反映底层错误
条件的内容。
Function,
则用 error 调用它。
Promise,该 Promise 会以
动作的结果兑现,
该结果表示为一个 InteractionOutput
对象;或以错误拒绝。该方法 MUST 运行以下步骤:
Promise promise,并
并行执行
后续步骤。
SecurityError
拒绝
promise
并停止。
[[td]].actions[actionName]。
object,
则用
NotFoundError
拒绝
promise
并停止。
[[interaction]].forms。
undefined,
则用
SyntaxError
拒绝
promise
并停止。
undefined 且小于
forms.length,则将
subscription.[[form]] 设置为
forms.[formIndex]。
[[form]] 设置为
forms 中一个 op 为
"invokeaction" 的
表单,
由实现选择。
[[form]] 是
failure,则用
SyntaxError
拒绝
promise
并停止。
promise 并停止。
Promise 来表示成功或
失败。
此算法每个
事件只允许一个活动的
Subscription。
如果在已有活动
Subscription
时创建新的
Subscription,
运行时将抛出
NotAllowedError。
ConsumedThing
对象的引用。
Promise promise,并
并行执行
后续步骤。
SecurityError
拒绝
promise
并停止。
Function,
则用
TypeError
拒绝
promise 并停止。
null,并且不是 Function,
则用
TypeError
拒绝
promise 并停止。
[[activeSubscriptions]][eventName]
不存在,
则用
NotAllowedError
拒绝
promise 并停止。
Subscription
对象,其
内部槽设置如下:
[[type]] 为
"event"。
[[name]] 为
eventName。
[[interaction]]
为 thing. [[td]].events[eventName]。
[[interaction]]
为 undefined,则用
NotFoundError
拒绝
promise 并停止。
[[thing]] 为
thing。
[[form]] 为
thing.[[interaction]].forms[formIndex]。
[[form]] 为
subscription.[[interaction]].forms
数组中一个由
实现定义的、op 为
"subscribeevent" 的表单。
[[form]]
不存在,
则用
SyntaxError
拒绝
promise 并停止。
[[form]]、options.uriVariables
中给定的可选 URI 模板,以及
options.data 中给定的可选订阅数据,
订阅由
eventName 标识的
事件。
[[activeSubscriptions]][eventName]
为 subscription。
[[form]] 和
subscription.[[interaction]]
运行 parse
interaction response 的结果,
调用 listener。
false,并抑制后续
通知。
NetworkError,并将其
message 设置为反映底层错误
条件的内容。
Function,
则用 error 调用它。
保存根据物 描述需要向 应用脚本公开的交互选项。
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
属性如果已定义,表示需要传递给交互的
额外不透明数据。
表示从属性名称到
InteractionOutput
对象的映射,该对象表示属性可以取的值。它用作涉及多个
属性同时进行的交互的
属性包。
表示从属性名称到
InteractionInput
的映射,后者表示属性可以取的值。它用作涉及多个
属性同时进行的交互的
属性包。
用户提供的回调,会收到一个类型为
InteractionOutput
的实参,用于观察属性变化以及处理
事件
通知。
由于订阅事件是
WoT 交互,并且
可能接受选项甚至数据,因此它们不使用
软件事件建模。
active
布尔属性表示该订阅是否处于活动状态,即
它未因错误或因调用 stop() 方法而
停止。
Subscription 的内部槽
Subscription
对象具有以下
内部槽:
| 内部槽 | 初始值 | 描述(非规范性) |
|---|---|---|
| [[type]] | null |
指示该
Subscription
指向哪个WoT
交互。该值可以是
"property"、"event" 或
null。
|
| [[name]] | null |
属性或 事件名称。 |
| [[interaction]] | null |
描述该 WoT 交互的物 描述片段。 |
| [[form]] | null |
与订阅关联的表单。 |
| [[thing]] | null |
与订阅关联的
ConsumedThing。
|
停止传递该订阅的通知。它
接受一个可选参数 options,并返回一个
。调用时,该方法
MUST 执行以下
步骤:
Promise
Promise promise,并
并行执行
后续步骤。
SecurityError
拒绝
promise
并停止。
[[interaction]] 的
forms 数组中与
formIndex 关联的表单。
[[form]] 运行
find a
matching unsubscribe form 算法的
结果。
SyntaxError
拒绝
promise
并停止。
[[type]] 为
"property",则通过
协议
绑定向底层
平台发出请求,使用
unsubscribeForm 以及
options 的
uriVariables 中给定的可选 URI 模板,停止观察由
[[name]] 标识的
属性。
[[type]] 为
"event",则通过
协议
绑定向底层
平台发出请求,使用
unsubscribeForm、options 的
uriVariables 中给定的可选 URI 模板,以及
options.data 中给定的可选取消订阅数据,
取消订阅由
[[name]] 标识的
事件。
false。
[[type]] 为
"event",则从
[[thing]].[[activeSubscriptions]]
移除 [[name]]
。
[[type]] 为
"property",则从
[[thing]].[[activeObservations]]
移除 [[name]]
。
Subscription
对象的上下文中,给定
subscribeForm 来查找匹配的 unsubscribe 表单,运行以下步骤:
[[interaction]].forms 中的每个
form,
null 并终止这些步骤。
下一个示例展示如何通过 URL 获取 TD,创建
ConsumedThing,
读取元数据(title)、读取属性值、订阅
属性变更、订阅 WoT 事件以及取消订阅。
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 的属性。
/*
* 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 ...]
}
最后,接下来的两个示例展示了如何使用来自
InteractionOutput 的
ReadableStream。
/*{
"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 记录的事件 总数。
/*
* "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 个对象
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 = {});
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
|
ExposedThingExposedThing
接口扩展了 ConsumedThing。
它从一个完整或部分的 ThingDescription
对象构造。
注意,现有的 ThingDescription
对象可以被可选地修改(例如通过在其 properties、
actions 和 events 内部
属性上添加或移除元素),并且所得对象可以用于
构造一个 ExposedThing
对象。这是当前添加和移除
属性、动作和事件定义的方式,如
示例中所示。
在调用 expose()
之前,
ExposedThing
对象不会服务任何请求。这允许先
构造 ExposedThing,
然后在开始服务请求之前初始化其
属性和服务
处理器。
ExposedThingInit
init 构造一个
ExposedThing,
运行以下步骤:
SecurityError
并停止。
ExposedThing
对象。
[[td]] 设置为
td。
返回
ExposedThing
对象的 [[td]],该对象表示
物的物描述。
应用可以
查询存储在 [[td]] 中的
物
元数据,以便在与其交互之前
内省其能力。
当收到读取某个属性的外部请求时被调用的函数,
并定义如何处理此类请求。它返回一个
,并以一个
PromiseReadableStream
对象,或一个符合 DataSchema 的
ECMAScript 值兑现,或者以
错误拒绝。
接受 name 和 handler 作为实参。设置 服务处理器,该处理器定义当收到读取与 name 匹配的指定属性的请求时 应该做什么。错误时抛出异常。返回 对 this 对象的引用以支持 链式调用。
注意,不需要注册用于处理读取多个或全部属性请求的处理器。
请求和回复在单个网络请求中传输,但
ExposedThing
可以通过多次调用单个读取
处理器来实现它们。
handler 回调函数应该实现读取一个属性,并且当从 底层平台收到读取一个属性的请求时, 实现 SHOULD 调用它。
对于任何给定的属性,最多 MUST 有一个处理器,
因此新添加的
处理器 MUST 替换先前的
处理器。如果没有为任何给定的属性初始化处理器,实现
SHOULD 基于
[[td]]
内部槽中提供的物描述
实现默认属性读取处理器。
SecurityError
并停止。
[[td]].properties[name]
不存在,
则抛出
NotFoundError
并停止。
[[readHandlers]][name]
设置为 handler。
NotSupportedError 并停止。
NotAllowedError 并停止。
[[td]].properties.name。
NotFoundError 并停止。
null。
[[readHandlers]]
内部槽中存在用于 interaction 的用户提供的
PropertyReadHandler,
则令 handler 为它。
null,则抛出
NotSupportedError 并停止。
这里返回的 value
SHOULD 要么符合
DataSchema,要么
SHOULD 是一个由
handler 创建的
ReadableStream 对象。
NotSupportedError 并停止。
NotAllowedError 并停止。
NotSupportedError 并停止。
NotAllowedError 并停止。
null。
接受 name 和 handler 作为实参。设置 服务处理器,该处理器定义当收到观察与 name 匹配的指定属性的请求时 应该做什么。错误时抛出异常。返回 对 this 对象的引用以支持 链式调用。
handler
回调函数应该实现读取一个属性,并
以一个
InteractionOutput
对象兑现,或以
错误拒绝。
对于任何给定的属性,最多 MUST 有一个处理器, 因此新添加的 处理器 MUST 替换先前的 处理器。如果没有为任何给定的属性初始化处理器,实现 SHOULD 基于物 描述实现默认属性 读取处理器。
SecurityError
并停止。
[[td]].properties[name]
不存在,
则抛出
NotFoundError
并停止。
[[observeHandlers]][name]
设置为 handler。
NotSupportedError 并停止。
NotAllowedError 并停止。
[[td]].properties[name]
不存在,
则在回复中发回一个 NotFoundError
并停止。
[[propertyObservers]][name],
以便能够通知属性值
变更。
每当 property 的值
发生变化时,应用
脚本都需要显式调用 emitPropertyChange()。
接受 name 和 handler 作为实参。设置 服务处理器,该处理器定义当收到取消观察与 name 匹配的指定属性的请求时 应该做什么。错误时抛出异常。返回 对 this 对象的引用以支持 链式调用。
handler 回调函数应该实现当实现收到 取消观察请求时应做什么。
对于任何给定的属性,最多 MUST 有一个处理器, 因此新添加的 处理器 MUST 替换先前的 处理器。如果没有为任何给定的属性初始化处理器,实现 SHOULD 基于物 描述实现默认处理器。
SecurityError
并停止。
[[td]].properties[name]
不存在,
则抛出
NotFoundError
并停止。
[[unobserveHandlers]][name]
设置为 handler。
NotSupportedError 并停止。
NotAllowedError 并停止。
[[td]].properties[name]
不存在,
则在回复中发回一个 NotFoundError
并停止。
[[unobserveHandlers]][name];
Function,
则使用 options 调用它,然后发回一个
遵循协议绑定的
回复并停止。
[[propertyObservers]][name]
存在,
则将其从 this.[[propertyObservers]] 中移除,
按照协议
绑定中定义的方式发回一个回复并停止。
NotFoundError
并停止。
Promise。
SecurityError
拒绝
promise
并停止。
[[td]].properties[name]。
undefined,
则用
NotFoundError
拒绝
promise
并停止。
undefined,则运行以下子步骤:
null.
[[readHandlers]] 中不
存在,
则拒绝
promise 并停止。
[[readHandlers]][name]。
null 或 undefined,
则拒绝
promise 并停止。
null 的结果。
[[propertyObservers]][name] 中的每个 observer,
运行以下子步骤:
此条款需要扩展,和/或 引用 [WOT-PROTOCOL-BINDINGS] 中的算法。
当收到写入某个属性的外部请求时被调用的函数,
并定义如何处理此类请求。接受
value 作为实参并返回一个
,当
由设置处理器时提供的名称标识的
属性值已更新时兑现;或者
如果未找到该属性或无法更新该
值,则以错误拒绝。
Promise
注意,如果需要,此回调函数中的代码 可以在更新属性之前读取该属性,以便 找出旧值。因此旧值 不会提供给此函数。
该值由实现以
InteractionOutput
对象形式提供,以便能够表示
未由 DataSchema 描述的值,例如
流。
接受 name 和 handler 作为实参。设置 服务处理器,该处理器定义当收到写入由设置 处理器时给定的 name 匹配的 属性的请求时 应该做什么。错误时抛出异常。返回 对 this 对象的引用以支持链式调用。
对于任何给定的属性,最多 MUST 有一个写入 处理器,因此新添加的 处理器 MUST 替换先前的 处理器。如果没有为任何给定 属性初始化写入处理器, 实现 SHOULD 在该 属性可写时实现 默认属性更新,并在该属性可观察时通知观察者 变化,这些均基于物 描述。
SecurityError
并停止。
[[td]].properties[name]
不存在,
则抛出
NotFoundError
并停止。
[[writeHandlers]][name]
设置为 handler。
"single":
NotSupportedError 并停止。
NotAllowedError 并停止。
[[td]].properties[name]。
undefined,
则在回复中返回一个 NotFoundError
并停止。
[[writeHandlers]][name]。
undefined,并且存在由实现提供的默认写入
处理器,则令
handler 为它。
undefined,则随回复发回一个
NotSupportedError 并停止。
"single",则按照协议
绑定
回复请求并报告成功,
然后停止。
NotSupportedError 并停止。
NotAllowedError 并停止。
"multiple",运行
update property
steps。如果这
失败,则用该错误回复请求并停止。
当收到调用某个动作
的外部请求时被调用的函数,并定义如何处理此类请求。它会在给定
params
并可选给定一个 options 对象时被调用。它返回一个
,该 Promise 以错误拒绝或
以动作返回的值兑现,该值作为 PromiseInteractionInput。
应用脚本 MAY 从
ActionHandler
返回一个 ReadableStream
对象。实现随后将使用该流来构造
动作的响应。
接受 name 和 action 作为实参。设置处理器 函数,该函数定义当收到请求以调用与 name 匹配的动作 时应做什么。错误时抛出 异常。返回对 this 对象的引用以 支持链式调用。
action 回调 函数将实现一个动作,并且当从 底层平台收到调用该动作的 请求时,实现 SHOULD 调用它。
对于任何给定的动作, 最多 MUST 有一个处理器, 因此新添加的处理器 MUST 替换 先前的处理器。
SecurityError
并停止。
[[td]].actions[name]。
undefined,
则抛出
一个 NotFoundError
并停止。
[[actionHandlers]][name]
设置为 action。
NotSupportedError 并停止。
NotAllowedError 并停止。
[[td]].properties[name]。
undefined,
则在回复中返回一个 NotFoundError
并停止。
[[actionHandlers]][name]。
undefined,则返回一个
NotSupportedError,并带有按照
协议
绑定创建的回复,然后停止。
当收到订阅某个事件的外部请求时被调用的函数,
并定义如何处理此类请求。它在给定由
实现提供并来自订阅者的
options 对象时被调用。它返回一个
,该 Promise 以错误拒绝或
在订阅被接受时兑现。
Promise
接受 name 和 handler 作为实参。设置 处理器函数,该函数定义当收到针对由 name 匹配的指定事件的 订阅请求时应做什么。错误时抛出异常。返回对 this 对象的引用以支持链式调用。
handler 回调函数 SHOULD 实现当收到 订阅请求时应做什么,例如必要的 初始化。注意,用于发出事件的处理器是单独设置的。
对于任何给定的事件,最多 MUST 有一个事件 订阅处理器,因此新添加的处理器 MUST 替换先前的 处理器。
SecurityError
并停止。
[[td]].events[name]。
undefined,
则抛出
一个 NotFoundError
并停止。
[[subscribeHandlers]][name]
设置为 handler。
this。
NotSupportedError 并停止。
NotAllowedError 并停止。
[[td]].events[name]。
undefined,
则发回一个 NotFoundError
并停止。
[[subscribeHandlers]][name]
是一个 Function,
则用 options 调用它并停止。
[[eventListeners]][name]
设置为 subscriber。
接受 name 和 handler 作为实参。设置 处理器函数,该函数定义当由 name 匹配的指定事件 被取消订阅时应做什么。错误时抛出异常。返回对 this 对象的引用以支持链式调用。
handler 回调函数 SHOULD 实现当收到 取消订阅请求时应做什么。
对于任何给定的事件,最多 MUST 有一个处理器, 因此新添加的处理器 MUST 替换 先前的处理器。
SecurityError
并停止。
[[td]].events[name]。
undefined,
则抛出
一个 NotFoundError
并停止。
[[unsubscribeHandlers]][name]
设置为 handler。
this。
NotSupportedError 并停止。
NotAllowedError 并停止。
[[td]].events[name]。
undefined,
则发回一个 NotFoundError
并停止。
[[unsubscribeHandlers]][name]
存在
并且是一个 Function,
则用 options 调用它并停止。
[[eventListeners]] 中 [=map/exists],
则移除
name。
this。[[eventListeners]].name。
undefined,则假定该
通知 response 将包含一个
空数据载荷,如协议
绑定所规定。
错误报告是协议 特定的,并由 实现封装。在客户端端,如果客户端 UA 检测到该错误, 则会调用随订阅传入的错误 监听器。
Promise promise,并
并行执行
后续步骤。
SecurityError
拒绝
promise
并停止。
[[td]].events.name。
NotFoundError
拒绝
promise
并停止。
Promise promise,并
并行执行
后续步骤。
SecurityError
拒绝
promise
并停止。
[[td]] 上运行 expand a TD 步骤。
[[td]] 上运行 validate a TD。如果失败,
则用
TypeError
拒绝
promise 并停止。
[[td]].properties
中的每个 key,
将 this.[[propertyObservers]].key
初始化为一个空
Array,以存储在值
变化时通知观察者所需的观察
请求数据。
[[td]].events
中的每个 key,
将 this.[[eventListeners]].key
初始化为一个空
Array,以存储在事件
发出时通知订阅者所需的订阅
请求数据。
[[td]] 来设置
WoT 交互,
如 [WOT-TD]
和 [WOT-PROTOCOL-BINDINGS] 中所解释。
向底层平台发出请求以初始化
协议
绑定,然后基于
协议
绑定,开始服务针对
WoT 交互
的外部请求(读取、写入和观察属性,调用
动作并管理
事件订阅)。实现 MAY 因任何原因拒绝此步骤
(例如,如果它们想对交互形式强制执行进一步检查和
约束)。
Error 对象 error
拒绝
promise,其中
error.message 设置为
协议
绑定看到的错误
代码,然后停止。
下一个示例展示如何基于预先构造的部分
TD 对象创建一个
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。
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
包含由算法替换的建议值。
发现是一种分布式应用,需要参与的网络节点 (客户端、服务器、目录服务)进行配置和支持。此 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
对象实现异步
迭代器概念。
ThingDiscoveryProcess
ThingDiscoveryProcess,
运行以下步骤:
null,则抛出一个
TypeError 并停止。
ThingDiscoveryProcess
对象。
[[filter]] 设置为
filter。
done
设置为 false。
error
设置为 null。
表示一个包含用于发现事物的约束的对象, 这些约束以键值对形式表示。
WebIDLdictionary ThingFilter {
object? fragment;
};
fragment 属性表示一个
模板对象,用于逐个属性地与发现的
事物进行匹配。
query
属性已暂时从 ThingFilter 中移除,
直到 WoT Discovery 任务组将其标准化。
它表示实现所接受的查询字符串,
例如 SPARQL 或 JSON 查询。
原计划在 WoT 运行时中本地实现支持,或在
TD 目录中作为服务远程实现。
error
属性设置为
SyntaxError,丢弃 td 并继续
发现过程。
此时,实现可以控制发现 过程的流程(例如根据内存限制, 对结果进行排队,或在队列变得过大时暂时停止 发现,或在队列被充分清空时 恢复发现)。对于每个 已发现/获取的 td,都会运行这些步骤。
[[filter]].fragment。
object,
则对于其中定义的每个 key:
asyncIterator 产生 td。
使用正确的 asyncIterator 术语改进此步骤。
保留最近一次错误。 如果实现认为应该报告该错误, 则实现可以选择 停止发现过程。
SecurityError
并停止。
done
属性设置为 true。
以下示例查找由本地硬件公开的ThingDescription
对象,这些对象属于事物,
无论其运行了多少个
WoT
运行时实例。使用 Discovery
对象提供的
asyncIterator,我们可以异步迭代结果,并
对获得的 ThingDescription
对象执行操作。
let url = "https://mythings.com/thing1";
let td = await WOT.requestThingDescription(url);
console.log("找到以下事物描述:" + td.title);
下一个示例查找列在 TD
目录服务中的ThingDescription
对象,这些对象属于事物。
为了安全起见,我们设置一个超时。
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 运行时 配置的任何方式进行,包括本地事物(如果 有可用的本地事物)。
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);
}
有关 Web of Things 的安全和隐私考虑事项的详细讨论, 包括可适用于各种情形的威胁模型,见信息性文档 [WOT-SECURITY]。 本节仅讨论与脚本和 WoT Scripting API 直接相关的安全和隐私风险以及 可能的缓解措施。
为提高 WoT 设备和服务安全性而建议采用的一组 最佳实践已记录在 [WOT-SECURITY] 中。 随着安全措施的发展,该文档可能会更新。 遵循这些实践并不能保证安全,但它 可能有助于避免常见的已知漏洞。
本节是规范性的,并包含与 WoT 脚本运行时 相关的特定风险。
破坏任何进程的一种典型方式,是通过其公开的某个接口 向其发送损坏的输入。这种方式也可以 通过脚本实例公开的 WoT 接口对该实例实施。
如果脚本遭到入侵或行为异常,并且脚本可以直接使用 所公开的原生设备接口,则底层物理设备 (以及潜在的周边环境)可能遭到损坏。如果这些接口的输入 缺乏安全检查,它们可能会使 底层物理设备(或环境)进入不安全 状态(即设备过热并爆炸)。
如果 WoT 脚本运行时支持制造后 配置或更新脚本、WoT 脚本运行时 或任何相关数据(包括安全凭据), 这可能成为主要攻击向量。攻击者可能尝试 在更新或配置过程中修改上述任何 元素,或者直接配置攻击者的代码 和数据。
通常,WoT 脚本运行时需要存储 配置给 WoT 设备、用于在 WoT 网络中运行的 安全凭据。如果攻击者能够破坏 这些凭据的机密性或完整性,那么 它就可能访问 WoT 资产、冒充 WoT 事物或设备,或者发起拒绝服务(DoS) 攻击。
本节是非规范性的。
本节描述与脚本 开发者相关的特定风险。
脚本实例可能接收由 TD 定义的数据格式,或由应用程序定义的数据格式。虽然 WoT 脚本运行时应该 对 TD 定义的所有输入字段执行验证, 但脚本仍可能被输入数据利用。
如果脚本在请求通过身份验证之前, 就对收到的请求执行繁重的功能处理, 则会带来很大的拒绝服务(DOS) 攻击风险。
API 原理通常属于单独的文档,但在 WoT 的情况下,上下文的复杂性足以说明 在此包含基本原理是合理的。
WoT 兴趣组和工作组已经探索了 多种 WoT 应用程序开发方法, 并且这些方法都已得到实现和测试。
可以开发仅使用 WoT 网络 接口的 WoT 应用程序,该接口通常由 WoT 网关公开, 此网关向客户端提供 RESTful API,并实现 与所支持 IoT 部署通信的 IoT 协议插件。此类实现之一是 Mozilla WebThings 平台。
WoT 事物 与软件对象具有很好的协同性,因此一个事物可以表示为 软件对象,其中属性表示为 对象属性,动作表示为方法, 事件 表示为事件。此外,元数据存储在特殊属性中。 消费和公开通过工厂方法完成,这些方法 生成一个直接表示远程 事物 及其交互的软件对象。此类实现之一是 Arena Web Hub 项目。
在下一个示例中,一个表示
与锁进行交互的事物如下所示:
status 属性和 open()
方法直接公开在对象上。
let lock = await WoT.consume(‘https://td.my.com/lock-00123’);
console.log(lock.status);
lock.open('withThisKey');
由于将事物直接映射到软件对象 存在一些挑战,因此本规范采用另一种 方法,公开软件对象以将 事物 元数据表示为数据属性,并将 WoT 交互表示为方法。一个 实现是 node-wot, 它属于 Eclipse ThingWeb 项目,也是本文档所规定 API 的当前参考 实现。
现在,同一个示例将如下所示:
status 属性和 open()
方法以间接方式表示。
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 时 需要考虑哪些因素的一个示例。
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 规范进行了验证。
用于消费和公开事物的工厂方法是异步的,并且会完整地
验证输入TD。此外,还可以通过提供已解析和验证的
TD 来构造 ConsumedThing
和 ExposedThing。
随后在 WoT 交互期间按需完成平台初始化。
早期草案使用 Observer 构造,但由于它尚未成为标准,因此需要一种 对嵌入式实现足够轻量的新设计。 因此,观察属性变化和 处理 WoT 事件 是通过回调注册完成的。
使用
readProperty()、
readMultipleProperties() 等函数名称,而不是通用多态
read() 函数的原因是,当前这些名称与
Web of Things (WoT) 事物描述 1.1
规范中
Form 定义的 "op" 词汇完全对应。
formIndex、
包括流在内的 InteractionData 的支持。
有关完整变更列表,请参阅 github 变更日志。还可以查看 最近关闭的问题。
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;
};
特别感谢前编辑 Johannes Hund(任职至 2017 年 8 月,当时就职于 Siemens AG)和 Kazuaki Nimura(任职至 2018 年 12 月)对本规范的开发工作。此外,编辑们 还要感谢 Dave Raggett、Matthias Kovatsch、Michael Koster、Elena Reshetova、Michael McCool 以及其他 WoT WG 成员所提供的意见、贡献和 指导。
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in: