Skip to content

Instantly share code, notes, and snippets.

@relu91
Last active May 25, 2020 09:23
Show Gist options
  • Save relu91/05928793fbed8e95b1db0035c9038bf7 to your computer and use it in GitHub Desktop.
Save relu91/05928793fbed8e95b1db0035c9038bf7 to your computer and use it in GitHub Desktop.
A curated list of use cases and examples about scripting api

WoT scripting API use cases

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

High level interactions [DRAFT]

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.

Interaction with DataSchema

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.

Strings

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.

No data schema

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.

Why do not use strings?

Some no text-based protocols might send raw bytes instead of base64 encodings. They could choose to do so for memory or computational constraints.

Middle level interactions [EARLY-DRAFT]

Sometimes applications might have more knowledge about the network infrastructure or data. Therefore they give hints to the underlying runtime.

  1. Choose which form to use
    • Prefer one href over the other
    • Prefer a particular contentType
  2. Choose which language to use
  3. Request data to be presented using another dataschema (?)

Use the interactionOption object.

Low level interactions [Draft]

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)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment