How to make a campaign of simulation?

A campaign of simulation usually involve making a variable change through several run or several nodes.

Makesense leverages Jinja2 to create very easily as many firmware as necessary by using templating. Instead of writing a C code directly we will write C code replacing the variables and function by templates variables that jinja2 will remplace by variables existing in a python code. By doing so we can for instance create very easily a loop iterating through a list of desired values for a variable and let makesense handle all the trouble of creating those files, compile them and deploy them on a testbed.

Step 1: Creating a C template

First we will create a simple C code that print a message through a loop.

#include <stdio.h>
#include "contiki.h"

static int my_value = {{ my_value }};

/*---------------------------------------------------------------------------*/
PROCESS(dummy_hello_world_process, "Hello world process");
AUTOSTART_PROCESSES(&dummy_hello_world_process);
/*---------------------------------------------------------------------------*/
PROCESS_THREAD(dummy_hello_world_process, ev, data)
{
  PROCESS_BEGIN();

  while(1) {
    printf("Hello, world. My value is %d\n", my_value);
  }

  PROCESS_END();
}
/*---------------------------------------------------------------------------*/
static int my_value = {{ my_value }};

It’s at this place that jinja2 will put the my_value variable. For more information check out the Jinja2 documentation.

This makefile will compile all the nodes.

SRC=$(wildcard [!symbols]*.c)
PROGS = $(patsubst %.c,%,$(SRC))
all: $(PROGS)

CONTIKI={{ contiki }}
TARGET={{ target }}

include $(CONTIKI)/Makefile.include

# vi:filetype=make:ts=4:sw=4:et

Step 2: Let’s loop

Suppose that you want to create firmware for 42 different values we would do it in the fabfile:


import os
import json
from os.path import join as pj

from fabric.api import task
from jinja2 import Environment, FileSystemLoader

# Default values
ROOT_DIR = os.path.dirname(__file__)
EXPERIMENT_FOLDER = pj(ROOT_DIR, "experiments")
TEMPLATE_FOLDER = pj(ROOT_DIR, "templates")
TEMPLATE_ENV = Environment(loader=FileSystemLoader(TEMPLATE_FOLDER))

@task
def my_special_function(name):
    """ This function will create 42 C files. """
    path = pj(EXPERIMENT_FOLDER, name)
    if not os.path.exists(path):
        os.makedirs(path)

    c_template = TEMPLATE_ENV.get_template("dummy_template.c")
    # We make the id start at 1 and finish at 42
    for value in range(1, 43):
        with open(pj(path, "dummy_%d.c" % value), "w") as f:
            f.write(c_template.render(my_value=value))

    # If you change the platform target and want to push to iotlab
    # don't forget to update the nodes names
    makefile_template = TEMPLATE_ENV.get_template("dummy_makefile")
    with open(pj(path, "Makefile"), "w") as f:
        f.write(makefile_template.render(contiki=CONTIKI_FOLDER,
                                             target="iotlab-m3"))

    config_template = TEMPLATE_ENV.get_template("dummy_iotlab.json")
    res = [
        {"nodes": ["m3-%d.grenoble.iot-lab.info" % num],
         "firmware_path": pj(path, "dummy_%d.iotlab-m3" % num)
       } for num in range(1, 43)]
    with open(pj(path, "iotlab.json"), "w") as f:
        f.write(json.dumps(res, sort_keys=True,
                  indent=4, separators=(',', ': ')))

Then we would call this function like any other fabric function

fab my_special_function:dummy

You should have files like:

  • dummy_1.iotlab-m3
  • ...
  • dummy_42.iotlab-m3

created in experiments/dummy

You should also have an iotlab.json looking like:

[
    {
        "firmware_path": "/home/sieben/Dropbox/workspace/makesense/experiments/prout/dummy_1.iotlab-m3",
        "nodes": [
            "m3-1.grenoble.iot-lab.info"
        ]
    },
    ...
]

Step 3: Push to iotlab

Then we simply have to push to iotlab:

fab push_iotlab:dummy