Two main functionalities of node-wot is to creating a WoT Thing and interacting with another WoT Thing. These can be also combined to have a Thing interacts with other Things.
Creating a WoT Thing is called exposing a Thing. Exposing a Thing creates a Thing Description that can be used to by others to interact with this Thing.
- Exposing a Thing:
let thing = WoT.produce({
name: "counter",
description: "counter example Thing"
});
//any other code to develop the Thing
thing.expose();
Here, an object named thing is produced. At this stage, it has only a name and a description for humans to read. 'thing.expose();' exposes/starts the exposed Thing in order to process external requests. This also creates a Thing Description that describes the interfaces of the thing Thing.
- Add a Property definition to the Thing.
Properties expose internal state of a Thing that can be directly accessed (get) and optionally manipulated (set).
thing.addProperty(
"counter",
{
type: "integer",
description: "current counter value",
observable: false,
writeable: true
},
123);
This creates a Property and initializes it with the value 123
. This value can be read by other Things.
You can create a Property that has a more complex type, such as an object. This is shown in the following:
thing.addProperty(
"color",
{
type: "object",
properties: {
r: { type: "integer", minimum: 0, maximum: 255 },
g: { type: "integer", minimum: 0, maximum: 255 },
b: { type: "integer", minimum: 0, maximum: 255 },
},
writable: true
},
{ r: 0, g: 0, b: 0 }
)
- Add a Property read handler
thing.setPropertyReadHandler(
"counter",
(propertyName) => {
console.log("Handling read request for " + propertyName);
return new Promise((resolve, reject) => {
resolve(Math.random(100));
})
});
You can specify if the Thing needs to something in case of a property read. Here, instead of reading a static value, a random value is generated just for this read case.
- Add a Property write handler
thing.setPropertyWriteHandler(
"brightness",
(value : any) => {
return new Promise((resolve, reject) => {
setBrightness(value);
resolve(value);
});
});
You can specify if the Thing needs to something in case of a property write. Here, the value written is used to set the brightness of an LED that requires a specific function (setBrightness()
)to do that.
- Add an Action definition to the Thing.
Actions offer functions of the Thing. These functions may manipulate the interal state of a Thing in a way that is not possible through setting Properties. Examples are changing internal state that is not exposed as Property, changing multiple Properties, changing Properties over time or with a process that shall not be disclosed. Actions may also be pure functions, that is, they do not use any internal state at all, e.g., for processing input data and returning the result directly. You can add an Action like in the following:
thing.addAction("increment");
Or you can specify what input
data it needs to be executed or what output
data it will respond with after the Action has been completed. In the following, an input data is specified:
thing.addAction(
"gradient",
{
input: { //here you can put output to specify output data
type: "array",
items: {
type: "object",
properties: {
r: { type: "integer", minimum: 0, maximum: 255 },
g: { type: "integer", minimum: 0, maximum: 255 },
b: { type: "integer", minimum: 0, maximum: 255 },
}
},
"minItems": 2
}
});
- Add an Action invoke handler
You need to write what will happen if an Action is invoked. This is done by setting an Action Handler:
thing.setActionHandler(
"increment",
() => {
console.log("Incrementing");
return thing.properties["counter"].get().then( (count) => {
let value = count + 1;
thing.properties["counter"].set(value);
});
}
);
Here, you see also how to access the properties of a Thing you are creating.
- Add an Event definition to the Thing.
The Event Interaction Pattern describes event sources that asynchronously push messages. Here not state, but state transitions (events) are communicated (e.g., "clicked"). Events may be triggered by internal state changes that are not exposed as Properties. Events usually follow strong consistency, where messages need to be queued to ensure eventual delivery of all occured events.
In the following, we add an Event onchange
:
thing.addEvent(
"onchange",
{
type: "number"
});
- Emit Event, i.e. notify all listeners subscribed to that Event.
setInterval( async () => {
++counter;
thing.events.onchange.emit(counter);
}, 5000);
Here the event is triggered in regular intervals but emitting an event can be done based on other internal state changes.
Interacting with another WoT Thing is called consuming a Thing and works by using its Thing Description.
- Fetch a Thing Description of a Thing given its URL.
WoT.fetch("http://localhost:8080/counter").then( async (td) => {
// Do something with the TD
}
- Consume a TD of a Thing, including parsing the TD and generating the protocol bindings in order to access lower level functionality.
WoT.fetch("http://localhost:8080/counter").then( async (td) => {
let thing = WoT.consume(td);
//do something with the consumed Thing
});
- On a consumed Thing
You can access all the interactions this Thing has and interact with them.
- Read the value of a Property or set of properties.
You can read the property values with the get
method. It is an asynchronous function that will take some time to complete. So you should handle it explicitely. Here we use the await functionality of node.js.
let read1 = await thing.properties.count.get();
console.info("count value is", read1);
- Set the value of a Property or a set of properties.
You can write to a property by using the set method.
thing.properties.color.set({ r: 255, g: 255, b: 0 } );
- Invoke an Action.
You can invoke an action by using the run
method. It is an asynchronous function that will take some time to complete. So you should handle it explicitely. Here we use the await functionality of node.js.
await thing.actions.increment.run();