编程语言
首页 > 编程语言> > W3C万维物联网解析:编程API篇

W3C万维物联网解析:编程API篇

作者:互联网

2019年10月21日,作者在“W3C万维物联网标准简介”一文中简单介绍了W3C Web of Things(WoT)工作组制定的WoT标准以及它们的最新状态:

规范当前状态
WoT ArchitectureCR
WoT Thing DescriptionCR
WoT Scripting APIWD,Working Draft
WoT Binding TemplatesWorking Group Note
WoT Security and Privacy ConsiderationsWorking Group Note

本系列将从WoT标准本身出发,对目前已经进入CR阶段(W3C标准的阶段参见下图)的WoT Architecture(WoT架构)、WoT Thing Description(WoT物描述)以及处于WD阶段的WoT Scripting API(WoT编程API)进行一次快速解析。

如下图所示,标准进入CR阶段意味着内容已经相对稳定,WD阶段则意味着较大的不确定性,而Working Group Note(工作组备忘)则变数很大。因此处于CR阶段的“架构”和“物描述”是值得花时间了解的(成为正式推荐标准REC的可能性很大),而处于WD阶段的编程API在2019年10月28日做了一次大的内容改版,几乎完全废弃了上一版的内容,只能说接近稳定状态,但编程API始终是开发者所喜闻乐见的,所以本系列也会介绍。

图片

W3C Process Document,https://www.w3.org/2019/Process-20190301/#recs-and-notes

1. 编程API简介

WoT Scripting API描述如何通过脚本暴露和消费物体,同时定义了通用的物发现API。基于WoT架构定义的“消费体”(consumed thing)和“暴露体”(consumed thing),这个规范提供了不同层次的交互操作能力。

首先,客户端通过消费TD(Thing Description)可以创建一个本地运行时资源模型,即消费体。消费体支持访问远程设备上的服务端物体暴露的属性、动作和事件。

其次,服务端负责暴露物体,为此需要:

2. 支持的场景

编程API支持以下脚本使用场景。

2.1 消费物

2.2 暴露物

2.3 发现物

3. 接口及类型定义

3.1 ThingDescription类型

typedef object ThingDescription;

下面是通过URL获取一个ThingDescription类型实例的示例:

例1:获取TD

