w3c / wot-thing-description

Web of Things (WoT) Thing Description
http://w3c.github.io/wot-thing-description/
Other
133 stars 63 forks source link

Describing initial connection #878

Open egekorkan opened 4 years ago

egekorkan commented 4 years ago

Coming from https://github.com/w3c/wot-binding-templates/issues/14 since not sure if it can be solved without implying any change to the TD specification.

If a Consumer needs to to establish a connection of some type before doing other interactions, should this be described in the TD in some way? Some examples are:

I think that they can be understood from the forms of the TD that a connection must be made once but it can be also understood that for each them a new connection needs to be established. Maybe we can extend the base to include more than just URIs? If we decide to not change anything in the TD, I would propose to add explanations in the binding templates document.

sebastiankb commented 4 years ago

In my view, this is a task that must be implemented at the binding level, and smart implementation should manage the communication session (open, keep, close, etc connection). In the case of MQTT as example, it is quite clear that there is a need to connect to a broker beforehand. This should be coordinated by the MQTT binding implementation.

zolkis commented 4 years ago

I see 3 options:

  1. Encapsulate interaction setup in the bindings. A consumer will be able to use the Thing right away.
  2. Make explicit Actions for the setup, which would belong to the Thing business logic. A consumer would need to use these Things in a specific way, by first needing to call some setup Actions.
  3. Extend the TD Interaction with a way to describe interaction setup/configuration functionality. A consumer will be able to use the Thing right away. However, implementations/bindings will have a standardized way to handle these cases.

IIUC, the request was for 3 and Sebastian argued for 1. I agree that 1. is desirable. When it cannot be done, a TD writer may consider 2, since we don't have 3. I wonder do we have consider doing 3 in the TD, or 1 and 2 would cover all known scenarios?

egekorkan commented 4 years ago

I would be against an approach with the setup information being 100% protocol specific. This setup requirement happens in multiple protocols and we would need to introduce completely new vocabulary terms for each protocol and even subprotocols. It would be better if there is some generalized vocabulary that is common that is then extended in binding templates document for the given protocol, like we talk about readproperty in the TD document and "cov:methodName":"GET" in the bindings document. We can have a root-level term called, let's say, setup that describes the setup requirements. This also makes it annoying from the Scripting API / node-wot perspective where one has to find the broker uri from the list of interactions and forms and put it into a configuration file in addition to the security information that might be needed to connect to the broker. See example here. This kind of vocabulary can be also for onboarding of a device. For example, in Philips HUE, any consumer who wants to interact with the HUE Bridge must do some requests to get an API key, which can be then used to send HTTP requests. At the moment, we either have to put the apikey in the URIs, which is a privacy/security concern or put it in the securityDefinitions where a human has to actually go read Philips' documentation and find out what needs to be done to get the key. An example is available here where you will see a cryptic string in the URIs.

sebastiankb commented 4 years ago

I have the impression that this discussion is about optimization in terms of providing information at a specific place. However, we have this kind of feature in the TD and can be used for:

This also makes it annoying from the Scripting API / node-wot perspective where one has to find the broker uri from the list of interactions and forms and put it into a configuration file in addition to the security information that might be needed to connect to the broker.

You can simple use the base term to define the broker URL. The interactions only define the topics in href.

At the moment, we either have to put the apikey in the URIs, which is a privacy/security concern or put it in the securityDefinitions where a human has to actually go read Philips' documentation and find out what needs to be done to get the key.

Well, for this we have defined the container securityDefinitions and should be used for this. Otherwise it would be useless. This kind of use case should be shared with the security TF.

mjkoster commented 4 years ago

I think we could consider a pattern as used in Node-RED, which keeps an object for each connection that is reusable across multiple communication endpoints.

benfrancis commented 4 years ago

In the case of WebSockets, Mozilla's implementation provides an WebSocket endpoint to connect to in the links member, e.g.

"links": [
  {
    "rel": "alternate",
    "href": "wss://mywebthingserver.com/things/lamp",
  }
]

The "alternate" link relation type indicates that the linked URI resolves to an alternate representation of the device using the WebSocket protocol rather than the default REST protocol over HTTP. This link metadata could also include the definition of a subprotocol e.g.

"links": [
  {
    "rel": "alternate",
    "href": "wss://mywebthingserver.com/things/lamp",
    "subprotocol": "webthing"
  }
]

The client then just needs to recognise this protocol "wss://" and sub-protocol "webthing" to know how to communicate with the device.

This approach should work for any protocol with a URI scheme.

sebastiankb commented 4 years ago

actually, this is kind of the similar philosophy which we follow with the base term at root level. E.g., you can use base to provide the base url of your http endpoint and the values of the href in the properties, actions, and events are relative to the base. However, so far the base is a simple string for URL information only.

Theoretically, we can consider extending the 'base', which also allows to be a JSON object containing vocabulary terms, as can be seen in @benfrancis examples.

egekorkan commented 4 years ago

I would support such an extension of the base term. We would however need to find a way to reference to the different base keys from different forms in interactions.

benfrancis commented 4 years ago

@sebastiankb wrote:

Theoretically, we can consider extending the 'base', which also allows to be a JSON object containing vocabulary terms

Can you provide an example of what this might look like? Bear in mind that a WebSocket endpoint for a thing might have a different URL scheme, port and even a different origin to the rest of the URLs in the Thing Description.

sebastiankb commented 4 years ago

@benfrancis sure, based on your provided example above it will be simple

"base":  {
    "rel": "alternate",
    "href": "wss://mywebthingserver.com/things/lamp",
    "subprotocol": "webthing"
  }

All interaction affordances must follow the base, unless it is locally overwritten by their forms definitions (there you can also providehref and subprotocol),

benfrancis commented 4 years ago

What happens if a single thing can be communicated with via multiple protocols? Would it have multiple base URIs?

Having a list of links to alternate representations of the thing over alternative protocols (to mirror the lists of links/forms in interactions) might make more sense.

(Note that I don't think Mozilla's implementation currently implements the base member, it uses the URI of the Thing Description itself as the base.)

sebastiankb commented 4 years ago

In the very early version of the TD we used base as an array. However, we decided to skip this, as arithmetic complexity may cause problems when constrained IoT devices need to implement this.

But maybe we should reconsider our decision because of new use cases, e.g., as mentioned here #803 about TD templates. I like to discuss this in the TD session of the VF2F meeting.

egekorkan commented 3 years ago

With @sebastiankb and @danielpeintner we sketched some ideas. Below is a TD that has multiple base that are used in different ways

{
        "@context":"...",
    "bases": {
        "webthing": {
            "href": "wss://mywebthingserver.com/things/lamp",
            "subprotocol": "webthing"
        },
        "base2": {
            "href": "http://localhost:8080"
        },
        "base3": {
            "href": "http://localhost:8081"
        },
        "base4": {
            "href": "coap://localhost:8081"
        },
        "base5": {
            "href": "mqtt://localhost:1883",
            "security": ["basic"]
        },
        "asyncAPIbase": {  //this is how asyncAPI does it
            "url": "development.gigantic-server.com",
            "description": "Development server",
            "protocol": "kafka",
            "protocolVersion": "1.0.0"
        }
    },
    "properties": {  //different properties for the different base values
        "myProperty1": {
            "forms": [{
                "dependsOn": "webthing", //one way using a new keyword dependsOn
                "href": ""
            }]
        },
        "myProperty2": {
            "forms": [{
                "href": "{{base2}}/property2" //using only templating mechanism
            }]
        },
        "myProperty3": {
            "forms": [{
                "href": "{{base3}}/property3"
            }]
        },
        "myProperty4": {
            "forms": [{
                "dependsOn": "base4",
                "href": "property4"
            }]
        },
        "myProperty5": {
            "forms": [{
                "dependsOn": "base5",
                "href": "property5" //for mqtt this becomes the topic name
            }]
        }
    }
}

Some discussions we had:

benfrancis commented 3 years ago

For WebSockets my conclusion is that all is needed to "describe the initial connection" is a Form which specifies a protocol and sub-protocol. The fact that this Form describes an endpoint providing a persistent connection which can be shared between interactions is implied by the protocol and sub-protocol in the Form.

Here's an example of a top level Form describing a connection which can be shared between all interaction affordances:

  "forms": [
    {
      "href": "wss://user.webthings.io/things/lamp1",
      "subprotocol": "webthing"
    }
  ]

All the other information a consumer needs to communicate over this connection would be provided by the sub-protocol specification, including how to serialise individual messages using the data schemas provided in interaction affordances.

If a consumer doesn't implement the specified sub-protocol, it should just ignore the Form.

I suggest that whether or not to support mutliple base URIs is an orthogonal issue.

danielpeintner commented 3 years ago

@benfrancis the proposal seems to work well for one binding in a TD.

What I am a bit unsure about is how the consumer knows for href a or b (in interaction forms) it should be using the http base or the wss (see example below)

{
    "@context": "...",
    "base": "http://example.com/",
    "forms": [{
        "href": "wss://user.webthings.io/things/lamp1",
        "subprotocol": "webthing"
    }],
    "properties": {
        "myProperty": {
            "forms": [{
                "href": "a"
            }, {
                "href": "b"
            }]
        }
    }
}

I suggest that whether or not to support mutliple base URIs is an orthogonal issue.

This makes perfect sense.

benfrancis commented 3 years ago

@egekorkan wrote:

      "myProperty1": {
          "forms": [{
              "dependsOn": "webthing", //one way using a new keyword dependsOn
              "href": ""
          }]
      },

I think that this example is meant to provide an example for the proposed webthing WebSocket sub-protocol.

Note that one of the requirements of that protocol is that a single WebSocket is shared by all interaction affordances of a Thing, so this form wouldn't exist in that case since it's specific to a single property. There would therefore be no need for the new dependsOn keyword in this case.

Even if another WebSocket sub-protocol defined that each interaction affordance has its own endpoint (which we have found to be an inefficient way to use WebSockets), I would argue that a WebSockets consumer should be smart enough to know that it only needs a single connection for each WebSocket URI.

@danielpeintner wrote:

What I am a bit unsure about is how the consumer knows for href a or b (in interaction forms) it should be using the http base or the wss

We do this in WebThings (every Thing has both separate HTTP endpoints for each interaction affordance resolved against a base URI and a single top level endpoint for WebSockets). In our case I guess we just interpret the absolute wss://user.webthings.io/things/lamp1 URI as overriding the base URI. I'm not sure whether this is compliant with the current spec or not? If the TD spec started to support multiple base URIs then yes that would get confusing.

Edit: See https://github.com/WebThingsIO/gateway/issues/2802#issuecomment-848736271 for a complete example of where I think we're headed with this in WebThings.

egekorkan commented 3 years ago

@benfrancis wrote:

Note that one of the requirements of that protocol is that a single WebSocket is shared by all interaction affordances of a Thing, so this form wouldn't exist in that case since it's specific to a single property

In W3C WoT at least, there are no assumptions that a property has a way to be interacted with if it does not have a form. Thus, to show that this property can be interacted with via any protocol, it needs a form with an op element. With my example, I did not want to say that there should be multiple sockets opened, just that this property should use the previously opened socket in the base element. I see this to be very similar to MQTT.

Regarding the forms in the root level: A form implies an op inside, which implies an interaction that achieves a business logic. In this initial connection description, we want to avoid implying an interaction but just describing a connection establishment.

What is quite interesting from your example is to actually think of using the subprotocol field more. We can maybe say that if you see protocol X in the subprotocol or in href, you should look into a certain location in the TD. This can be bases or links or some other field we define.

benfrancis commented 3 years ago

@egekorkan wrote:

In W3C WoT at least, there are no assumptions that a property has a way to be interacted with if it does not have a form.

Does it say that in a specification somewhere or is that just an interpretation? As far as I can see the Thing Description specification says that top level forms "can be used to describe meta interactions", but it doesn't say they can't be used for other types of interactions.

Regarding the forms in the root level: A form implies an op inside, which implies an interaction that achieves a business logic.

As I understand it the op member of a form is optional, and top level forms have no default value for op. This seems to leave the door open for a sub-protocol to specify that a top level WebSocket endpoint can be used for a writeproperty operation for example, using form metadata from the root level and data schemas from the interaction affordance level.

I previously raised a question about this in #1070 and my conclusion from that discussion was that a top level form with a subprotocol member and no op member would be a valid way to describe this.

sebastiankb commented 3 years ago

The advantage of @benfranci's proposal is that we don't need to change that much. We just need more clear assertions. Maybe we can also introduce a new op value like "initConnection" or "baseConnection".

egekorkan commented 3 years ago

