CÔNG TY TNHH ĐẦU TƯ THƯƠNG MẠI VÀ DỊCH VỤ HỮU QUYẾT
Số 2, ngách 1/1 Phố Thiên Hiền, Phường Mỹ Đình 1, Quận Nam Từ Liêm, Thành phố Hà Nội.
MST: 0110557925
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: