TinyOS
NesC
TOSSIM
Misc
Suggestions
top-level configuration file (Skeleton.nc) |
configuration Skeleton { } implementation { components Main, SkeletonM; Main.StdControl -> SkeletonM.StdControl; } |
In the above file we see that the minimal application uses two components, Main and SkeletonM. Main is part of the framework provided by TinyOS. SkeletonM is the top-level implementation module. The line Main.StdControl -> SkeletonM.StdControl; links the StdControl interface provided by the SkeletonM module to Main's StdControl.
top-level implementation module (SkeletonM.nc) |
module SkeletonM { provides { interface StdControl; } } implementation { command result_t StdControl.init() { return SUCCESS; } command result_t StdControl.start() { return SUCCESS; } command result_t StdControl.stop() { return SUCCESS; } } |
In the above file we see that the module declares that it provides the StdControl interface. In the implementation block we see that three command implementations. As the module declares that it provides the StdControl interface, the module must provide an implementation of the StdControl interface. All standard TinyOS interfaces can be found in the tos/interfaces directory of the TinyOS tree.
Makefile |
COMPONENT=Skeleton include ../Makerules |
The above file defines the component that will be built using the make-based TinyOS build system. The second line assumes that these files reside in subdirectory of apps in the TinyOS tree (i.e. apps/Skeleton).
This code example demonstrates using the Timer parameterized interface and the Leds interface.
top-level configuration file (TimedLed.nc) |
configuration TimedLed { } implementation { components Main, TimedLedM, TimerC, LedsC; Main.StdControl -> TimedLedM.StdControl; Main.StdControl -> TimerC.StdControl; //The Timer component is an example of a parameterized component. TimedLedM.RedTimer -> TimerC.Timer[unique("Timer")]; TimedLedM.GreenTimer -> TimerC.Timer[unique("Timer")]; TimedLedM.YellowTimer -> TimerC.Timer[unique("Timer")]; TimedLedM.Leds -> LedsC.Leds; } |
In the above code, notice the multiple mappings of the TimerC component. TimerC provides and implementation of the Timer interface. The implementation is parameterized, allowing a convenient means of mapping an implementation to multiple instances of the component. The Simple Message Example (below) demonstrates another parameterized component.
top-level implementation module (TimedLedM.nc) |
module TimedLedM { provides { interface StdControl; } uses { //we define which components our implementation will //use. Note that we alias the Timer interface to allow for //three distinct instances of the component. interface Timer as RedTimer; interface Timer as GreenTimer; interface Timer as YellowTimer; interface Leds; } } implementation { command result_t StdControl.init() { //initializes the Leds component call Leds.init(); return SUCCESS; } command result_t StdControl.start() { call RedTimer.start(TIMER_REPEAT,1024); call GreenTimer.start(TIMER_REPEAT,2 * 1024); call YellowTimer.start(TIMER_REPEAT,3 * 1024); return SUCCESS; } command result_t StdControl.stop() { call RedTimer.stop(); call GreenTimer.stop(); call YellowTimer.stop(); return SUCCESS; } //event handlers for the timers event result_t RedTimer.fired() { call Leds.redToggle(); return SUCCESS; } event result_t GreenTimer.fired() { call Leds.greenToggle(); return SUCCESS; } event result_t YellowTimer.fired() { call Leds.yellowToggle(); return SUCCESS; } } |
The
In the implementation block we have the command definitions required by the StdControl interface. In the StdControl.init() command we invoke a command the Leds.init() command. That is to say, we invoke the init() command that is provided by the component that provides the Leds interface. In this case, we are invoking the command on the LedsC component. This component was mapped to the TimedLedM.Leds interface in the TimedLed.nc configuration file using the line TimedLed.Leds -> LedsC. This is a key point. In the implementation module file you either will provide an interface (typically only StdControl when implementing a top-level module) or you will use one or more components provided by TinyOS to implement your programs logic. In your implementation file you specify which interfaces you will need to use. In the configuration file you specify which component implementating the interface you will be using. This is called wiring.
This implementation also contains three event handlers for the event fired() that is declared in the Timer interface. Remember, you can find the definition of interfaces in the tos/interfaces directory. Most events in TinyOS are used as callback functions for split-phase operations. For example, in the StdControl.start() command the RedTimer.start(...) command is invoked. After the specified amount of time passes, a lower-level component signals the event RedTimer.fired(). In the intervening time (between the invocation of the command and the signalling of the event) other code may be executed, although that is not well demonstrated in the above program. It is your responsibility to provide the declared event handlers in all interfaces that your implementation uses. We will see more event handlers in the Simple Messaging example below.
Makefile |
COMPONENT=TimedLed include ../Makerules |
The above file defines the component that will be built using the make-based TinyOS build system. The second line assumes that these files reside in subdirectory of apps in the TinyOS tree (i.e. apps/TimedLed).
So what does TimedLed do?
In this section a simple example of using TOSSIM debug statements is provided. For a detailed description see the TOSSIM User Manual.
TimedLedM.nc with TOSSIM debug statements |
module TimedLedM { provides { interface StdControl; } uses { interface Timer as RedTimer; interface Timer as GreenTimer; interface Timer as YellowTimer; interface Leds; } } implementation { command result_t StdControl.init() { call Leds.init(); dbg(DBG_USR1,"TimedLed initialized\n"); return SUCCESS; } command result_t StdControl.start() { call RedTimer.start(TIMER_REPEAT,1024); call GreenTimer.start(TIMER_REPEAT,2 * 1024); call YellowTimer.start(TIMER_REPEAT,3 * 1024); dbg(DBG_USR1,"TimedLed started\n"); return SUCCESS; } command result_t StdControl.stop() { call RedTimer.stop(); call GreenTimer.stop(); call YellowTimer.stop(); return SUCCESS; } //event handlers for the timers event result_t RedTimer.fired() { dbg(DBG_USR1,"RedTimer.fired()\n"); call Leds.redToggle(); return SUCCESS; } event result_t GreenTimer.fired() { dbg(DBG_USR1,"GreenTimer.fired()\n"); call Leds.greenToggle(); return SUCCESS; } event result_t YellowTimer.fired() { dbg(DBG_USR1,"YellowTimer.fired()\n"); call Leds.yellowToggle(); return SUCCESS; } } |
In the above, you see lines such as DBG(DBG_USR1,"some string literal\n");. These lines cause TOSSIM to
output the string literal provided to the console if the DBG environment variable includes the
What will be printed to the TOSSIM console assuming the DBG environment variable is set to "usr1"?
From the apps/TimedLed subdirectory, the following command will compile the TimedLed application to run in TOSSIM:
We define a simple message structure in a header file. The payload of the message contains
the source identifier (TOS_LOCAL_ADDRESS) of the message sender and the sequence number of the
message. In addition to defining the structure of the message, we must assign the message a
unique AM_TYPE. This AM_TYPE is used in conjunction with the parameterized messaging
component.
In the implementation module we declare the use of the ReceiveMsg and the SendMsg interface. Looking back
at the configuration, we see that GenericComm component provides both of these interfaces. Notice that we have defined variables to
be used by the program. In particular, not the variable out of type TOS_Msg. TOS_Msg is the actual message structure
that is sent over the radio. TOS_Msg has a member named data. The message type you define must be able to fit in data.
That is, the message type you define cannot be larger than 29 bytes. You may have noticed that we made the source identifier a member of
our message structure. This is necessary (if you want to know who the message is from) as TOS_Msg does not include the source as part of the
header information (TOS_Msg is defined in tos/types/AM.h).
Two new concepts are demonstrated in the above implementation module. We use a task to implement the sending of a message. You can find out more about
tasks, events and commands in the NesC Language Reference Manual. As all variables defined outside an event, task or command are global to component, you may have to control access to them using an atomic block. In the above module, access to out is controlled using the variable txState. All potentially unsafe changes to txState occur in an atomic block. You can find out more about TinyOS concurrency in the NesC Language Reference Manual.
When run, SimpleMessage produces the following output:
make pc
This will produce the following output:
You may or may not receive the warnings -- but you must see compiled TimedLed to build/pc/main.exe. Notice that the binary
was placed in build/pc/main.exe.
Setting the DBG environment variable
Before running TimedLed in TOSSIM you must set the DBG environment variable. The following command will
output DBG_USR1 debug statements and display the state of the LEDs:
export DBG=led,usr1
See the TOSSIM User Manual for more details.
Running TimedLed in TOSSIM
The following command will run TimedLed on one node in TOSSIM with the simulation speed set to real-time:
build/pc/main.exe -l=1.0 1
See the TOSSIM User Manual for more command options. You should
see similar output in the TOSSIM console:
Typing CTRL-C will terminate TOSSIM.
Simple Messaging Example
The following example illustrates messaging using the GenericComm component. The
example also demonstrates the use of a task.
Message header file (message.h)
//we must assign an AM_TYPE to the message
enum{
AM_SIMPLEMSG = 55
};
typedef struct SimpleMsg
{
uint16_t sourceId;
uint16_t sequenceNo;
}SimpleMsg;
typedef SimpleMsg *SimpleMsgPtr;
When defining the AM_TYPE, the name must be of the form AM_'MYMSG' where 'MYMSG' is the name
of the message struct capitalized. The AM_TYPE for all messages in your application must be unique. We also define
a pointer for the message type as a convenience. If you are not familiar with unsigned types (e.g. uint8_t), there are many
online resources describing them.
top-level configuration file (SimpleMessage.nc)
includes message;
configuration SimpleMessage {
}
implementation {
components Main, SimpleMessageM, GenericComm, TimerC;
Main.StdControl -> SimpleMessageM.StdControl;
Main.StdControl -> GenericComm;
Main.StdControl -> TimerC;
//messaging
SimpleMessageM.RcvSimpleMsg -> GenericComm.ReceiveMsg[AM_SIMPLEMSG];
SimpleMessageM.SndSimpleMsg -> GenericComm.SendMsg[AM_SIMPLEMSG];
//timer
SimpleMessageM.SendTimer -> TimerC.Timer[unique("Timer")];
}
In the configuration file, we include (includes message;) the message header file. The AM_TYPE is
used in two wirings.
top-level implementation module (SimpleMessageM.nc)
module SimpleMessageM {
provides {
interface StdControl;
}
uses {
interface ReceiveMsg as RcvSimpleMsg;
interface SendMsg as SndSimpleMsg;
interface Timer as SendTimer;
}
}
implementation {
enum {RADIO_BUSY=0, RADIO_FREE=1};
uint8_t txState;
TOS_Msg out;
uint8_t sequenceNo;
command result_t StdControl.init() {
txState = RADIO_FREE;
sequenceNo = 0;
dbg(DBG_USR1,"SimpleMessage initialized\n");
return SUCCESS;
}
command result_t StdControl.start() {
call SendTimer.start(TIMER_REPEAT,512 + TOS_LOCAL_ADDRESS * 1024);
dbg(DBG_USR1,"SimpleMessage started\n");
return SUCCESS;
}
command result_t StdControl.stop() {
call SendTimer.stop();
return SUCCESS;
}
task void sendSimpleMsg() {
SimpleMsgPtr simpleMsgPtr;
result_t retval;
simpleMsgPtr = (SimpleMsgPtr)(out.data);
simpleMsgPtr->sourceId = TOS_LOCAL_ADDRESS;
simpleMsgPtr->sequenceNo = sequenceNo++;
retval = call SndSimpleMsg.send(TOS_BCAST_ADDR,sizeof(struct SimpleMsg),&out);
if (retval != SUCCESS) {
atomic {
sequenceNo--;
txState = RADIO_FREE;
}
dbg(DBG_USR1,"Failed to queue message for send\n");
}
}
event result_t SndSimpleMsg.sendDone( TOS_MsgPtr pmsg, result_t success ) {
uint8_t sn;
atomic {
txState = RADIO_FREE;
sn = sequenceNo - 1;
}
dbg(DBG_USR1,"Sent message \%x \n",sn);
return SUCCESS;
}
event TOS_MsgPtr RcvSimpleMsg.receive(TOS_MsgPtr pmsg) {
SimpleMsgPtr mptr = (SimpleMsgPtr)(pmsg->data);
dbg(DBG_USR1,"Received messsage \%x from source \%x \n",mptr->sequenceNo,mptr->sourceId);
return pmsg;
}
event result_t SendTimer.fired() {
if(txState == RADIO_FREE) {
atomic {
txState = RADIO_BUSY;
}
dbg(DBG_USR1,"Posting task to send message\n");
post sendSimpleMsg();
}
return SUCCESS;
}
}
Makefile
COMPONENT=SimpleMessage
include ../Makerules