DSN New User Programming Tutorial
June 2007

Hello World: My First Program

This section is designed to provide a brief overview of the fundamentals of the DSN specification language and then allow you to build a quick Count to LEDS application. Subsequent sections will gradually introduce additional features of DSN as you build more complex applications.

DSN programs are specified using Snlog, which is a dialect of Datalog. As a consequence, programs are composed of rules, facts, predicates, and tuples. Predicates are the equivalent of tables in databases and tuples, which are instantiations of predicates with specified parameter values, are like rows of a table. Rules and facts, which are denoted by statements ending with a period, are composed of predicates and tuples.

Rules are of the following form:
head(A, B, C) :- bodyOne(A, B), bodyTwo(B, C, _).

In this example, bodyOne, bodyTwo, and head are predicates. In essence, this statement says the following: If specific instances (tuples) of bodyOne and bodyTwo exist that satisfy both predicates, then an instance of the head predicate is created. In other words, if there exists an instance of bodyOne with parameter values A and B, respectively, and there exists an instance of bodyTwo with B and C as the values for the first two parameters (the underscore indicates we don't care what the third parameter value is), where B is the same value in both instances, then an instance of head will be created with parameters values A, B, and C.

However, such a specification is incomplete in the sense that it does not specify where the processing of the rule occurs, and where the tuples are located, two very important aspects of distributed execution. DSN deals with this aspect by requiring the first parameter of all predicates to be '@X', where X indicates where the tuples to be evaluated are stored. Therefore, the rule from above would actually be written as:

head(@Z,A,B,C) :- bodyOne(@Z,A,B), bodyTwo(@Z,B,C,_).

An important requirement of DSN is that all tuples being evaluated for a rule reside on the same node. In other words the first parameter of all predicates on the right side of the rule must match. By the same token, while the head tuple that is created can reside on the local node, it can also be sent to another node by specifying a different location as the first parameter, as such:

head(@C,A,B,C) :- bodyOne(@Z,A,B), bodyTwo(@Z,B,C,_).

All tuples that are created as a result of the execution of this rule will be sent to and stored at node C. By the same token, DSN allows the user to specify a tuple should be broadcast, rather than unicast, by specifying '*' as the location.

The next question is then how often and when these rules are executed. Each node has runs a query process locally. Everytime a new tuple is inserted into a table, either through the execution of another rule, or received from another node, any rules in which that predicate appears in the body (right side) are executed. One can imagine rules being processed in a sequential chain fashion if the head predicates of some rules appear in the body portion in other rules.

By the same token, if one wants to simply create a specific instance of a predicate, i.e a tuple, they can use a fact:

bodyOne(@0,10,20).

This fact creates a specific instance of the bodyOne predicate with parameter values, 10 and 20, at node 0.

First Steps

In this section we look at the specification for Count to Leds, a DSN program that runs locally. The example is intended to allow you to get your feet wet and gain your first exposure to DSN programming; technical details will be covered in more detail in subsequent sections.

Begin by opening up the file: snlog2/raw/cnt-to-leds.snl. The output should be as follows (line numbers added to facilitate referencing):

[1]  ! import('defs.snl').

[2]  ! typedef(leds,uint16_t,uint8_t,uint8_t).
[3]  ! typedef(leds,BuiltinLed).

[4]  timerx(@0,3,2000).
[5]  timerx(@0,4,1000).
[6]  timerx(@0,5,500).
[7]  timer(@S,Tid,Tval) :- timerx(@S,Tid,Tval).

[8]  timer(@S,Tid,Tval) :- timer(@S,Tid,Tval).

[9]  leds(@S,1,2) :- timer(@S,3,Tval).
[10] leds(@S,2,2) :- timer(@S,4,Tval).
[11] leds(@S,0,2) :- timer(@S,5,Tval). 

The first line is importing defs.snl, which contains needed definitions for some functions and predicates, and likely will be included in any DSN program. Line 2 is in essence a variable declaration, telling the compiler the format of the leds predicate. The leds predicate is an example of a built-in predicate, which are used to export hardware or low-level capabilities through a predicate-based interface. Line 3 tells the compiler that leds is indeed a built-in predicate, and the code for implementing it is stored in the BuiltinLed module.

The interface for the leds predicate is as follows:

leds(@Source,Color,State)

Since only numeric values can be used as parameter values, here are the conversions:

Color:
Red = 0
Yellow/Blue = 1
Green = 2

State/Action:
On = 0
Off = 1
Toggle = 2

In order to achieve periodicity, which is necessary for our program, we need to discuss the use of timers in DSN. Timers are another example of built-in predicates. To interface with timers, users can use the following predicate:

timer(@Src,TimerID,TimerVal)

The TimerID is unique for each timer, similar to the use of parameters for Timers in TinyOS, and TimerVal represents the period the timer is set for. When the timer predicate is used in the body of a rule, the rule is triggered when that particular rule fires, and when the predicate appears in the head of the rule, this indicates the rule is being set.

Rules 4, 5, and 6 are facts that are used to initialize 3 timers, one for each LED. Upon closer examination, we actually see that these are timerx predicates, rather than timer. The reason for this is that currently DSN does not allow built-in predicates to be used in facts. Consequently, we use a dummy predicate, timerx, that has the same exact interface in the rules. Then in line 7, we initialize 3 different timer tuples (by virtue of their existing 3 different timerx tuples). (Note: The location for these facts is set to '0', because we assume that is the address you will assign to the mote on which you will download this program to. Also, we did not declare the timerx predicate. This is because if all the default options are used for a predicate, as would be the case for a dummy predicate, DSN can infer the predicate interface, even without a declaration.)

Now the one difficulty with this rule is that it only creates a one-shot timer, although we want a periodic one. In order to achieve this, line 8 is added.

Lets think about this logically. We know that if an instance of the timer predicate is created, this in essence sets the specified timer (i.e. with that timer ID) to a one-shot period specified by TimerVal. By the same token, timer predicates in the body only trigger rule execution when the timer fires. Therefore, line 8 says if a one-shot timer fires, set that same timer with the same period it just had, creating a chain of one-shot timers with the same period, which is the definition of a periodic timer.

Lines 9, 10 and 11 control the leds. Each timer is associated with an LED: (3 for yellow, 4 for green, and 5 for red). Based on the format outlined above, each rule specifies that when the specific timer fires, the associated LED should be toggled. The 1:2:4 ratio of the timer periods creates the counting effect.

To run this program, follow the steps outlined in the previous section, except using this file, snlog2/raw/cnt-to-leds.snl. After the program is done, your mote should begin counting using the LEDs. Congrats, you've finished your first program!