The IoT-LAB experimental platform allows users to conduct remote experiments on wireless sensor board (such as an Arduino board with an Xbee module). For this purpose,a board called open-node, can be connected to the IoT-LAB gateway, via the usb port. Below is a picture of an open-node connected to a gateway.
On the linux distribution installed on the gateway, runs a Python module providing a REST API for open-node management. The gateway can perform open-node commands like flashing a firmware or switch on his power supply. This document shows how to integrate a new open-node in the Iot-Lab platform. This integration is based on a plugin system provided by the Python module.
Your node must be powered by USB. You must have developed at least two firmwares for your node: an autotest firmware and an idle firmware (described below).
You can get the Python module code by cloning the git repository:
$ git clone git@github.com:iot-lab/iot-lab-gateway.git
You will need to implement:
- firmwares
- node_ Python class
- udev rules
This section present the module architecture.
|
+-gateway_code/
| |
| +-static/..... Contains the firmwares and the configuration file
| | |
| | +-----m3_idle.elf
| | +-----m3_autotest.elf
| | +-----[...]
| |
| +-open_nodes/... Contains the code to interact with the open-node, you will put your code here
| |
| +-----node_m3.py
| +-----[...]
|
+-bin/
|
+-rules.d .................. Directory for the udev rules
+-----m3.rules
+-----[...]
Between two plugs, the device name as detected by the
embedded linux running on the gateway can change: for example,
an unique device can be successively detected as ttyUSB0 and then
as ttyUSB1. As it is essential to have a fixed name for your device,
you have to write a udev rules specific for your device. We already
developed rules for connecting to widely used microcontroller interfaces:
J-LINK, STLINK and CMSIS-DAP. If your board uses one of these, then you
don't need to write a .rules
file in bin/rules.d
. Otherwise, you
will have to write a udev rule file.
To do this, you must create a file (e.g. named your_node.rules
with your_node
the name of your node). Using the command udevadm
, you have to
retrieve some information to identify your node such as the serial
id, the vendor id or the product id. You must set a name for the
device and set the right group (dialout) and the right mode (664).
The usual convention is to name device ttyON_NODENAME
.
Below is the example of the udev rule for the M3 node:
SUBSYSTEM=="tty", SUBSYSTEMS=="usb", ENV{ID_SERIAL}=="IoT-LAB_M3",
ENV{ID_USB_INTERFACE_NUM}=="01", SYMLINK+="iotlab/ttyON_M3"
SUBSYSTEM=="usb", ATTR{idProduct}=="6011", ATTR{idVendor}=="0403",
MODE="0664", GROUP="dialout"
As said previously, the application is based on a system of plugin: if you want to add a node, you just have to write a file to interface with the application in the correct place to be loaded by the plugin system.
When the application is launched, it reads the name of your open node in the file
/var/local/config/board_type
and then will use the class that implements that type. Usually this class is in the python file
gateway_code/open_node/node_{nameofyournode}.py
but the node_
naming is not mandatory, it just needs to be imported at some point.
In order to write plugin code for an open node, you just need to subclass
OpenNodeBase
located in gateway_code.nodes
and add a TYPE attribute in it:
from gateway_code.nodes import OpenNodeBase
class Customnode(OpenNodeBase):
TYPE = 'custom_node'
and put that file in gateway_code/open_nodes
There are methods that are mandatory to implement (enforced through abc.abstractmethod) so that your class can be instantiated to control a node, you can look at doc/node_example.py for an open node example.
For example, if stm32nucleo
is registered on the gateway,
the class used by the gateway will be
from gateway_code.nodes import OpenNodeBase
class Customnode(OpenNodeBase):
TYPE = 'stm32nucleo'
In many cases, it's not difficult to add a new node based on a node that is close to it
or to a common type of node. Nodes using openOCD to connect to it can
derive from gateway_code.open_nodes.common.node_openocd.NodeOpenOCDBase
,
mostly just providing a OPENOCD_CFG_FILE
attribute is enough.
We recommend you look at all the examples of open nodes supported by the platform for inspiration: gateway_code/open_nodes
During the experiment and the tests, the application use several attributes located in your class. These attributes are the characteristics of your node and we can find for example, the name of your device as chosen in the udev rules (e.g. tty); the baudrate which allows the communication between the gateway and your node (e.g. BAUDRATE).
There are some mandatory attributes, and the gateway code will not start if your custom node doesn't implement them
You can add your own attributes as needed by your class.
The API provides services. These services are loaded dynamically, concerning what your open-node allows to do. These services are based on method implemented in your file: if the API need a specific method to create a service, the gateway_code will check if your class implements this method. If it doesn’t, the service will not be available.
Some services are always needed, such as the start and stop of an experiment. The methods required by these services are mandatory and are the following:
setup
: Perform all the actions required to properly start an experiment.teardown
: Perform all the actions required to properly stop an experiment.flash
: Flash a firmware on the open-node.
Other services will be available if the corresponding methods are implemented. These methods are the following:
reset
: Reset the open-node.debug_start
: Start the open-node debugger.debug_stop
: Stop the open-node debugger.
Do not hesitate to watch the other implementations of open nodes to have
a better idea of what the application expects. For an example, you can
look at doc/node_example.py
When one of the function described above terminates with a success, the
return value must be 0
(the number 0). When the return value is different from 0, the
application knows that something went wrong.
Some methods such as reset
, flash
or debug_start
, need to interact
with the serial port of the open node. To do this, you will have to use
a programmer software and write code to interact with it. Openocd
,
Avrdude
, and others, are already available but maybe you will need to use another
programmer and implement your own python file in the gateway_code/utils/
folder. If you need a specific kernel driver to be available, don't
hesitate to ask the IoT-lab admin team
To add your custom node in the IoT-Lab platform, you must write two
firmwares. These firmwares must be put
in the gateway_code/static/
folder
The idle firmware, must be a program with a consumption as low as possible (no led blinking, and no infinite loop). This firmware is flashed on the open node when no experiment is running.
The autotest firmware is used during the testing phase. The IoT-LAB platform
allows you to perform autotests for your device. It could be useful to
see if the sensor on your node are still alive and send valid
data. During the testing phase, the gateway will flash the autotest
firmware on the open-node and will then send some commands to the
open-node. For example, if your node embeds a light sensor, the gateway
will send the following command on the serial port: get_light
. Your
autotest firmware will receive this order, ask to the light sensor a
measure and then send back to the gateway:
ACK get_light <value_of_measure> lux
. Thanks to this, the gateway will
know that the light sensor on your open-node works. An annex
file gather all the autotest command which can be send by the gateway.
Among all these tests, two are mandatory: get_time
and echo
.
This test ensures that your open-node is answering on the serial.
echo
command must behave as described bellow:
Gateway message : 'echo HELLO WORLD'
Node answer : 'HELLO WORLD'
Gateway message : 'echo ACK'
Node answer : 'ACK'
To inform the application what kind of command your autotest firmware
can handle, you must write the name of the test in the list
AUTOTEST_AVAILABLE
as shown in the template file.
You can find the code (based on RIOT operating system) for the autotest and idle firmwares for some boards supported on IoT-LAB on ci-firmwares
For unit-testing and integration-testing it's recommended to use the provided Docker images, but you can test without Docker if you want.
The purpose of unit tests is to verify your Python code, independently from connections to a physical board.
The purpose of integration tests is to verify that your flashing tool and firmwares behave as expected when called by the gateway code.
The udev-rules must be setup on the host computer in order to map the custom iotlab TTY in the docker container:
make setup-udev-rules
You should read DOCKER.md for prerequisites
Build the testing image:
make build-docker-image-test
To run the unit tests with the provided Docker image, just run:
make test
To run the integration tests inside a docker container, just run:
make BOARD={node_name} integration-test
You will need to manually install all the needed
dependencies, see INSTALL.md, and tox
for running the tests.
If you don't have tox, install it with
pip install tox
To run the unit tests:
make local-test
To run the integration tests:
make BOARD={node_name} local-integration-test
Bellow you will find autotest commands that can be sent by the gateway during the tests and the corresponding format of the return expected.
Command | Expected answer format |
---|---|
echo arg1 arg2 ... |
arg1 arg2 ... |
get_time |
ACK get_time 122953 T_UNIT |
Command | Answer format |
---|---|
get_uid |
ACK get_uid 05D8FF323632483343037109 |
get_gyro |
ACK get_gyro X. Y. Z. dps |
get_magneto |
ACK get_magneto X. Y. Z. gauss |
get_accelero |
ACK get_accelero X. Y. Z. g |
get_pressure |
ACK get_pressure P. mbar |
get_light |
ACK get_light L. lux |
test_gpio |
ACK test_gpio |
test_i2c |
ACK test_i2c |
radio_pkt [channel] [power] |
ACK radio_pkt CHANNEL POWER |
radio_ping_pong [channel] [power] |
ACK radio_ping_pong CHANNEL POWER |
leds_on [flag] |
ACK leds_on [flag] |
leds_off [flag] |
ACK leds_off [flag] |
leds_blink [flag] [time] |
ACK leds_blink [flag] [time] |
test_pps_start |
ACK test_pps_start |
test_pps_get |
ACK test_pps_get |
test_pps_stop |
ACK test_pps_stop |
Docker for Mac does not support adding a host device to a container (e.g. docker run --device
). source
The use of Docker Toolbox is recommended.
Start Docker Toolbox with the Docker Quickstart Terminal, then do this additionnal steps on the boot2docker VM used by Docker Toolbox:
-
Install udev rules.
$ docker-machine ssh docker@default:~$ git clone https://www.github.com/iot-lab/iot-lab-gateway.git && cd iot-lab-gateway docker@default:~$ sudo cp bin/rules.d/* /etc/udev/rules.d/. docker@default:~$ sudo udevadm control --reload
-
Thanks to the VirtualBox GUI or via VBoxManage command line, add a USB device filter for the device used as Open Node.
$ VBoxManage list usbhost $ VBoxManage usbfilter add 1 --target default --name M3 --vendorid 0403 --productid 6010
-
(optionnal) Thanks to the VirtualBox GUI or via VBoxManage command line, add a NAT port forwarding rule to be able to call
http://localhost:8080
from the host.$ VBoxManage controlvm default natpf1 "bottle,tcp,127.0.0.1,8080,,8080" $ VBoxManage controlvm default natpf1 "serial_redirection,tcp,127.0.0.1,20000,,20000"
Build the docker image and run docker-run
as explained above.
There might be similar problems when running under Windows with certain versions of Docker for Windows, or inside a Ubuntu VM on a Windows host, don't hesitate to contact us if you are facing problems with the setup. The setup should be similar to the above, udev rules should be set on the host, and devices forwarded correctly to the place where the Docker daemon can reach them correctly.