Copyright © 2017-2023 World Wide Web Consortium. 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 (WoT) Architecture 1.1 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 TD Directory.
This specification 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 (WoT) Thing Description 1.1 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. 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 Group Note using the Note track.
This Group Note is endorsed by the Web of Things Working Group, but is not endorsed by W3C itself nor its Members.
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.
The W3C Patent Policy does not carry any licensing requirements or commitments on this document.
This document is governed by the 12 June 2023 W3C Process Document.
WoT provides layered interoperability based on how Things are used: "consumed" and "exposed", as defined in the Web of Things (WoT) Architecture 1.1 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.
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 business use cases listed in the [WOT-USE-CASES] document may be implemented using this API, based on the scripting use case scenarios described here.
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.
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 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.
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, the ThingDiscoveryProcess
discover()
method, the
exploreDirectory()
method, and the
requestThingDescription()
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].
The generic WoT terminology is defined in [WOT-ARCHITECTURE]: Thing, Thing Description (in short TD), Partial TD, Web of Things (in short WoT), WoT Interface, Protocol Bindings, WoT Runtime, Consuming a Thing Description, TD Directory, Property, Action, Event, DataSchema, Form, SecurityScheme, NoSecurityScheme 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 Schema is defined in these specifications.
Promise
,
Error,
JSON,
JSON.stringify,
JSON.parse,
internal method and
internal slot are defined in [ECMASCRIPT].
WebIDLtypedef 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 (WoT) Thing Description 1.1 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 (WoT) Thing Description 1.1 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 ThingDescription
objects be validated before used as parameters. This
specification defines a basic TD validation as follows.
TypeError
"
and stop.
Additional steps may be added to fill the default values of mandatory fields.
Defines 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
};
WebIDLpartial namespace WOT
{
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 stop.
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.
WebIDLtypedef object ExposedThingInit
;
partial namespace WOT
{
Promise<ExposedThing
> produce
(ExposedThingInit
init);
};
Promise
that resolves with an
ExposedThing
object that extends ConsumedThing
with a server interface, i.e. the ability to define request
handlers. The init
object is an instance of the ExposedThingInit
type. Specifically, an ExposedThingInit
value is a dictionary used for the initialization of an
ExposedThing
and it represents a Partial TD as described in
the [WOT-ARCHITECTURE].
As such, it has the same structure of a Thing Description
but it may omit some information. The method MUST run the following steps:
Promise
promise and execute the next steps
in parallel.
SecurityError
and stop.
ExposedThing
object constructed with init.
SyntaxError
and stop.
"securityDefinitions"
],
make a request to the underlying platform to check if it
is supported by at least one Protocol Binding.
If not, then remove scheme from td.
"security"
]
does not exist in
td.["securityDefinitions"
],
then remove security
from td.
authority
it is
not recognized by the runtime as valid, remove
href from
form.The editors find this step vague. It will be improved or removed in the next iteration.
title
generate a runtime unique name and assign to
title
.@context
assign the latest supported Thing Description context
URI.instance
assign the string 1.0.0
.forms
generate a list of Forms using the available
Protocol
Bindings and content types encoders. Then
assign the obtained list to forms
.
security
assign the label of the first supported SecurityScheme in
securityDefinitions
field. If no
SecurityScheme
is found generate a NoSecurityScheme
called nosec
and assign the string
nosec
to security
.
The discussion about how to properly
generate a value for security
is
still open. See issue
#299
href
define
formStub as the partial Form that does not
have href
. Generate a valid
url using the first
Protocol
Binding that satisfy the requirements of
formStub. Assign url to href
. If not
Protocol
Binding can be found remove formStub
from td.
title
,
@context
, instance
,
forms
, security
, and
href
.required
execute the following
steps:
Array
then remove all its elements equal
to the elements in optionalstring
then if value is equal
to one of the elements in optional remove key from
exposedThingInitSchemaThe validating an object with JSON Schema steps are still under discussion. Currently this specification reference to the validation process of JSONSchema. Please follow this document when validating init with exposedThingInitSchema. Notice that the working group is evaluating an alternative formal approach.
WebIDLpartial namespace WOT
{
Promise<ThingDiscoveryProcess
> discover
(optional ThingFilter
filter = {});
};
ThingDescription
objects for Thing Descriptions
that match an optional filter argument of type
ThingFilter
.
The method MUST run the following
steps:
Promise
promise and execute the next steps
in parallel.
SecurityError
and stop.
NotSupportedError
and stop.
ThingDiscoveryProcess
object.
[[filter]]
to
filter.
[[url]]
to
undefined
.
undefined
or null
, reject
promise with
NotSupportedError
and stop.
OperationError
and stop.
WebIDLpartial namespace WOT
{
Promise<ThingDiscoveryProcess
> exploreDirectory
(USVString url,
optional ThingFilter
filter = {});
};
ThingDescription
objects for Thing Descriptions
that match an optional filter argument of type
ThingFilter
.
The method MUST run the following
steps:
Promise
promise and execute the next steps
in parallel.
SecurityError
and stop.
NotSupportedError
and stop.
ThingDiscoveryProcess
object.
[[url]]
to
url.
[[filter]]
to
filter.
This is a placeholder for more details in the discovery algorithm. Implementations should follow the procedures described in the [WOT-DISCOVERY] and [WOT-PROTOCOL-BINDINGS] specifications. Some normative steps are indicated below.
NotSupportedError
and terminate
these steps.
undefined
or null
,
reject
promise with
NotSupportedError
and stop.
From this point on, errors are
recorded only on
error
, but don't affect
promise any
longer.
WebIDLpartial namespace WOT
{
Promise<ThingDescription
> requestThingDescription
(USVString url);
};
Promise
promise and execute the next steps
in parallel.
SecurityError
and stop.
NotSupportedError
and stop.
NotFoundError
and stop.
As specified in the
Web of Things (WoT) Thing Description 1.1
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.
WebIDLtypedef any DataSchemaValue
;
typedef (ReadableStream or DataSchemaValue
) 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].
The possible values MUST be of type
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.
Belongs to the WoT Consumer conformance
class. An InteractionOutput
object is always created by the implementations and exposes
the data returned from WoT Interactions to
application scripts.
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)]
interface InteractionOutput
{
readonly attribute ReadableStream? data
;
readonly attribute boolean dataUsed
;
readonly attribute Form? form
;
readonly attribute DataSchema? schema
;
Promise<ArrayBuffer> arrayBuffer
();
Promise<DataSchemaValue
> 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).
contentType
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 stop.
ReadableStream
or if dataUsed is
true
, or if form is not an
object
or if schema or its type are
null
or undefined
, then
reject
promise with
NotReadableError
and stop.
application/json
and if a mapping is not
available in the Protocol Bindings
from form.contentType to
[JSON-SCHEMA],
reject
promise with
NotSupportedError
and stop.
true
.application/json
and if a mapping is
available in the Protocol
Bindings from
form.contentType to
[JSON-SCHEMA],
transform bytes with that mapping.
Promise
promise and execute the next steps
in parallel.
ReadableStream
or if dataUsed is
true
, reject
promise with
NotReadableError
and stop.
true
.
ArrayBuffer
whose contents are
bytes. If that throws, reject
promise with that
exception and stop.
"null"
and if
payload is not null
, throw
TypeError
and stop, otherwise return
null
.
"boolean"
and
payload is a falsy value or its byte length is
0, return false
, otherwise return
true
."integer"
or
"number"
,
TypeError
and stop.
RangeError
and stop.
"string"
, return
payload."array"
, run these
sub-steps:
TypeError
and stop.
RangeError
and stop.
"object"
, run
these sub-steps:
object
,
throw
TypeError
and stop.
SyntaxError
and stop.
ConsumedThing
object thing, in order
to create
interaction request given a source, form and schema,
run these steps:
InteractionOutput
object.
null
and set
idata.[[value]]
to
undefined
.
ReadableStream
object, let
idata.data be source, return
idata and stop.
null
, run
these sub-steps:
"null"
and
source is not
"null"
, throw
TypeError
and stop.
"boolean"
and
source is a
falsy value, set
idata.[[value]]
to
false
, otherwise set it to
true
.
"integer"
or
"number"
and source is not a number, or
if form.minimum is defined and
source is
smaller, or if form.maximum is defined and
source is
bigger, throw
RangeError
and stop.
"string"
and
source is not
a string, let idata.[[value]]
be
the result of running
serialize JSON to bytes given source. If that is
failure, throw
SyntaxError
and stop.
"array"
, run
these sub-steps:
TypeError
and stop.
RangeError
and stop.
[[value]]
to source.
"object"
, run
these sub-steps:
TypeError
and stop.
TypeError
and stop.
SyntaxError
and stop.
[[value]]
to source.
ReadableStream
created from
idata.[[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
InteractionOutput
interface is used every time implementations provide data to
scripts, while InteractionInput
is used when the scripts pass data to the implementation.
When a ConsumedThing
reads data, it receives it from the implementation as an
InteractionOutput
object.
An ExposedThing
read handler
provides the read data to the implementation as
InteractionInput
.
When a ConsumedThing
writes data, it provides it to the implementation as
InteractionInput
.
An ExposedThing
write
handler receives data from to implementation as an
InteractionOutput
object.
When a ConsumedThing
invokes an Action,
it provides the parameters as InteractionInput
and receives the output of the Action as an InteractionOutput
object.
An ExposedThing
action handler
receives arguments from the implementation as an
InteractionOutput
object and provides Action output as
InteractionInput
to the implementation.
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.
Represents a client API to operate a Thing. Belongs to the WoT Consumer conformance class.
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);
The writeAllProperties()
method is
still under discussion. Meanwhile, use the
writeMultipleProperties()
method instead.
ConsumedThing
A ConsumedThing
object has the following
internal slots:
Internal Slot | Initial value | Description (non-normative) |
---|---|---|
[[td]] | null |
The Thing
Description of the ConsumedThing .
|
[[activeSubscriptions]] | {} |
An ordered
map keyed on
a string
name representing the Event and
value is a
Subscription
object.
|
[[activeObservations]] | {} |
An ordered
map keyed on
a string
name representing a Property and
value is a
Subscription
object.
|
After fetching
a Thing Description as
a JSON object, one can create a ConsumedThing
object.
ConsumedThing
with the ThingDescription
td, run the
following steps:
SyntaxError
and stop.
ConsumedThing
object.
[[td]]
of
thing to
td.
Returns the [[td]]
of the
ConsumedThing
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.
Promise
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 stop.
[[td]]
.properties.propertyName.
undefined
,
reject
promise with a
NotFoundError
and stop.
readproperty
, selected by
the implementation.
SyntaxError
and stop.
SyntaxError
and stop.
Promise
that resolves with a
PropertyReadMap
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 stop.
[[td]]
.forms
array, otherwise let form be the Form in
[[td]]
.forms
array whose op is
readmultipleproperties
, as selected by the
implementation.
SyntaxError
and stop.
null
.
NotSupportedError
and stop.
[[td]]
.properties[key].
Promise
that resolves with a
PropertyReadMap
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 stop.
[[interaction]]
.forms.
undefined
,
reject
promise with a
SyntaxError
and stop.
undefined
and is less than
forms.length, set
subscription.[[form]]
to
forms.[formIndex].
[[form]]
to a
Form
in forms whose op is
"readallproperties"
, as selected by the
implementation.
[[form]]
is
failure, reject
promise with a
SyntaxError
and stop.
NotSupportedError
and stop.
[[td]]
.properties[key].
Promise
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 stop.
[[td]]
.properties[propertyName].
undefined
,
reject
promise with a
NotFoundError
and stop.
undefined
, let form be the
Form
associated with formIndex in the
interaction.forms array,
otherwise let form be a Form in
interaction.forms whose
op is writeproperty
, as
selected by the implementation.
SyntaxError
and stop.
promise
with that exception and stop.
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.
Promise
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 stop.
[[td]]
.forms
array, otherwise let form be a Form in
[[td]]
.forms
array whose op is
writemultipleproperties
, as selected by
the implementation.
SyntaxError
and stop.
[[td]]
.properties[name].
null
or
undefined
or is not writeable
reject
promise with
NotSupportedError
and stop.
null
.[[td]]
.properties[name].
promise
with that exception and stop.
NotSupportedError
and stop.
Promise
that resolves on success
and rejects on failure.
This algorithm allows for only one active
Subscription
per Property. If a new
Subscription
is made while an existing Subscription
is active the runtime will throw an NotAllowedError
.
ConsumedThing
object.
Promise
promise and execute the next steps
in parallel.
SecurityError
and stop.
Function
,
reject
promise with a
TypeError
and stop.
null
and is not a Function
,
reject
promise with a
TypeError
and stop.
[[activeObservations]]
[propertyName]
[=map/exists], reject
promise with a
NotAllowedError
and stop.
Subscription
object with its
internal slots set as follows:
[[type]]
be
"property"
.
[[name]]
be
propertyName.
[[interaction]]
be [[td]]
.properties[propertyName].
[[thing]]
be
thing.
[[interaction]]
.forms.
undefined
,
reject
promise with a
SyntaxError
and stop.
undefined
and is less than
forms.length, set
subscription.[[form]]
to
forms.[formIndex].
[[form]]
to a
Form in forms
whose op is
"observeproperty"
, as selected by the
implementation.
[[form]]
is
failure, reject
promise with a
SyntaxError
and stop.
[[interaction]]
is undefined
, reject
promise with a
NotFoundError
and stop.
[[activeObservations]]
[|propertyName]
to subscription and resolve
promise.
[[form]]
and
subscription.[[interaction]]
.
If that throws, reject
promise with that
exception and stop.
false
and suppress further
notifications.
NetworkError
and set its
message to reflect the underlying error
condition.
Function
,
invoke it given error.
Promise
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 stop.
[[td]]
.actions[actionName].
object
,
reject
promise with a
NotFoundError
and stop.
[[interaction]]
.forms.
undefined
,
reject
promise with a
SyntaxError
and stop.
undefined
and is less than
forms.length, set
subscription.[[form]]
to
forms.[formIndex].
[[form]]
to a
Form
in forms whose op is
"invokeaction"
, as selected by the
implementation.
[[form]]
is
failure, reject
promise with a
SyntaxError
and stop.
promise
with that exception and stop.
Promise
to signal success or
failure.
This algorithm allows for only one active
Subscription
per Event. If a new
Subscription
is made while an existing Subscription
is active the runtime will throw an NotAllowedError
.
ConsumedThing
object.
Promise
promise and execute the next steps
in parallel.
SecurityError
and stop.
Function
,
reject
promise with a
TypeError
and stop.
null
and is not a Function
,
reject
promise with a
TypeError
and stop.
[[activeSubscriptions]]
[eventName]
does not exist,
reject
promise with a
NotAllowedError
and stop.
Subscription
object with its
internal slots set as follows:
[[type]]
be
"event"
.
[[name]]
be
eventName.
[[interaction]]
be thing. [[td]]
.events[eventName].
[[interaction]]
is undefined
, reject
promise with a
NotFoundError
and stop.
[[thing]]
be
thing.
[[form]]
be
thing.[[interaction]]
.forms[formIndex].
[[form]]
be
an
implementation-defined Form from the
subscription.[[interaction]]
.forms
array whose op is
"subscribeevent"
.
[[form]]
does not exist,
reject
promise with a
SyntaxError
and stop.
[[form]]
, optional URI templates given
in options.uriVariables
and optional subscription data given in options.data.
[[activeSubscriptions]]
[eventName]
to subscription.
[[form]]
and
subscription.[[interaction]]
.
false
and suppress further
notifications.
NetworkError
and set its
message to reflect the underlying error
condition.
Function
,
invoke it given error.
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 (WoT) Thing Description 1.1 specification, to be able to describe existing RESTful 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.
Represents a map of Property names to an InteractionOutput
object that represents the value the Property can take. It is
used as a property bag for interactions that involve multiple
Properties at
once.
Represents a map of Property names to an InteractionInput
that represents the value the Property can take. It is used
as a property bag for interactions that involve multiple
Properties at
once.
User provided callback that is given an argument of type
InteractionOutput
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.
User 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.
Represents 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 Subscription
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. |
[[thing]] | null |
The ConsumedThing
associated with the subscription.
|
Stops delivering notifications for the subscription. It
takes an optional parameter options and returns a
. When invoked, the method
MUST execute the following
steps:Promise
Promise
promise and execute the next steps
in parallel.
SecurityError
and stop.
[[interaction]]
's
forms array.
[[form]]
.
SyntaxError
and stop.
[[type]]
is
"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.
[[type]]
is
"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.data.
false
.
[[type]]
is
"event"
, remove [[name]]
from
[[thing]]
.[[activeSubscriptions]]
.
[[type]]
is
"property"
, remove [[name]]
from
[[thing]]
.[[activeObservations]]
.
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.
Subscription
object, run the following steps:
[[interaction]]
.forms,
0
.
"unobserveproperty"
if
[[type]]
is
"property"
or if
form.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
ConsumedThing
,
read metadata (title), read property value, subscribe to
property change, subscribe to a WoT event, unsubscribe.
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 InteractionOutput
to read a property without a 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 recorded 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
The ExposedThing
interface is the server API to operate the Thing that allows defining request
handlers, Property, Action, and Event interactions.
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 = {});
An ExposedThing
object has the following
internal slots:
Internal Slot | Initial value | Description (non-normative) |
---|---|---|
[[td]] | null |
The Thing
Description of the ExposedThing .
|
[[readHandlers]] | {} |
A
Map with property names as keys and
PropertyReadHandler s
as values
|
[[writeHandlers]] | {} |
A
Map with property names as keys and
PropertyWriteHandler s
as values
|
[[observeHandlers]] | {} |
A
Map with property names as keys and
PropertyReadHandler s
as values
|
[[unobserveHandlers]] | {} |
A
Map with property names as keys and
Function s
as values
|
[[actionHandlers]] | {} |
A
Map with action names as keys and
ActionHandler s
as values
|
[[subscribeHandlers]] | {} |
A
Map with event names as keys and
EventSubscriptionHandler s
as values
|
[[unsubscribeHandlers]] | {} |
A
Map with event names as keys and
EventSubscriptionHandler s
as values
|
[[propertyObservers]] | {} |
A
Map with property names as keys and
an
Array of listeners as values
|
[[eventListeners]] | {} |
A
Map with event names as keys and
Array of listeners as values
|
ExposedThing
The ExposedThing
interface extends ConsumedThing
.
It is constructed from a full or partial ThingDescription
object.
Note that an existing ThingDescription
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 ExposedThing
object. This is the current way of adding and removing
Property, Action and Event definitions, as
illustrated in the examples.
Before invoking expose(), the
ExposedThing
object does not serve any requests. This allows first
constructing ExposedThing
and then initialize its Properties and service
handlers before starting serving requests.
ExposedThing
with the ExposedThingInit
init, run the
following steps:
SecurityError
and stop.
ExposedThing
object.
[[td]]
of
thing to
td.
Returns the [[td]]
of the
ExposedThing
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.
A 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
and resolves with an
Promise
ReadableStream
object or an
ECMAScript value conforming to DataSchema, or rejects with
an error.
Takes 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 stop.
[[td]]
.properties[name]
does not exist,
throw
NotFoundError
and stop.
[[readHandlers]]
[name]
to handler.
NotSupportedError
according to the Protocol
Bindings and stop.
NotAllowedError
according to the Protocol
Bindings and stop.
[[td]]
.properties.name.
NotFoundError
and stop.
null
.PropertyReadHandler
in
[[readHandlers]]
internal slot for interaction, let
handler be that.
null
, throw
NotSupportedError
and stop.
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 stop.
NotAllowedError
according to the Protocol
Bindings and stop.
NotSupportedError
according to the Protocol
Bindings and stop.
NotAllowedError
according to the Protocol
Bindings and stop.
null
.
Takes 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
InteractionOutput
object or reject with
an error.
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 stop.
[[td]]
.properties[name]
does not exist,
throw
NotFoundError
and stop.
[[observeHandlers]]
[name]
to handler.
NotSupportedError
according to the Protocol
Bindings and stop.
NotAllowedError
according to the Protocol
Bindings and stop.
[[td]]
.properties[name]
does not exist,
send back a NotFoundError
in the reply and stop.
[[propertyObservers]]
[name],
in order to be able to notify about Property value
changes.
Every time the value of property
changes, emitPropertyChange()
needs to be explicitly called by the application
script.
Takes 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 stop.
[[td]]
.properties[name]
does not exist,
throw
NotFoundError
and stop.
[[unobserveHandlers]]
[name]
to handler.
NotSupportedError
according to the Protocol
Bindings and stop.
NotAllowedError
according to the Protocol
Bindings and stop.
[[td]]
.properties[name]
does not exist,
send back a NotFoundError
in the reply and stop.
[[unobserveHandlers]]
[name];
Function
,
invoke that given options, then send back a
reply following the Protocol Bindings
and stop.
[[propertyObservers]]
[name]
exists,
remove it from this.[[propertyObservers]]
,
send back a reply as defined in the Protocol
Bindings and stop.
NotFoundError
in the reply as defined in the Protocol Bindings
and stop.
Promise
.
SecurityError
and stop.
[[td]]
.properties[name].
undefined
,
reject
promise with
NotFoundError
and stop.
undefined
, run the following sub-steps:
null.
[[readHandlers]]
, reject
promise and stop.
[[readHandlers]]
[name].
null
or undefined
,
reject
promise and stop.
null
.[[propertyObservers]]
[name],
run the following sub-steps:
This clause needs expanding and/or refer to an algorithm in [WOT-PROTOCOL-BINDINGS].
A 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
, 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.Promise
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
InteractionOutput
object in order to be able to represent values that are not
described by a DataSchema, such as
streams.
Takes 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 stop.
[[td]]
.properties[name]
does not exist,
throw
NotFoundError
and stop.
[[writeHandlers]]
[name]
to handler.
"single"
:
NotSupportedError
according to the Protocol
Bindings and stop.
NotAllowedError
according to the Protocol
Bindings and stop.
[[td]]
.properties[name].
undefined
,
return a NotFoundError
in the reply and stop.
[[writeHandlers]]
[name].
undefined
and if there is a default write
handler provided by the implementation, let
handler be that.
undefined
, send back a
NotSupportedError
with the reply and stop.
"single"
, reply to
the request reporting success, following the Protocol Bindings
and stop.
NotSupportedError
according to the Protocol
Bindings and stop.
NotAllowedError
according to the Protocol
Bindings and stop.
"multiple"
. If that
fails, reply to the request with that error and stop.
A 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 given params
and optionally with an options object. It returns a
that rejects with an error or
resolves with the value returned by the Action as Promise
InteractionInput
.
Application scripts MAY return a ReadableStream
object from an ActionHandler
.
Implementations will then use the stream for constructing
the Action's response.
Takes 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 stop.
[[td]]
.actions[name].
undefined
,
throw
a NotFoundError
and stop.
[[actionHandlers]]
[name]
to action.
NotSupportedError
according to the Protocol
Bindings and stop.
NotAllowedError
according to the Protocol
Bindings and stop.
[[td]]
.properties[name].
undefined
,
return a NotFoundError
in the reply and stop.
[[actionHandlers]]
[name].
undefined
, return a
NotSupportedError
with the reply created by
following the Protocol
Bindings and stop.
A 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 given an options object provided by the
implementation and coming from subscribers. It returns a
that rejects with an error or
resolves when the subscription is accepted.Promise
Takes 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 stop.
[[td]]
.events[name].
undefined
,
throw
a NotFoundError
and stop.
[[subscribeHandlers]]
[name]
to handler.
this
.
NotSupportedError
according to the Protocol
Bindings and stop.
NotAllowedError
according to the Protocol
Bindings and stop.
[[td]]
.events[name].
undefined
,
send back a NotFoundError
and stop.
[[subscribeHandlers]]
[name]
is a Function
,
invoke it given options and stop.
[[eventListeners]]
[name]
to subscriber.
Takes 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 stop.
[[td]]
.events[name].
undefined
,
throw
a NotFoundError
and stop.
[[unsubscribeHandlers]]
[name]
to handler.
this
.
NotSupportedError
according to the Protocol
Bindings and stop.
NotAllowedError
according to the Protocol
Bindings and stop.
[[td]]
.events[name].
undefined
,
send back a NotFoundError
and stop.
[[unsubscribeHandlers]]
[name]
exists
and is a Function
,
invoke it given options and stop.
[[eventListeners]]
,
remove
name.
this
.[[eventListeners]]
.name.
undefined
, assume that the
notification response will contain an
empty data payload as specified by Protocol
Bindings.
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.
Promise
promise and execute the next steps
in parallel.
SecurityError
and stop.
[[td]]
.events.name.
NotFoundError
and stop.
Promise
promise and execute the next steps
in parallel.
SecurityError
and stop.
[[td]]
.
[[td]]
. If that fails,
reject
promise with a
TypeError
and stop.
[[td]]
.properties
initialize this.[[propertyObservers]]
.key
to an empty
Array
in order to store observe
request data needed to notify the observers on value
changes.
[[td]]
.events
initialize this.[[eventListeners]]
.key
to an empty
Array
in order to store subscribe
request data needed to notify the subscribers on event
emission.
[[td]]
as
explained in [WOT-TD]
and [WOT-PROTOCOL-BINDINGS].
Make a request to the underlying platform to initialize
the Protocol
Bindings and then start serving external requests
for WoT Interactions
(read, write and observe Properties, invoke
Actions and manage
Event subscriptions), based
on the Protocol
Bindings. Implementations MAY reject this step for any reason
(e.g. if they want to enforce further checks and
constraints on interaction forms).
Error
object error with
error.message set to the error
code seen by the Protocol
Bindings and stop.
Promise
promise and execute the next steps
in parallel.
SecurityError
and stop.
Error
object error with its
message set to the error code seen by the
Protocol
Bindings and stop.
The next example illustrates how to create an
ExposedThing
based on a partial TD object
constructed beforehand.
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 ExposedThing
:
take its td property, add or modify it, then
create another ExposedThing
with that.
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);
}
The following will cover a set of examples for the
generation of a Thing Description
from an ExposedThingInit
using expand an
ExposedThingInit steps. As hypothesis the runtime
supports HTTP and COAP protocol bindings and it is hosted at
192.168.0.1.
The next example shows how to exploit a ExposedThingInit
to create a simple Thing Description
with one Property with the default
values.
TODO: add more examples where the ExposedThingInit
contains suggested values that are replaced by the
algorithm.
Discovery 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 ThingDiscoveryProcess
object provides the properties and methods controlling the
discovery process and returning the results.
WebIDL[SecureContext, Exposed=(Window,Worker)]
interface ThingDiscoveryProcess
{
constructor
(optional ThingFilter
filter = {});
readonly attribute boolean done
;
readonly attribute Error? error
;
undefined stop
();
async iterable<ThingDescription
>;
};
The ThingDiscoveryProcess
object has the following
internal slots.
Internal Slot | Initial value | Description (non-normative) |
---|---|---|
[[filter]] | undefined |
The ThingFilter
object used in discovery.
|
[[url]] | undefined |
A URL
representing the TD Directory in a
discovery.
|
The done
property is true
if the discovery has been stopped or completed with no more
results to report.
The error
property represents the last
error that occurred during the discovery process. Typically
used for critical errors that stop discovery.
The ThingDiscoveryProcess
object implements the async
iterator concept.
ThingDiscoveryProcess
ThingDiscoveryProcess
with a filter, run the
following steps:
null
, throw a
TypeError
and stop.
ThingDiscoveryProcess
object.
[[filter]]
to
filter.
done
to false
.
error
to null
.
Represents an object containing the constraints for discovering Things as key-value pairs.
WebIDLdictionary ThingFilter
{
object? fragment
;
};
The fragment
property represents a
template object used for matching property by property
against discovered Things.
The query
property was temporarily removed from ThingFilter
,
until it is standardized in the WoT Discovery task force.
It represented a query string accepted by the
implementation, for instance a SPARQL or JSON query.
Support was to be implemented locally in the WoT Runtime or remotely
as a service in a TD Directory.
The url property was removed. It used to represent the target entity serving the discovery request, for instance the URL of a TD Directory, or the URL of a directly targeted Thing, but these are implemented by dedicated methods now.
error
property to
SyntaxError
, discard td and continue the
discovery process.
At this point implementations MAY control the flow of the discovery process (depending on memory constraints, for instance queue the results, or temporarily stop discovery if the queue is getting too large, or resume discovery when the queue is emptied sufficiently). These steps are run for each discovered/fetched td.
[[filter]]
.fragment
.
object
,
then for each key defined in it:
asyncIterator
.
Improve this step using proper asyncIterator terminology.
The last error is retained. Implementations MAY choose to stop the discovery process if they consider it should be reported.
Error
object. Set
error.name to
"DiscoveryError"
.
error
to error.
done
to true
and terminate these steps.
SecurityError
and stop.
done
property to true
.
The following example finds ThingDescription
objects of Things
that are exposed by local hardware, regardless how many
instances of WoT
Runtime it is running Using the
asyncIterator
provided by the Discovery
object, we can iterate asynchronously over the results and
perform operations with the obtained ThingDescription
objects.
let url = "https://mythings.com/thing1";
let td = await WOT.requestThingDescription(url);
console.log("Found Thing Description for " + td.title);
The next example finds ThingDescription
objects of Things
listed in a TD
Directory service. We set a timeout for safety.
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);
}
The next example is for a generic discovery, by any means provisioned to the WOT runtime, including local Things, if any is available.
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);
}
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 commonly 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.
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 RESTful 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 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 (WoT) Thing Description 1.1 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 (WoT) Thing Description 1.1 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 (WoT) Thing Description 1.1 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 (WoT) Thing Description 1.1 specification.
The factory methods for consuming and exposing Things are asynchronous and fully
validate the input TD. In
addition, one can also construct ConsumedThing
and ExposedThing
by providing a parsed and validated TD. Platform initialization is then
done when needed during the WoT interactions.
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 (WoT) Thing Description 1.1
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 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
;
};
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.
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: