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
包含由算法替换的建议值。
Discovery는 참여하는 네트워크 노드 (클라이언트, 서버, 디렉터리 서비스)의 프로비저닝과 지원이 필요한 분산 애플리케이션입니다. 이 API는 다양한 IoT 배포에서 지원되는 일반적인 discovery 방식의 클라이언트 측을 모델링합니다.
ThingDiscoveryProcess
객체는 discovery process를 제어하고 결과를 반환하는
속성과 메서드를 제공합니다.
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
객체에는 다음
internal slot이 있습니다.
| Internal Slot | 초기값 | 설명(비규범적) |
|---|---|---|
| [[filter]] | undefined |
discovery에서 사용되는
ThingFilter
객체입니다.
|
| [[url]] | undefined |
discovery에서 TD Directory를 나타내는
URL입니다.
|
done
속성은 discovery가 중지되었거나 더 이상 보고할 결과 없이
완료된 경우 true입니다.
error
속성은 discovery process 중 발생한 마지막 오류를 나타냅니다. 일반적으로
discovery를 중지시키는 심각한 오류에 사용됩니다.
ThingDiscoveryProcess
객체는 async
iterator 개념을 구현합니다.
ThingDiscoveryProcess
구성하기
ThingDiscoveryProcess를
생성하려면 다음 단계를 실행합니다:
null이 아니면,
TypeError를
throw하고
중지합니다.
ThingDiscoveryProcess
객체를 discovery로 둡니다.
[[filter]]를
filter로 설정합니다.
done을
false로 설정합니다.
error를
null로 설정합니다.
Thing을 discovery하기 위한 제약 조건을 key-value 쌍으로 포함하는 객체를 나타냅니다.
WebIDLdictionary ThingFilter {
object? fragment;
};
fragment 속성은 발견된
Thing과
속성별로 매칭하는 데 사용되는
템플릿 객체를 나타냅니다.
query
속성은 WoT Discovery 태스크포스에서 표준화될 때까지
ThingFilter에서
일시적으로 제거되었습니다.
이는 구현이 수락하는 query string, 예를 들어 SPARQL 또는 JSON query를
나타냈습니다. 지원은 WoT Runtime에서
로컬로 구현되거나, TD Directory의 서비스로
원격 구현될 예정이었습니다.
url 속성은 제거되었습니다. 이 속성은 discovery 요청을 제공하는 대상 엔티티, 예를 들어 TD Directory의 URL 또는 직접 대상으로 지정된 Thing의 URL을 나타냈지만, 이제는 전용 메서드로 구현됩니다.
error
속성을
SyntaxError로 설정하고,
td를 폐기한 다음
discovery process를 계속합니다.
이 시점에서 구현은 discovery process의 흐름을 제어할 수 MAY 있습니다 (예를 들어 메모리 제약에 따라 결과를 큐에 넣거나, 큐가 너무 커지면 discovery를 일시적으로 중지하거나, 큐가 충분히 비워지면 discovery를 재개할 수 있습니다). 이 단계들은 발견/가져온 각 td에 대해 실행됩니다.
[[filter]].fragment를
fragment로 둡니다.
object이면,
그 안에 정의된 각 key에 대해:
asyncIterator를 사용하여
td를 yield합니다.
적절한 asyncIterator 용어를 사용하여 이 단계를 개선합니다.
마지막 오류가 유지됩니다. 구현은 오류가 보고되어야 한다고 판단하면 discovery process를 중지하기로 선택할 수 MAY 있습니다.
Error 객체를
error로 둡니다.
error.name을
"DiscoveryError"로 설정합니다.
error를
error로 설정합니다.
done을
true로 설정하고 이 단계를 종료합니다.
SecurityError를
throw하고
중지합니다.
done
속성을 true로 설정합니다.
다음 예제는 로컬 하드웨어에 의해 노출된
Thing의
ThingDescription
객체를 찾습니다. 실행 중인
WoT
Runtime 인스턴스 수와 관계없이,
Discovery 객체가 제공하는
asyncIterator를 사용하여 결과를 비동기적으로 순회하고,
얻은 ThingDescription
객체로 작업을 수행할 수 있습니다.
let url = "https://mythings.com/thing1";
let td = await WOT.requestThingDescription(url);
console.log("Found Thing Description for " + td.title);
다음 예제는 TD
Directory 서비스에 나열된
Thing의
ThingDescription
객체를 찾습니다. 안전을 위해 timeout을 설정합니다.
let discovery = await WOT.exploreDirectory("http://directory.wotservice.org");
setTimeout( () => {
discovery.stop();
console.log("Discovery stopped after timeout.");
},
3000);
for await (const td of discovery) {
console.log("Found Thing Description for " + td.title);
let thing = new ConsumedThing(td);
console.log("Thing name: " + thing.getThingDescription().title);
};
if (discovery.error) {
console.log("Discovery stopped because of an error: " + error.message);
}
다음 예제는 WOT runtime에 프로비저닝된 어떤 방식으로든 수행되는 일반 discovery를 위한 것으로, 사용 가능한 경우 로컬 Things도 포함합니다.
let discovery = await WOT.discover();
setTimeout( () => {
discovery.stop();
console.log("Stopped open-ended discovery");
},
10000);
for await (const td of discovery) {
console.log("Found Thing Description for " + td.title);
};
if (discovery.error) {
console.log("Discovery stopped because of an error: " + error.message);
}
다양한 상황에 맞게 조정할 수 있는 위협 모델을 포함하여 Web of Things의 보안 및 프라이버시 고려사항에 대한 자세한 논의는 정보성 문서 [WOT-SECURITY]에 제시되어 있습니다. 이 섹션에서는 스크립트와 WoT Scripting API에 직접 관련된 보안 및 프라이버시 위험과 가능한 완화책만 논의합니다.
WoT devices 및 services의 보안을 개선하기 위한 권장 best practices 세트는 [WOT-SECURITY]에 문서화되어 있습니다. 그 문서는 보안 조치가 발전함에 따라 업데이트될 수 있습니다. 이러한 관행을 따른다고 보안이 보장되는 것은 아니지만, 일반적으로 알려진 취약점을 피하는 데 도움이 될 수 있습니다.
이 섹션은 규범적이며 WoT Scripting Runtime과 관련된 구체적인 위험을 포함합니다.
어떤 프로세스를 손상시키는 일반적인 방법은 노출된 인터페이스 중 하나를 통해 손상된 입력을 보내는 것입니다. 이는 script instance가 노출하는 WoT interface를 사용하여 수행될 수 있습니다.
script가 손상되었거나 오동작하는 경우, script가 직접 노출된 native device interface를 사용할 수 있다면 기본 물리적 장치 (및 잠재적으로 주변 환경)가 손상될 수 있습니다. 이러한 인터페이스에 입력에 대한 안전성 검사가 없다면, 기본 물리적 장치(또는 환경)를 안전하지 않은 상태(예: 장치가 과열되어 폭발)로 만들 수 있습니다.
WoT Scripting Runtime이 제조 이후의 script 프로비저닝이나 업데이트, WoT Scripting Runtime 또는 관련 데이터(보안 자격 증명 포함)를 지원한다면, 이는 주요 공격 벡터가 될 수 있습니다. 공격자는 업데이트 또는 프로비저닝 과정에서 위에서 설명한 요소 중 하나를 수정하려 하거나, 공격자의 코드와 데이터를 직접 프로비저닝하려 할 수 있습니다.
일반적으로 WoT Scripting Runtime은 WoT network에서 작동하기 위해 WoT device에 프로비저닝된 보안 자격 증명을 저장해야 합니다. 공격자가 이러한 자격 증명의 기밀성 또는 무결성을 손상시킬 수 있다면, WoT assets에 접근하거나, WoT things 또는 devices를 가장하거나, Denial-Of-Service(DoS) 공격을 만들 수 있습니다.
이 섹션은 비규범적입니다.
이 섹션은 script 개발자와 관련된 구체적인 위험을 설명합니다.
script instance는 TD가 정의한 데이터 형식 또는 애플리케이션이 정의한 데이터 형식을 수신할 수 있습니다. WoT Scripting Runtime은 TD가 정의한 모든 입력 필드에 대해 검증을 수행해야 SHOULD 하지만, script는 여전히 입력 데이터에 의해 악용될 수 있습니다.
요청이 인증되기 전에 script가 수신한 요청에 대해 무거운 기능 처리를 수행하면, Denial-Of-Service(DOS) 공격에 큰 위험을 제공합니다.
API 근거는 보통 별도의 문서에 속하지만, WoT의 경우 context의 복잡성이 여기에 기본 근거를 포함하는 것을 정당화합니다.
WoT Interest Group과 Working Group은 모두 구현되고 테스트된 WoT 애플리케이션 개발을 위한 다양한 접근 방식을 탐구했습니다.
WoT network interface만 사용하는 WoT 애플리케이션을 개발할 수 있습니다. 이는 일반적으로 클라이언트에게 RESTful API를 제공하고 지원되는 IoT 배포와 통신하는 IoT protocol plugins를 구현하는 WoT gateway에 의해 노출됩니다. 그러한 구현 중 하나는 Mozilla WebThings 플랫폼입니다.
WoT Thing은 software object와 좋은 시너지를 보이므로, Thing은 software object로 표현될 수 있으며, Properties는 object properties로, Action은 methods로, Event는 events로 표현됩니다. 또한 metadata는 특수 properties에 저장됩니다. consuming 및 exposing은 remote Thing과 그 interactions를 직접 나타내는 software object를 생성하는 factory methods로 수행됩니다. 그러한 구현 중 하나는 Arena Web Hub 프로젝트입니다.
다음 예제에서 lock과의 interactions를 나타내는
Thing은
다음과 같습니다:
status 속성과 open()
메서드는 객체에 직접 노출됩니다.
let lock = await WoT.consume(‘https://td.my.com/lock-00123’);
console.log(lock.status);
lock.open('withThisKey');
Thing을 software object로 직접 매핑하는 방식에는 몇 가지 어려움이 있었기 때문에, 이 명세는 Thing metadata를 data property로, WoT interactions를 methods로 표현하기 위해 software object를 노출하는 다른 접근 방식을 취합니다. 한 구현은 이 문서에 명시된 API의 현재 참조 구현인 node-wot이며, 이는 Eclipse ThingWeb 프로젝트에 속합니다.
이제 같은 예제는 다음과 같이 보입니다:
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) Thing Description 1.1 명세를 밀접하게 따르는 세 번째 옵션을 탐구하기로 결정했습니다. 이를 기반으로 단순 API도 구현할 수 있습니다. Scripting은 WoT의 선택적 모듈이므로, 이는 WoT network interface만 사용하는 애플리케이션을 위한 여지를 남깁니다. 따라서 위의 세 가지 접근 방식 모두 Web of Things (WoT) Thing Description 1.1 명세에서 지원됩니다.
또한 WoT network interface는 많은 언어와 runtime에서 구현될 수 있습니다. 이 API를 WoT를 위한 Scripting API를 설계할 때 고려해야 할 사항의 예로 간주하십시오.
fetch(url) 메서드는 이 API의 이전 버전에 포함되어 있었습니다.
그러나 이제 URL이 주어진 TD를 가져오는 것은
Fetch API 또는
HTTP client library와 같은 외부 메서드로 수행되어야 하며,
이러한 메서드는 fetch 세부사항을 지정하기 위한 이미 표준화된 옵션을 제공합니다.
그 이유는 단순 fetch 작업(대부분의 사용 사례를 포함)은 이 API에서 수행될 수 있었지만,
다양한 fetch 옵션이 필요할 때 기존 작업을 중복하여 이 API에서 그러한 옵션을
다시 노출할 이유가 없었기 때문입니다.
TD 가져오기가 범위에서 제외되었고, TD 검증도 Web of Things (WoT) Thing Description 1.1 명세에서 외부적으로 정의되므로, 그것 역시 범위에서 제외됩니다. 이 명세는 Web of Things (WoT) Thing Description 1.1 명세에 따라 검증된 parsed JSON object로서의 TD를 기대합니다.
Thing을
consume하고 expose하는
factory methods는 비동기적이며 입력
TD를 완전히 검증합니다. 또한,
parsed되고 검증된 TD를
제공하여
ConsumedThing 및
ExposedThing도
구성할 수 있습니다. 그러면 platform 초기화는 WoT interactions 중 필요할 때 수행됩니다.
이전 초안은 Observer 구조를 사용했지만, 표준이 되지 않았기 때문에 embedded implementations에 충분히 가벼운 새로운 설계가 필요했습니다. 따라서 Property 변경 관찰과 WoT Event 처리는 callback registrations로 수행됩니다.
일반적인 polymorphic read() 함수 대신
readProperty(),
readMultipleProperties() 등의 함수 이름을 사용하는 이유는,
현재 이름들이
Web of Things (WoT) Thing Description 1.1
명세의
Form 정의에서 가져온 "op" vocabulary에
정확히 매핑되기 때문입니다.
전체 변경사항 목록은 github change log를 참조하십시오. 또한 최근 닫힌 issues를 볼 수 있습니다.
WebIDLtypedef object ThingDescription;
[SecureContext, Exposed=(Window,Worker)]
namespace WOT {
// methods defined in UA conformance classes
};
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: