Maté
Safe and Rapid Sensor Network Programming

Last updated 22 Mar. 2004

Introduction

Maté allows users to program TinyOS networks with short, high level event handler scripts. Users install a virtual machine TinyOS application on motes, and a Java application compiles scripts to the VM bytecodes. These bytecodes run in a sandboxed VM environment, so buggy programs cannot crash a mote. Event handlers have version numbers, and motes automatically install new versions they hear. Once a single copy of a newer program is installed, the network will automatically propagate it to every node.

Maté simplifies programming by providing a synchronous interface to TinyOS operations. For example, the send() function blocks until the underlying sendDone event is handled by TinyOS. This makes code much less complex, especially for novice programmers. Currently, Maté programs are written in a simple, BASIC-like scripting language called TinyScript. We are working on making the architecture flexible enough for alternative scripting language options.

As the core of Maté is a virtual machine, it also provides a form of execution protection. Unlike TinyOS applications, which have no user/kernel boundary, Maté programs cannot crash a mote or render it unresponsive. Maté catches errors at run time (e.g. accessing an invalid heap variable) and has a mechanism for alerting a user to them (all the LEDs blink and it sends a message), including simple debugging information on the code that caused the error.

Maté has an event-based execution model; pieces of code are run in response to events, such as a timer or a packet reception. The presence of blocking operations requires that event handlers be able to run in parallel; the presence of shared variables could make race conditions a significant problem. Maté handles this issue by having handlers implicitly lock all shared resources they use; the set of shared resources used is computed with a full program analysis when code is installed. Therefore, a programmer can write several Maté event handlers that share variables and be sure there will be no race conditions without having to consider the necessary synchronization.

Building and Installing Maté

Maté allows users to build customized virtual machines for specific application deployments. However, it also has two simple example VMs which you can build and use.

Maté's code resides in two directories. The first, tools/java/net/tinyos/script is the Java toolchain for building VMs and writing scripts. The second, tos/lib/VM, is the actual VM TinyOS code.

The first step to using Maté is to build the Java toolchain. Go to tools/java/net/tinyos/script and type make. This builds the two principal tools, VMBuilder and Scripter, as well as all of their supporting code (parsers, etc.).

Once you've built the toolchain, the next step is to build a Maté VM and associated scripting environment. Go to tos/lib/VM/samples. You should see two files: bombilla.vmsf and simple.vmsf. These files describe VMs. Build Bombilla by typing:


$ java net.tinyos.script.VMBuilder -nw bombilla.vmsf

	      
You must do this from the samples directory. You should see output similar to this:

Creating files
Generating language-based instructions.
Organizing function primitives.
Making Config file
Making constants file
Making component file
164 of 256 opcodes used.

Making makefile (haha!)

 	      

Running VMBuilder on this file creates an application in tinyos-1.x/apps/Bombilla. There should be four files: Makefile, MateConstants.h, MateTopLevel.nc and vm.vmdf.

Go there and type make pc. Running make should create a new directory, vm_specific, which contains several Java classes for the Bombilla VM, as well as build a TOSSIM version of the VM. You can correspondingly make mica2 for mica2 nodes. Note that every time you build a Maté executable, make regenerates the Java files. Correspondingly, make reinstall.num can be useful.

Build Bombilla for the hardware that you have. Install the VM on a single node, with ID 0 (make reinstall.0). Plug that mote into your serial port. Then, from the Bombilla directory, type


$ java net.tinyos.script.Scripter -comm serial

 	      

If you aren't using a serial port, or have some specific options you need (e.g., -comm serial@COM2 or -comm sf), change the parameter correspondingly.

This will load a Scripter interface for Bombilla. It should look something like this:


The Scripter Interface

This is the interface for writing scripts to install on a Maté network.

The Scripter Interface

The Scripter interface is broken into three major parts. We'll step through each of them, one by one.

The left panel has two fields, Mote ID and Version Number. In Mote ID you should enter the ID of the mote at the end of the serial port; in this case, it's 0, so you don't need to change anything. You generally do not need to change the Version Number field; the interface will take care of that for you. However, sometimes you need to manually change it. For now, leave it alone.


The Left Scripter Panel

Below the two fields is a selection for which event handler you which to program. The Bombilla VM has two event handlers: Once and Timer0. The Once event handler runs once, when new code is installed. Timer0 runs periodically at some frequency. By default, it is disabled. Other VMs can handle different events than these, such as packet reception, target detection, or processing requests. Bombilla is a simple one, however, so only has these two.

Below the selection is an area called Shared Variables. As a later section shows, scripts can declare variables that are shared across scripts. This area displays all of the shared variables currently in use. For now, it's empty. To the right of this display is the Inject button, and a greyed-out button, which you can ignore. Inject is used to introduce a new program to a network.


The Center Scripter Panel

The center panel has a single area, Program Text. This is where you write TinyScript code to compile and install in a network.


The Right Scripter Panel

The right panel has two areas. On top is a list of Primitives. These are functions, beyond general TinyScript operations, that scripts can call. Selecting one of them brings up a brief description in the lower panel.

When you quit Scripter, you should always do so through the File menu. Scripter saves some state about the current session to a file, and quitting without using this menu item prevents this from happening.

A First Program: CntToLeds

Select the Timer0 handler in the left panel. Then type this program into the program text area:


private counter;
counter = counter + 1;
call led(counter & 7);

	      
This is CntToLeds in TinyScript; it keeps a private variable counter, which it increments each time it executes. It then calls led() with the low three bits, to display them. Press Inject.

In the shell you started Scripter from, you should see output similar to this:


Generating assembly.
Assembling to instruction set.
Sending program: 38 81 00 40 38 87 03 1c 02 
Sending 1 chunks (9,28): +
Garbage collecting variables

	      
This says that it was able to correctly compile the program to assembly code, translate that assembly to the VM opcodes, then send it. Scripter breaks larger programs up into "chunks," which is sends one by one. In this case, the program is nine bytes long and the chunk size is 28 bytes, so it sent a single chunk. Once the program has been sent, the Scripter checks what variables all of the handlers use to see if any shared variables (more on that later) can be reclaimed (garbage collected).

When the Timer0 event fires, Maté will run this code. However, Timer0 doesn't fire when the VM boots. We need to tell the VM to start firing Timer0. We'll do this with the Once handler. We'll need to use one of the Primitives, settimer0. Click on it in the right panel to read its description.

Select the Once handler, and type this program:

call settimer0(10);
	      
This will start firing Timer0 at 1Hz, or once per second. Hit Inject. You should see the LEDs start ticking.

TinyScript

TinyScript programs have two sections. The first is the variable declaration region. In the Timer handler above, this was the private counter; statement. All variables must be declared before any statements. There are three kinds of variables: private, which only that handler can access, shared, which all handlers can access, and buffer variables, which are shared. For example, this program declares a shared variable, a private variable, a buffer, and turns on the red LED:

shared data;
private value;
buffer cache;

call led(1); ! Turn on red led
	      
Comments can be entered with !, which comments out everything until the end of line.

Private and shared valuables can be either sensor readings, generated by primitives such as temp or light, or integers. Sensor readings are immutable; trying to modify them will result in a run-time error. You also cannot compare with with anything except sensor values of the same type (e.g., light with light). For example:

private cond;
shared value;

cond = call rand() & 1;
if (cond) then
  value = call light();
else
  value = 20;
end if

value = value / 2;  ! This will fail if value is a light reading
	      
If you need to modify sensor readings, say, for averaging, then you can cast them to integers with the cast primitive. This is a version of the above program that will execute without a run-time error:
private cond;
shared value;

cond = call rand() & 1;
if (cond) then
  value = call cast(call light());
else
  value = 20;
end if

value = value / 2;  ! Now it works
	      

Buffers are indexed with []; by default, they have ten elements. An empty index implies the tail of the buffer; call cast(buf[]); will return an integer value of the end of the buffer, while buf[] = 0; will append to a buffer.

Buffers are typed. An empty buffer has no type. The bclear primitive empties a buffer. A buffer takes the type of the first element put into it (integer, light reading, etc.), and a run-time error occurs if any other type is put into it. Here is a program that sends a light reading over a tree-based ad-hoc collection routing protocol:

buffer buf;

call bclear(buf);
buf[] = call light();
call send(buf);
	      

TinyScript supports basic arithmetic operations, such as addition (+), multiplication (*), division (/), modulus (%), as well as boolean operations such as and (AND) as well as or (OR), as well as their binary (logical) equivalents (&, |).

Primitives are invoked with the call keyword (e.g., call led(1);). If the primitive has a return value (check its documentation), it must be stored. For example, rand() returns a random value. call random(); will cause a compile error, while rval = call random(); will not.

Control structures include if ... then as shown above, as well as loops. For example, to put ten successive light values into a buffer:

buffer buf;
private i;

call bclear(buf);

for i=0 to 9 
  buf[] = call light();
next i

call send(buf);
	      

There are a limited number of buffers and variables. By default, there are 16 shared variables, eight private variables (per handler), and eight buffers.

A full reference for TinyScript can be found in the standard documentation directory. This tutorial only covers a few major points.

Errors

When Maté encounters an error in a program (e.g. trying to add a light and temperture value), it enters an error state. In this error state, it stops running capsules. Instead, it periodically toggles all of the LEDs (all of them blinking on and off is an indication something has gone wrong) and sends a packet over the UART. The packet is a MatéErrorMsg and contains information on the cause of the error.

A Simple Program

Here is a simple program, where each mote will periodically (one every ten seconds) sample its temperature and light sensors, but those values into a buffer, and route those to a base station. Note that, for this to work, there must be a routing base station (a node with ID 0).

If Timer0 is running from the earlier part of the tutorial, stop it with the Once handler by call settimer0(0);. Then, install this as Timer0:

buffer buf;
private counter;

counter = counter + 1;
if (counter >= 10) then
  call bclear(buf);
  buf[] = call cast(call light());
  buf[] = call cast(call temp());
  counter = 0;
  call send(buf);
end if
	      

followed by this as Once:
call settimer0(10);
	      
Alternatively, instead of maintaining the counter value, you could call settimer0(100);.

Data Consistency

By default, multiple Maté handlers can run concurrently; the VM interleaves their execution at instruction-level granularity. When there are shared variables (and buffers), this can lead to possible race conditions. Using installation-time program analysis, Maté ensures race-free, deadlock-free execution of handlers. If two handlers can safely run concurrently (they share no variables), then the scheduler lets them do so.

This can fail due to handler installation. Notably, as handlers are installed immediately upon reception, a context can be in the midst of a computation when it is halted due to new code being installed.

This means that to ensure perfect data consistency, the values of shared variables cannot be transferred from one version of a handler to the next. For example, before installing a new version of the clock handler, one could install a clock handler that clears out the shared variables, restoring them to a consistent state.

We are currently working on a handler installation mechanism that will preserve execution atomicity; this understandably becomes difficult in the presence of buggy handlers that do not halt.

Further Use

This document just touches on Maté and some simple programs that you can write with the example Bombilla VM. Maté allows you to build customized VMs, with user-specified event handlers and primitives. For example, you can build a scripting environment that receives and sends packets, reacts to target detection, or acts as a data aggregation engine. For more information, please refer to the Maté manual

Maté is in active development. If you find bugs, please enter them in the TinyOS bug tracker on Sourceforge. Feature requests and questions should be sent to the standard TinyOS users list, tinyos-users@millennium.berkeley.edu. You can sign up here.


Tutorial Index