The WoT Profile Specification defines a Profiling Mechanism and a WoT Core Profile, which enables out of the box interoperability among things and devices. Out of the box interoperability implies, that devices can be integrated into various application scenarios without deep level adaptations. Typically only minor configuration operations are necessary (such as entering a network key, or IP address) to use the device in a certain scenario. These actions can be done by anyone without specific training.

The WoT Core Profile defines a set of constraints and rules, which compliant thing descriptions have to adopt to guarantee interoperability.

These rules are prescriptive, to ensure that compliant implementations satisfy the semantic guarantees implied by them. We call this set of rules a Profile.

The WoT Profile Specification as defined in this document serves two purposes:

Devices that constrain their use of the Thing Description to the WoT Core Profile can interoperate with each other out-of-the-box.

Note that the core profile is not exclusive. Device implementers are free to adopt other features of the thing description that go beyond the constraints of the core profile, however the interoperability guarantees of the core profile hold only for the WoT Core Profile subset.

The name WoT Core Profile is still under discussion in the group and is used as a working title. It is subject to change after the profile specification has reached a certain level of maturity.

Motivation for a Profile

The W3C WoT Thing Architecture [[wot-architecture11]] and WoT Thing Description [[wot-thing-description]] define a powerful description mechanism and a format to describe myriads of very different devices, which may be connected over various protocols. The format is very flexible and open and puts very few normative requirements on devices that implement it.

However, this flexibility de-facto prevents interoperability, since, without additional rules, it allows implementers to make many choices that do not provide guarantees of common behavior between implementations.

Introduction

The W3C WoT Architecture [[wot-architecture11]] and the WoT Thing Description [[wot-thing-description]] have been developed as a versatile format, that allows describing the interactions between multiple devices and protocols.

This flexibility permits an easy integration of new device types and protocols, however it risks interoperability, since there are no guarantees that two devices which are formally spec-compliant, will be able to communicate.

To increase adoption of the WoT specifications, interoperability between on premise devices, edge devices and the cloud is essential. Even if every manufacturer is implementing the current Thing Description specification in full flexibility, there is no interoperability guarantee; many choices are still left to the implementations and there are very few normative requirements that a device has to fulfill.

Deployment Scenarios

A Thing Description can be used in two fundamentally different deployment scenarios:

For green field deployments, where the implementations are being carried out and corresponding thing descriptions are being created, it is easier to achieve full interoperability by using a small, extensible Core Profile.

In the brown field area, due to the nature of existing deployments and protocols, a broad spectrum of variations and potentially high complexity of thing descriptions inhibits interoperability and will most likely lead to additional profiles of the WoT Thing Description and domain-specific thing consumer implementations.

The WoT Core Profile can be used by green field deployments and gives guidance to new implementers of the WoT specifications. It has already proved in brown-field scenarios in the PlugFests, where existing devices, that already existed as products, prototypes or demonstrators, were described with Thing Descriptions that are constrained to the Core Profile.

Why a Core Profile?

During the recent WoT PlugFests there were many de-facto agreements on the use of a small constrained subset of interaction patterns and protocol choices. These de-facto agreements select a common subset of the WoT Thing Description, based on proven interoperability among manufacturers.

The aim of this specification is to formalize these agreements by defining a WoT Core Profile based on the choices that were made by the implementers of PlugFest devices.

The WoT Core Profile contains additional normative requirements that MUST be satisfied by devices to be compliant to the profile.

WoT-Core-Profile-Picture
WoT Core Profile - A Subset of Affordances

Adoption of the WoT Core Profile will significantly limit the implementation burden of device and cloud implementors.

The WoT Core Profile was defined with the following main goals:

It makes choices on the required metadata fields as well as the supported interactions and protocol endpoints. It introduces some constraints on data schemas for properties and actions which are required for resource constrained devices in real-world deployments. The format does not forbid the use of additional elements of the WoT Thing Description for vendor specific extensions, however this will impact interoperability.

Out-of-the-box interoperability

Devices, which implement the Core Profile, are out-of-the-box interoperable with other Core Profile compliant devices. Furthermore, the Core Profile simplifies device validation and compliance testing since a corresponding conformance test suite can be defined.

It is also a goal of the WoT Core Profile to ensure that compliant files can be understood by humans - therefore descriptions, dates, and author fields are either mandatory or highly recommended.

Structure of this document

to be added.

A device or consumer implementation complies with this specification if it follows the normative statements in the present document.

A JSON Schema [[?JSON-SCHEMA]] to validate the compliance of a Thing Description with the core profile is provided in Appendix .

Terminology

This specification uses the same terminology as the WoT Architecture and Thing Description specifications.

For convenience of the reader, we use the terms keyword and field for the linguistic notion vocabulary term as defined in the Thing Description Specification.

We use the terms device and thing in an interchangeable manner.

Additional Definitions:

Profile
A set of prescriptive rules, to ensure that compliant implementations satisfy the semantic guarantees that are implied by them.
WoT Core Profile
The subset of the Thing Description defined by the present document.
Core Profile
Synonym for WoT Core Profile.
Core Data Model
A Data Model that conforms to the subset of the Thing Description specification as defined in section .
Thing Description
Synonym for WoT Thing Description.
WoT Thing Description
The Web of Things Thing Description as defined in [[wot-thing-description11]].

Profiling Mechanism

In order to conform with a profile, a Web Thing MUST conform with all the normative statements in the profile's specification.

In order to denote that a given Web Thing conforms to one or more profiles, its Thing Description MUST include a profile member [[wot-thing-description11]]. The value of the profile member MUST be set to either a valid URI [[RFC3986]] identifying a single profile, or an array of valid URIs identifying multiple profiles.

    {
      "@context": "https://www.w3.org/2019/wot/td/v1",
      "id": "urn:dev:ops:32473-WoTLamp-1234",
      "profile": "https://www.w3.org/2022/wot/profile/core/v1",
      "title": "My Lamp",
      "description": "A web connected lamp",
      ...
    }
    
    {
      "@context": "https://www.w3.org/2019/wot/td/v1",
      "id": "urn:dev:ops:32473-WoTLamp-1234",
      "profile": [
        "https://www.w3.org/2022/wot/profile/core/v1",
        "https://www.w3.org/2022/wot/profile/constrained/v1"
      ],
      "title": "My Lamp",
      "description": "A web connected lamp",
      ...
    }
    

WoT Core Profile

This section defines the Core Profile, which includes:
  1. An identifier to denote that a given Thing Description [[wot-thing-description]] conforms to the WoT Core Profile
  2. A protocol binding for communicating with a Web Thing using JSON [[JSON]] payloads over the HTTP [[HTTP11]] protocol, which all conforming Web Things and Consumers [[wot-architecture]] MUST support
  3. A list of link relation types [[wot-thing-description]] which a Web Thing MAY use and a Consumer MUST support
  4. A list of security schemes [[wot-thing-description]] which a Web Thing MAY use and a Consumer MUST support
  5. A list of discovery mechanisms [[wot-discovery]] which a Web Thing MAY use and a Consumer MUST support

The name "Core Profile" is still under discussion (see #21).

Identifier

In order to denote that a given Web Thing conforms to the Core Profile, its Thing Description MUST have a profile member [[wot-thing-description]] with a value of https://www.w3.org/2022/wot/profile/core/v1.

Protocol Binding

This section defines a protocol binding which describes how a Consumer communicates with a Web Thing [[wot-architecture11]] using JSON [[JSON]] payloads over the HTTP [[HTTP11]] protocol.

A Consumer or Web Thing conforming to the WoT Core Profile MUST implement this protocol binding.

The examples provided throughout the Core Profile Protocol Binding section describe how a Consumer would communicate with a Web Thing which produces the following Thing Description:

        {
          "@context": "https://www.w3.org/2019/wot/td/v1",
          "id": "https://mywebthingserver.com/things/lamp",
          "profile": "https://www.w3.org/2022/wot/profile/core",
          "base": "https://mywebthingserver.com/things/lamp/",
          "title": "My Lamp",
          "description": "A web connected lamp",
          "securityDefinitions": {
            "oauth2": {
              "scheme": "oauth2",
              "flow": "code",
              "authorization": "https://mywebthingserver.com/oauth/authorize",
              "token": "https://mywebthingserver.com/oauth/token"
            }
          },
          "security": "oauth2",
          "properties": {
            "on": {
              "type": "boolean",
              "title": "On/Off",
              "description": "Whether the lamp is turned on",
              "forms": [{"href": "./properties/on"}]
            },
            "level" : {
              "type": "integer",
              "title": "Brightness",
              "description": "The level of light from 0-100",
              "unit": "percent",
              "minimum" : 0,
              "maximum" : 100,
              "forms": [{"href": "./properties/level"}]
            }
          },
          "actions": {
            "fade": {
              "title": "Fade",
              "description": "Fade the lamp to a given level",
              "input": {
                "type": "object",
                "properties": {
                  "level": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 100,
                    "unit": "percent"
                  },
                  "duration": {
                    "type": "integer",
                    "minimum": 0,
                    "unit": "milliseconds"
                  }
                }
              },
              "forms": [{"href": "./actions/fade"}]
            }
          },
          "events": {
            "overheated": {
              "title": "Overheated",
              "data": {
                "type": "number",
                "unit": "degree celsius"
              },
              "description": "The lamp has exceeded its safe operating temperature",
              "forms": [{
                "href": "./events/overheated",
                "subprotocol": "sse"
              }]
            }
          },
          "forms": [
            {
              "op": ["readallproperties", "writemultipleproperties"],
              "href": "./properties"
            },
            {
              "op": "queryallactions",
              "href": "./actions"
            },
            {
              "op": ["subscribeallevents", "unsubscribeallevents"],
              "href": "./events",
              "subprotocol": "sse"
            }
          ]
        }
      

The informative declarative protocol binding example in Appendix A illustrates how the concrete protocol binding defined in this section would have to be represented in a Thing Description if it did not use the profiling mechanism. In other words, if all of the defaults and protocol specifics defined in this section were instead provided declaratively using hypermedia Forms, with all defaults made explicit.

Properties

readproperty

The URL of a Property resource to be used when reading the value of a property MUST be obtained from a Thing Description by locating a Form inside the corresponding PropertyAffordance for which:

  • After defaults have been applied, its op member contains the value readproperty
  • After being resolved against a base URL where applicable, the URI scheme [[RFC3986]] of the value of its href member is http or https

The resolved value of the href member MUST then be used as the URL of the Property resource.

In order to read the value of a property, a Consumer MUST send an HTTP request to a Web Thing with:

  • Method set to GET
  • URL set to the URL of the Property resource
  • Accept header set to application/json
          GET /things/lamp/properties/on HTTP/1.1
          Host: mythingserver.com
          Accept: application/json
          

If a Web Thing receives an HTTP request following the format above and the Consumer has permission to read the corresponding property, then upon successfully reading the value of the property it MUST send an HTTP response with:

  • Status code set to 200
  • Content-Type header set to application/json
  • A body with the value of the property serialized in JSON
          HTTP/1.1 200 OK
          Content-Type: application/json
          false
          
writeproperty

The URL of a Property resource to be used when writing the value of a property MUST be obtained from a Thing Description by locating a Form inside the corresponding PropertyAffordance for which:

  • After defaults have been applied, its op member contains the value writeproperty
  • After being resolved against a base URL where applicable, the URI scheme [[RFC3986]] of the value of its href member is http or https

The resolved value of the href member MUST then be used as the URL of the Property resource.

In order to write the value of a property, a Consumer MUST send an HTTP request to a Web Thing with:

  • Method set to PUT
  • URL set to the URL of the Property resource
  • Content-Type header set to application/json
  • A body with a requested new value for the property serialized in JSON
          PUT /things/lamp/properties/on HTTP/1.1
          Host: mythingserver.com
          Content-Type: application/json
          true
          

If a Web Thing receives an HTTP request following the format above and the Consumer has permission to write the corresponding property, then upon successfully writing the value of the property it MUST send an HTTP response with:

  • Status code set to 204
          HTTP/1.1 204 No Content
          
observeproperty

The URL of a Property resource to be used when observing the value of a property MUST be obtained from a Thing Description by locating a Form inside the corresponding PropertyAffordance for which:

  • Its op member contains the value observeproperty
  • After being resolved against a base URL where applicable, the URI scheme [[RFC3986]] of the value of its href member is http or https
  • Its subprotocol member has a value of sse

The resolved value of the href member MUST then be used as the URL of the Property resource.

In order to observe a property, a Consumer MUST follow the Server-Sent Events [[EVENTSOURCE]] specification to open a connection with the Web Thing at the URL of the Property resource.

This involves the Consumer sending an HTTP request to the Web Thing with:

  • Method set to GET
  • URL set to the URL of the Property resource
  • Accept header set to text/event-stream
  • Connection header set to keep-alive
            GET /things/lamp/properties/level HTTP/1.1
            Host: mythingserver.com
            Accept: text/event-stream
            Connection: keep-alive
          

For Consumers implemented in JavaScript [[ECMASCRIPT]] and executed in a runtime which exposes the EventSource interface, a Server-Sent Events connection can be initiated using the EventSource constructor.

              const levelSource = new EventSource('/things/lamp/properties/level');
            

If a Web Thing receives an HTTP request following the format above and the Consumer has permission to observe the corresponding property, then it MUST follow the Server-Sent Events [[EVENTSOURCE]] specification to maintain an open connection with the Consumer and push a property value to the Consumer each time the value of the specified property changes.

This involves the Web Thing initially sending an HTTP response to the Consumer with:

  • Status code set to 200
  • Content-Type header set to text/event-stream
            HTTP/1.1 200 OK
            Content-Type: text/event-stream
          

Whenever the value of the specified property changes while the Web Thing has an open connection with a Consumer, the Web Thing MUST send a property value to the Consumer using the event stream format in the Server-Sent Events [[EVENTSOURCE]] specification. For each message sent, the Web Thing MUST set the event field to the name of the PropertyAffordance and populate the data field with the property value, serialized in JSON and following the data schema specified in the PropertyAffordance. The id field SHOULD be set to a unique identifier for the property change, for use when re-establishing a dropped connection (see below). It is RECOMMENDED that the identifier is a timestamp representing the time at which the property changed, in the "date-time" format specified by [[RFC3339]].

            event: level\n
            data: 42\n
            id: 2021-11-17T15:33:20.827Z\n\n
          

If the connection between the Consumer and Web Thing drops (except as a result of the unobserve operation defined below), the Consumer MUST re-establish the connection following the steps outlined in the Server-Sent Events specification [[EVENTSOURCE]]. Once the connection is re-established the Web Thing SHOULD, if possible, send any missed property changes which occured since the last change specified by the Consumer in a Last-Event-ID header.

unobserveproperty

In order to stop observing a property, a Consumer MUST terminate the corresponding Server-Sent Events connection with the Web Thing as specified in the Server-Sent Events specification [[EVENTSOURCE]].

For Consumers implemented in JavaScript [[ECMASCRIPT]] and executed in a runtime which exposes the EventSource interface, a Server-Sent Events connection can be terminated using the close() method on an EventSource [[EVENTSOURCE]] object.

              levelSource.close();
            

readallproperties

The URL of a Properties resource to be used when reading the value of all properties at once MUST be obtained from a Thing Description by locating a Form inside the top level forms member for which:

  • Its op member contains the value readallproperties
  • After being resolved against a base URL where applicable, the URI scheme [[RFC3986]] of the value of its href member is http or https

The resolved value of the href member MUST then be used as the URL of the Properties resource.

In order to read the value of all properties, a Consumer MUST send an HTTP request to a Web Thing with:

  • Method set to GET
  • URL set to the URL of the Properties resource
  • Accept header set to application/json
          GET /things/lamp/properties HTTP/1.1
          Host: mythingserver.com
          Accept: application/json
          

If a Web Thing receives an HTTP request following the format above, then upon successfully reading the values of all the readable properties to which the Consumer has permission to access, it MUST send an HTTP response with:

  • Status code set to 200
  • Content-Type header set to application/json
  • A body with the values of all readable properties serialized in JSON, as an object keyed by property name
          HTTP/1.1 200 OK
          Content-Type: application/json
          {
            "on": false,
            "level": 100
          }
          
writemultipleproperties

The URL of a Properties resource to be used when writing the value of multiple properties at once MUST be obtained from a Thing Description by locating a Form inside the top level forms member for which:

  • Its op member contains the value writemultipleproperties
  • After being resolved against a base URL where applicable, the URI scheme [[RFC3986]] of the value of its href member is http or https

The resolved value of the href member MUST then be used as the URL of the Properties resource.

In order to write the value of multiple properties at once, a Consumer MUST send an HTTP request to a Web Thing with:

  • Method set to PUT
  • URL set to the URL of the Properties resource
  • Content-Type header set to application/json
  • A body with requested new values for the writable properties serialized in JSON, as an object keyed by property name
          PUT /things/lamp/properties HTTP/1.1
          Host: mythingserver.com
          Content-Type: application/json
          {
            "on": true,
            "level": 50
          }
          

If a Web Thing receives an HTTP request following the format above, then upon successfully writing the values of the requested writable properties it MUST send an HTTP response with:

  • Status code set to 204

          HTTP/1.1 204 No Content
          
observeallproperties

The URL of a properties resource to be used when observing changes to all properties of a Web Thing MUST be obtained from a Thing Description by locating a Form inside the top level forms member of a Thing Description for which:

  • Its op member contains the value observeallproperties
  • After being resolved against a base URL where applicable, the URI scheme [[RFC3986]] of the value of its href member is http or https
  • Its subprotocol member has a value of sse

The resolved value of the href member MUST then be used as the URL of the properties resource.

In order to observe changes to all properties of a Web Thing, a Consumer MUST follow the Server-Sent Events [[EVENTSOURCE]] specification to open a connection with the Web Thing at the URL of the properties resource.

This involves the Consumer sending an HTTP request to the Web Thing with:

  • Method set to GET
  • URL set to the URL of the properties resource
  • Accept header set to text/event-stream
  • Connection header set to keep-alive
            GET /things/lamp/properties HTTP/1.1
            Host: mythingserver.com
            Accept: text/event-stream
            Connection: keep-alive
          

For Consumers implemented in JavaScript [[ECMASCRIPT]] and executed in a runtime which exposes the EventSource interface, a Server-Sent Events connection can be initiated using the EventSource constructor.

              const lampPropertiesSource = new EventSource('/things/lamp/properties');
            

If a Web Thing receives an HTTP request following the format above then it MUST follow the Server-Sent Events [[EVENTSOURCE]] specification to maintain an open connection with the Consumer and push new property values to the Consumer for all properties for which it has permission to observe.

This involves the Web Thing initially sending an HTTP response to the Consumer with:

  • Status code set to 200
  • Content-Type header set to text/event-stream
            HTTP/1.1 200 OK
            Content-Type: text/event-stream
          

Whenever a property changes while the Web Thing has an open connection with a Consumer, the Web Thing MUST send the new property value to the Consumer using the event stream format in the Server-Sent Events [[EVENTSOURCE]] specification. For each message sent, the Web Thing MUST set the event field to the name of the PropertyAffordance and populate the data field with the new property value. The property data MUST follow the data schema specified in the PropertyAffordance and MUST be serialized in JSON. The id field SHOULD be set to a unique identifier for the event, for use when re-establishing a dropped connection (see below). It is RECOMMENDED that the identifier is a timestamp representing the time at which the property changed, in the "date-time" format specified by [[RFC3339]].

            event: level\n
            data: 42\n
            id: 2021-11-17T15:33:20.827Z\n\n
          

If the connection between the Consumer and Web Thing drops (except as a result of the unobserveallproperties operation defined below), the Consumer MUST re-establish the connection following the steps outlined in the Server-Sent Events specification [[EVENTSOURCE]]. Once the connection is re-established the Web Thing SHOULD, if possible, send any missed property changes which occured since the last change specified by the Consumer in a Last-Event-ID header.

unobserveallproperties

In order to unobserve all properties, a Consumer MUST terminate the corresponding Server-Sent Events connection with the properties endpoint of the Web Thing, following the steps specified in the Server-Sent Events specification [[EVENTSOURCE]].

For Consumers implemented in JavaScript [[ECMASCRIPT]] and executed in a runtime which exposes the EventSource interface, a Server-Sent Events connection can be terminated using the close() method on an EventSource [[EVENTSOURCE]] object.

              lampPropertiesSource.close();
            

The readmultipleproperties operation is currently excluded due to the complexities of the request payload format and because it doesn't add much functionality over readproperty and readallproperties. The writeallproperties operation is currently excluded because it is just a special case of writemultipleproperties.

Actions

invokeaction

The URL of an Action resource to be used when invoking an action MUST be obtained from a Thing Description by locating a Form inside the corresponding ActionAffordance for which:

  • After defaults have been applied, the value of its op member is invokeaction
  • After being resolved against a base URL where applicable, the URI scheme [[RFC3986]] of the value of its href member is http or https

The resolved value of the href member MUST then be used as the URL of the Action resource.

In order to invoke an action on a Web Thing, a Consumer MUST send an HTTP request to the Web Thing with:

  • Method set to POST
  • URL set to the URL of the Action resource
  • Accept header set to application/json
  • Content-Type header set to application/json
  • A body with an input to the action, if any, serialized in JSON
          POST /things/lamp/actions/fade HTTP/1.1
          Host: mythingserver.com
          Content-Type: application/json
          Accept: application/json
          {
            "level": 100,
            "duration": 5
          }
          

If a Web Thing receives an HTTP request following the format above then it MUST respond with one of three response formats:

  1. Synchronous Action Response
  2. Asynchronous Action Response
  3. Error Response

For long-running actions which are not expected to finish executing within the timeout period of an HTTP request (e.g. 30 to 120 seconds), it is RECOMMENDED that a Web Thing respond with an Asynchronous Action Response so that a Consumer may continue to monitor the status of an action request with a queryaction operation on a dynamically created ActionStatus resource, after the initial invokeaction response.

For short-lived actions which are expected to finish executing within the timeout period of an HTTP request, a Web Thing MAY wait until the action has completed to send a Synchronous Action Response.

If a Web Thing encounters an error in attempting to execute an action before responding to the invokeaction request, then it MUST send an Error Response.

Conforming Consumers MUST support all three types of response to the initial invokeaction request, but support for subsequent operations on an ActionStatus resource is OPTIONAL.

ActionStatus object

The status of an action invocation request is represented by an ActionStatus object which includes the following members:

Member Description Assignment Type
status The status of the action request. mandatory string (one of pending, running, completed or failed)
output The output data, if any, of a completed action which MUST conform with the output data schema of the corresponding ActionAffordance. optional any type
error An error message, if any, associated with a failed action which MUST use the JSON serialization of the Problem Details format [[RFC7807]] (only needed in response to a queryaction operation). optional object
href The [[URL]] of an ActionStatus resource which can be polled with a queryaction operation to get the latest status of the action, the URI scheme [[RFC3986]] of which MUST resolve to http or https (only needed for an Asynchronous Action Response). optional string
timeRequested A timestamp indicating the time at which the Thing received the request to execute the action, in ISO format [[ISO8601-1]]. mandatory string
timeEnded A timestamp indicating the time at which the Thing successfully completed executing the action, or failed to execute the action, in ISO format [[ISO8601-1]]. optional string
Synchronous Action Response

If providing a Synchronous Action Response, a Web Thing MUST send an HTTP response with:

  • Status code set to 200
  • Content-Type header set to application/json
  • A body containing an ActionStatus object serialized in JSON
            HTTP/1.1 200 OK
            Content-Type: application/json
            {
              "status": "completed",
              "timeRequested": "2021-11-10T11:43:20.135Z",
              "timeEnded": "2021-11-10T11:43:25.135Z"
            }
          
Asynchronous Action Response

If providing an Asynchronous Action Response, a Web Thing MUST send an HTTP response containing the URL of an ActionStatus resource, the URI scheme [[RFC3986]] of which MUST resolve to http or https. The response MUST have:

  • Status code set to 201
  • Content-Type header set to application/json
  • Location header set to the URL of the ActionStatus resource
  • A body containing an ActionStatus object serialized in JSON, with its href member set to the URL of the ActionStatus resource
            HTTP/1.1 201 CREATED
            Content-Type: application/json
            Location: /things/lamp/actions/fade/123e4567-e89b-12d3-a456-426655
            {
              "status": "pending",
              "href": "/things/lamp/actions/fade/123e4567-e89b-12d3-a456-426655",
              "timeRequested": "2021-11-10T11:43:19.135Z"
            }
          
queryaction

A queryaction operation is used to query the current state of an ongoing action request.

A Web Thing which provides Asynchronous Action Responses to an invokeaction operation on an Action MUST also support queryaction operations on that same Action. A Web Thing which only provides Synchronous Action Responses to an invokeaction operation on an Action SHOULD NOT support queryaction operations on that same Action.

The URL of an ActionStatus resource to be used in a queryaction operation MUST be obtained from the Location header of an Asynchronous Action Response, or the href member of the ActionStatus object in its body.

In order to query the status of an action request, a Consumer MUST send an HTTP request to a Web Thing with:

  • Method set to GET
  • URL set to the URL of the ActionStatus resource
  • Accept header set to application/json
          GET /things/lamp/actions/fade/123e4567-e89b-12d3-a456-426655
          Host: mythingserver.com
          Accept: application/json
          

If a Web Thing receives an HTTP request following the format above and the Consumer has permission to query the corresponding ActionStatus resource, then upon successfully reading the status of the action request it MUST send an HTTP response with:

  • Status code set to 200
  • Content-Type header set to application/json
  • A body containing an ActionStatus object representing the current status of the action request, serialized in JSON
            HTTP/1.1 200 OK
            Content-Type: application/json
            {
              "status": "running",
              "timeRequested": "2021-11-10T11:43:19.135Z"
            }
          

If the queried action failed to execute, then the status member of the ActionStatus object MUST be set to "failed" and the error member may optionally provide additional error information conforming to the Problem Details format [[RFC7807]].

            HTTP/1.1 200 OK
            Content-Type: application/json
            {
              "status": "failed",
              "error": {
                "type": "https://mythingserver.com/docs/errors/invalid-level",
                "title": "Invalid value for level provided",
                "invalid-params": [
                  {
                    "name": "level",
                    "reason": "Must be a valid number between 0 and 100"
                  }
                ]
              },
              "timeRequested": "2021-11-10T11:43:19.135Z",
              "timeEnded": "2021-11-10T11:43:20.513Z"
            }
          
cancelaction

A cancelaction operation is used to cancel an ongoing Action request.

A Web Thing which provides Asynchronous Action Responses to an invokeaction operation on an Action MAY also support cancelaction operations on that same Action. A Web Thing which only provides Synchronous Action Responses to an invokeaction operation on an Action SHOULD NOT support cancelaction operations on that same Action.

The URL of an ActionStatus resource to be used in a cancelaction operation MUST be obtained from the Location header of an Asynchronous Action Response, or the href member of the ActionStatus object in its body.

In order to cancel an action request, a Consumer MUST send an HTTP request to a Web Thing with:

  • Method set to DELETE
  • URL set to the URL of the ActionStatus resource
            DELETE /things/lamp/actions/fade/123e4567-e89b-12d3-a456-426655 HTTP/1.1
            Host: mythingserver.com
          

If a Web Thing receives an HTTP request following the format above and the Consumer has permission to cancel the corresponding Action request, then upon successfully cancelling Action it MUST send an HTTP response with:

  • Status code set to 204
            HTTP/1.1 204 No Content
          
queryallactions

The URL of an Actions resource to be used when querying the status of all ongoing action requests MUST be obtained from a Thing Description by locating a Form inside the top level forms member for which:

  • Its op member contains the value queryallactions
  • After being resolved against a base URL where applicable, the URI scheme [[RFC3986]] of the value of its href member is http or https

The resolved value of the href member MUST then be used as the URL of the Actions resource.

In order to query the status of all ongoing action requests, a Consumer MUST send an HTTP request to a Web Thing with:

  • Method set to GET
  • URL set to the URL of the Actions resource
  • Accept header set to application/json
          GET /things/lamp/actions HTTP/1.1
          Host: mythingserver.com
          Accept: application/json
          

If a Web Thing receives an HTTP request following the format above, then upon successfully retreiving the status of all ongoing action requests to which the Consumer has permission to access, it MUST send an HTTP response with:

  • Status code set to 200
  • Content-Type header set to application/json
  • A body containing an object, keyed by Action name, with the value of each object member being an array of ActionStatus objects representing the action requests, serialized in JSON. Each array MUST be sorted in reverse chronological order such that the most recent action request appears first.
          HTTP/1.1 200 OK
          Content-Type: application/json
          {
            "fade": [
              {
                "status": "completed",
                "href": "/things/lamp/actions/fade/123e4567-e89b-12d3-a456-426655",
                "timeRequested": "2021-11-10T11:43:19.135Z",
                "timeEnded": "2021-11-10T11:43:20.513Z"
              },
              {
                "status": "failed",
                "href": "/things/lamp/actions/fade/123e4567-e89b-12d3-a456-558329",
                "timeRequested": "2021-11-10T11:42:15.133Z",
                "timeEnded": "2021-11-10T11:42:22.524Z"
              },
              {
                "status": "running",
                "href": "/things/lamp/actions/fade/123e4567-e89b-12d3-a457-434656",
                "timeRequested": "2021-11-10T11:41:53.351Z"
              },
              {
                "status": "pending",
                "href": "/things/lamp/actions/fade/123e4567-e89b-12d3-a457-ea9519",
                "timeRequested": "2021-11-10T11:39:53.651Z"
              }
            ]
          }
          
When an Action request is cancelled with a cancelaction operation, its ActionStatus object is deleted and need not be retained. For all other Action requests it is assumed that a Web Thing will store the ActionStatus object so that its status may later be queried with a queryaction or queryallactions operation. It is not expected that ActionStatus objects should be retained indefinitely, they may be stored in volatile memory and/or periodically pruned. The length of time for which to retain ActionStatus objects is expected to be implementation-specific and may depend on application-specific requirements or resource constraints.

Events

The Core Profile uses Server-Sent Events [[EVENTSOURCE]] as a mechanism for Consumers to subscribe to events emitted by a Web Thing.

Consumers are not required to implement the EventSource JavaScript API from the Server-Sent Events specification in order to conform with this profile. Any programming language may be used to consume an event stream.

subscribeevent

The URL of an Event resource to be used when subscribing to an event MUST be obtained from a Thing Description by locating a Form inside the corresponding EventAffordance for which:

  • After defaults have been applied, its op member contains the value subscribeevent
  • After being resolved against a base URL where applicable, the URI scheme [[RFC3986]] of the value of its href member is http or https
  • Its subprotocol member has a value of sse

The resolved value of the href member MUST then be used as the URL of the Event resource.

In order to subscribe to an event, a Consumer MUST follow the Server-Sent Events [[EVENTSOURCE]] specification to open a connection with the Web Thing at the URL of the Event resource.

This involves the Consumer sending an HTTP request to the Web Thing with:

  • Method set to GET
  • URL set to the URL of the Event resource
  • Accept header set to text/event-stream
  • Connection header set to keep-alive
          GET /things/lamp/events/overheated HTTP/1.1
          Host: mythingserver.com
          Accept: text/event-stream
          Connection: keep-alive
          

For Consumers implemented in JavaScript [[ECMASCRIPT]] and executed in a runtime which exposes the EventSource interface, a Server-Sent Events connection can be initiated using the EventSource constructor.

              const overheatedEventSource = new EventSource('/things/lamp/events/overheated');
            

If a Web Thing receives an HTTP request following the format above and the Consumer has permission to subscribe to the corresponding event, then it MUST follow the Server-Sent Events [[EVENTSOURCE]] specification to maintain an open connection with the Consumer and push event data to the Consumer as events of the specified type are emitted.

This involves the Web Thing initially sending an HTTP response to the Consumer with:

  • Status code set to 200
  • Content-Type header set to text/event-stream
          HTTP/1.1 200 OK
          Content-Type: text/event-stream
          

Whenever an event of the specified type occurs while the Web Thing has an open connection with a Consumer, the Web Thing MUST send event data to the Consumer using the event stream format in the Server-Sent Events [[EVENTSOURCE]] specification. For each message sent, the Web Thing MUST set the event field to the name of the EventAffordance and populate the data field with event data, if any. The event data MUST follow the data schema specified in the EventAffordance and MUST be serialized in JSON. The id field SHOULD be set to a unique identifier for the event, for use when re-establishing a dropped connection (see below). It is RECOMMENDED that the identifier is a timestamp representing the time at which the event occurred, in the "date-time" format specified by [[RFC3339]].

            event: overheated\n
            data: 90\n
            id: 2021-11-16T16:53:50.817Z\n\n
          

If the connection between the Consumer and Web Thing drops (except as a result of the unsubscribe operation defined below), the Consumer MUST re-establish the connection following the steps outlined in the Server-Sent Events specification [[EVENTSOURCE]]. Once the connection is re-established the Web Thing SHOULD, if possible, send any missed events which occured since the last event specified by the Consumer in a Last-Event-ID header.

unsubscribeevent

In order to unsubscribe from an event, a Consumer MUST terminate the corresponding Server-Sent Events connection with the Web Thing as specified in the Server-Sent Events specification [[EVENTSOURCE]].

For Consumers implemented in JavaScript [[ECMASCRIPT]] and executed in a runtime which exposes the EventSource interface, a Server-Sent Events connection can be terminated using the close() method on an EventSource [[EVENTSOURCE]] object.

              overheatedEventSource.close();
            

subscribeallevents

The URL of an events resource to be used when subscribing to all events emitted by a Web Thing MUST be obtained from a Thing Description by locating a Form inside the top level forms member of a Thing Description for which:

  • Its op member contains the value subscribeallevents
  • After being resolved against a base URL where applicable, the URI scheme [[RFC3986]] of the value of its href member is http or https
  • Its subprotocol member has a value of sse

The resolved value of the href member MUST then be used as the URL of the events resource.

In order to subscribe to all events emitted by a Web Thing, a Consumer MUST follow the Server-Sent Events [[EVENTSOURCE]] specification to open a connection with the Web Thing at the URL of the events resource.

This involves the Consumer sending an HTTP request to the Web Thing with:

  • Method set to GET
  • URL set to the URL of the events resource
  • Accept header set to text/event-stream
  • Connection header set to keep-alive
            GET /things/lamp/events HTTP/1.1
            Host: mythingserver.com
            Accept: text/event-stream
            Connection: keep-alive
          

