Copyright © 2017-2020 W3C® (MIT, ERCIM, Keio, Beihang). W3C liability, trademark and permissive document license rules apply.
The Web of Things is made of entities (Things) that can describe their capabilities in a machine-interpretable Thing Description (TD) and expose these capabilities through the WoT Interface, that is, network interactions modeled as Properties (for reading and writing values), Actions (to execute remote procedures with or without return values) and Events (for signaling notifications).
The main Web of Things (WoT) concepts are described in the Web of Things Architecture specification.
Scripting is an optional building block in WoT and it is typically used in gateways or browsers that are able to run a WoT Runtime and script management, providing a convenient way to extend WoT support to new types of endpoints and implement WoT applications such as Thing Directory.
This document describes an application programming interface (API) representing the WoT Interface that allows scripts to discover, operate Things and to expose locally defined Things characterized by WoT Interactions specified by a script.
The APIs defined in this document deliberately follow the Web of Things Thing Description specification closely. It is possible to implement more abstract APIs on top of them, or implementing directly the WoT network facing interface (i.e. the WoT Interface).
This specification is implemented at least by the Eclipse Thingweb project also known as node-wot, which is considered the reference open source implementation at the moment. Check its source code, including examples.
This section describes the status of this document at the time of its publication. Other documents may supersede this document. A list of current W3C publications and the latest revision of this technical report can be found in the W3C technical reports index at https://www.w3.org/TR/.
Implementers need to be aware that this specification is considered unstable. Vendors interested in implementing this specification before it eventually reaches the Candidate Recommendation phase should subscribe to the repository and take part in the discussions.
Please contribute to this draft using the GitHub Issues page of the WoT Scripting API repository. For feedback on security and privacy considerations, please use the WoT Security and Privacy Issues.
This document was published by the Web of Things Working Group as a Working Group Note.
Publication as a Working Group Note does not imply endorsement by the W3C Membership.
This is a draft document and may be updated, replaced or obsoleted by other documents at any time. It is inappropriate to cite this document as other than work in progress.
This document was produced by a group operating under the 1 August 2017 W3C Patent Policy.
This document is governed by the 15 September 2020 W3C Process Document.
WoT provides layered interoperability based on how Things are used: "consumed" and "exposed", as defined in the Web of Things Architecture terminology.
By consuming a TD, a client Thing creates a local runtime resource model that allows accessing the Properties, Actions and Events exposed by the server Thing on a remote device.
Exposing a Thing requires:
This specification describes how to expose and consume Things by a script. Also, it defines a generic API for Thing discovery.
Typically scripts are meant to be used on bridges or gateways that expose and control simpler devices as WoT Things and have means to handle (e.g. install, uninstall, update etc.) and run scripts.
This specification does not make assumptions on how the WoT Runtime handles and runs scripts, including single or multiple tenancy, script deployment and lifecycle management. The API already supports the generic mechanisms that make it possible to implement script management, for instance by exposing a manager Thing whose Actions (action handlers) implement script lifecycle management operations.
This section is non-normative.
The following scripting use cases are supported in this specification:
After evaluating dynamic modifications to Thing Descriptions through several versions of this API, the editors concluded that the simplest way to represent these use cases is to take an existing TD, modify it (i.e. add or remove definitions) and then create a new Thing based on the modified TD.
This specification used to be a Working Draft which was expected to become a W3C Recommendation. However, it is now a WG Note which contains informative statements only. Therefore we need to consider how to deal with the description within this Conformance section.
As well as sections marked as non-normative, all authoring guidelines, diagrams, examples, and notes in this specification are non-normative. Everything else in this specification is normative.
The key words MAY, MUST, and SHOULD in this document are to be interpreted as described in BCP 14 [RFC2119] [RFC8174] when, and only when, they appear in all capitals, as shown here.
This specification describes the conformance criteria for the following classes of user agent (UA).
Due to requirements of small embedded implementations,
splitting WoT client and server interfaces was needed. Then,
discovery is a distributed application, but typical scenarios
have been covered by a generic discovery API in this
specification. This resulted in using 3 conformance classes for
a UA that implements this API, one for
client, one for server, and one for discovery. An application
that uses this API can introspect for the presence of the
consume()
, produce()
and
discover()
methods on the WoT API object in order
to determine which conformance class the UA implements.
Implementations of this conformance class MUST implement the
interface and the ConsumedThing
consume()
method on the
WoT
API object.
Implementations of this conformance class MUST implement
interface and the ExposedThing
produce()
method on the
WoT
API object.
Implementations of this conformance class MUST implement the
interface and the ThingDiscovery
discover()
method on the
WoT
API object.
These conformance classes MAY be implemented in a single UA.
This specification can be used for implementing the WoT Scripting API in multiple programming languages. The interface definitions are specified in [WEBIDL].
The UA may be implemented in the browser, or in a separate runtime environment, such as Node.js or in small embedded runtimes.
Implementations that use ECMAScript executed in a browser to implement the APIs defined in this document MUST implement them in a manner consistent with the ECMAScript Bindings defined in the Web IDL specification [WEBIDL].
Implementations that use TypeScript or ECMAScript in a runtime to implement the APIs defined in this document MUST implement them in a manner consistent with the TypeScript Bindings defined in the TypeScript specification [TYPESCRIPT].
ThingDescription
typeWebIDLtypedef object ThingDescription
;
Represents a Thing Description (TD) as defined in [WOT-TD]. It is expected to be a parsed JSON object that is validated using JSON Schema validation.
Fetching a TD given a URL should be done with an external method, such as the Fetch API or a HTTP client library, which offer already standardized options on specifying fetch details.
try {
let res = await fetch('https://tds.mythings.biz/sensor11');
// ... additional checks possible on res.headers
let td = await res.json();
let thing = await WOT.consume(td);
console.log("Thing name: " + thing.getThingDescription().title);
} catch (err) {
console.log("Fetching TD failed", err.message);
}
Note that the Web of Things Thing Description specification allows using a shortened Thing Description by the means of defaults and requiring clients to expand them with default values specified in the Web of Things Thing Description specification for the properties that are not explicitly defined in a given TD.
The [WOT-TD]
specification defines how a TD should be validated. Therefore,
this API expects the
objects be validated before used as parameters. This
specification defines a basic TD validation as follows.ThingDescription
TypeError
"
and abort these steps.
Additional steps may be added to fill the default values of mandatory fields.
WOT
namespaceDefines the WoT API object as a singleton and contains the API methods, grouped by conformance classes.
WebIDL[SecureContext, Exposed=(Window,Worker)]
namespace WOT
{
// methods defined in UA conformance classes
};
consume()
methodWebIDLpartial namespaceWOT
{ Promise<ConsumedThing
>consume
(ThingDescription
td); };
Promise
that resolves with a ConsumedThing
object that represents a client interface to operate with
the Thing.
The method MUST run the following
steps:
Promise
promise and execute the
next steps in
parallel.
SecurityError
and abort these steps.
ConsumedThing
object constructed from td.
Implementations encapsulate the complexity of how to use the Protocol Bindings for implementing WoT interactions. In the future elements of that could be standardized.
Note the difference between constructing
ConsumedThing
and using the consume()
method: the latter
also initializes the protocol bindings, whereas a simple
constructed object will not have WoT Interactions
initialized until they are invoked.
produce()
methodWebIDLpartial namespaceWOT
{ Promise<ExposedThing
>produce
(ThingDescription
td); };
Promise
that resolves with an ExposedThing
object that extends ConsumedThing
with a server interface, i.e. the ability to define request
handlers. The method MUST run the
following steps:
Promise
promise and execute the
next steps in
parallel.
SecurityError
and abort these steps.
ExposedThing
object constructed with td.
discover()
methodWebIDLpartial namespaceWOT
{ThingDiscovery
discover
(optionalThingFilter
filter = null); };
ThingDescription
objects for Thing Descriptions
that match an optional filter argument of type
ThingFilter
.
The method MUST run the following
steps:
SecurityError
"
and abort these steps.
ThingDiscovery
object discovery
with filter.
As specified in the
Web of Things Thing Description specification,
WoT
interactions extend DataSchema and include a number
of possible Forms, out of
which one is selected for the interaction. The
Form contains a contentType
to describe the
data. For certain content types, a DataSchema is defined, based on
JSON Schema, making
possible to represent these contents as JavaScript types and
eventually set range constraints on the data.
InteractionInput
typeWebIDLtypedef anyDataSchemaValue
; typedef (ReadableStream orDataSchemaValue
)InteractionInput
;
Belongs to the WoT Consumer conformance class and represents the WoT Interaction data provided by application scripts to the UA.
DataSchemaValue
is an
ECMAScript value that is accepted for DataSchema defined in
[WoT-TD] (i.e.
null, boolean, number, string, array, or object).
ReadableStream
is meant to be used for WoT Interactions that
don't have a DataSchema in the Thing
Description, only a Form
's
contentType
that can be represented by a
stream.
In practice, any
ECMAScript value may be used for WoT Interactions
that have a DataSchema defined in the
Thing Description, or
which can be mapped by implementations to the
Form
's
contentType
defined in the Thing
Description.
The algorithms in this document specify how exactly input data is used in WoT Interactions.
InteractionOutput
interfaceBelongs to the WoT Consumer conformance
class. An
object is always created by the implementations and exposes
the data returned from WoT Interactions to
application scripts.InteractionOutput
This interface exposes a convenience function which should
cover the vast majority of IoT use cases: the value() function. Its
implementation will inspect the data, parse it if adheres to
a DataSchema, or otherwise fail
early, leaving the underlying stream undisturbed so that
application scripts could attempt reading the stream
themselves, or handling the data as ArrayBuffer
.
WebIDL[SecureContext, Exposed=(Window,Worker)] interfaceInteractionOutput
{ readonly attribute ReadableStream?data
; readonly attribute booleandataUsed
; readonly attribute Form?form
; readonly attribute DataSchema?schema
; Promise<ArrayBuffer>arrayBuffer
(); Promise<any>value
(); };
The data
property
represents the raw payload in WoT Interactions as a
ReadableStream
,
initially null
.
The dataUsed
property tells whether the data stream has been
disturbed. Initially false
.
The form
attribute
represents the Form
selected from the Thing Description for
this WoT
Interaction, initially null
.
The schema
attribute represents the DataSchema (defined in
[WoT-TD]) of the
payload as a JSON
object, initially null
.
The [[value]] internal slot represents the
parsed value of the WoT Interaction,
initially undefined
(note that null
is a valid value).
value()
functioncontentType
of the interaction
Form. The method
MUST run the following steps:
Promise
promise and execute the
next steps in
parallel.
undefined
,
resolve promise with
that value and abort these steps.
ReadableStream
or if dataUsed is
true
, or if form is
null
or if schema or its
type are null
or
undefined
, reject promise with
NotReadableError
and abort these steps.
application/json
and if a mapping is not
available in the Protocol Bindings
from form's contentType to
[JSON-SCHEMA],
reject promise with
NotSupportedError
and abort these steps.
true
.application/json
and if a mapping is
available in the Protocol Bindings
from form's contentType to
[JSON-SCHEMA],
transform bytes with that mapping.
arrayBuffer()
functionPromise
promise and execute the
next steps in
parallel.
ReadableStream
or if dataUsed is
true
, reject promise with
NotReadableError
and abort these steps.
true
.
ArrayBuffer
whose contents are
bytes. If that throws, reject
promise with that
exception and abort these steps.
"null"
and if
payload is not null
, throw
TypeError
and abort these steps,
otherwise return null
.
"boolean"
and
payload is a falsey value or its byte length
is 0, return false
, otherwise return
true
."integer"
or
"number"
,
TypeError
and abort these steps.
RangeError
and abort these steps.
"string"
, return
payload."array"
, run these
sub-steps:
TypeError
and abort these steps.
RangeError
and abort these steps.
"object"
, run
these sub-steps:
TypeError
and abort these steps.
SyntaxError
and abort these steps.
ConsumedThing
object thing, in order
to create
interaction request given a source, form and schema,
run these steps:
InteractionOutput
object whose form is set to
form, whose schema is set to
schema, whose [[value]]
internal slot is undefined
and whose
data is null
.
ReadableStream
object, let
idata's data be source, return
idata and abort these steps.
null
, run
these sub-steps:
"null"
and
source is
not, throw
TypeError
and abort these steps.
"boolean"
and
source is a
falsy value, set idata's [[value]]
value to false
, otherwise to
true
."integer"
or
"number"
and source is not a number, or
if form's
minimum is defined and source is smaller, or if
form's maximum
is defined and source is bigger, throw
RangeError
and abort these steps.
"string"
and
source is not
a string, let idata's [[value]] be the
result of running
serialize JSON to bytes on source. If that is
failure, throw
SyntaxError
and abort these steps.
"array"
, run
these sub-steps:
TypeError
and abort these
steps.
RangeError
and abort these
steps.
"object"
, run
these sub-steps:
TypeError
and abort these
steps.
TypeError
and abort these
steps.
SyntaxError
and abort these steps.
ReadableStream
created from
idata's [[value]]
internal slot as its underlying
source.
ConsumedThing
object thing, in order
to parse
interaction response given response,
form and schema, run these steps:
InteractionOutput
object.
ReadableStream
with the payload data of
response as its underlying
source.
false
.InteractionInput
and InteractionOutput
As illustrated in the next pictures, the
interface is used every time implementations provide data to
scripts, while InteractionOutput
is used when the scripts pass data to the implementation.InteractionInput
When a
reads data, it receives it from the implementation as an
ConsumedThing
object.InteractionOutput
An
read handler
provides the read data to the implementation as
ExposedThing
.InteractionInput
When a
writes data, it provides it to the implementation as
ConsumedThing
.InteractionInput
An
write
handler receives data from to implementation as an
ExposedThing
object.InteractionOutput
When a
invokes an Action
data, it provides the parameters as ConsumedThing
and receives the output of the Action as an InteractionInput
object.InteractionOutput
An
action handler
receives arguments from the implementation as an
ExposedThing
object and provides Action output as
InteractionOutput
to the implementation.InteractionInput
The algorithms in this API define the errors to be reported to application scripts.
The errors reported to the other communication end are mapped and encapsulated by the Protocol Bindings.
This topic is still being discussed in Issue #200. A standardized error mapping would be needed in order to ensure consistency in mapping script errors to protocol errors and vice versa. In particular, when algorithms say "error received from the Protocol Bindings", that will be factored out as an explicit error mapping algorithm. Currently, that is encapsulated by implementations.
ConsumedThing
interfaceRepresents a client API to operate a Thing. Belongs to the WoT Consumer conformance class.
WebIDL[SecureContext, Exposed=(Window,Worker)] interfaceConsumedThing
{constructor
(ThingDescription
td); Promise<InteractionOutput
>readProperty
(DOMString propertyName, optionalInteractionOptions
options = null); Promise<PropertyMap
>readAllProperties
(optionalInteractionOptions
options = null); Promise<PropertyMap
>readMultipleProperties
( sequence<DOMString> propertyNames, optionalInteractionOptions
options = null); Promise<undefined>writeProperty
(DOMString propertyName,InteractionInput
value, optionalInteractionOptions
options = null); Promise<undefined>writeMultipleProperties
(PropertyMap
valueMap, optionalInteractionOptions
options = null); /*Promise<undefined> writeAllProperties(PropertyMap valueMap, optional InteractionOptions options = null);*/ Promise<InteractionOutput
>invokeAction
(DOMString actionName, optionalInteractionInput
params = null, optionalInteractionOptions
options = null); Promise<Subscription
>observeProperty
(DOMString name,InteractionListener
listener, optionalErrorListener
onerror, optionalInteractionOptions
options = null); Promise<Subscription
>subscribeEvent
(DOMString name,InteractionListener
listener, optionalErrorListener
onerror, optionalInteractionOptions
options = null);ThingDescription
getThingDescription
(); }; dictionaryInteractionOptions
{ unsigned longformIndex
; objecturiVariables
; anydata
; }; [SecureContext, Exposed=(Window,Worker)] interfaceSubscription
{ readonly attribute booleanactive
; Promise<undefined>stop
(optionalInteractionOptions
options = null); }; typedef objectPropertyMap
; callbackInteractionListener
= undefined(InteractionOutput
data); callbackErrorListener
= undefined(Error error);
The writeAllProperties()
method is
still under discussion. Meanwhile, use the
writeMultipleProperties()
method instead.
A
object has the following
internal slots:ConsumedThing
Internal Slot | Initial value | Description (non-normative) |
---|---|---|
[[td]] | null |
The Thing
Description of the .
|
ConsumedThing
After fetching
a Thing Description as
a JSON object, one can create a
object.ConsumedThing
ConsumedThing
with the ThingDescription
td, run the
following steps:
SyntaxError
and abort these steps.
ConsumedThing
object.
getThingDescription()
methodReturns the
internal slot [[td]] of the
object that represents the Thing Description of
the ConsumedThing
.
Applications may consult the Thing metadata stored in [[td]]
in order to introspect its capabilities before interacting
with it.ConsumedThing
readProperty()
methodPromise
that resolves with a Property value represented
as an InteractionOutput
object or rejects on error. The method MUST run the following steps:
Promise
promise and execute the
next steps in
parallel.
SecurityError
and abort these steps.
readproperty
, selected by
the implementation.
SyntaxError
and abort these steps.
SyntaxError
and abort these steps.
readMultipleProperties()
methodPromise
that resolves with a PropertyMap
object that maps keys from propertyNames to values returned by
this algorithm. The method MUST
run the following steps:
Promise
promise and execute the
next steps in
parallel.
SecurityError
and abort these steps.
readmultipleproperties
, selected by the
implementation.
SyntaxError
and abort these steps.
null
.
NotSupportedError
and abort these steps.
readAllProperties()
methodPromise
that resolves with a PropertyMap
object that maps keys from Property names to values
returned by this algorithm. The method MUST run the following steps:
Promise
promise and execute the
next steps in
parallel.
SecurityError
and abort these steps.
readallproperties
, selected by the
implementation.
SyntaxError
and abort these steps.
NotSupportedError
and abort these steps.
writeProperty()
methodPromise
that resolves on success and rejects on failure. The method
MUST run the following steps:
Promise
promise and execute the
next steps in
parallel.
SecurityError
and abort these steps.
writeproperty
, selected
by the implementation.
SyntaxError
and abort these steps.
As discussed in Issue #193, the design decision is that write interactions only return success or error, not the written value (optionally). TDs should capture the schema of the Property values, including precision and alternative formats. When a return value is expected from the interaction, an Action should be used instead of a Property.
writeMultipleProperties()
methodPromise
that resolves on success and rejects on failure. The method
MUST run the following steps:
Promise
promise and execute the
next steps in
parallel.
SecurityError
and abort these steps.
writemultipleproperties
, selected by the
implementation.
SyntaxError
and abort these steps.
null
or undefined
or is not
writeable
reject promise with
NotSupportedError
and abort these steps.
null
.
NotSupportedError
and abort these steps.
observeProperty()
methodPromise
that resolves on success and rejects on failure. The method
MUST run the following steps:
Promise
promise and execute the
next steps in
parallel.
SecurityError
and abort these steps.
Function
,
reject promise with a
TypeError
and abort these steps.
null
and is not a Function
,
reject promise with a
TypeError
and abort these steps.
Subscription
object with its
internal slots set as follows:
"property"
."observeproperty"
, selected by the
implementation.
SyntaxError
and abort these steps.
false
and suppress
further notifications.
NetworkError
and set its
message to reflect the underlying error
condition.
Function
,
invoke it with error.
invokeAction()
methodPromise
that resolves with the result of the Action represented as an
InteractionOutput
object, or rejects with an error. The method MUST run the following steps:
Promise
promise and execute the
next steps in
parallel.
SecurityError
and abort these steps.
invokeaction
,
selected by the implementation.
SyntaxError
and abort these steps.
subscribeEvent()
methodPromise
to signal success or failure. The method MUST run the following steps:
Promise
promise and execute the
next steps in
parallel.
SecurityError
and abort these steps.
Function
,
reject promise with a
TypeError
and abort these steps.
null
and is not a Function
,
reject promise with a
TypeError
and abort these steps.
Subscription
object with its
internal slots set as follows:
"event"
."subscribeevent"
, selected by the
implementation.
SyntaxError
and abort these steps.
false
and suppress
further notifications.
NetworkError
and set its
message to reflect the underlying error
condition.
Function
,
invoke it with error.
InteractionOptions
dictionary Holds the interaction options that need to be exposed for application scripts according to the Thing Description.
The formIndex
property, if defined, represents an application hint for
which Form
definition, identified by this index,
of the TD to use
for the given WoT interaction. Implementations SHOULD use the Form
with this
index for making the interaction, but MAY override this value if the index is not
found or not valid. If not defined, implementations
SHOULD attempt to use the
Form
definitions in order of appearance as
listed in the TD for the
given Wot Interaction.
The uriVariables
property
if defined, represents the URI template variables to be used
with the WoT Interaction that are represented as parsed
JSON objects defined in [WOT-TD].
The support for URI variables comes from the need, exposed by the Web of Things Thing Description specification, to be able to describe existing REST-ful endpoints that use them. However, it should be possible to write a Thing Description that would use Actions for representing this kind of interactions and model the URI variables as action parameters. In that case, implementations can serialize the parameters as URI variables, and therefore, the options parameter could be dismissed.
The data
property if defined, represents
additional opaque data that needs to be passed to the
interaction.
PropertyMap
typeRepresents a map of Property names as strings to a value that the Property can take. It is used as a property bag for interactions that involve multiple Properties at once.
It could be defined in Web IDL (as well as
)
as a maplike
interface from string to any.ThingDescription
InteractionListener
callbackUser provided callback that is given an argument of type
and is used for observing Property changes and handling
Event notifications.
Since subscribing to Events are WoT interactions and
might take options or even data, they are not modelled with
software events.InteractionOutput
ErrorListener
callbackUser provided callback that is given an argument of type
Error
and is used for conveying critical and non-critical errors
from the Protocol Bindings to
applications.
Subscription
interfaceRepresents a subscription to Property change and Event interactions.
The active
boolean
property denotes if the subscription is active, i.e. it is
not stopped because of an error or because of invocation of
the stop()
method.
Subscription
Subscription
object has the following
internal slots:
Internal Slot | Initial value | Description (non-normative) |
---|---|---|
[[type]] | null |
Indicates what WoT
Interaction the
refers to. The value can be either
"property" or "event" or
null .
|
[[name]] | null |
The Property or Event name. |
[[interaction]] | null |
The Thing Description fragment that describes the WoT interaction. |
[[form]] | null |
The Form associated with the subscription. |
stop()
methodStops delivering notifications for the subscription. It
takes an optional parameter options and returns a
Promise
.
When invoked, the method MUST
execute the following steps:
Promise
promise and execute the
next steps in
parallel.
SecurityError
and abort these steps.
SyntaxError
and abort these steps.
"property"
, make a
request to the underlying platform via the Protocol Bindings
to stop observing the Property identified by
[[name]] with unsubscribeForm and optional URI
templates given in options'
uriVariables.
"event"
, make
a request to the underlying platform via the Protocol Bindings
to unsubscribe from the Event identified by [[name]]
with unsubscribeForm, with optional URI
templates given in options'
uriVariables and optional unsubscribe data
given in options's data.
false
and resolve promise.
To find a matching
unsubscribe form given subscribeForm in
the context of a
object, run the following steps:
Subscription
This algorithm is under development and is
non-normative. Implementations MAY choose another algorithm to find a
matching unsubscribe
Form to a given
subscribe
Form.
0
.
"unobserveproperty"
if [[type]] is
"property"
or if form's
op is "unsubscribeevent"
if
[[type]] is"event"
,
null
and terminate these steps.The next example illustrates how to fetch a TD by URL, create a
,
read metadata (title), read property value, subscribe to
property change, subscribe to a WoT event, unsubscribe.ConsumedThing
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 {
// subscribe to property change for “temperature”
await thing.observeProperty("temperature", async (data) => {
try {
console.log("Temperature changed to: " + await data.value());
} catch (error) {
console.error("Cannot read the observed property temperature");
console.error(error);
}
});
// subscribe to the “ready” event defined in the TD
await thing.subscribeEvent("ready", async (eventData) => {
try {
console.log("Ready; index: " + await eventData.value());
// run the “startMeasurement” action defined by TD
await thing.invokeAction("startMeasurement", { units: "Celsius" });
console.log("Measurement started.");
} catch (error) {
console.error("Cannot read the ready event or startMeasurement failed");
console.error(error)
}
});
} catch (e) {
console.log("Error starting measurement.");
}
setTimeout(async () => {
try {
const temperatureData = await thing.readProperty("temperature")
const temperature = await temperatureData.value();
console.log("Temperature: " + temperature);
await thing.unsubscribe("ready");
console.log("Unsubscribed from the ‘ready’ event.");
} catch (error) {
console.log("Error in the cleanup function");
}
}, 10000);
The following shows an advance usage of
to read a property without a InteractionOutput
DataSchema
.
/*
* takePicture affordance form:
* "form": {
* "op": "invokeaction",
* "href" : "http://camera.example.com:5683/takePicture",
* "response": {
* "contentType": "image/jpeg",
* "contentCoding": "gzip"
* }
*}
* See https://www.w3.org/TR/wot-thing-description/#example-23
*/
let response;
let image;
try {
response = await thing.invokeAction(“takePicture”));
image = await response.value() // throws NotReadableError --> schema not defined
} catch(ex) {
image = await response.arrayBuffer();
// image: ArrayBuffer [0x1 0x2 0x3 0x5 0x15 0x23 ...]
}
Finally, the next two examples shows the usage of a
ReadableStream
from an
.InteractionOutput
/*{
"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("live video stoped");
return;
}
const decoded = decode(value)
UI.show(decoded)
// Read some more, and call this function again
return reader.read().then(processText);
});
Here consider that the JSON object is too big to be read wholly in the memory. Therefore, we use streaming processing to get the total number of the events recorded by the remote Web Thing.
/*
* "eventHistory":
* {
* "description" : "A long list of the events recorderd by this thing",
* "type": "array",
* "forms": [
* {
* "op": "readproperty",
* "href": "http://recorder.example.com/eventHistory",
* }
* ]
* }
*/
// Example of streaming processing: counting json objects
let objectCounter = 0
const parser = new Parser() //User library for json streaming parsing (i.e. https://github.com/uhop/stream-json/wiki/Parser)
parser.on('data', data => data.name === 'startObject' && ++objectCounter);
parser.on('end', () => console.log(`Found ${objectCounter} objects.`));
const response = await thing.readProperty(“eventHistory”)
await response.data.pipeTo(parser);
// Found N objects
ExposedThing
interfaceThe
interface is the server API to operate the Thing that allows defining request
handlers, Property, Action, and Event interactions.ExposedThing
WebIDL[SecureContext, Exposed=(Window,Worker)] interfaceExposedThing
{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);ExposedThing
setActionHandler
(DOMString name,ActionHandler
action);ExposedThing
setEventSubscribeHandler
(DOMString name,EventSubscriptionHandler
handler);ExposedThing
setEventUnsubscribeHandler
(DOMString name,EventSubscriptionHandler
handler);ExposedThing
setEventHandler
(DOMString name,EventListenerHandler
eventHandler); Promise<undefined>emitEvent
(DOMString name,InteractionInput
data); Promise<undefined>expose
(); Promise<undefined>destroy
();ThingDescription
getThingDescription
(); }; callbackPropertyReadHandler
= Promise<any>( optionalInteractionOptions
options = null); callbackPropertyWriteHandler
= Promise<undefined>(InteractionOutput
value, optionalInteractionOptions
options = null); callbackActionHandler
= Promise<InteractionInput
>(InteractionOutput
params, optionalInteractionOptions
options = null); callbackEventSubscriptionHandler
= Promise<undefined>( optionalInteractionOptions
options = null); callbackEventListenerHandler
= Promise<InteractionInput
>();
An
object has the following
internal slots:ExposedThing
Internal Slot | Initial value | Description (non-normative) |
---|---|---|
[[td]] | null |
The Thing Description of the Thing. |
ExposedThing
The
interface extends ExposedThing
.
It is constructed from a full or partial ConsumedThing
object.ThingDescription
Note that an existing
object can be optionally modified (for instance by adding
or removing elements on its properties,
actions and events internal
properties) and the resulting object can used for
constructing an ThingDescription
object. This is the current way of adding and removing
Property, Action and Event definitions, as
illustrated in the examples.ExposedThing
Before invoking expose(), the
object does not serve any requests. This allows first
constructing ExposedThing
and then initialize its Properties and service
handlers before starting serving requests.ExposedThing
ExposedThing
with the ThingDescription
td, run the
following steps:
SecurityError
and abort these steps.
ExposedThing
object.
getThingDescription()
methodReturns the
internal slot [[td]] of the
object that represents the Thing Description of
the Thing. Applications may
consult the Thing
metadata stored in [[td]] in order to introspect its
capabilities before interacting with it.ExposedThing
PropertyReadHandler
callbackA function that is called when an external request for
reading a Property is received and
defines what to do with such requests. It returns a
Promise
and resolves with an ReadableStream
object or an
ECMAScript value conforming to DataSchema, or rejects with
an error.
setPropertyReadHandler()
methodTakes as arguments name and handler. Sets the service handler that defines what to do when a request is received for reading the specified Property matched by name. Throws on error. Returns a reference to this object for supporting chaining.
Note that there is no need to register handlers
for handling requests for reading multiple or all Properties. The request
and reply are transmitted in a single network request, but
the ExposedThing
may implement them using multiple calls to the single read
handler.
The handler callback function should implement reading a Property and SHOULD be called by implementations when a request for reading a Property is received from the underlying platform.
There MUST be at most one handler for any given Property, so newly added handlers MUST replace the previous handlers. If no handler is initialized for any given Property, implementations SHOULD implement a default property read handler based on the Thing Description provided in the [[td]] internal slot.
SecurityError
and abort these steps.
NotFoundError
and abort these steps.
NotSupportedError
according to the Protocol
Bindings and abort these steps.
NotAllowedError
according to the Protocol
Bindings and abort these steps.
NotFoundError
and abort these steps.
null
.null
, throw
NotSupportedError
and abort these
steps.
The value returned here
SHOULD either conform to
DataSchema or it
SHOULD be an
ReadableStream
object created by
the handler.
NotSupportedError
according to the Protocol
Bindings and abort these steps.
NotAllowedError
according to the Protocol
Bindings and abort these steps.
NotSupportedError
according to the Protocol
Bindings and abort these steps.
NotAllowedError
according to the Protocol
Bindings and abort these steps.
null
.
setPropertyObserveHandler()
methodTakes as arguments name and handler. Sets the service handler that defines what to do when a request is received for observing the specified Property matched by name. Throws on error. Returns a reference to this object for supporting chaining.
The handler
callback function should implement reading a Property and resolve with
an
object or reject with an error.InteractionOutput
There MUST be at most one handler for any given Property, so newly added handlers MUST replace the previous handlers. If no handler is initialized for any given Property, implementations SHOULD implement a default property read handler based on the Thing Description.
SecurityError
and abort these steps.
NotFoundError
and abort these steps.
NotSupportedError
according to the Protocol
Bindings and abort these steps.
NotAllowedError
according to the Protocol
Bindings and abort these steps.
NotFoundError
in the reply and abort these steps.
Every time the value of property
changes, emitPropertyChange()
needs to be explicitly called by the application
script.
setPropertyUnobserveHandler()
methodTakes as arguments name and handler. Sets the service handler that defines what to do when a request is received for unobserving the specified Property matched by name. Throws on error. Returns a reference to this object for supporting chaining.
The handler callback function should implement what to do when an unobserve request is received by the implementation.
There MUST be at most one handler for any given Property, so newly added handlers MUST replace the previous handlers. If no handler is initialized for any given Property, implementations SHOULD implement a default handler based on the Thing Description.
SecurityError
and abort these steps.
NotFoundError
and abort these steps.
NotSupportedError
according to the Protocol
Bindings and abort these steps.
NotAllowedError
according to the Protocol
Bindings and abort these steps.
NotFoundError
in the reply and abort these steps.
Function
defined for name on
property, invoke that with options, send back a reply
following the Protocol Bindings
and abort these steps.
NotFoundError
in the reply and abort these steps.
emitPropertyChange()
methodPromise
promise and execute the
next steps in
parallel.
SecurityError
and abort these steps.
NotFoundError
and abort these steps.
null.
null
, abort these steps.null
.PropertyWriteHandler
callbackA function that is called when an external request for
writing a Property is received and
defines what to do with such requests. Takes as argument
value and returns a
Promise
,
resolved when the value of the Property - identified by the
name provided when setting the handler has been updated -, or
rejects with an error if the property is not found or the
value cannot be updated.
Note that the code in this callback function can read the property before updating it in order to find out the old value, if needed. Therefore the old value is not provided to this function.
The value is provided by implementations as an
object in order to be able to represent values that are not
described by a DataSchema, such as
streams.InteractionOutput
setPropertyWriteHandler()
methodTakes as arguments name and handler. Sets the service handler that defines what to do when a request is received for writing the Property matched by name given when setting the handler. Throws on error. Returns a reference to this object for supporting chaining.
Note that even for readonly Properties it is possible to specify a write handler, as explained in Issue 199. In this case, the write handler may define in an application-specific way to fail the request.
There MUST be at most one write handler for any given Property, so newly added handlers MUST replace the previous handlers. If no write handler is initialized for any given Property, implementations SHOULD implement default property update if the Property is writeable and notifying observers on change if the Property is observable, based on the Thing Description.
SecurityError
and abort these steps.
NotFoundError
and abort these steps.
"single"
:
NotSupportedError
according to the Protocol
Bindings and abort these steps.
NotAllowedError
according to the Protocol
Bindings and abort these steps.
NotFoundError
in the reply and abort these steps.
null
.null
, send back a
NotSupportedError
with the reply and abort
these steps.
"single"
, reply to
the request reporting success, following the Protocol Bindings
and abort these steps.
NotSupportedError
according to the Protocol
Bindings and abort these steps.
NotAllowedError
according to the Protocol
Bindings and abort these steps.
"multiple"
. If that
fails, reply to the request with that error and abort
these steps.
ActionHandler
callbackA function that is called when an external request for
invoking an Action
is received and defines what to do with such requests. It is
invoked with params
and optionally with an options object. It returns a
Promise
that rejects with an error or resolves with the value
returned by the Action
as
.InteractionInput
Application scripts MAY return a ReadableStream
object from an
.
Implementations will then use the stream for constructing
the Action's response.ActionHandler
setActionHandler()
methodTakes as arguments name and action. Sets the handler function that defines what to do when a request is received to invoke the Action matched by name. Throws on error. Returns a reference to this object for supporting chaining.
The action callback function will implement an Action and SHOULD be called by implementations when a request for invoking the Action is received from the underlying platform.
There MUST be at most one handler for any given Action, so newly added handlers MUST replace the previous handlers.
SecurityError
and abort these steps.
NotFoundError
and abort these steps.
NotSupportedError
according to the Protocol
Bindings and abort these steps.
NotAllowedError
according to the Protocol
Bindings and abort these steps.
NotFoundError
in the reply and abort these steps.
null
.
null
, return a
NotSupportedError
with the reply created by
following the Protocol
Bindings and abort these steps.
EventListenerHandler
callbackA function that is called when an associated Event is triggered and provides
the data to be sent with the Event to subscribers. Returns a
Promise
that resolves with
value that represents the Event data, or rejects with an
error.InteractionInput
Applications MAY
return ReadableStream
from an
Implementations will then use the stream provided in
EventListenerHandler
when constructing the event notification.InteractionOutput
EventSubscriptionHandler
callbackA function that is called when an external request for
subscribing to an Event is
received and defines what to do with such requests. It is
invoked with an options object provided by the
implementation and coming from subscribers. It returns a
Promise
that rejects with an error or resolves when the subscription
is accepted.
setEventSubscribeHandler()
methodTakes as arguments name and handler. Sets the handler function that defines what to do when a subscription request is received for the specified Event matched by name. Throws on error. Returns a reference to this object for supporting chaining.
The handler callback function SHOULD implement what to do when an subscribe request is received, for instance necessary initializations. Note that the handler for emitting Events is set separately.
There MUST be at most one event subscribe handler for any given Event, so newly added handlers MUST replace the previous handlers.
SecurityError
and abort these steps.
NotFoundError
and abort these steps.
this
.
NotSupportedError
according to the Protocol
Bindings and abort these steps.
NotAllowedError
according to the Protocol
Bindings and abort these steps.
NotFoundError
and abort these steps.
setEventUnsubscribeHandler()
methodTakes as arguments name and handler. Sets the handler function that defines what to do when the specified Event matched by name is unsubscribed from. Throws on error. Returns a reference to this object for supporting chaining.
The handler callback function SHOULD implement what to do when an unsubscribe request is received.
There MUST be at most one handler for any given Event, so newly added handlers MUST replace the previous handlers.
SecurityError
and abort these steps.
NotFoundError
and abort these steps.
this
.
NotSupportedError
according to the Protocol
Bindings and abort these steps.
NotAllowedError
according to the Protocol
Bindings and abort these steps.
NotFoundError
and abort these steps.
this
.setEventHandler()
methodTakes as arguments name and eventHandler. Sets the event handler function for the specified Event matched by name. Throws on error. Returns a reference to this object for supporting chaining.
The eventHandler callback function will implement what to do when the event is emitted. It SHOULD resolve with a value that represents the Event data, or reject with an error.
There MUST be at most one handler for any given Event, so newly added handlers MUST replace the previous handlers.
SecurityError
and abort these steps.
NotFoundError
and abort these steps.
this
.null
,
The error reporting is protocol specific and it is encapsulated by implementations. On the client end, the error listener passed with the subscription will be invoked if the client UA detects the error.
emitEvent()
methodPromise
promise and execute the
next steps in
parallel.
SecurityError
and abort these steps.
NotFoundError
and abort these steps.
This will trigger the handling events steps.
expose()
methodPromise
promise and execute the
next steps in
parallel.
SecurityError
and abort these steps.
TypeError
and abort these steps.
Error
object error with error's
message set to the error code seen by the
Protocol Bindings
and abort these steps.
destroy()
methodPromise
promise and execute the
next steps in
parallel.
SecurityError
and abort these steps.
Error
object error with its message set
to the error code seen by the Protocol Bindings
and abort these steps.
The next example illustrates how to create an
based on a partial TD object
constructed beforehands.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);
// initialize Properties
await thing1.writeProperty("temperature", 0);
// add service handlers
thing1.setPropertyReadHandler("temperature", () => {
return readLocalTemperatureSensor(); // Promise
});
// start serving requests
await thing1.expose();
} catch (err) {
console.log("Error creating ExposedThing: " + err);
}
The next example illustrates how to add or modify a
Property definition
on an existing
:
take its td property, add or modify it, then
create another ExposedThing
with that.ExposedThing
try {
// create a deep copy of thing1's 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 = [...]; // update
var thing2 = new ExposedThing(instance);
// TODO: add service handlers
await thing2.expose();
});
} catch (err) {
console.log("Error creating ExposedThing: " + err);
}
ThingDiscovery
interfaceDiscovery is a distributed application that requires provisioning and support from participating network nodes (clients, servers, directory services). This API models the client side of typical discovery schemes supported by various IoT deployments.
The
object is constructed given a filter and provides the
properties and methods controlling the discovery process.ThingDiscovery
WebIDL[SecureContext, Exposed=(Window,Worker)] interfaceThingDiscovery
{constructor
(optionalThingFilter
filter = null); readonly attributeThingFilter
?filter
; readonly attribute booleanactive
; readonly attribute booleandone
; readonly attribute Error?error
; undefinedstart
(); Promise<ThingDescription
>next
(); undefinedstop
(); };
The
interface has a ThingDiscovery
next()
method and a
done
property, but it is not an Iterable.
Look into Issue
177 for rationale.
The discovery results
internal slot is an internal queue for temporarily storing
the found
objects until they are consumed by the application using the
next() method. Implementations
MAY optimize the size of this queue
based on e.g. the available resources and the frequency of
invoking the next() method.ThingDescription
The filter
property
represents the discovery filter of type ThingFilter
specified for the discovery.
The active
property is
true
when the discovery is actively ongoing on
protocol level (i.e. new TDs may still arrive) and
false
otherwise.
The done
property is
true
if the discovery has been completed with no
more results to report and discovery results is also
empty.
The error
property
represents the last error that occurred during the discovery
process. Typically used for critical errors that stop
discovery.
ThingDiscovery
ThingDiscovery
with a filter or type
ThingFilter
,
run the following steps:
The start() method sets
active to
true
. The stop()
method sets the active property to
false, but done may be still
false
if there are
objects in the discovery results not
yet consumed with next().ThingDescription
During successive calls of next(), the active property may
be true
or false
, but the done property is set
to false
by next() only when both the active property is
false
and discovery results is
empty.
DiscoveryMethod
enumerationWebIDLtypedef DOMString DiscoveryMethod
;
Represents the discovery type to be used:
ThingFilter
dictionaryRepresents an object containing the constraints for discovering Things as key-value pairs.
WebIDLdictionaryThingFilter
{ (DiscoveryMethod
or DOMString)method
= "any"; USVString?url
; USVString?query
; object?fragment
; };
The method
property represents the discovery type that should be used in
the discovery process. The possible values are defined by the
DiscoveryMethod
enumeration that MAY be extended by
string values defined by solutions (with no guarantee of
interoperability).
The url
property represents
additional information for the discovery method, such as the
URL of the target entity serving the discovery request, for
instance the URL of a Thing Directory (if
method is
"directory"
), or otherwise the URL of a directly
targeted Thing.
The query
property represents a
query string accepted by the implementation, for instance a
SPARQL or JSON query. Support may be implemented locally in
the WoT
Runtime or remotely as a service in a Thing
Directory.
The fragment
property represents a
template object used for matching property by property
against discovered Things.
start()
method
SecurityError
and abort these steps.
NotSupportedError
and abort these steps.
NotSupportedError
and abort these
steps.
ThingDescription
objects.
"any"
,
use the widest discovery method supported by the
underlying platform.
"local"
, use the local Thing Directory
for discovery. Usually that defines Things deployed
in the same device, or connected to the device in
slave mode (e.g. sensors connected via Bluetooth or a
serial connection).
"directory"
, use the remote
Thing Directory
specified in |filter.url|.
"multicast"
, use all the multicast
discovery protocols supported by the underlying
platform.
true
.
SyntaxError
, discard td and continue the
discovery process.
false
,
discard td
and continue the discovery process.
false
in any checks, discard td and continue the
discovery process.
Error
object. Set error's name to
"DiscoveryError"
.
false
.
false
.
next()
methodThingDescription
object. The method MUST run the
following steps:
Promise
promise and execute the
next steps in
parallel.
true
, wait until the discovery results
internal slot is not empty.
false
, set the done property
to true
and reject promise.
ThingDescription
object td from discovery results.
stop()
methodfalse
.
The following example finds
objects of Things
that are exposed by local hardware, regardless how many
instances of WoT
Runtime it is running. Note that the discovery can end
(become inactive) before the internal discovery
results queue is emptied, so we need to continue reading
ThingDescription
objects until done. This is typical with local and directory
type discoveries.ThingDescription
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);
The next example finds
objects of Things
listed in a Thing Directory service.
We set a timeout for safety.ThingDescription
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);
}
The next example is for an open-ended multicast discovery, which likely won't complete soon (depending on the underlying protocol), so stopping it with a timeout is a good idea. It will likely deliver results one by one.
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);
A detailed discussion of security and privacy considerations for the Web of Things, including a threat model that can be adapted to various circumstances, is presented in the informative document [WOT-SECURITY]. This section discusses only security and privacy risks and possible mitigations directly relevant to the scripts and WoT Scripting API.
A suggested set of best practices to improve security for WoT devices and services has been documented in [WOT-SECURITY]. That document may be updated as security measures evolve. Following these practices does not guarantee security, but it might help avoid common known vulnerabilities.
This section is normative and contains specific risks relevant for the WoT Scripting Runtime.
A typical way to compromise any process is to send it a corrupted input via one of the exposed interfaces. This can be done to a script instance using WoT interface it exposes.
In case a script is compromised or misbehaving, the underlying physical device (and potentially surrounded environment) can be damaged if a script can use directly exposed native device interfaces. If such interfaces lack safety checks on their inputs, they might bring the underlying physical device (or environment) to an unsafe state (i.e. device overheats and explodes).
If the WoT Scripting Runtime supports post-manufacturing provisioning or updates of scripts, WoT Scripting Runtime or any related data (including security credentials), it can be a major attack vector. An attacker can try to modify any above described element during the update or provisioning process or simply provision attacker's code and data directly.
Typically the WoT Scripting Runtime needs to store the security credentials that are provisioned to a WoT device to operate in WoT network. If an attacker can compromise the confidentiality or integrity of these credentials, then it can obtain access to the WoT assets, impersonate WoT things or devices or create Denial-Of-Service (DoS) attacks.
This section is non-normative.
This section describes specific risks relevant for script developers.
A script instance may receive data formats defined by the TD, or data formats defined by the applications. While the WoT Scripting Runtime SHOULD perform validation on all input fields defined by the TD, scripts may be still exploited by input data.
If a script performs a heavy functional processing on received requests before the request is authenticated, it presents a great risk for Denial-Of-Service (DOS) attacks.
The generic WoT terminology is defined in [WOT-ARCHITECTURE]: Thing, Thing Description (in short TD), Web of Things (in short WoT), WoT Interface, Protocol Bindings, WoT Runtime, Consuming a Thing Description, Thing Directory, Property, Action, Event, DataSchema, Form etc.
WoT Interaction is a synonym for Interaction Affordance. An Interaction Affordance (or shortly, affordance) is the term used in [WOT-TD] when referring to Thing capabilities, as explained in TD issue 282. However, this term is not well understood outside the TD semantic context. Hence for the sake of readability, this document will use the previous term WoT interaction or, simply, interaction instead.
WoT network interface synonym for WoT Interface
JSON-LD is defined in [JSON-LD] as a JSON document that is augmented with support for Linked Data.
JSON Schema is defined in these specifications.
The terms URL, URL scheme, URL host, URL path, URL record, parse a URL, absolute-URL string, path-absolute-URL string, basic URL parser are defined in [URL].
The terms MIME type, Parsing a MIME type, Serializing a MIME type, valid MIME type string, JSON MIME type are defined in [MIMESNIFF].
The terms UTF-8 encoding, UTF-8 decode, encode, decode are defined in [ENCODING].
string, parse JSON from bytes and serialize JSON to bytes, are defined in [INFRA].
Promise
,
Error,
JSON,
JSON.stringify,
JSON.parse,
internal method and
internal slot are defined in [ECMASCRIPT].
The terms browsing context, top-level browsing context, global object, current settings object, executing algorithms in parallel are defined in [HTML5] and are used in the context of browser implementations.
The term secure context is defined in [WEBAPPSEC].
IANA media types (formerly known as MIME types) are defined in RFC2046.
The terms hyperlink reference and relation type are defined in [HTML5] and RFC8288.
API rationale usually belongs to a separate document, but in the WoT case the complexity of the context justifies including basic rationale here.
The WoT Interest Group and Working Group have explored different approaches to application development for WoT that have been all implemented and tested.
It is possible to develop WoT applications that only use the WoT network interface, typically exposed by a WoT gateway that presents a REST-ful API towards clients and implements IoT protocol plugins that communicate with supported IoT deployments. One such implementation is the Mozilla WebThings platform.
WoT Things show good synergy with software objects, so a Thing can be represented as a software object, with Properties represented as object properties, Actions as methods, and Events as events. In addition, metadata is stored in special properties. Consuming and exposing is done with factory methods that produce a software object that directly represents a remote Thing and its interactions. One such implementation is the Arena Web Hub project.
In the next example, a Thing that represents
interactions with a lock would look like the following: the
status property and the open()
method are directly exposed on the object.
let lock = await WoT.consume(‘https://td.my.com/lock-00123’);
console.log(lock.status);
lock.open('withThisKey');
Since the direct mapping of Things to software objects have had some challenges, this specification takes another approach that exposes software objects to represent the Thing metadata as data property and the WoT interactions as methods. One implementation is node-wot in the the Eclipse ThingWeb project, which is the current reference implementation of the API specified in this document.
The same example now would look like the following: the
status property and the open()
method are represented indirectly.
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');
In conclusion, the WoT WG decided to explore the third option that closely follows the Web of Things Thing Description specification. Based on this, a simple API can also be implemented. Since Scripting is an optional module in WoT, this leaves room for applications that only use the WoT network interface. Therefore all three approaches above are supported by the Web of Things Thing Description specification.
Moreover, the WoT network interface can be implemented in many languages and runtimes. Consider this API an example for what needs to be taken into consideration when designing a Scripting API for WoT.
The fetch(url)
method has been part of this
API in earlier versions. However, now fetching a TD given a URL should be done with
an external method, such as the Fetch API or a
HTTP client library, which offer already standardized options
on specifying fetch details. The reason is that while simple
fetch operations (covering most use cases) could be done in
this API, when various fetch options were needed, there was
no point in duplicating existing work to re-expose those
options in this API.
Since fetching a TD has been scoped out, and TD validation is defined externally in the Web of Things Thing Description specification, that is scoped out, too. This specification expects a TD as parsed JSON object that has been validated according to the Web of Things Thing Description specification.
The factory methods for consuming and exposing Things are asynchronous and fully
validate the input TD. In
addition, one can also construct
and ConsumedThing
by providing a parsed and validated TD. Platform initialization is then
done when needed during the WoT interactions.ExposedThing
Earlier drafts used the Observer construct, but since it has not become standard, a new design was needed that was light enough for embedded implementations. Therefore observing Property changes and handling WoT Events is done with callback registrations.
The reason to use function names like
readProperty()
,
readMultipleProperties()
etc. instead of a
generic polymorphic read()
function is that the
current names map exactly to the "op"
vocabulary
from the
Form definition in the
Web of Things Thing Description specification.
formIndex
,
InteractionData
including streams.For a complete list of changes, see the github change log. You can also view the recently closed issues.
WebIDLtypedef objectThingDescription
; [SecureContext, Exposed=(Window,Worker)] namespaceWOT
{ // methods defined in UA conformance classes }; partial namespaceWOT
{ Promise<ConsumedThing
>consume
(ThingDescription
td); }; partial namespaceWOT
{ Promise<ExposedThing
>produce
(ThingDescription
td); }; partial namespaceWOT
{ThingDiscovery
discover
(optionalThingFilter
filter = null); }; typedef anyDataSchemaValue
; typedef (ReadableStream orDataSchemaValue
)InteractionInput
; [SecureContext, Exposed=(Window,Worker)] interfaceInteractionOutput
{ readonly attribute ReadableStream?data
; readonly attribute booleandataUsed
; readonly attribute Form?form
; readonly attribute DataSchema?schema
; Promise<ArrayBuffer>arrayBuffer
(); Promise<any>value
(); }; [SecureContext, Exposed=(Window,Worker)] interfaceConsumedThing
{constructor
(ThingDescription
td); Promise<InteractionOutput
>readProperty
(DOMString propertyName, optionalInteractionOptions
options = null); Promise<PropertyMap
>readAllProperties
(optionalInteractionOptions
options = null); Promise<PropertyMap
>readMultipleProperties
( sequence<DOMString> propertyNames, optionalInteractionOptions
options = null); Promise<undefined>writeProperty
(DOMString propertyName,InteractionInput
value, optionalInteractionOptions
options = null); Promise<undefined>writeMultipleProperties
(PropertyMap
valueMap, optionalInteractionOptions
options = null); /*Promise<undefined> writeAllProperties(PropertyMap valueMap, optional InteractionOptions options = null);*/ Promise<InteractionOutput
>invokeAction
(DOMString actionName, optionalInteractionInput
params = null, optionalInteractionOptions
options = null); Promise<Subscription
>observeProperty
(DOMString name,InteractionListener
listener, optionalErrorListener
onerror, optionalInteractionOptions
options = null); Promise<Subscription
>subscribeEvent
(DOMString name,InteractionListener
listener, optionalErrorListener
onerror, optionalInteractionOptions
options = null);ThingDescription
getThingDescription
(); }; dictionaryInteractionOptions
{ unsigned longformIndex
; objecturiVariables
; anydata
; }; [SecureContext, Exposed=(Window,Worker)] interfaceSubscription
{ readonly attribute booleanactive
; Promise<undefined>stop
(optionalInteractionOptions
options = null); }; typedef objectPropertyMap
; callbackInteractionListener
= undefined(InteractionOutput
data); callbackErrorListener
= undefined(Error error); [SecureContext, Exposed=(Window,Worker)] interfaceExposedThing
{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);ExposedThing
setActionHandler
(DOMString name,ActionHandler
action);ExposedThing
setEventSubscribeHandler
(DOMString name,EventSubscriptionHandler
handler);ExposedThing
setEventUnsubscribeHandler
(DOMString name,EventSubscriptionHandler
handler);ExposedThing
setEventHandler
(DOMString name,EventListenerHandler
eventHandler); Promise<undefined>emitEvent
(DOMString name,InteractionInput
data); Promise<undefined>expose
(); Promise<undefined>destroy
();ThingDescription
getThingDescription
(); }; callbackPropertyReadHandler
= Promise<any>( optionalInteractionOptions
options = null); callbackPropertyWriteHandler
= Promise<undefined>(InteractionOutput
value, optionalInteractionOptions
options = null); callbackActionHandler
= Promise<InteractionInput
>(InteractionOutput
params, optionalInteractionOptions
options = null); callbackEventSubscriptionHandler
= Promise<undefined>( optionalInteractionOptions
options = null); callbackEventListenerHandler
= Promise<InteractionInput
>(); [SecureContext, Exposed=(Window,Worker)] interfaceThingDiscovery
{constructor
(optionalThingFilter
filter = null); readonly attributeThingFilter
?filter
; readonly attribute booleanactive
; readonly attribute booleandone
; readonly attribute Error?error
; undefinedstart
(); Promise<ThingDescription
>next
(); undefinedstop
(); }; typedef DOMStringDiscoveryMethod
; dictionaryThingFilter
{ (DiscoveryMethod
or DOMString)method
= "any"; USVString?url
; USVString?query
; object?fragment
; };
Special thanks to former editor Johannes Hund (until August 2017, when at Siemens AG) and Kazuaki Nimura (until December 2018) for developing this specification. Also, the editors would like to thank Dave Raggett, Matthias Kovatsch, Michael Koster, Elena Reshetova, Michael McCool as well as the other WoT WG members for their comments, contributions and guidance.