-
Notifications
You must be signed in to change notification settings - Fork 63
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
HTTP Binding for providing historical events. #892
Comments
Just an idea: You can have a property called // pastEvents property
{
"type": "array",
"items": {
"type": "string", // or more like the date time information
"@type":"EventAffordance"
}
} I am not very sure since this would mean that you would expect the |
Providing the events through a property is workable, and this technique could also cover the missing ActionRequest concept from the Mozilla WOT spec. It would be nice to have a stronger contract for this though, possibly by a "gethistoricalevents" op form. This seems to be similar in scope to "readmultipleproperties" as an optional extension onto the affoardance. |
We have discussed this as well but for us this is specific for an implementation and should not be integrated into the standard. Given further interest from other implementations and members, it can be in the future. |
I always try to restrict the use of properties for actual physical properties of a device which cause a change of state in the real world, and avoid using them to represent things like internal firmware settings or data logs. Using properties for event logs or action queues feels like a hack to shoehorn an API/protocol that can't currently be expressed via the Thing Description into the TD Information Model. The HTTP Protocol Binding currently describes how to get and set properties and invoke actions, but not how to receive events or cancel or query the state of an action request. I think this behaviour could be defined for HTTP and WebSockets through an extended HTTP Protocol Binding or default profile, and a WebSocket sub-protocol. |
See also, section 2.5 of the WG Charter.
|
The action queue are indeed something we are working on. I was not aware of the event queue statement of the working charter, so this is my bad. Just a question though, how does the Consumer use the information provided in the past events? |
Let's not mix the data model of a specific Thing with the general WoT interaction model. An Event interaction is meant to model a subscription based transient notification about a state change. I would not use Event interactions to represent/convey time series. Depending on network conditions are sampling frequency + data size, it would be a race. Modeling these with Action interactions seem more suitable, as it could be adapted better to the use case. We could do proofs of concept with these and see what would be worth standardizing as WoT mechanism for handling streams. I agree we should check if Actions need refinement. (We also discussed aspects of synchronicity vs atomicity in action sequences/transactions.) |
Events are obviously tricky to map onto HTTP in general because it has no built-in push mechanism (e.g. like CoAP does). Therefore, in its REST API the Mozilla WebThings implementation provides events using a GET on the URL of an event resource which can be polled at intervals if necessary. This GET request could just return the latest event, but then the client may miss some events. The response to the GET request therefore contains an array of recent events with timestamps, so the client can figure out what events have been emitted since the last GET. This is obviously an imperfect solution because it still requires the resource to be polled often enough to receive every event, or for the server to keep track of which clients have received which events. This is why there is also a separate push mechanism in the WebSocket API to receive events as they are emitted, but the event resource in the REST API provides a fallback for clients which don't support WebSockets. There are of course other potential solutions to receiving events over HTTP like long-polling and Server Sent Events, but none of them are perfect and currently the HTTP Protocol Binding in the Thing Description specification doesn't specify any one solution as a default. |
Events can contain data. The format of these data is described by the
Rather than discuss this abstractly, here are some concrete examples of events in Mozilla's WoT capability schemas which have been mapped onto real devices:
None of these events could be modelled as an action as far as I can tell, because they are events emitted by a device, not actions invoked upon the device. The more common way to model a time series of data would be through properties (e.g. streaming temperature values from a temperature property over a WebSocket). But events can contain data in some cases too. |
In my case, the use case is to fetch past interactions for display. For example, imagine a TD-speaking website using the HTTP binding wants to show the times when a motion detector has detected motion. For an example of this out in the home automation world, OpenHab can provide a timeline of events. Home Assistant also provides timelines, graphs, and event counters (as seen in the first image of the readme). This information is useful largely for its historical readings, as it would not be particularly useful if the user could only be told of motion detection that has happened while the website is open. Admittedly, a property that contains an array of previous values could work here, but knowledge of historical event values is a useful construct. I feel it would be preferable to implement this as a concern of the event, but alternatively an extension that lets me describe a property as offering up historical data for a given event would also be acceptable. In the latter case, my library would auto-generate such properties for each event. My justification for having this be part of EventAffordance is that, to represent events as a semantic concept, it should provide not only the ability to observe events but also to know of past occurrences of the event. Pushing this off on a property does seem a misuse of the concepts, as things like a list of previous motion detection cannot be said to be a part of the state of a motion detector. And while temperature and heating/cooling status can be said to be part of an air handler's state, having a property be an array of timestamps and previous states again seems closer to an event concern than one of state. |
So we'd need a new interaction construct that would be able to describe a series of past Event interactions (presumably configured during subscribe). That would make event subscription the "meta-action" to configure past events delivery, which could happen in two (perhaps configurable) ways,
For this we'd need to modify the contract of |
From the discussion, it seems that the use case is indeed present. In the new TD version we are planning to not introduce any breaking changes, so changing what
|
Adding options to |
Treating this as a new op seems to be the best option in my opinion. I can add to the conversation around using subscribeevent though. From the viewpoint of a subscription, this reminds me of hot and cold observables, used in rx / rxjs. In short:
In this case, I still believe a new op might better serve this though. While I do intend to both receive past events and subscribe to new ones at the same time, it might be better to keep the two ideas separate. I am not fully convinced either way through. The problem of the data format for a readhistoricalevents op is one I don't have answers for. For one, we would need to somehow pass along timestamp data, which might not come over when the event is in its subscription form. This means either wrapping each event in a timestamped envelope similar to the current Mozilla WOT implementation, or allowing the TD to specify a different schema for historical events. A third option could just be to leave events in the same data format, but trust that the event will provide the timestamp on its own, in both subscribe and historical mode. A problem with both approaches is that devices presumably exist indefinitely, so the historical action list is unbounded. There should be a way to both limit the number of items the client asks for, and to explain to the client the limits on the device itself. Both of these could be done through either a hard count on the number of historical events, or by the age of the event. It may even be advisable to provide both limits on the same event (IE: TD declares an event supporting the previous 100 events over the past 2 weeks). |
There could be subscription options to tell the client prefs, and server limitations could just manifest in how the events/data is delivered.
A separate op would work if someone first subscribes to an Event and then would fetch event history - otherwise it's a race. I guess the separate op would deliver the events in bulk. I am thinking we'd need that bulk delivery mechanism even for normal Events if we wanted to support reporting high frequency data, in order to avoid network+processing roundtrips, then expose as an iterable, rather than firing a local event every time - it all depends on reporting rate, sampling rate, sample size, network conditions and the capabilities of the client and server. But it would be a simpler solution to separate interactions with bulk delivery vs single delivery, so we may want to stick with the current assumption that Event interactions should be used for state change notifications, eventually carrying small and sparse data (like in the use cases @benfrancis quoted). History of such Events would then be supported by a separate op, with options. |
For example, DDS has QoS of History to configure how many (or all) values should be retained. |
Beside of the technical discussion we should have "historical events" as a use case and shared it with the Arch TF. I will provide an issue for it. |
There is a "Event Sourcing" pattern for historical events. (from Microsoft Azure documentation), which might provide an useful input to the use case discussion. When to use this pattern
This pattern might not be useful in the following situations:
|
Support for event queues is in the current charter and I'm conscious we haven't come up with a solution for this yet. This is also needed in order to make WebThings W3C compliant (see WebThingsIO/gateway#2806 and WebThingsIO/gateway#2807). This feature would be particularly useful for an HTTP WoT consumer which doesn't support a separate event mechanism like Server Sent Events or WebSockets, but is also useful for all consumers which want to get historical event data (e.g. which occurred before a WebSocket was opened). Proposed Solution
What do people think about a "events": {
"overheated": {
"title": "Overheated",
"type": "number",
"unit": "degree celsius",
"description": "The lamp has exceeded its safe operating temperature",
"forms": [{
"op": "readpastevents",
"href": "https://foo.webthings.io/things/lamp/events/overheated"
}]
}
}
There could also be a "forms": [
{
"op": "readallpastevents",
"href": "https://foo.webthings.io/things/lamp/events"
}
[ Challenge 1: Operation naming I'm open to suggestions on naming since Challenge 2: Payload representation Another challenge is how to describe the payload of such resources, since its desirable to include timestamps in the data. Currently WebThings returns payloads like this for both event and events resources: [
{
"overheated": {
"data": 102,
"timestamp": "2017-01-25T15:01:35+00:00"
}
},
{
"overheated": {
"data": 101,
"timestamp": "2017-01-24T13:02:45+00:00"
}
}
] But one of the things we're trying to do in order to be W3C compliant is to remove these types of object wrappers which are described in the Web Thing API specification rather than declaratively inside the TD. For the payloads returned for individual event affordances we could remove the event name from the payload as that is implicit, e.g. [
{
"data": 102,
"timestamp": "2017-01-25T15:01:35+00:00"
},
{
"data": 101,
"timestamp": "2017-01-24T13:02:45+00:00"
}
] We could then try to describe this payload declaratively in the TD, e.g. {
"events": {
"overheated": {
"title": "Overheated",
"type": "array",
"items": {
"type": "object",
"properties": {
"data": {
"type": "integer"
},
"timestamp": {
"type": "string",
"format": "date-time"
}
}
},
"unit": "degree celsius",
"description": "The lamp has exceeded its safe operating temperature",
"forms": [{
"op": "readpastevents",
"href": "https://foo.webthings.io/things/lamp/events/overheated"
}]
}
}
} A downside of describing the payload declaratively in the Thing Description in this way is that it makes the method of providing timestamps implementation specific which will be harder for consumers to make sense of. Additional semantic annotations might be needed to better explain the semantic meaning of the payloads. For the {
"overheated": [
{
"data": 102,
"timestamp": "2017-01-25T15:01:35+00:00"
},
{
"data": 101,
"timestamp": "2017-01-24T13:02:45+00:00"
}
],
"alarm": [
{
"timestamp": "2017-01-25T15:01:35+00:00"
},
{
"timestamp": "2017-01-24T13:02:43+00:00"
}
]
} Challenge 3: Pagination Another challenge is how to decide how many events to show at a time (WebThings Gateway just picks an arbitrary number), and whether pagination features are needed like are being discussed for the Directory Service API. And if so how those features would be described in the Thing Description. I acknowledge that the above is all a bit complicated and is different to existing operations in that it describes historical data (there is no way to get past values of properties for example). The working group may therefore decide only to specify the However, if no solution is found to this problem some consequences will be:
|
I think this can be done like
I think this is a more difficult thing to solve.
This would also change the Data Schema for a single and historical data, i.e. the example you have would not describe the Data Schema of a single event (btw we wrap event data in a {
"events": {
"overheated": {
"data": {
"type": "integer"
},
"historicalData": {
"type": "array",
"items": {
"type": "object",
"properties": {
"data": {
"$ref": "#/events/overheated/data"
},
"timestamp": {
"type": "string",
"format": "date-time"
}
}
}
},
"forms": [{
"op": "subscribeevent",
"href": "https://foo.webthings.io/things/lamp/events/overheated",
"subprotocol": "sse"
}, {
"op": "readpastevents",
"href": "https://foo.webthings.io/things/lamp/events/overheated"
}]
}
}
} Still the Regarding pagination, it might be also described in the Regarding your last points: I don't think that they should be removed, historical data is very useful for dashboards showing graph of a value over time. If no solution is found, the worst-case solution would be to define a property and link it to the events |
This makes sense, but not every device might [be able to] implement it. About pagination, an iterator/cursor approach would work on a JS API level, but on protocol level it needs to be supported.
I think that |
I support the idea of introducing a new operation type for this goal, as long as we don't start to spawn one new operation type every time we found that something is missing. I like the approach because is protocol binding independent so that existing implementations do not need any new update, very nice 👍🏻 . Just to mention the possible impact points of the proposal:
Now the bad news, this feature is not backward compatible. In the sense that a TD that has What if we define a meta/pseudo resource/propertyaffordance(?) called the {
"events": {
"overheated": {
"data": {
"type": "integer"
},
"eventLog": {
/* Event log implements dataschema
so that we can describe it's content
similarly to Ege's example */
"forms":[
{
"op": "readeventlog",
"href": "https://foo.webthings.io/things/lamp/events/overheated/log"
}
]
},
"forms": [
{
"op": "subscribeevent",
"href": "https://foo.webthings.io/things/lamp/events/overheated",
"subprotocol": "sse"
}
]
}
},
"eventLog":{
"forms": [
{
"op": "readalleventlogs", // or simply reuse "readeventlog"
"href": "https://foo.webthings.io/things/lamp/events/logs",
"subprotocol": "sse"
}
]
}
} We might even exploit this design pattern to solve #302 and other problems related to the state management of actions. The biggest pro right now is that it's backward compatible (i.e., an old client would just ignore it). I also kinda see it as a possible good pattern for future extestions, but what do people think? is it too verbose/complex? |
@relu91 wrote:
I'm afraid this is an inevitable consequence of the form-based declarative protocol binding approach of the W3C TD spec. This list is only going to grow over time. For 1.1 we are already discussing:
Ugh, really? Are you sure this isn't backwards compatible? The normative text in section 5.3.4.2 says:
Therefore, whilst the list of operations is fixed in 1.0, consumers should be expecting that future versions of the specification may extend this list. It also doesn't say that operations MUST NOT be arbitrarily set by servients, only that they SHOULD NOT, so consumers would trip up on producers which don't follow this guidance. The JSON Schema in section B does indeed assert that If the list of operations really can't be extended, then that was a significant oversight and I don't see how it's possible to fulfill may of the goals set out in the current charter like extending the Thing Description vocabulary, extending protocol binding vocabulary and complex interactions. If this is the case, and the version number is the problem, then rather than using workarounds like defining new nested forms inside interaction affordances (which is a clever solution, but is going to create a real mess over time) I'd advocate simply skipping to 2.0! The charter doesn't say "Web of Things (WoT) Thing Description 1.1", or anything about backwards compatibility, it just says "Web of Things (WoT) Thing Description (Update)". |
I am not against it, as I said this approach is scalable and well integrated with the current model. It was just a warning to not be too permissive 😅 . I think it would be weird to discuss an operation that would be easily implemented using the existing mechanisms. We might need to describe a rationale that rules the new operation introduction.
Yeah, I was referring to the JSON schema. Naively I thought that it would follow the normative sections, but as the text is more permissive I think we have no problem then!
Yep, the nested object was more like a temporary solution to be changed in 2.0, still it might be used as a means to the describe the log dataschema as @egekorkan is pointing out. About Challenge 2: About challenge 3: If we want to be declarative as in other places, this would imply to resume the idea of the nested object to describe the inputs. Would the Following the declarative path, I would go with the definition of semantic tags (e.g., |
Just on this point, backwards compatibility is very poorly defined, you can even ask the TD Task Force members and you will get different answers. If a TD that used to validate now does not, it is serious compatibility issue. However, a new TD does not have to be consumed by an old consumer without any problems. The Regarding adding more and more operations, I think it is inevitable. Scripting API will have to cover those as well. Most programming APIs keep adding new functions/methods as well so maybe it will be seen badly. However, this should spice up the profile discussions ;) |
@relu91 wrote:
I agree.
Actually the spec does define this: "The data schema for each of these meta-interactions is constructed by combining the data schemas of each PropertyAffordance instance in a single ObjectSchema instance, where the properties Map of the ObjectSchema instance contains each data schema of the PropertyAffordances identified by the name of the corresponding PropertyAffordances instance." In my example I applied the same rule to
I like the approach being proposed for the Directory Service API in w3c/wot-discovery#130 (rendered here) which is based on the Linked Data Platform Paging specification using Link and Content-Range headers in responses. The actual payload just ends up being a JSON array because all the pagination metadata is contained in HTTP headers. It might be possible to describe this in a Thing Description but it's a bit of a push, plus it's HTTP specific and I'm not sure whether other protocols have an equivalent. "forms": [{
"op": "readpastevents",
"href": "https://foo.webthings.io/things/lamp/events/overheated?offset={offset}&limit={limit}",
"htv:methodName": "GET",
"response": {
"htv:headers": [
{
"htv:fieldName": "Link"
},
{
"htv:fieldName": "Content-Range"
}
],
},
"uriVariables": {
"offset": {
"type": "number",
"default": 0
},
"limit": {
"type": "number",
"default": 10
}
}
}] The above follows the WIP example from the Directory Service API, but I don't think it's complete because I think the htv:headers also needs to specify the meaning of the content of the Link and Content-Range headers (see https://www.w3.org/TR/wot-binding-templates/#example-21-http-vocabulary-example-for-header-options for how you describe the content of headers). |
To follow up on this, for the time being I think we've concluded not to try to describe the historical event log WebThings exposes in the Thing Description and to instead implement new Server-Sent Events endpoints which are described in the Thing Description instead. This will most likely follow the events API proposed for the WoT Core Profile in w3c/wot-profile#92 |
from today's TD call:
|
I am looking at implementing Events for a TD-providing project with HTTP bindings. The event affoardance seem to cover subscribing to a stream of events, but is there any mechanism for requesting an array of past events?
For context, I have a project that originally started following the Mozilla WOT spec, and I am now adapting it to cover the W3C TD spec instead. Mozilla specifies the event endpoint as an array of past events with timestamps, and this is something I would like to be able to model with a TD.
The text was updated successfully, but these errors were encountered: