In this document, we aim to introduce how to use the node-wot libraries to implement an Exposed Thing. Differently from the Thingweb Hands-on, here we will show to use TypeScript along with node-wot as an npm dependency rather than cloning a GitHub repository. The aim is to establish an easy-to-understand and reusable template that can be used to implement any Exposed Thing. Some devices that use this template can be found here.
Using TypeScript gives type safety and the separation of source and build directories. Using node-wot as an npm dependency allows faster installation and also being able to install only what is needed for the given project. For example, this way one can install only the CoAP binding if the device will only communicate with CoAP.
The source files can be found at the examples folder of node-wot. You should modify this code to match your needs.
The structure can be seen in two parts:
index.js
that is run with npm run start
where the different protocol bindings and parameters such as a TD directory address is added. In this file one would expect to see no device specific libraries or logic. For example, if you are moving a robot from left to right, a handler for this action should not be present. Once the index.js
is set up, there should not be any need to edit this file, the work should continue in the src
folder.src
folder is where the TypeScript sources are found. Upon running npm run build
the contents of this folder will be transcompiled into JavaScript and will be found in dist
folder. The TypeScript file src/base.ts
is where the actual logic of the Exposed Thing is. In this file, we add the TD, different interaction affordances, handlers and TD registration.We can thus summarize it like:
index.js
: Starting point of the Exposed Thing, analogous to index.html
of websites. This is also common practice for npm packages.src
folder: Logic of the Exposed Thing in TypeScript source formatdist
folder: Transcompiled logic of the Exposed Thing in JavaScript source format that is used by index.js
package.json
: npm dependencies of the Exposed Thing project. Here you will find the different protocol bindings.Here, we will explain what every (or most!) of the lines do. If you just want to use it, skip to What to change and get running.
wot-typescript-definitions
is needed in order for TypeScript to understand what is WoT
, ExposedThing
etc.{
...
"devDependencies": {
"typescript": "3.3.1",
"wot-typescript-definitions": "0.8.0-SNAPSHOT.22", //update this accordingly
"@types/node": "11.9.4",
"tslint": "5.12.1"
}
...
}
@node-wot/core
is always needed so that functions like produce()
, start()
are available.
We also use the request library for registration of the TD to a directory via HTTP and the ajv for validation of inputs with the DataSchema of the TD.
Below is an example with the HTTP binding, but you can change it to CoAP or MQTT or anything else that node-wot supports.{
...
"dependencies": {
"@node-wot/binding-http": "0.8.0", //update this accordingly
"@node-wot/core": "0.8.0", //update this accordingly
"request": "2.88.0",
"ajv": "^6.10.2"
}
...
}
scripts (lines 25-30): These are the scripts that are used to build, run and clean the repository.
Other than the clean
script, they are cross-platform, i.e. can run on Linux, macOS or Windows.
others: You can also change your package name, description, author, license etc. and adapt to your needs.
index.js
is the entry point of the script. Here we create and add the necessary servers such as HTTP, CoAP or MQTT to the servient and then start it.
Upon start, a WoT object will be created and passed to the base.ts
(or base.js
when transcompiled) where the actual device implementation is.
WotDevice = require("./dist/base.js").WotDevice
(line 2) links to the actual device implementation. In case you change base.ts
with another name, you should adapt this line.const TD_DIRECTORY = ""
(line 9) specifies the TD directory URI. You can comment it out to remove the registration function.Servient = require("@node-wot/core").Servient
(line 12) and var servient = new Servient();
(line 25) are responsible for creating a servient. Then different servers (bindings) can be added before starting it. A servient can even contain multiple exposed things.package.json
file should be also changed and npm install
should be run to ensure that the dependencies are installed. A change on the bindings should not cause any change in the base.ts
file.WoTDevice
created in the second line. You can add more than one WoTDevice
(could be called anything). node-wot puts multiple ExposedThings under one servient differentiating them by their names. Thus, one can have http://localhost:8080/counter/
and http://localhost:8080/mywotdevice/
served from the servient.This is where the logic of the ExposedThing is implemented and its TD can be seen.
produce()
which are used with a servient. As it can be seen, in base.ts
there is no link to a servient that is created in the index.js
file and used to pass a WoT object. Similar to the CLI, this file relies on the WoT object which is defined using the wot-typescript-definitions
in line 15.TD_DIRECTORY
is not defined in index.js
this method will not be executed.thing
, deviceWoT
and td
. thing
is instantiated as a result of the produce()
method in lines 88-91. td
is available from an ExposedThing using the getThingDescription()
method. To avoid multiple calls to this method, we save it as the td
attribute. deviceWoT
attribute is assigned from the WoT
argument passed in the constructor is used for instantiating everything. Once the constructor gets an ExposedThing, methods for initializing different interaction affordances are called, such as initializeProperties()
.WoT.produce()
takes as TD as argument and returns an ExposedThing. In this script, we use a TD with 1 property, 1 action and 1 event in order to demonstrate scripting for all interaction affordances. They are all named myX
with X being an Interaction Affordance like property, action or event."myProperty"
, we add a corresponding private member myProperty
initializeProperties()
method (line 164): This method exists in order to cleanly separate where the property handlers are created versus where they are assigned to properties. In this method, we define the properties of the WotDevice
as class members, set their initial values and assign their corresponding read and write handlers.myProperty
the initial value ""
.readPropertyHandler
by assigning the corresponding myPropertyReadHandler
, defined in line 124, as its callback.setPropertyWriteHandler
and assign the myPropertyWriteHandler
method as its callbackinitilizeActions()
method (line 171): This method exists in order to cleanly separate where the action handlers are created versus where they are assigned to actions. For an action with input data, the input validation would also happen here. By assigning to invoke handler to this.myActionHandler
, we call a specific action invoke handler that can be easily encapsulated, allowing easy reusability.myActionHandler
method for an example)myPropertyReadHandler()
(line 124): This is read-handler that can be called from any setPropertyReadHandler
. The logic of reading, like reading a GPIO pin, should happen here. The value of the property will be then returned in return this.myProperty
. Use of async-await allows easier programming of asynchronous operations happening inside a property handler.myPropertyWriteHandler()
(line 129): This is write-handler that can be called from any setPropertyWriteHandler
. The logic of writing, like assigning the input data to the corresponding variable, should happen here. This is where you also should call the emitPropertyChange
method to signal that there is a change to your property if needed. Use of async-await allows easier programming of asynchronous operations happening inside a property handler.myActionHandler()
(line 134): This is action invoke handler that can be called from any setActionHandler
. The logic of the action handling, like rotating a robot arm, should happen here. The result of the action will be then returned in return outputData
. Note that there should be no outputData
returned if the action has no output
member. Also, we demonstrate how to emit an event in line 148. Use of async-await allows easier programming of asynchronous operations happening inside an action handler.If you don’t need to understand everything in the code, just make sure you do the following at least before installing, building and running:
package.json
:package.json
to include the bindings you want, e.g. add "@node-wot/binding-coap": "0.8.0",
to dependencies.name
, version
etc.index.js
:index.js
in 3 locations:CoapServer = require("@node-wot/binding-coap").CoapServer
(line 15)var coapServer = new CoapServer({port: 5683});
(line 20)servient.addServer(coapServer);
(line 28)index.js
(line 9)base.ts
:this.WoT.produce()
method in line 28listenToMyEvent()
in line 162 if neededinitializeProperties
of line 171:initializeActions()
of line 178:ajv
for input validation.# Using Ubuntu
curl -fsSL https://deb.nodesource.com/setup_17.x | sudo -E bash -
sudo apt-get install -y nodejs
You will need to change 10.x to a newer version if needed
npm install
npm run build
npm run start
Version 0.8.X handles properties and interaction inputs differently than previous versions. Below is the list of changes:
WoT.InteractionInput
objects have a member function value()
that returns a promise that will resolve to the value of the input when available. You can either use input.value().then(...)
or await input.value()
in an async
function.Resulting from the discussions in the Scripting API, the API has changed on 28.10.2019. This was a rather big change that also resulted changes in node-wot. Below is a list of changes:
produce()
method. Before only non interaction information were passed.myExposedThing.td
and have to pass through myExposedThing.getThingDescription()
WoTFactory
is replaced by WoT
in wot-typescript-definitions
. You thus see WoT.produce()
instead of thingFactory.produce()
produce()
method, you don’t need addProperty
, addAction
and addEvent
.this.thing.events[myEvent].emit(myPayload);
there is this.thing.emitEvent(myEvent,myPayload);
setActionHandler
whereas before you could assign the handler in the addAction
like addAction(name,metadata,handler)
.addProperty
, you have to do a writeProperty(myInitialValue)
at some point.thing.id="thingweb:io:example"
.You can see a git diff here