The following document describes some possible scenarios of the interactions between WoT Scripts and the underlying runtime. In particular, it focuses on how scripts will use interaction affordances to communicate with a remote Consumed Thing.
The use cases are divided into three categories: high, middle, and low-level interactions. The different levels express a different level of control about data transmission/acquisition/translation. At the lowest level the application has the complete control of the data packets transmitted/acquired over the network, wheres at the highest level most of these details are hidden by the implementation.
note: Currently, security use cases are ignored
In this use case the application does not have any particular requirements about how the data is retrieved or transmitted. Therefore, it seeks the easiest way to obtain/communicate the desired information. Those scenarios can be further dived in to two subcategories: when the interaction affordance has a DataSchema and when it has not.
The application interacts with the CosumeThing through plain javascript objects that follow the DataSchema. For example read operation of a property affordance:
/*
* Temperature Range affordance:
* {...
* "temperatureRange":
* {
* "description" : "Shows the operational range of this device",
* "type": "object",
* "properties": {
* "min": { "type": "number" },
* "max": { "type": "number" }
* },
* "forms": [
* {
* "op": "readproperty",
* "href": "http://mylamp.example.com/status",
* }
* ]
* }
*... }
*/
assert(td.properties.temperatureRangere.type!= undefined)
const response = await thing.readProperty(“temperatureRange”));
const range = response.value
// range {min: 15, max: 20} or write operation in other property affordance:
/*
* Temperature Range affordance (extended):
* {...
* "temperatureRange":
* {
* "description" : "The operational range of this device",
* "type": "object",
* "properties": {
* "min": { "type": "number" },
* "max": { "type": "number" }
* },
* "forms": [
* {
* "op": "readproperty",
* "href": "http://mylamp.example.com/status",
* },
* {
* "op": "writeproperty",
* "href": "http://mylamp.example.com/status",
* }
* ]
* }
*... }
*/
const range = {min: 20, max: 50}
const response = await thing.writeProperty(“writeproperty”,range));
In this cases the runtime is required to serialize/deserilaze javascript object to the target content-type and enconding. Moreover, if the data is split in multiple data packets the runtime should buffer the data until completition.
Following JSONschema, strings may be used as a way to trasmit/recive raw byte data. For example, a property may present this form and data schema:
{
"lastPicture": {
"description" : "the last picture of a collection",
"type": "string",
"contentType": "image/png",
"contentCoding": "base64"
"forms": [
{
"op": "readproperty",
"href": "coaps://mylamp.example.com/lastPicture",
"cov:methodName" : "GET",
"contentType": "application/json"
},
{
"op": "writeproperty",
"href": "http://mylamp.example.com/lastPicture",
"htv:methodName" : "PUT",
"contentType": "application/json"
}
]
}}Consequently, a Script can access image data using high level interaction operations:
assert(td.properties.temperatureRangere.type!= undefined)
const response = await thing.readProperty(“lastPicture”));
const rawImage = response.value
// rawImage "gDNWBz1SEQew4aRp+Zv4Ka6Im+EwViTsnt2orjXQ6w5Zp/hefSNSJUHXa1yepHmEDvk=....." On the other hand a writing operation might be:
assert(td.properties.temperatureRangere.type!= undefined)
const rawImage = "gDNWBz1SEQew4aRp+Zv4Ka6Im+EwViTsnt2orjXQ6w5Zp/hefSNSJUHXa1yepHmEDvk=....."
const response = await thing.writeProperty(“lastPicture”,rawImage));In contrast to the normal flow, the servient runtime should not decode the content and pass it unaltered to the application. Those scenarios, are pretty common inside web applications where images can be displayed using their base64 encoding.
When the affordance has not data schema provided the application might still seek an easy way to access the data. This is the case when there are no strict memory or computational constraints, such as when the application runs on a browser. Another case is when the data can be read as a whole because it is not Live Streaming Data. Consequently, the application can read/write the value using a DataView object:
/*
* takePicture affordance form:
* "form": {
* "op": "invokeaction",
* "href" : "http://camera.example.com:5683/takePicture",
* "response": {
* "contentType": "image/jpeg",
* "contentCoding": "gzip"
* }
*}
* See https://www.w3.org/TR/wot-thing-description/#example-23
*/
const response = await thing.invokeAction(“takePicture”));
const image = response.blob
// image: DataView [0x1 0x2 0x3 0x5 0x15 0x23 ...]In this case the runtime is supposed to deconding/enconding and expose/read data using a DataView object.
Some no text-based protocols might send raw bytes instead of base64 encodings. They could choose to do so for memory or computational constraints.
Sometimes applications might have more knowledge about the network infrastructure or data. Therefore they give hints to the underlying runtime.
- Choose which form to use
- Prefer one
hrefover the other - Prefer a particular
contentType
- Prefer one
- Choose which language to use
- Request data to be presented using another dataschema (?)
Use the interactionOption object.
Clients may require fine control over the data communication process. These use cases can be trivial like displaying a progress bar about the competition of a write operation, or complex when clients may prefer streaming because of their limited memory space. Another complex use case is when data sources are intrinsically in a streaming fashion such as live video transmitted over HLS or RSTP.
Therefore, those client may use the low level interface to interact with remote Things. This interface allows scripts to use ReadableStream objects as a way to comunicate with the remote servient.
For example, this script will display a percentage of the completation of a write operation.
/*
* random property form:
* "form": {
* "op": "writeproperty",
* "href" : "coap://example.com:5683/random",
* "contentType":"application/octect-stream"
* }
*/
const stream = new ReadableStream({
count : 0,
start(controller){},
pull(controller){
const value = Math.random()
this.count++
console.log(this.count * 10, "%")
controller.enqueue(value)
if(this.count >= 10){
controller.close()
}
}
})
const response = await thing.writeProperty(“randomData”,stream));
console.log("done!")
/*
* console output:
* 10%
* 20%
* 30%
* ...
* 100%
* done!
*/Notice: the completation percentage does not really reflect the amout of data sent over the network. Different protocols may decide to buffer the data before sending it. However, it still gives an hint about the process completition.
Another example is the following, where a client reads a live video stream from a device.
{
"video": {
"description" : "the video stream of this camera",
"forms": [
{
"op": "readproperty",
"href": "http://camera.example.com/live",
"subprotocol": "hls"
"contentType": "video/mp4"
}
]
}}reader.read().then(function processVideo({ done, value }) {
if (done) {
console.log("live video stoped");
return;
}
const decoded = decode(value)
UI.show(decoded)
// Read some more, and call this function again
return reader.read().then(processText);
});Finally, in the next code snippet a client reads a JSON document using the streaming interface instead of using high-level API. One reason because of the client chooses this level of interaction is a low memory available which may not fit the JSON document.
/*
* EventHistory property
* {...
* "eventHistory":
* {
* "description" : "A long list of the events recorderd by this thing",
* "type": "array",
* "forms": [
* {
* "op": "readproperty",
* "href": "http://recorder.example.com/eventHistory",
* }
* ]
* }
*... }
*/
// Example of streaming processing: counting json objects
let objectCounter = 0
const parser = new Parser() //User library for json streaming parsing (i.e. https://github.com/uhop/stream-json/wiki/Parser)
parser.on('data', data => data.name === 'startObject' && ++objectCounter);
parser.on('end', () => console.log(`Found ${objectCounter} objects.`));
const response = await thing.readProperty(“eventHistory”)
response.data.pipeTo(parser)