Open egekorkan opened 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.
I see 3 options:
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?
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.
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.
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.
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.
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.
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.
@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.
@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
),
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.)
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.
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:
base
instead of bases
, old implementations will be confused and not work{{}}
then an old implementation will not find the protocol and fail like an implementation not finding binding for MQTTdependsOn
can/should be changed. 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.
@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.
@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
orb
(in interactionforms
) it should be using thehttp
base or thewss
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.
@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.
@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.
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".
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?
from today's TD call:
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. 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:
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)propertyStatus
messages and there's no way to unobserve propertiesaddEventSubscription
message but not unsubscribeactionStatus
messages which don't map onto any current operation in the W3C spec* 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:
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.
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.
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:
"forms": {
"type": "array",
"minItems": 1,
"items": {
"$ref": "#/definitions/form_element_property"
}
}
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.
@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.
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.
from today's TD call:
from today's TD call:
from today's TD call:
* 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?
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.
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.
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
?
I like this, another option would be endpoints
from today's TD call:
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.