That flexibility of the op keyword should not be used for these purposes. A valid TD does not mean a usable TD. Actually, the editor's spec has somehow changed from the old one in that the op does not have a default value. If you look at https://www.w3.org/TR/wot-thing-description/#form , you see that it is not optional. However, the root level one has no default assignment and it is thus sadly open for interpretation.

If you look at the Playground's full schema that is used for validating TDs that should not have any assumptions, you see that at form_element_root, op is mandatory. This is mostly my mistake for not specifying it in the TD somehow.

All WoT consumers should think from the op point of view, i.e. I want to do this operation for this affordance, let me find a form that has this. Having meta interactions like what @sebastiankb pointed above makes more sense but how can one link to it from other forms?

sebastiankb commented 3 years ago

from today's TD call:

benfrancis commented 3 years ago

On the call today we discussed the possibility of an "open" op which could be used in a top level form to provide the endpoint of a WebSocket connection. It was also noted that forms are mandatory for interaction affordances and therefore in the case of a device which only supports WebSockets it would be necessary to provide a form for each interaction affordance, e.g. specifying the same WebSocket URL for each interaction. I noted that in the case of a device which provides both HTTP and WebSocket forms, it may not technically be necessary to provide a WebSocket for for every interaction affordance under the current specification. I also noted that it's currently valid to provide a top level form without an op member.

Below I have attempted two example Thing Descriptions for a device exposed by WebThings Gateway.

  1. With both a top-level form and interaction-level forms for each affordance
  2. With only a top level form for WebSockets (with no op member) but interaction-level forms for HTTP

1. Top-level form + interaction-level forms

{
  "@context": [
     "https://www.w3.org/2019/wot/td/v1",
     "https://webthings.io/schemas/"
  ],
  "@type": ["Light", "OnOffSwitch"],
  "id": "https://user1.webthings.io/things/lamp1/",
  "title": "My Lamp",
  "description": "A web connected lamp",
  "base": "https://user1.webthings.io/things/lamp1/",
  "securityDefinitions": { ... },
  "security": "oauth2",
  "properties": {
    "on": {
      "@type": "OnOffProperty",
      "type": "boolean",
      "title": "On/Off",
      "description": "Whether the lamp is turned on",
      "forms": [
        {
          "href": "properties/on"
        },
        {
          "href": "wss://user1.webthings.io/things/lamp1",
          "subprotocol": "webthing",
          "op": "writeproperty"
        }
      ],
    },
    "brightness" : {
      "@type": "BrightnessProperty",
      "type": "integer",
      "title": "Brightness",
      "description": "The level of light from 0-100",
      "minimum" : 0,
      "maximum" : 100,
      "forms": [
        {
          "href": "properties/brightness"
        },
        {
          "href": "wss://user1.webthings.io/things/lamp1",
          "subprotocol": "webthing",
          "op": "writeproperty"
        }
      ],
    }
  },
  "actions": {
    "fade": {
      "@type": "FadeAction",
      "title": "Fade",
      "description": "Fade the lamp to a given level",
      "input": {
        "type": "object",
        "properties": {
          "level": {
            "type": "integer",
            "minimum": 0,
            "maximum": 100
          },
          "duration": {
            "type": "integer",
            "minimum": 0,
            "unit": "milliseconds"
          }
        }
      },
      "forms": [
        {
          "href": "actions/fade"
        },
        {
          "href": "wss://user1.webthings.io/things/lamp1",
          "subprotocol": "webthing",
          "op": "invokeaction"
        }
      ],
    }
  },
  "events": {
    "overheated": {
      "title": "Overheated",
      "@type": "OverheatedEvent",
      "data": {
        "type": "number",
        "unit": "degree celsius"
      },
      "description": "The lamp has exceeded its safe operating temperature",
      "forms": [
        {
          "href": "events/overheated",
          "subprotocol": "sse",
        },
        {
          "href": "wss://user1.webthings.io/things/lamp1",
          "subprotocol": "webthing",
          "op": "subscribeevent"
        }
      ],
    }
  },
  "forms": [
    {
      "op": "readallproperties",
      "href": "properties"
    },
    {
      "op": "subscribeallevents",
      "href": "events",
      "subprotocol": "sse",
    },
    {
      "href": "wss://user1.webthings.io/things/lamp1",
      "subprotocol": "webthing",
      "op": ["open", "writemultipleproperties", "observeallproperties"]
    }
  ],
  "links": [
    {
      "rel": "alternate",
      "type": "text/html",
      "href": ""
    }
  ]
}

Notes:

  1. Some of these forms are a little awkward since the current Web Thing WebSocket API used by WebThings does not map neatly onto the operation types and defaults in the Thing Description 1.1 spec. This is because that API predates even the Thing Description 1.0 specification.* Specifically:
    1. writeProperty messages can be used to write individual properties and write multiple properties at once, but not to read properties (since it's a pub/sub style protocol)
    2. All properties are observed by default using propertyStatus messages and there's no way to unobserve properties
    3. It's possible to subscribe to events using an addEventSubscription message but not unsubscribe
    4. There are actionStatus messages which don't map onto any current operation in the W3C spec
  2. There's a lot of redundancy in the TD due to repeating the same WebSocket URL numerous times
  3. The "open" operation feels a bit out of place to me because unlike all the other operations it describes a network connection rather than an interaction with the actual device

* I expect this situation to be better in the future standardised Web Thing Protocol, but still not a clear 1:1 mapping with the operations in the W3C specification.

1. Top-level form only

{
  "@context": [
     "https://www.w3.org/2019/wot/td/v1",
     "https://webthings.io/schemas/"
  ],
  "@type": ["Light", "OnOffSwitch"],
  "id": "https://user1.webthings.io/things/lamp1/",
  "title": "My Lamp",
  "description": "A web connected lamp",
  "base": "https://user1.webthings.io/things/lamp1/",
  "securityDefinitions": { ... },
  "security": "oauth2",
  "properties": {
    "on": {
      "@type": "OnOffProperty",
      "type": "boolean",
      "title": "On/Off",
      "description": "Whether the lamp is turned on",
      "forms": [{"href": "properties/on"}]
    },
    "brightness" : {
      "@type": "BrightnessProperty",
      "type": "integer",
      "title": "Brightness",
      "description": "The level of light from 0-100",
      "minimum" : 0,
      "maximum" : 100,
      "forms": [{"href": "properties/brightness"}]
    }
  },
  "actions": {
    "fade": {
      "@type": "FadeAction",
      "title": "Fade",
      "description": "Fade the lamp to a given level",
      "input": {
        "type": "object",
        "properties": {
          "level": {
            "type": "integer",
            "minimum": 0,
            "maximum": 100
          },
          "duration": {
            "type": "integer",
            "minimum": 0,
            "unit": "milliseconds"
          }
        }
      },
      "forms": [{"href": "actions/fade"}]
    }
  },
  "events": {
    "overheated": {
      "title": "Overheated",
      "@type": "OverheatedEvent",
      "data": {
        "type": "number",
        "unit": "degree celsius"
      },
      "description": "The lamp has exceeded its safe operating temperature",
      "forms": [{
        "href": "events/overheated",
        "subprotocol": "sse"
      }]
    }
  },
  "forms": [
    {
      "op": "readallproperties",
      "href": "properties"
    },
    {
      "op": "subscribeallevents",
      "href": "events",
      "subprotocol": "sse",
    },
    {
      "href": "wss://mywebthingserver.com/things/lamp",
      "subprotocol": "webthing"
    }
  ],
  "links": [
    {
      "rel": "alternate",
      "type": "text/html",
      "href": ""
    }
  ]
}

Notes:

  1. This version doesn't include interaction-level forms for WebSockets, just a single top level Form specifying the URL of the WebSocket endpoint and the "webthing" sub-protocol.
  2. The idea here is that the specification of the webthing sub-protocol would define all the details about how to perform each type of operation over the WebSocket connection and how to serialise messages using the data schemas provided in each interaction affordance.
  3. Technically this Thing Description is still valid because there are still HTTP forms for each interaction affordance, but that wouldn't be the case for a WebSocket-only device

My conclusion from this exercise is that it would be possible to take the first approach for the Thing Descriptions exposed by WebThings Gateway, but it makes for a messy Thing Description with a lot of redundancy. The latter approach would be cleaner for WebThings Gateway, but the same approach wouldn't work for WebSocket-only devices.

benfrancis commented 3 years ago

Clutching at straws here, but although the forms member of an InteractionAffordance is mandatory, could it be an empty array? I'd ideally like to avoid the mess and redundancy of the first example in my previous comment and the mandatory status of forms seems to be the main cause. We could argue for removing that constraint in 2.0 but for 1.1 it probably wouldn't be backwards compatible.

I still think that for WebSockets a single top level Form with a subprotocol member and no op member would be the cleanest solution.

relu91 commented 3 years ago

Clutching at straws here, but although the forms member of an InteractionAffordance is mandatory, could it be an empty array?

So my understanding is that they could not be empty. Searching in the spec I found two hints supporting it:

Now, as we all know, the JSON Schema is non-normative (although I think that 99% of our developer audience would use it to validate their TDs) but the TD ontology model should be normative. Maybe, @sebastiankb or @mmccool can confirm that statement.

I'd ideally like to avoid the mess and redundancy of the first example in my previous comment and the mandatory status of forms seems to be the main cause. We could argue for removing that constraint in 2.0 but for 1.1 it probably wouldn't be backwards compatible.

Yes, sadly it is verbose but IMO we can't do too much about it in 1.1. Basically we built our cage without knowing it 🤣 . One idea that came to my mind is to exploit canonicalization as a process to be really backward compatible here -> only Canonical TDs will have forms specified everywhere. But I think it is mostly a hack.

In 2.0 we can think more in-depth about redundancy forms and maybe think more about connection-oriented protocols.

benfrancis commented 3 years ago

@relu91 wrote:

So my understanding is that they could not be empty.

OK.

Yes, sadly it is verbose but IMO we can't do too much about it in 1.1. Basically we built our cage without knowing it

That does seem to be the case, yes.

In the specific case of the Thing Descriptions exposed by WebThings Gateway I'd suggest we could potentially work around it though. Given the gateway also provides an HTTP endpoint for every interaction affordance I'd suggest it is technically possible to have a single top level form describing the WebSocket endpoint and still be compliant with the specification, since the forms members of the individual interaction affordances will not be empty. It's a hack that could see us through to a 2.0 and is unlikely to cause backwards compatibility problems in practice given that there's currently no clear specification for how to deal with WebSocket endpoints anyway. We can take that discussion to https://github.com/WebThingsIO/gateway/issues/2806 if you like.

relu91 commented 3 years ago

it might be better to be not too much prescriptive in how we describe this new functionality. As described in https://github.com/WebThingsIO/gateway/issues/2806#issuecomment-887354929, forcing implementations to have those redundant forms might hinder the alignment of webthings.io implementation. My suggestion is to use SHOULD when explaining how the top-level form would affect forms definitions in affordances.

sebastiankb commented 3 years ago

from today's TD call:

sebastiankb commented 3 years ago

from today's TD call:

sebastiankb commented 3 years ago

from today's TD call:

benfrancis commented 3 years ago
* there will be also a new op type like "open" --> naming is still under discussion

Discussion of this part had to be rushed during the meeting.

I personally find the idea of an "open" operation a bit strange because it doesn't actually describe an operation on a device, only how to set up a connection in order to carry out other operations. If Forms at the interaction affordance level are made optional then in the case of WebSockets I don't think an "open" operation type is really necessary since a single WebSocket endpoint can be just shared between interactions using a specified subprotocol. Perhaps it is needed for other protocols though?

sebastiankb commented 3 years ago

Thats a good point. The op value is maybe not perfect for this. However, the idea is simply to have some sort of identifier that says that this information is only used to establish the connection and the connection is kept open all the time. Beside of WS, this would be relevant for MQTT, OPC UA and Modbus.

Such a forms entry can be also used to define some generic communication pattern like HTTP header settings that should be applied to all interactions. This would avoid redundant definition at the local interaction forms level.

sebastiankb commented 3 years ago

from today's TD call: Its seems ok that we not need a special op value for initializing a connections since at the top level there will always a single entry of an protocol that keeps the session open all the time.

egekorkan commented 3 years ago

I agree that opening a connection is not an affordance level operation but it should be explicitly described somewhere. Maybe a new construct at the Thing level? OpenAPI has servers but since we do not have just server-client protocols, we can have connections ?

sebastiankb commented 3 years ago

I like this, another option would be endpoints

sebastiankb commented 2 years ago

from today's TD call: