Copyright © 2017-2021 W3C ® ( MIT , ERCIM , Keio , Beihang ). W3C liability , trademark and permissive document license rules apply.
The Web of Things is made of entities ( Thing s) 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), Action s (to execute remote procedures with or without return values) and Event s (for signaling notifications).
The main Web of Things (WoT) concepts are described in the Web of Things 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 Thing Directory .
This specification describes an application programming interface (API) representing the WoT Interface that allows scripts to discover, operate Thing s and to expose locally defined Thing s characterized by WoT Interactions specified by a script.
The APIs defined in this document deliberately follow the Web of Things 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 an Editor's Draft.
Publication as an Editor's Draft does not imply endorsement by W3C and 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.
This document was produced by a group operating under the 1 August 2017 W3C Patent Policy . W3C maintains a public list of any patent disclosures made in connection with the deliverables of the group; that page also includes instructions for disclosing a patent. An individual who has actual knowledge of a patent which the individual believes contains Essential Claim(s) must disclose the information in accordance with section 6 of the W3C Patent Policy .
This document is governed by the 2 November 2021 W3C Process Document .
WoT provides layered interoperability based on how Thing s are used: "consumed" and "exposed", as defined in the Web of Things 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 Thing s and have means to handle (e.g. install, uninstall, update etc.) and run scripts.
This specification does not make assumptions on how the WoT Runtime handles and runs scripts, including single or multiple tenancy, script deployment and lifecycle management. The API already supports the generic mechanisms that make it possible to implement script management, for instance by exposing a manager Thing whose Actions (action handlers) implement script lifecycle management operations.
This section is non-normative.
The following scripting use cases are supported in this specification:
After evaluating dynamic modifications to Thing Descriptions through several versions of this API, the editors concluded that the simplest way to represent these use cases is to take an existing TD , modify it (i.e. add or remove definitions) and then create a new Thing based on the modified TD .
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
and
the
ThingDiscovery
discover()
method
on
the
WoT
API
object
.
These conformance classes MAY be implemented in a single UA .
This specification can be used for implementing the WoT Scripting API in multiple programming languages. The interface definitions are specified in [ WEBIDL ].
The UA may be implemented in the browser, or in a separate runtime environment, such as Node.js or in small embedded runtimes.
Implementations that use ECMAScript executed in a browser to implement the APIs defined in this document MUST implement them in a manner consistent with the ECMAScript Bindings defined in the Web IDL specification [ WEBIDL ].
Implementations that use TypeScript or ECMAScript in a runtime to implement the APIs defined in this document MUST implement them in a manner consistent with the TypeScript Bindings defined in the TypeScript specification [ TYPESCRIPT ].
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 , Thing 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-LD is defined in [ JSON-LD ] as a JSON document that is augmented with support for Linked Data.
JSON Schema is defined in these specifications .
Promise
,
Error
,
JSON
,
JSON.stringify
,
JSON.parse
,
internal
method
and
internal
slot
are
defined
in
[
ECMASCRIPT
].
ThingDescription
type
WebIDL
typedef
object
ThingDescription
;
Represents a Thing Description ( TD ) as defined in [ WOT-TD ]. It is expected to be a parsed JSON object that is validated using JSON Schema validation .
Fetching a TD given a URL should be done with an external method, such as the Fetch API or a HTTP client library, which offer already standardized options on specifying fetch details.
try {
let res = await fetch('https://tds.mythings.biz/sensor11');
// ... additional checks possible on res.headers
let td = await res.json();
let thing = await WOT.consume(td);
console.log("Thing name: " + thing.getThingDescription().title);
} catch (err) {
console.log("Fetching TD failed", err.message);
}
Note that the Web of Things Thing Description 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 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
abort
these
steps.
Additional steps may be added to fill the default values of mandatory fields.
WOT
namespace
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
};
consume()
method
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
abort
these
steps.
ConsumedThing
object
constructed
from
td
.
Implementations encapsulate the complexity of how to use the Protocol Bindings for implementing WoT interactions . In the future elements of that could be standardized.
Note
the
difference
between
constructing
ConsumedThing
and
using
the
consume()
method:
the
latter
also
initializes
the
protocol
bindings,
whereas
a
simple
constructed
object
will
not
have
WoT
Interactions
initialized
until
they
are
invoked.
produce()
method
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
abort
these
steps.
ExposedThing
object
constructed
with
init
.
SyntaxError
and
abort
these
steps.
securityDefinitions
check
if
it
is
supported
by
at
least
one
Protocol
Binding
.
If
not
remove
scheme
security
is
defined
but
it
is
not
contained
in
securityDefinitions
remove
security
authority
it
is
not
recognized
by
the
runtime
as
a
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
assing
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
optional
string
then
if
value
is
equal
to
one
of
the
elements
in
optional
remove
key
from
exposedThingInitSchema
The 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.
discover()
method
WebIDLpartial namespace WOT
{
ThingDiscovery
discover
(optional ThingFilter
filter = null);
};
ThingDescription
objects
for
Thing
Description
s
that
match
an
optional
filter
argument
of
type
ThingFilter
.
The
method
MUST
run
the
following
steps:
SecurityError
"
and
abort
these
steps.
ThingDiscovery
object
discovery
with
filter
.
As
specified
in
the
Web
of
Things
Thing
Description
1.1
specification,
WoT
interactions
extend
DataSchema
and
include
a
number
of
possible
Form
s,
out
of
which
one
is
selected
for
the
interaction.
The
Form
contains
a
contentType
to
describe
the
data.
For
certain
content
types,
a
DataSchema
is
defined,
based
on
JSON
Schema
,
making
possible
to
represent
these
contents
as
JavaScript
types
and
eventually
set
range
constraints
on
the
data.
InteractionInput
type
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
]
(i.e.
null,
boolean,
number,
string,
array,
or
object).
ReadableStream
is
meant
to
be
used
for
WoT
Interactions
that
don't
have
a
DataSchema
in
the
Thing
Description
,
only
a
Form
's
contentType
that
can
be
represented
by
a
stream.
In
practice,
any
ECMAScript
value
may
be
used
for
WoT
Interactions
that
have
a
DataSchema
defined
in
the
Thing
Description
,
or
which
can
be
mapped
by
implementations
to
the
Form
's
contentType
defined
in
the
Thing
Description
.
The algorithms in this document specify how exactly input data is used in WoT Interactions .
InteractionOutput
interface
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
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).
value()
function
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
abort
these
steps.
ReadableStream
or
if
dataUsed
is
true
,
or
if
form
is
null
or
if
schema
or
its
type
are
null
or
undefined
,
reject
promise
with
NotReadableError
and
abort
these
steps.
application/json
and
if
a
mapping
is
not
available
in
the
Protocol
Bindings
from
form
's
contentType
to
[
JSON-SCHEMA
],
reject
promise
with
NotSupportedError
and
abort
these
steps.
true
.
application/json
and
if
a
mapping
is
available
in
the
Protocol
Bindings
from
form
's
contentType
to
[
JSON-SCHEMA
],
transform
bytes
with
that
mapping.
arrayBuffer()
function
Promise
promise
and
execute
the
next
steps
in
parallel
.
ReadableStream
or
if
dataUsed
is
true
,
reject
promise
with
NotReadableError
and
abort
these
steps.
true
.
ArrayBuffer
whose
contents
are
bytes
.
If
that
throws,
reject
promise
with
that
exception
and
abort
these
steps.
"null"
and
if
payload
is
not
null
,
throw
TypeError
and
abort
these
steps,
otherwise
return
null
.
"boolean"
and
payload
is
a
falsey
value
or
its
byte
length
is
0,
return
false
,
otherwise
return
true
.
"integer"
or
"number"
,
TypeError
and
abort
these
steps.
RangeError
and
abort
these
steps.
"string"
,
return
payload
.
"array"
,
run
these
sub-steps:
TypeError
and
abort
these
steps.
RangeError
and
abort
these
steps.
"object"
,
run
these
sub-steps:
TypeError
and
abort
these
steps.
SyntaxError
and
abort
these
steps.
ConsumedThing
object
thing
,
in
order
to
create
interaction
request
given
a
source
,
form
and
schema
,
run
these
steps:
InteractionOutput
object
whose
form
is
set
to
form
,
whose
schema
is
set
to
schema
,
whose
[[value]]
internal
slot
is
undefined
and
whose
data
is
null
.
ReadableStream
object,
let
idata
's
data
be
source
,
return
idata
and
abort
these
steps.
null
,
run
these
sub-steps:
"null"
and
source
is
not,
throw
TypeError
and
abort
these
steps.
"boolean"
and
source
is
a
falsy
value,
set
idata
's
[[value]]
value
to
false
,
otherwise
to
true
.
"integer"
or
"number"
and
source
is
not
a
number,
or
if
form
's
minimum
is
defined
and
source
is
smaller,
or
if
form
's
maximum
is
defined
and
source
is
bigger,
throw
RangeError
and
abort
these
steps.
"string"
and
source
is
not
a
string,
let
idata
's
[[value]]
be
the
result
of
running
serialize
JSON
to
bytes
on
source
.
If
that
is
failure,
throw
SyntaxError
and
abort
these
steps.
"array"
,
run
these
sub-steps:
TypeError
and
abort
these
steps.
RangeError
and
abort
these
steps.
"object"
,
run
these
sub-steps:
TypeError
and
abort
these
steps.
TypeError
and
abort
these
steps.
SyntaxError
and
abort
these
steps.
ReadableStream
created
from
idata
's
[[value]]
internal
slot
as
its
underlying
source
.
ConsumedThing
object
thing
,
in
order
to
parse
interaction
response
given
response
,
form
and
schema
,
run
these
steps:
InteractionOutput
object.
ReadableStream
with
the
payload
data
of
response
as
its
underlying
source
.
false
.
InteractionInput
and
InteractionOutput
As
illustrated
in
the
next
pictures,
the
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
data,
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.
ConsumedThing
interface
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 = null);
Promise<PropertyReadMap
> readAllProperties
(
optional InteractionOptions
options = null);
Promise<PropertyReadMap
> readMultipleProperties
(
sequence<DOMString> propertyNames,
optional InteractionOptions
options = null);
Promise<undefined> writeProperty
(DOMString propertyName,
InteractionInput
value,
optional InteractionOptions
options = null);
Promise<undefined> writeMultipleProperties
(
PropertyWriteMap
valueMap,
optional InteractionOptions
options = null);
/*Promise<undefined> writeAllProperties(
PropertyWriteMap valueMap,
optional InteractionOptions options = null);*/
Promise<InteractionOutput
> invokeAction
(DOMString actionName,
optional InteractionInput
params = null,
optional InteractionOptions
options = null);
Promise<Subscription
> observeProperty
(DOMString name,
InteractionListener
listener,
optional ErrorListener
onerror,
optional InteractionOptions
options = null);
Promise<Subscription
> subscribeEvent
(DOMString name,
InteractionListener
listener,
optional ErrorListener
onerror,
optional InteractionOptions
options = null);
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 = null);
};
[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.
A
ConsumedThing
object
has
the
following
internal
slots
:
Internal Slot | Initial value | Description ( non-normative ) |
---|---|---|
[[td]] |
null
|
The
Thing
Description
of
the
ConsumedThing
.
|
ConsumedThing
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
abort
these
steps.
ConsumedThing
object.
getThingDescription()
method
Returns
the
internal
slot
[[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.
readProperty()
method
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
abort
these
steps.
undefined
,
reject
promise
with
a
NotFoundError
and
abort
this
steps.
readproperty
,
selected
by
the
implementation.
SyntaxError
and
abort
these
steps.
SyntaxError
and
abort
these
steps.
readMultipleProperties()
method
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
abort
these
steps.
readmultipleproperties
,
selected
by
the
implementation.
SyntaxError
and
abort
these
steps.
null
.
NotSupportedError
and
abort
these
steps.
readAllProperties()
method
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
abort
these
steps.
readallproperties
,
selected
by
the
implementation.
SyntaxError
and
abort
these
steps.
NotSupportedError
and
abort
these
steps.
writeProperty()
method
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
abort
these
steps.
undefined
,
reject
promise
with
a
NotFoundError
and
abort
this
steps.
writeproperty
,
selected
by
the
implementation.
SyntaxError
and
abort
these
steps.
promise
with
that
exception
and
abort
these
steps.
As discussed in Issue #193 , the design decision is that write interactions only return success or error, not the written value (optionally). TD s should capture the schema of the Property values, including precision and alternative formats. When a return value is expected from the interaction, an Action should be used instead of a Property .
writeMultipleProperties()
method
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
abort
these
steps.
writemultipleproperties
,
selected
by
the
implementation.
SyntaxError
and
abort
these
steps.
null
or
undefined
or
is
not
writeable
reject
promise
with
NotSupportedError
and
abort
these
steps.
null
.
promise
with
that
exception
and
abort
these
steps.
NotSupportedError
and
abort
these
steps.
observeProperty()
method
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
abort
these
steps.
Function
,
reject
promise
with
a
TypeError
and
abort
these
steps.
null
and
is
not
a
Function
,
reject
promise
with
a
TypeError
and
abort
these
steps.
Subscription
object
with
its
internal
slots
set
as
follows:
"property"
.
"observeproperty"
,
selected
by
the
implementation.
SyntaxError
and
abort
these
steps.
undefined
,
reject
promise
with
a
NotFoundError
and
abort
this
steps.
false
and
suppress
further
notifications.
NetworkError
and
set
its
message
to
reflect
the
underlying
error
condition.
Function
,
invoke
it
with
error
.
invokeAction()
method
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
abort
these
steps.
undefined
,
reject
promise
with
a
NotFoundError
and
abort
this
steps.
invokeaction
,
selected
by
the
implementation.
SyntaxError
and
abort
these
steps.
promise
with
that
exception
and
abort
these
steps.
subscribeEvent()
method
Promise
to
signal
success
or
failure.
The
method
MUST
run
the
following
steps:
Promise
promise
and
execute
the
next
steps
in
parallel
.
SecurityError
and
abort
these
steps.
Function
,
reject
promise
with
a
TypeError
and
abort
these
steps.
null
and
is
not
a
Function
,
reject
promise
with
a
TypeError
and
abort
these
steps.
Subscription
object
with
its
internal
slots
set
as
follows:
"event"
.
"subscribeevent"
,
selected
by
the
implementation.
SyntaxError
and
abort
these
steps.
undefined
,
reject
promise
with
a
NotFoundError
and
abort
this
steps.
false
and
suppress
further
notifications.
NetworkError
and
set
its
message
to
reflect
the
underlying
error
condition.
Function
,
invoke
it
with
error
.
InteractionOptions
dictionary
Holds the interaction options that need to be exposed for application scripts according to the Thing Description .
The
formIndex
property,
if
defined,
represents
an
application
hint
for
which
Form
definition,
identified
by
this
index,
of
the
TD
to
use
for
the
given
WoT
interaction.
Implementations
SHOULD
use
the
Form
with
this
index
for
making
the
interaction,
but
MAY
override
this
value
if
the
index
is
not
found
or
not
valid.
If
not
defined,
implementations
SHOULD
attempt
to
use
the
Form
definitions
in
order
of
appearance
as
listed
in
the
TD
for
the
given
Wot
Interaction.
The
uriVariables
property
if
defined,
represents
the
URI
template
variables
to
be
used
with
the
WoT
Interaction
that
are
represented
as
parsed
JSON
objects
defined
in
[
WOT-TD
].
The support for URI variables comes from the need, exposed by the Web of Things Thing Description 1.1 specification, to be able to describe existing REST-ful endpoints that use them. However, it should be possible to write a Thing Description that would use Action s 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.
PropertyReadMap
type
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.
PropertyWriteMap
type
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.
InteractionListener
callback
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.
ErrorListener
callback
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.
Subscription
interface
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. |
stop()
method
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
abort
these
steps.
SyntaxError
and
abort
these
steps.
"property"
,
make
a
request
to
the
underlying
platform
via
the
Protocol
Bindings
to
stop
observing
the
Property
identified
by
[[name]]
with
unsubscribeForm
and
optional
URI
templates
given
in
options
'
uriVariables
.
"event"
,
make
a
request
to
the
underlying
platform
via
the
Protocol
Bindings
to
unsubscribe
from
the
Event
identified
by
[[name]]
with
unsubscribeForm
,
with
optional
URI
templates
given
in
options
'
uriVariables
and
optional
unsubscribe
data
given
in
options
's
data
.
false
and
resolve
promise
.
Subscription
object,
run
the
following
steps:
This
algorithm
is
under
development
and
is
non-normative
.
Implementations
MAY
choose
another
algorithm
to
find
a
matching
unsubscribe
Form
to
a
given
subscribe
Form
.
0
.
"unobserveproperty"
if
[[type]]
is
"property"
or
if
form
's
op
is
"unsubscribeevent"
if
[[type]]
is
"event"
,
null
and
terminate
these
steps.
The
next
example
illustrates
how
to
fetch
a
TD
by
URL,
create
a
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 recorderd by this thing",
* "type": "array",
* "forms": [
* {
* "op": "readproperty",
* "href": "http://recorder.example.com/eventHistory",
* }
* ]
* }
*/
// Example of streaming processing: counting json objects
let objectCounter = 0
const parser = new Parser() //User library for json streaming parsing (i.e. https://github.com/uhop/stream-json/wiki/Parser)
parser.on('data', data => data.name === 'startObject' && ++objectCounter);
parser.on('end', () => console.log(`Found ${objectCounter} objects.`));
const response = await thing.readProperty(“eventHistory”)
await response.data.pipeTo(parser);
//
Found
N
objects
ExposedThing
interface
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);
ExposedThing
setActionHandler
(DOMString name, ActionHandler
action);
ExposedThing
setEventSubscribeHandler
(DOMString name,
EventSubscriptionHandler
handler);
ExposedThing
setEventUnsubscribeHandler
(DOMString name,
EventSubscriptionHandler
handler);
ExposedThing
setEventHandler
(DOMString name,
EventListenerHandler
eventHandler);
Promise<undefined> emitEvent
(DOMString name,
InteractionInput
data);
Promise<undefined> expose
();
Promise<undefined> destroy
();
ThingDescription
getThingDescription
();
};
callback PropertyReadHandler
= Promise<InteractionInput
>(
optional InteractionOptions
options = null);
callback PropertyWriteHandler
= Promise<undefined>(
InteractionOutput
value,
optional InteractionOptions
options = null);
callback ActionHandler
= Promise<InteractionInput
>(
InteractionOutput
params,
optional InteractionOptions
options = null);
callback EventSubscriptionHandler
= Promise<undefined>(
optional InteractionOptions
options = null);
callback
EventListenerHandler
=
Promise
<
InteractionInput
>
();
An
ExposedThing
object
has
the
following
internal
slots
:
Internal Slot | Initial value | Description ( non-normative ) |
---|---|---|
[[td]] |
null
|
The Thing Description of the Thing . |
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
abort
these
steps.
ExposedThing
object.
getThingDescription()
method
Returns
the
internal
slot
[[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.
PropertyReadHandler
callback
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.
setPropertyReadHandler()
method
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
abort
these
steps.
NotFoundError
and
abort
these
steps.
NotSupportedError
according
to
the
Protocol
Bindings
and
abort
these
steps.
NotAllowedError
according
to
the
Protocol
Bindings
and
abort
these
steps.
NotFoundError
and
abort
these
steps.
null
.
null
,
throw
NotSupportedError
and
abort
these
steps.
The
value
returned
here
SHOULD
either
conform
to
DataSchema
or
it
SHOULD
be
an
ReadableStream
object
created
by
the
handler
.
NotSupportedError
according
to
the
Protocol
Bindings
and
abort
these
steps.
NotAllowedError
according
to
the
Protocol
Bindings
and
abort
these
steps.
NotSupportedError
according
to
the
Protocol
Bindings
and
abort
these
steps.
NotAllowedError
according
to
the
Protocol
Bindings
and
abort
these
steps.
null
.
setPropertyObserveHandler()
method
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
abort
these
steps.
NotFoundError
and
abort
these
steps.
NotSupportedError
according
to
the
Protocol
Bindings
and
abort
these
steps.
NotAllowedError
according
to
the
Protocol
Bindings
and
abort
these
steps.
NotFoundError
in
the
reply
and
abort
these
steps.
Every
time
the
value
of
property
changes,
emitPropertyChange()
needs
to
be
explicitly
called
by
the
application
script.
setPropertyUnobserveHandler()
method
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
abort
these
steps.
NotFoundError
and
abort
these
steps.
NotSupportedError
according
to
the
Protocol
Bindings
and
abort
these
steps.
NotAllowedError
according
to
the
Protocol
Bindings
and
abort
these
steps.
NotFoundError
in
the
reply
and
abort
these
steps.
Function
defined
for
name
on
property
,
invoke
that
with
options
,
send
back
a
reply
following
the
Protocol
Bindings
and
abort
these
steps.
NotFoundError
in
the
reply
and
abort
these
steps.
emitPropertyChange()
method
Promise
promise
and
execute
the
next
steps
in
parallel
.
SecurityError
and
abort
these
steps.
NotFoundError
and
abort
these
steps.
null.
null
,
abort
these
steps.
null
.
PropertyWriteHandler
callback
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.
setPropertyWriteHandler()
method
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
abort
these
steps.
NotFoundError
and
abort
these
steps.
"single"
:
NotSupportedError
according
to
the
Protocol
Bindings
and
abort
these
steps.
NotAllowedError
according
to
the
Protocol
Bindings
and
abort
these
steps.
NotFoundError
in
the
reply
and
abort
these
steps.
null
.
null
,
send
back
a
NotSupportedError
with
the
reply
and
abort
these
steps.
"single"
,
reply
to
the
request
reporting
success,
following
the
Protocol
Bindings
and
abort
these
steps.
NotSupportedError
according
to
the
Protocol
Bindings
and
abort
these
steps.
NotAllowedError
according
to
the
Protocol
Bindings
and
abort
these
steps.
"multiple"
.
If
that
fails,
reply
to
the
request
with
that
error
and
abort
these
steps.
ActionHandler
callback
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
with
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.
setActionHandler()
method
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
abort
these
steps.
NotFoundError
and
abort
these
steps.
NotSupportedError
according
to
the
Protocol
Bindings
and
abort
these
steps.
NotAllowedError
according
to
the
Protocol
Bindings
and
abort
these
steps.
NotFoundError
in
the
reply
and
abort
these
steps.
null
.
null
,
return
a
NotSupportedError
with
the
reply
created
by
following
the
Protocol
Bindings
and
abort
these
steps.
EventListenerHandler
callback
A
function
that
is
called
when
an
associated
Event
is
triggered
and
provides
the
data
to
be
sent
with
the
Event
to
subscribers.
Returns
a
that
resolves
with
Promise
InteractionInput
value
that
represents
the
Event
data,
or
rejects
with
an
error.
Applications
MAY
return
ReadableStream
from
an
EventListenerHandler
Implementations
will
then
use
the
stream
provided
in
InteractionOutput
when
constructing
the
event
notification.
EventSubscriptionHandler
callback
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
with
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
setEventSubscribeHandler()
method
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
abort
these
steps.
NotFoundError
and
abort
these
steps.
this
.
NotSupportedError
according
to
the
Protocol
Bindings
and
abort
these
steps.
NotAllowedError
according
to
the
Protocol
Bindings
and
abort
these
steps.
NotFoundError
and
abort
these
steps.
setEventUnsubscribeHandler()
method
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
abort
these
steps.
NotFoundError
and
abort
these
steps.
this
.
NotSupportedError
according
to
the
Protocol
Bindings
and
abort
these
steps.
NotAllowedError
according
to
the
Protocol
Bindings
and
abort
these
steps.
NotFoundError
and
abort
these
steps.
this
.
setEventHandler()
method
Takes as arguments name and eventHandler . Sets the event handler function for the specified Event matched by name . Throws on error. Returns a reference to this object for supporting chaining.
The eventHandler callback function will implement what to do when the event is emitted. It SHOULD resolve with a value that represents the Event data, or reject with an error.
There MUST be at most one handler for any given Event , so newly added handlers MUST replace the previous handlers.
SecurityError
and
abort
these
steps.
NotFoundError
and
abort
these
steps.
this
.
null
,
The error reporting is protocol specific and it is encapsulated by implementations. On the client end, the error listener passed with the subscription will be invoked if the client UA detects the error.
emitEvent()
method
Promise
promise
and
execute
the
next
steps
in
parallel
.
SecurityError
and
abort
these
steps.
NotFoundError
and
abort
these
steps.
This will trigger the handling events steps.
expose()
method
Promise
promise
and
execute
the
next
steps
in
parallel
.
SecurityError
and
abort
these
steps.
TypeError
and
abort
these
steps.
Error
object
error
with
error
's
message
set
to
the
error
code
seen
by
the
Protocol
Bindings
and
abort
these
steps.
destroy()
method
Promise
promise
and
execute
the
next
steps
in
parallel
.
SecurityError
and
abort
these
steps.
Error
object
error
with
its
message
set
to
the
error
code
seen
by
the
Protocol
Bindings
and
abort
these
steps.
The
next
example
illustrates
how
to
create
an
ExposedThing
based
on
a
partial
TD
object
constructed
beforehands.
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.
ThingDiscovery
interface
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
ThingDiscovery
object
is
constructed
given
a
filter
and
provides
the
properties
and
methods
controlling
the
discovery
process.
WebIDL[SecureContext, Exposed=(Window,Worker)]
interface ThingDiscovery
{
constructor
(optional ThingFilter
filter = null);
readonly attribute ThingFilter
? filter
;
readonly attribute boolean active
;
readonly attribute boolean done
;
readonly attribute Error? error
;
undefined start
();
Promise<ThingDescription
> next
();
undefined stop
();
};
The
ThingDiscovery
interface
has
a
next()
method
and
a
done
property,
but
it
is
not
an
Iterable
.
Look
into
Issue
177
for
rationale.
The
discovery
results
internal
slot
is
an
internal
queue
for
temporarily
storing
the
found
ThingDescription
objects
until
they
are
consumed
by
the
application
using
the
next()
method.
Implementations
MAY
optimize
the
size
of
this
queue
based
on
e.g.
the
available
resources
and
the
frequency
of
invoking
the
next()
method.
The
filter
property
represents
the
discovery
filter
of
type
ThingFilter
specified
for
the
discovery.
The
active
property
is
true
when
the
discovery
is
actively
ongoing
on
protocol
level
(i.e.
new
TD
s
may
still
arrive)
and
false
otherwise.
The
done
property
is
true
if
the
discovery
has
been
completed
with
no
more
results
to
report
and
discovery
results
is
also
empty.
The
error
property
represents
the
last
error
that
occurred
during
the
discovery
process.
Typically
used
for
critical
errors
that
stop
discovery.
ThingDiscovery
ThingDiscovery
with
a
filter
or
type
ThingFilter
,
run
the
following
steps:
The
start()
method
sets
active
to
true
.
The
stop()
method
sets
the
active
property
to
false
,
but
done
may
be
still
false
if
there
are
ThingDescription
objects
in
the
discovery
results
not
yet
consumed
with
next()
.
During
successive
calls
of
next()
,
the
active
property
may
be
true
or
false
,
but
the
done
property
is
set
to
false
by
next()
only
when
both
the
active
property
is
false
and
discovery
results
is
empty.
DiscoveryMethod
enumeration
WebIDL
typedef
DOMString
DiscoveryMethod
;
Represents the discovery type to be used:
ThingFilter
's
url
.
ThingFilter
's
url
.
ThingFilter
dictionary
Represents an object containing the constraints for discovering Thing s as key-value pairs.
WebIDLdictionary ThingFilter
{
DiscoveryMethod
method
= "directory";
USVString? url
;
object? fragment
;
};
The
DOMString
query
property
was
temporarily
removed
from
ThingFilter
,
until
it
is
standardized
in
the
WoT
Discovery
task
force.
The
method
property
represents
the
discovery
type
that
should
be
used
in
the
discovery
process.
The
possible
values
are
defined
by
the
DiscoveryMethod
enumeration.
The
url
property
represents
the
URL
of
the
target
entity
serving
the
discovery
request,
for
instance
the
URL
of
a
Thing
Directory
(if
method
is
"directory"
),
or
the
URL
of
a
directly
targeted
Thing
(if
method
is
"direct"
)
The
fragment
property
represents
a
template
object
used
for
matching
property
by
property
against
discovered
Thing
s.
start()
method
SecurityError
and
abort
these
steps.
NotSupportedError
and
abort
these
steps.
ThingDescription
objects.
"directory"
,
use
the
remote
Thing
Directory
specified
in
|filter.url|.
"direct"
,
use
the
remote
Thing
specified
in
|filter.url|.
true
.
SyntaxError
,
discard
td
and
continue
the
discovery
process.
false
in
any
checks,
discard
td
and
continue
the
discovery
process.
Error
object.
Set
error
's
name
to
"DiscoveryError"
.
false
.
false
.
next()
method
ThingDescription
object.
The
method
MUST
run
the
following
steps:
Promise
promise
and
execute
the
next
steps
in
parallel
.
true
,
wait
until
the
discovery
results
internal
slot
is
not
empty.
false
,
set
the
done
property
to
true
and
reject
promise
.
ThingDescription
object
td
from
discovery
results
.
stop()
method
false
.
The
following
example
finds
ThingDescription
objects
of
Thing
s
that
are
exposed
by
local
hardware,
regardless
how
many
instances
of
WoT
Runtime
it
is
running.
Note
that
the
discovery
can
end
(become
inactive)
before
the
internal
discovery
results
queue
is
emptied,
so
we
need
to
continue
reading
ThingDescription
objects
until
done.
This
is
typical
with
local
and
directory
type
discoveries.
let discovery = new ThingDiscovery({ method: "direct" });
do {
let td = await discovery.next();
console.log("Found Thing Description for " + td.title);
}
while
(!discovery.done);
The
next
example
finds
ThingDescription
objects
of
Thing
s
listed
in
a
Thing
Directory
service.
We
set
a
timeout
for
safety.
let discoveryFilter = {
method: "directory", // default value, no need to specify
url: "http://directory.wotservice.org"
};
let discovery = new ThingDiscovery(discoveryFilter);
setTimeout( () => {
discovery.stop();
console.log("Discovery stopped after timeout.");
},
3000);
do {
let td = await discovery.next();
console.log("Found Thing Description for " + td.title);
let thing = new ConsumedThing(td);
console.log("Thing name: " + thing.getThingDescription().title);
} while (!discovery.done);
if (discovery.error) {
console.log("Discovery stopped because of an error: " + error.message);
}
A detailed discussion of security and privacy considerations for the Web of Things, including a threat model that can be adapted to various circumstances, is presented in the informative document [ WOT-SECURITY ]. This section discusses only security and privacy risks and possible mitigations directly relevant to the scripts and WoT Scripting API.
A suggested set of best practices to improve security for WoT devices and services has been documented in [ WOT-SECURITY ]. That document may be updated as security measures evolve. Following these practices does not guarantee security, but it might help avoid common known vulnerabilities.
This section is normative and contains specific risks relevant for the WoT Scripting Runtime.
A typical way to compromise any process is to send it a corrupted input via one of the exposed interfaces. This can be done to a script instance using WoT interface it exposes.
In case a script is compromised or misbehaving, the underlying physical device (and potentially surrounded environment) can be damaged if a script can use directly exposed native device interfaces. If such interfaces lack safety checks on their inputs, they might bring the underlying physical device (or environment) to an unsafe state (i.e. device overheats and explodes).
If the WoT Scripting Runtime supports post-manufacturing provisioning or updates of scripts, WoT Scripting Runtime or any related data (including security credentials), it can be a major attack vector. An attacker can try to modify any above described element during the update or provisioning process or simply provision attacker's code and data directly.
Typically the WoT Scripting Runtime needs to store the security credentials that are provisioned to a WoT device to operate in WoT network. If an attacker can compromise the confidentiality or integrity of these credentials, then it can obtain access to the WoT assets, impersonate WoT things or devices or create Denial-Of-Service (DoS) attacks.
This section is non-normative.
This section describes specific risks relevant for script developers.
A script instance may receive data formats defined by the TD, or data formats defined by the applications. While the WoT Scripting Runtime SHOULD perform validation on all input fields defined by the TD, scripts may be still exploited by input data.
If a script performs a heavy functional processing on received requests before the request is authenticated, it presents a great risk for Denial-Of-Service (DOS) attacks.
API rationale usually belongs to a separate document, but in the WoT case the complexity of the context justifies including basic rationale here.
The WoT Interest Group and Working Group have explored different approaches to application development for WoT that have been all implemented and tested.
It is possible to develop WoT applications that only use the WoT network interface , typically exposed by a WoT gateway that presents a REST-ful API towards clients and implements IoT protocol plugins that communicate with supported IoT deployments. One such implementation is the Mozilla WebThings platform.
WoT Thing s show good synergy with software objects, so a Thing can be represented as a software object, with Properties represented as object properties, Action s as methods, and Event s 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 Thing s to software objects have had some challenges, this specification takes another approach that exposes software objects to represent the Thing metadata as data property and the WoT interactions as methods. One implementation is node-wot in the the Eclipse ThingWeb project, which is the current reference implementation of the API specified in this document.
The
same
example
now
would
look
like
the
following:
the
status
property
and
the
open()
method
are
represented
indirectly.
let res = await fetch(‘https://td.my.com/lock-00123’);
let td = await res.json();
let lock = new ConsumedThing(td);
console.log(lock.readProperty(‘status’));
lock.invokeAction(‘open’,
'withThisKey'
);
In conclusion, the WoT WG decided to explore the third option that closely follows the Web of Things Thing Description 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 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 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 Thing Description 1.1 specification.
The
factory
methods
for
consuming
and
exposing
Thing
s
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 Event s is done with callback registrations.
The
reason
to
use
function
names
like
readProperty()
,
readMultipleProperties()
etc.
instead
of
a
generic
polymorphic
read()
function
is
that
the
current
names
map
exactly
to
the
"op"
vocabulary
from
the
Form
definition
in
the
Web
of
Things
Thing
Description
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
{
ThingDiscovery
discover
(optional ThingFilter
filter = null);
};
typedef any DataSchemaValue
;
typedef (ReadableStream or DataSchemaValue
) InteractionInput
;
[SecureContext, Exposed=(Window,Worker)]
interface InteractionOutput
{
readonly attribute ReadableStream? data
;
readonly attribute boolean dataUsed
;
readonly attribute
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 = null);
Promise<PropertyReadMap
> readAllProperties
(
optional InteractionOptions
options = null);
Promise<PropertyReadMap
> readMultipleProperties
(
sequence<DOMString> propertyNames,
optional InteractionOptions
options = null);
Promise<undefined> writeProperty
(DOMString propertyName,
InteractionInput
value,
optional InteractionOptions
options = null);
Promise<undefined> writeMultipleProperties
(
PropertyWriteMap
valueMap,
optional InteractionOptions
options = null);
/*Promise<undefined> writeAllProperties(
PropertyWriteMap valueMap,
optional InteractionOptions options = null);*/
Promise<InteractionOutput
> invokeAction
(DOMString actionName,
optional InteractionInput
params = null,
optional InteractionOptions
options = null);
Promise<Subscription
> observeProperty
(DOMString name,
InteractionListener
listener,
optional ErrorListener
onerror,
optional InteractionOptions
options = null);
Promise<Subscription
> subscribeEvent
(DOMString name,
InteractionListener
listener,
optional ErrorListener
onerror,
optional InteractionOptions
options = null);
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 = null);
};
[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);
ExposedThing
setActionHandler
(DOMString name, ActionHandler
action);
ExposedThing
setEventSubscribeHandler
(DOMString name,
EventSubscriptionHandler
handler);
ExposedThing
setEventUnsubscribeHandler
(DOMString name,
EventSubscriptionHandler
handler);
ExposedThing
setEventHandler
(DOMString name,
EventListenerHandler
eventHandler);
Promise<undefined> emitEvent
(DOMString name,
InteractionInput
data);
Promise<undefined> expose
();
Promise<undefined> destroy
();
ThingDescription
getThingDescription
();
};
callback PropertyReadHandler
= Promise<InteractionInput
>(
optional InteractionOptions
options = null);
callback PropertyWriteHandler
= Promise<undefined>(
InteractionOutput
value,
optional InteractionOptions
options = null);
callback ActionHandler
= Promise<InteractionInput
>(
InteractionOutput
params,
optional InteractionOptions
options = null);
callback EventSubscriptionHandler
= Promise<undefined>(
optional InteractionOptions
options = null);
callback EventListenerHandler
= Promise<InteractionInput
>();
[SecureContext, Exposed=(Window,Worker)]
interface ThingDiscovery
{
constructor
(optional ThingFilter
filter = null);
readonly attribute ThingFilter
? filter
;
readonly attribute boolean active
;
readonly attribute boolean done
;
readonly attribute Error? error
;
undefined start
();
Promise<ThingDescription
> next
();
undefined stop
();
};
typedef DOMString DiscoveryMethod
;
dictionary ThingFilter
{
DiscoveryMethod
method
= "directory";
USVString? url
;
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: