Understanding Templates

Templates are at the heart of IoTIFY's Network & Database simulator. Let's have a closer look at them.

Introduction

A template is a javascript document which describes how your virtual device will behave during simulation. Every aspect of device behavior which needs to be modelled should be described in the template.

When a network simulation is started, the simulator will process template and create virtual devices. Each virtual device will then independently behave and connect to the IoT cloud platform, until your simulation is running.

If you are familiar with C++ or any other object oriented design, think of templates as Classes and virtual devices as Objects.

The template contains information about messages to be sent to the cloud platform and connectivity. When you are writing a template, you will need to define the body of a javascript function. The function must return a string or a Javascript buffer object, which is to be sent to your IoT cloud platform upon each iteration. The template function is invoked with a special variable called state. Let’s discuss this in further detail.

Anatomy of a template

A template describes how a single device behaves during the simulation. The basic structure of a template editor could be described as follows.

In this example we are specifying a MQTT template. Following is the description of the key elements of the template UI

Protocol Specifier: A template can only be created for a single protocol. This field specifies which protocol the template is implementing. If you want to change the protocol, always start from a new template as settings of one protocol will not work with other protocol.

Server Settings: Depending upon the connectivity protocol specified, you will need to provide the settings to connect to the server. These settings will largely depend upon the cloud platform you are connecting to. Important thing to
note here that the settings could be made dynamic with a handlebar pattern {{ }}.

E.g. instead of specifying your HTTP API path to

/api/post?query=1&val=2

You could use

/api/post?{{state.queryparams}}

and modify the query parameters dynamically by changing state.queryparams in your message function as follows

{
// dynamically build a query
state.queryparams = "query="+ chance.integer() + "&val="+ chance.normal();
return JSON.stringify(state);
}

Simulation logic

Once you have specified the server connectiviy, its time to specify the logic of your device simulation. The simulation logic is entirely dependent upon the complexity of the device and scenario. However the entire logic needs to be specified in in 3 main functions as follows:

Device Initialization (Optional)

In the function, you will need to setup device specific parameters. An example could be to generate a unique MAC address which is used as a device identity. You could also use the setup function to provision your device and perform any one time enrollment of credentials.

A most common use of this function is to populate your state object with default values which will be used subsequently in the simulation.

Message Function (Mandatory)

The messaging function represents the primary behavior of the device and should return the payload which needs to be sent to the cloud platform.

This function is called with state object which serves as a temporary memory or scratchpad for the individual device. It is important to note that state object is not shared across the devices, so each simulated device has its own copy of the state object.

The majority of the logic of device simulation will be implemented inside the message function, so pay special attention to it.

Teardown Function (Optional)

Similar to initialization function, teardown function will be invoked exactly once at the end of the simulation. The function will be passed state object, so it could be used to consolidate the test results, post them into an external database or decommission the simulated device etc.

Lifecycle of the simulation

We learnt that a template specifies how a device behaves during the simulation. Based on the information provided in the above three functions, here is how the actual simulation is executed for each device:-

The setup functions will be invoked exactly once in the begining of the simulation. Afterwards, the message function will be invoked for every iteration. The output of the message function will be sent to your cloud platform. After all the iterations are finished, the teardown function will be invoked exactly once.

Above diagram specifies lifecycle of only a single device. If there are more than one device in the simulation, every one of them will have its own lifecycle and will behave independently of others.

State variable

Every IoT device has several important parameters to be reported to the cloud platform. An example could be a temperature value read through a sensor or the current CPU load. The device has also some internal parameters such as avaiable SSD and memory usage, which are required to be tracked. While modelling the device, you will need a local storage to save these values and any other intermediate variable which needs to be preserved across the simulation. state is a JSON object which can be used for this purpose. E.g. consider following code snippet of a simple template:

{
// if temperature has not been defined previously
if (state['temperature'] === undefined ) {
state['temperature'] = 50;
}
state['temperature']++;
return JSON.stringify(state);
}

here, in the very first iteration, the state object will be empty. So we will initialize the temperature value with 50. Since state is preserved across simulation, upon each subsequent call of the template, the temperature variable will increment by 1.

Getting current iteration via index()

When the template is running, it might be required to do some actions based on a specific iteration, e.g. for every 10th call, you would like to send a special message. Simply call index() function to find out which iteration you are into. The iteration starts from 0 and continues upto the number of iterations you have specified in simulate tab.

{
if (index() % 10 == 0 ) {
return "Special Message"
}
return "Normal message";
}

Know your client ID

If you are simulating more than 1 clients, you could find the current client ID in the template by accessing the variable _meta.clientId. This is an integer starting from 0 and going upto the maximum number of clients specified. Make sure you don’t override the value, though.

{
if (_meta.clientId === 0 ) {
return "I am the leader"
}
return "We are the followers";
}

Returning contents

It is important that at the end of processing, function body must return a value to be sent to the cloud platform. The return value could either be a string or a binary Buffer, depending upon what values are accepted by your cloud platform provider. If you would like to skip sending anything for that particular iteration, simply return ‘undefined’ as a string.

{
if ( index() % 2 == 0 ) {
return 'undefined';
}
else {
return 'hello world';
}
}

In the above example, every alternate invocation will be skipped and every other iteration will return ‘hello world’ to the cloud platform.

Helper libraries available in Templates

You could write and use most of the native Javascript functions in the template. There are many additional helper functions available in the scope of the template body. Let’s have a short look at them.

Chancejs

Chancejs is a random content generation library which is quite useful to generate data. E.g. to generate a random street addresss

state['street'] = chance.address();
state['city'] = chance.city();
state['zip'] = chance.zip();
return JSON.stringify(state);

And voila, you have a perfectly looking fake street address.

Momentjs

Momentjs is another super useful library, especially to manipulate time. In below example, we will calculate the elapsed time since the simulation started (iteration 0)

{
if ( state['start'] === undefined ) {
state['start'] = moment.now();
}
state['elapsed'] = moment(state['start']).fromNow();
return JSON.stringify(state);
}

UnderscoreJS

UnderscoreJS is another popular library which is a must have for serious javascript programmer. Here is an example of filtering even values in an array

{
var evens = _.filter([1, 2, 3, 4, 5, 6], function(num){
return num % 2 == 0; }
);
return JSON.stringify(evens);
}

deasync JS

Deasync could be used to create synchronous behavior out of async or callback based function. Though async functions should be used as much as possible, in some cases, deasync could make life easier. E.g. in the code snippet below, we are waiting for the callback function to finish while calling setTimeout() async function.

{
var ret;
setTimeout(function(){
ret = "hello";
},3000);
while(ret === undefined) {
deasync.sleep(100);
}
}

JSON web tokens

JSON web tokens are gaining popularity as an authorization mechanism. IOTIFY template now supports creating JWT by integrating jsonwebtoken NPM library JSON Web Token

{
// You could use {{state.token}} in the HTTP auth header
// The function is evaluated before the headers.
state.token = jwt.sign({ foo: 'bar' }, 'shhhhh');
}

Note that use of deasync function slows down the excution significantly.

Native builtin object

There are several built in module available in the function body. Math is one of them which is quite useful for arithmatic.

We have written several helper libraries which can be used in the template. Check them out in the next section.