try {  let res = await fetch('https://tds.mythings.biz/sensor11');  // ... 可以对res.headers进行额外检查  let td = await res.json();  let thing = new ConsumedThing(td);  console.log("Thing name: " + thing.getThingDescription().title);} catch (err) {  console.log("Fetching TD failed", err.message);}

此外,规范也定义了如何扩展TD和验证TD。

3.2 WOT接口

[SecureContext, Exposed=(Window,Worker)]interface WOT {  // methods defined in UA conformance classes  Promise<ConsumedThing> consume(ThingDescription td);  Promise<ExposedThing> produce(ThingDescription td);  ThingDiscovery discover(optional ThingFilter filter = null);};

WOT接口的实例将以某种名称暴露在window和worker中。由上面定义可知,WOT接口包含3个方法:

这3个方法可分别用于在客户端、服务端创建消费体、产生暴露体和发现TD。

3.3 ConsumedThing接口

ConsumedThing接口的定义如下所示,ConsumedThing的实例即消费体,拥有一系列操作物体的客户端API。

[SecureContext, Exposed=(Window,Worker)]interface ConsumedThing {  constructor(ThingDescription td);  Promise<any> readProperty(        DOMString propertyName,        optional InteractionOptions options = null        );  Promise<PropertyMap> readAllProperties(        optional InteractionOptions options = null        );  Promise<PropertyMap> readMultipleProperties(        sequence<DOMString> propertyNames,        optional InteractionOptions options = null        );  Promise<void> writeProperty(        DOMString propertyName,        any value,        optional InteractionOptions options = null        );  Promise<void> writeMultipleProperties(        PropertyMap valueMap,        optional InteractionOptions options = null        );  Promise<any> invokeAction(        DOMString actionName,        optional any params = null,        optional InteractionOptions options = null        );  Promise<void> observeProperty(        DOMString name,        WotListener listener,        optional InteractionOptions options = null        );  Promise<void> unobserveProperty(DOMString name);  Promise<void> subscribeEvent(        DOMString name,        WotListener listener,        optional InteractionOptions options = null        );  Promise<void> unsubscribeEvent(DOMString name);  ThingDescription getThingDescription();};dictionary InteractionOptions {  object uriVariables;};typedef object PropertyMap;callback WotListener = void(any data);

下面我们简单看一看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 " + thing.getThingDescription().title + " consumed.");} catch(e) {    console.log("TD fetch error: " + e.message); },};try {    // 订阅属性“temperature”变化的通知    await thing.observeProperty("temperature", value => {         console.log("Temperature changed to: " + value);    });    // 订阅TD中定义的“ready”事件    await thing.subscribeEvent("ready", eventData => {         console.log("Ready; index: " + eventData);         // 返回TD中定义的“startMeasurement”动作         await thing.invokeAction("startMeasurement", { units: "Celsius" });         console.log("Measurement started.");    });} catch(e) {    console.log("Error starting measurement.");}setTimeout( () => {    console.log("Temperature: " + await thing.readProperty("temperature"));    await thing.unsubscribe("ready");    console.log("Unsubscribed from the 'ready' event.");},10000);

3.4 ExposedThing接口

ExposedThing接口是用于操作物体的服务器API,支持定义请求处理程序、属性、动作和事件接口。

[SecureContext, Exposed=(Window,Worker)]interface ExposedThing: ConsumedThing {  ExposedThing setPropertyReadHandler(        DOMString name,        PropertyReadHandler readHandler        );  ExposedThing setPropertyWriteHandler(        DOMString name,        PropertyWriteHandler writeHandler        );  ExposedThing setActionHandler(        DOMString name, ActionHandler action);        void emitEvent(DOMString name, any data);        Promise<void> expose();        Promise<void> destroy();        };  callback PropertyReadHandler = Promise<any>(        optional InteractionOptions options = null            );  callback PropertyWriteHandler = Promise<void>(        any value,            optional InteractionOptions options = null            );  callback ActionHandler = Promise<any>(        any params,            optional InteractionOptions options = null            );

ExposedThing接口扩展ConsumedThing接口,可以基于一个完整的或不完整的ThingDescription对象构建实例。ExposedThing从ConsumedThing继承了以下方法:

这些方法跟ConsumedThing中的方法拥有同样的算法,不同之处在于对底层平台发送请求可能会以本地方法或库实现,不一定需要调用网络操作。

ExposedThing中ConsumedThing接口的实现提供默认的方法与ExposedThing交互。

构建ExposedThing之后,应用脚本可以初始化它的属性并设置可选的读、写和动作请求处理程序(即由实现提供的默认值)。应用脚本提供的处理程序可以使用默认处理程序,从而扩展默认行为,但也可以绕过它们,重写默认行为。最后,应用脚本会在ExposedThing上调用expose(),以便开始服务外部请求。

以下是ExposedThing接口定义的成员。

以下示例展示了如何基于先构造的不完全的TD对象创建ExposedThing。

例3:创建包含简单属性的ExposedThing

try {let temperaturePropertyDefinition = { type: "number", minimum: -50, maximum: 10000};let tdFragment = { properties: {   temperature: temperaturePropertyDefinition }, actions: {   reset: {     description: "Reset the temperature sensor",     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("Error creating ExposedThing: " + err);}

下面的例子展示了如何在已有的ExposedThing上添加或修改属性定义:取得其td属性,增加或修改,然后再用它创建另一个ExposedThing。

例4:添加对象属性

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: "Get status object", input: null, output: {   status : statusValueDefinition; }, forms: [...]};instance.events["onstatuschange"] = statusValueDefinition;instance.forms = [...]; // updatevar thing2 = new ExposedThing(instance);// TODO: add service handlersawait thing2.expose();});} catch (err) {console.log("Error creating ExposedThing: " + err);}

3.5 ThingDiscovery接口

发现是分布式应用,需要网络节点(客户端、服务器、目录服务)的供应与支持。这个API对多种IoT部署场景支持的典型发现模式的客户端进行建模。

ThingDiscovery对象接收一个过滤器参数,并提供了控制发现过程的属性和方法。

[SecureContext, Exposed=(Window,Worker)]interface ThingDiscovery {  constructor(optional ThingFilter filter = null);  readonly attribute ThingFilter? filter;  readonly attribute boolean active;  readonly attribute boolean done;  readonly attribute Error? error;  void start();  Promise<ThingDescription> next();  void stop();};

发现结果对应的内部槽位是一个内部队列,用于临时存储发现的ThingDescription对象,直到应用通过next()方法消费掉。

构建ThingDiscovery的过程如下。

下面总结一下start()、next()和stop()方法。

3.5.1 DiscoveryMethod枚举

typedef DOMString DiscoveryMethod;

表示要发现的类型:

3.5.2 ThingFilter字典

包含发现物体限制类型名值对的对象。

dictionary ThingFilter {  (DiscoveryMethod or DOMString) method = "any";  USVString? url;  USVString? query;  object? fragment;};


3.5.3 ThingDiscovery示例

以下例子展示了发现本地硬件暴露的物体的ThingDescription的过程,不考虑本地硬件上运行着多少WoT运行时。注意,发现对象可能在内部发现结果队列变空之前告终(变不活跃),因此需要持续读取ThingDescription对象,直到done属性为true。这是典型的本地和目录类型的发现过程。

例5:发现本地硬件上暴露的物体

let discovery = new ThingDiscovery({ method: "local" });do {  let td = await discovery.next();  console.log("Found Thing Description for " + td.title);  let thing = new ConsumedThing(td);  console.log("Thing name: " + thing.getThingDescription().title);} while (!discovery.done);

下面的例子展示如何发现一个物体目录服务上列出的物体的ThingDescription。为安全起见,设置了超时。

例6:通过目录发现物体

let discoveryFilter = {method: "directory",url: "http://directory.wotservice.org"};let discovery = new ThingDiscovery(discoveryFilter);setTimeout( () => { discovery.stop(); console.log("Discovery stopped after timeout.");},3000);do {let td = await discovery.next();console.log("Found Thing Description for " + td.title);let thing = new ConsumedThing(td);console.log("Thing name: " + thing.getThingDescription().title);} while (!discovery.done);if (discovery.error) {console.log("Discovery stopped because of an error: " + error.message);}

接下来的例子展示了一个开放式的多播发现过程,可能不会很快结束(取决于底层协议),因此最好通过超时来结束。这种情况也倾向于一个一个地交付结果。

例7:在网络上发现物体

let discovery = new ThingDiscovery({ method: "multicast" });setTimeout( () => { discovery.stop(); console.log("Stopped open-ended discovery");},10000);do {let td = await discovery.next();let thing = new ConsumedThing(td);console.log("Thing name: " + thing.getThingDescription().title);} while (!discovery.done);

4. 扩展阅读

关于W3C成立WoT工作组制定WoT标准的初衷,可以参考该文章。


标签:W3C,万维,name,物体,API,Promise,参数,TD,属性
来源: https://blog.51cto.com/u_15127660/2784443