For Consumers implemented in JavaScript [[ECMASCRIPT]] and executed in a runtime which exposes the EventSource interface, a Server-Sent Events connection can be initiated using the EventSource constructor.

              const lampEventsSource = new EventSource('/things/lamp/events');
            

If a Web Thing receives an HTTP request following the format above then it MUST follow the Server-Sent Events [[EVENTSOURCE]] specification to maintain an open connection with the Consumer and push event data to the Consumer for all event types for which it has permission to subscribe.

This involves the Web Thing initially sending an HTTP response to the Consumer with:

  • Status code set to 200
  • Content-Type header set to text/event-stream
            HTTP/1.1 200 OK
            Content-Type: text/event-stream
          

Whenever an event occurs while the Web Thing has an open connection with a Consumer, the Web Thing MUST send event data to the Consumer using the event stream format in the Server-Sent Events [[EVENTSOURCE]] specification. For each message sent, the Web Thing MUST set the event field to the name of the EventAffordance and populate the data field with event data, if any. The event data MUST follow the data schema specified in the EventAffordance and MUST be serialized in JSON. The id field SHOULD be set to a unique identifier for the event, for use when re-establishing a dropped connection (see below).

            event: overheated\n
            data: 90\n
            id: 2021-11-16T16:53:50.817Z\n\n
          

If the connection between the Consumer and Web Thing drops (except as a result of the unsubscribeallevents operation defined below), the Consumer MUST re-establish the connection following the steps outlined in the Server-Sent Events specification [[EVENTSOURCE]]. Once the connection is re-established the Web Thing SHOULD, if possible, send any missed events which occured since the last event specified by the Consumer in a Last-Event-ID header.

unsubscribeallevents

In order to unsubscribe from all events, a Consumer MUST terminate the corresponding Server-Sent Events connection with the events endpoint of the Web Thing, following the steps specified in the Server-Sent Events specification [[EVENTSOURCE]].

For Consumers implemented in JavaScript [[ECMASCRIPT]] and executed in a runtime which exposes the EventSource interface, a Server-Sent Events connection can be terminated using the close() method on an EventSource [[EVENTSOURCE]] object.

              lampEventsSource.close();
            

Error Responses

If any of the operations defined above are unsuccessful then the Web Thing MUST send an HTTP response with an HTTP error code which describes the reason for the failure. It is RECOMMENDED that error responses use one of the following HTTP error codes:

  • 400 Bad Request
  • 401 Unauthorized
  • 403 Forbidden
  • 404 Not Found
  • 500 Internal Server Error

Web Things MAY respond with other valid HTTP error codes (e.g. 418 I'm a teapot), but Consumers MAY interpret those error codes as a generic 4xx or 5xx error with no special defined behaviour.

If we define the finite set of error responses as above then we should also define what a Consumer should do if it receives a 3xx redirect type response.

If an HTTP error response contains a body, the content of that body MUST conform with with the Problem Details format [[RFC7807]].

Security

Below is a list of security schemes [[wot-thing-description]] which conformant Web Things MAY use and conformant Consumers MUST support:

The list of security schemes to include in the Core Profile is still under discussion.

A Thing MAY implement multiple security schemes.

Security schemes MUST be applied using the top level security member of a Thing and MUST NOT be applied to individual Forms.

Please see WoT Security Best Practices for implementation advice.

Discovery

In order to a conform with the Core Profile, a Web Thing's Thing Description [[wot-thing-description]] MUST be retrievable from a URL [[URL]] using the HTTP protocol [[HTTP11]], to enable its discovery using a Direct Introduction Mechanism [[wot-discovery]].

In order to a conform with the Core Profile, a Consumer MUST be able to retrieve a Thing Description when provided with its HTTP URL.

JSON Schema of the Core Profile

A Thing Description can be syntactically validated with the JSON Schema [[?JSON-SCHEMA]] for compliance with the core profile.

Todo: Define a JSON-SCHEMA.

Core Profile Declarative Protocol Binding Example

The following informative Thing Description illustrates how the concrete protocol binding defined in the Core Profile would have to be represented by the Web Thing described by EXAMPLE 3 if it did not use the profiling mechanism. In other words, if all of the defaults and protocol specifics defined in the Core Profile protocol binding were instead provided declaratively using hypermedia Forms, with all defaults made explicit.

This example is only intended as an illustration of how Web Things conforming to the Core Profile can benefit from more concise Thing Descriptions. A Web Thing which does use the profiling mechanism would only need to use the significantly more concise form shown in EXAMPLE 3 in order to provide the same information.

    {
      "@context": "https://www.w3.org/2019/wot/td/v1",
      "id": "https://mywebthingserver.com/things/lamp",
      "base": "https://mywebthingserver.com/things/lamp/",
      "title": "My Lamp",
      "description": "A web connected lamp",
      "securityDefinitions": {
        "oauth2": {
          "scheme": "oauth2",
          "flow": "code",
          "authorization": "https://mywebthingserver.com/oauth/authorize",
          "token": "https://mywebthingserver.com/oauth/token"
        }
      },
      "security": "oauth2",
      "properties": {
        "on": {
          "type": "boolean",
          "title": "On/Off",
          "description": "Whether the lamp is turned on",
          "forms": [
            {
              "op": "readproperty",
              "href": "./properties/on",
              "htv:methodName": "GET",
              "htv:headers": [{
                "htv:fieldName": "Accept",
                "htv:fieldValue": "application/json"
              }],
              "response": {
                "htv:statusCodeNumber": 200,
                "contentType": "application/json"
              }
            },
            {
              "op": "writeproperty",
              "href": "./properties/on",
              "htv:methodName": "PUT",
              "contentType": "application/json",
              "response": {
                "htv:statusCodeNumber": 204
              }
            }
          ]
        },
        "level" : {
          "type": "integer",
          "title": "Brightness",
          "description": "The level of light from 0-100",
          "unit": "percent",
          "minimum" : 0,
          "maximum" : 100,
          "forms": [
            {
              "op": "readproperty",
              "href": "./properties/level",
              "htv:methodName": "GET",
              "htv:headers": [{
                "htv:fieldName": "Accept",
                "htv:fieldValue": "application/json"
              }],
              "response": {
                "htv:statusCodeNumber": 200,
                "contentType": "application/json"
              }
            },
            {
              "op": "writeproperty",
              "href": "./properties/level",
              "htv:methodName": "PUT",
              "contentType": "application/json",
              "response": {
                "htv:statusCodeNumber": 204
              }
            }
          ]
        }
      },
      "actions": {
        "fade": {
          "title": "Fade",
          "description": "Fade the lamp to a given level",
          "input": {
            "type": "object",
            "properties": {
              "level": {
                "type": "integer",
                "minimum": 0,
                "maximum": 100,
                "unit": "percent"
              },
              "duration": {
                "type": "integer",
                "minimum": 0,
                "unit": "milliseconds"
              }
            }
          },
          "forms": [
            {
              "op": "invokeaction",
              "href": "./actions/fade",
              "htv:methodName": "POST",
              "contentType": "application/json",
              "htv:headers": [{
                "htv:fieldName": "Accept",
                "htv:fieldValue": "application/json"
              }],
              "response": {
                "htv:statusCodeNumber": 201,
                "htv:headers": [
                  {
                    "htv:fieldName": "Location",
                    "htv:fieldValue": "/fade/{id}"
                  }
                ],
                "contentType": "application/json",
                "schema": "actionStatus"
              },
              "additionalResponses": [
                {
                  "htv:statusCodeNumber": 200,
                  "htv:headers": [
                    {
                      "htv:fieldName": "Location",
                      "htv:fieldValue": "/fade/{id}"
                    }
                  ],
                  "contentType": "application/json",
                  "schema": "actionStatus"
                }
              ]
            },
            {
              "op": "queryaction",
              "href": "./actions/fade/{id}",
              "htv:methodName": "GET",
              "htv:headers": [{
                "htv:fieldName": "Accept",
                "htv:fieldValue": "application/json"
              }],
              "response": {
                "contentType": "application/json",
                "schema": "actionStatus"
              }
            },
            {
              "op": "cancelaction",
              "href": "./actions/fade/{id}",
              "htv:methodName": "DELETE",
              "contentType": "application/json"
            }
          ],
          "uriVariables": {
            "id": {
              "type": "string",
              "description": "identifier of action request"
            }
          }
        }
      },
      "events": {
        "overheated": {
          "title": "Overheated",
          "data": {
            "type": "number",
            "unit": "degree celsius"
          },
          "description": "The lamp has exceeded its safe operating temperature",
          "forms": [
            {
              "op": "subscribeevent",
              "href": "./events/overheated",
              "subprotocol": "sse",
              "htv:methodName": "GET",
              "htv:headers": [
                {
                  "htv:fieldName": "Accept",
                  "htv:fieldValue": "text/event-stream"
                },
                {
                  "htv:fieldName": "Connection",
                  "htv:fieldValue": "keep-alive"
                }
              ],
              "response": {
                "htv:statusCodeNumber": 200,
                "contentType": "text/event-stream"
              }
            },
            {
              "op": "unbsubscribeevent",
              "href": "./events/overheated",
              "subprotocol": "sse"
            }
          ]
        }
      },
      "forms": [
        {
          "op": "readallproperties",
          "href": "./properties",
          "htv:methodName": "GET",
          "htv:headers": [{
            "htv:fieldName": "Accept",
            "htv:fieldValue": "application/json"
          }],
          "response": {
            "htv:statusCodeNumber": 200,
            "contentType": "application/json"
          }
        },
        {
          "op": "writemultipleproperties",
          "href": "./properties",
          "htv:methodName": "PUT",
          "contentType": "application/json",
          "response": {
            "htv:statusCodeNumber": 204
          }
        },
        {
          "op": "queryallactions",
          "href": "./actions",
          "htv:methodName": "GET",
          "htv:headers": [{
            "htv:fieldName": "Accept",
            "htv:fieldValue": "application/json"
          }],
          "response": {
            "contentType": "application/json",
            "schema": "actionStatusList"
          }
        },
        {
          "op": "subscribeallevents",
          "href": "./events",
          "subprotocol": "sse",
          "htv:methodName": "GET",
          "htv:headers": [
            {
              "htv:fieldName": "Accept",
              "htv:fieldValue": "text/event-stream"
            },
            {
              "htv:fieldName": "Connection",
              "htv:fieldValue": "keep-alive"
            }
          ],
          "response": {
            "htv:statusCodeNumber": 200,
            "contentType": "text/event-stream"
          }
        },
        {
          "op": "unsubscribeallevents",
          "href": "./events",
          "subprotocol": "sse"
        }
      ],
      "schemaDefinitions": {
        "actionStatus": {
          "type": "object",
          "properties": {
            "status": {
              "type": "string",
              "enum": [
                "pending",
                "running",
                "completed",
                "failed"
              ]
            },
            "error": {
              "type": "object"
            },
            "href": {
              "type": "string",
              "format": "uri",
              "const": "./actions/fade/{id}"
            }
          },
          "required": [
            "status"
          ]
        },
        "actionStatusList": {
          "type": "object",
          "additionalProperties": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "status": {
                  "type": "string",
                  "enum": [
                    "pending",
                    "running",
                    "completed",
                    "failed"
                  ]
                },
                "error": {
                  "type": "object"
                },
                "href": {
                  "type": "string",
                  "format": "uri",
                  "const": "./actions/fade/{id}"
                }
              },
              "required": [
                "status"
              ]
            }
          }
        }  
      }
    }