[Tutorial Home] [Lesson 1] [Lesson 2] [Lesson 3] [Lesson 4] [Lesson 5] [Lesson 6]

Lesson 6: Adding Events

Last modified: Fri Dec 10 12:50:14 PST 2004

Introduction

Lesson 5 shows you how to write new functions to incorporate into Maté VMs. This lesson shows you how to write new event handlers, so you can customize what causes your VM to execute. You write a new context component that executes every time the mote sends a packet.

Context Components

Every Maté handler has an associated context component. The component is responsible for allocating a Maté execution context structure for the handler to run on, and triggering the context to run when an appropriate TinyOS event occurs. Like functions, context components usually have three files: a context description file (.cdf), a module implementing the context, and a configuration that wires the module to all of the other components it needs.

A CDF must have a CONTEXT element that describes the context. This element must have three tags, DESC, ID, and NAME. DESC is a descriptive string which the VMBuilder GUI displays to the user. VMBuilder and Scripter use the NAME tag to uniquely identify a context. VMBuilder uses the value of the NAME tag to generate unique identifier constants referring to the context and its handler. For example, if NAME=EXAMPLE, VMBuilder generates the constants MATE_CONTEXT_EXAMPLE and MATE_HANDLER_EXAMPLE. When Scripter injects a new piece of code, it uses the HANDLER context to identify which handler the code corresponds to, so the VM can install it properly. When a VM sends an error message to the user, Scripter uses the CONTEXT constants to map the unique ID to the name of the context that triggered the error.

The ID element is currently unused, but will be used in the future. The issue with using unique IDs is that two different VMs with the same contexts cannot share code, as they might have different IDs for the same context. Explicit, global IDs for contexts solves this problem. A node will map the global ID to its local ID for the context.

Contexts can have functions associated with them. For example, the Timer contexts have the settimer functions to control their firing rate, while the Trigger context has the trigger and triggerbuf functions. A context description file, in addition to containing a CONTEXT element, can contain FUNCTION elements which VMBuilder will automatically include when the context is incorporated into a VM. For example, this is lib/VM/contexts/Timer0Context.cdf:

<CONTEXT name="Timer0" desc="A periodic timer." id=7>

<FUNCTION name=SETTIMER0 opcode=settimer0 numparams=1 param1=value returnval=FALSE desc="Takes a single parameter, the interval (in tenths of a second) between timer firings for the Timer 0 context. Calling with a time of zero stops the timer.">

Context modules must use three interfaces: MateContextSynch, for interacting with the Mat´ scheduler, MateHandlerStore, for knowing when new code has arrived, and MateEngineStatus, for knowing when the VM has rebooted. Whenever new code arrives or the VM reboots, a context component must halt its context by calling MateContextSynch.halt(). Currently, the VM core signals reboot whenever any new code arrives, making one of the calls to halt redundant. However, better installation-time analysis may change this in the future, so a component should handle both, just to be sure. Contexts need to initialize their handler with MateHandlerStore when TinyOS boots, so context components usually provide a StdControl interface.

In addition to these standard interfaces, contexts usually provide or use a few others, which control when the context executes. For example, the Timer0 context uses and provides Timer interfaces for controllingits time. The signature of Timer0ContextM is:

module Timer0ContextM {
  provides {
    interface StdControl;
    interface Timer;
  }
  
  uses {
    interface StdControl as SubControlTimer;
    interface Timer as ClockTimer;      
    interface MateContextSynch as Synch;
    interface MateHandlerStore as HandlerStore;
    interface MateEngineStatus as EngineStatus;
  }
}

The provided Timer interface allows the settimer0 function component to manipulate Timer0's Timer; it forwards calls to the underlying ClockTimer. When ClockTimer fires, Timer0ContextM submits its context to the Maté scheduler, if it is not already running.

We'll step through the implementation of Timer0COntextM, one part at a time. The source file resides int tos/lib/VM/contexts. You've already seen the module signature. The module allocates a MateContext, which it initializes in StdControl.init():

implementation {
  MateContext timerContext;
  
  command result_t StdControl.init() {
    result_t rval = call SubControlTimer.init();
    timerContext.which = MATE_CONTEXT_TIMER0;
    timerContext.rootHandler = MATE_HANDLER_TIMER0;

    rval &= call HandlerStore.initializeHandler();
    call Synch.initializeContext(&timerContext);
    return rval;
  }

  command result_t StdControl.start() {
    return call SubControlTimer.start();
  }

  command result_t StdControl.stop() {
    call ClockTimer.stop();
    return call SubControlTimer.stop();
  }

init() first initializes the underlying Timer component, then sets two fields in the context. The first field is the context identifier, which is the MATE_CONTEXT constant corresponding to the context name. The second field is the handler identifier, which refers to what code handler the VM should run when the context starts executing. Next, init() tells HandlerStore to initialize its handler. The component providing HandlerStore provides it as a parameterized interface: the parameter is the handler ID. This allows it to know which handler to initialize. Finally, init() initializes the actual context with Synch.initializeContext: the two fields, which and rootHandler must be set before this is called. start() just starts the underlying Timer component (but not the ClockTimer itself, which settimer0 controls). stop, however, stops both the component and the ClockTimer.

Next is the ClockTimer.fired() event, and the task it uses to run the context:

  task void ClockEventTask() {
    if (timerContext.state == MATE_STATE_HALT) {
      dbg(DBG_USR1, "VM: Timer 0 context running.\n");
      timerContext.currentHandler = timerContext.rootHandler;
      call Synch.initializeContext(&timerContext);
      call Synch.resumeContext(&timerContext, &timerContext);
    }
    else {
      // drop the event
    }
  }
  
  event result_t ClockTimer.fired() {
    post ClockEventTask();
    return SUCCESS;
  }

When the underlying ClockTimer fires, the Timer0ContextM posts ClockEventTask, which ignores the event if the context is already running. Otherwise, it re-initializes the context (e.g., setting the program counter to the first instruction) and submits it to the Maté scheduler with resumeContext.

As it uses the MateHandlerStore and MateEngineStatus interfaces, a context component must handle two events, when new code arrives and when the VM reboots. In both cases, the component should halt the context if it is not already halted.

  event void HandlerStore.handlerChanged() {
    dbg(DBG_USR3, "Timer0Context: Handler changed.\n");
    if (timerContext.state != MATE_STATE_HALT) {
      call Synch.haltContext(&timerContext);
    }
  }

  event void EngineStatus.rebooted() {
    dbg(DBG_USR1, "Timer0Context: VM rebooted.\n");
    if (timerContext.state != MATE_STATE_HALT) {
      call Synch.haltContext(&timerContext);
    }
  }

Finally, Timer0ContextM forwards calls made to its provided Timer to the ClockTimer it uses:

  command result_t Timer.start(char type, uint32_t interval) {
    return call ClockTimer.start(type, interval);
  }

  command result_t Timer.stop() {
    return call ClockTimer.stop();
  }
  
 default event result_t Timer.fired() {
   return SUCCESS;
 }

The Timer0 context includes the settimer0 function, which calls the Timer commands on Timer0ContextM. Another approach is to have a context component provide instances of the MateBytecode interface. The function configurations can then just wire to these implementations. For example, instead of having a OPsettimer0M module, Timer0ContextM could include

provides interface MateBytecode as SetTimer;

and the OPsettimer0 configuration could just wire to it:

MateBytecode = Timer0ContextM.SetTimer;

This approach is useful if a function needs to access context state. For example, the Trigger context allows a program to pass a buffer to the Trigger handler as a parameter to the trigger function, which also causes the context to run. The function copies the buffer into the Trigger component (so the caller can continue to access its buffer), which can be accessed with the triggerbuf function. In this case, the Trigger component implements both functions. The trigger function copies the passed buffer into a component variable, and the triggerbuf function pushes that buffer onto the operand stack.

Unlike functions, context configurations do not provide an interface; they self-wire themselves to the Maté core. VMBuilder has to wire functions because it is responsible for generating the instruction set of a VM; contexts, in contrast, have no corresponding values that VMBuilder must generate, so can just wire themselves. For example, this is Timer0Context.nc:

includes Mate;

configuration Timer0Context {
  provides interface Timer;
}

implementation {
  components Timer0ContextM as Context, TimerC;
  components MContextSynchProxy as ContextSynch;
  components MateEngine as VM;
  components MHandlerStoreProxy as Store;

  Timer = Context;
  
  Context.SubControlTimer -> TimerC;
  Context.ClockTimer -> TimerC.Timer[unique("Timer")];
  Context.Synch -> ContextSynch;
  Context <- VM.SubControl;
  Context.HandlerStore -> Store.HandlerStore[MATE_HANDLER_TIMER0];
  Context.EngineStatus -> VM;
}

Just putting a context component in the top-level Maté configuration component list will include it and all of its wirings.

SendCounterContext

Now that you've seen a simple context, you'll implement a new one. The SendDone context runs every time a mote sends a packet; it runs when GenericComm's stand-alonesendDone event fires (not when the parameterized SendMsg.sendDone events fire).

Go to your extensions directory and create a CDF, named SendCounterContext.cdf. Put a single CONTEXT element in it:

<CONTEXT name=SendCounter desc="Runs every time a mote sends a packet." id=100>

Next, create a module, named SendCounterContextM.nc. Like all other contexts, it needs to provide StdControl and use MateContextSynch, MateHandlerStore, and MateEngineStatus. Additionally, it needs to use the event void sendDone() for triggering context execution:

includes Mate;

module SendCounterContextM {
  provides {
    interface StdControl;
    event result_t sendDone();
  }
  uses {
    interface MateContextSynch as Synch;
    interface MateHandlerStore as HandlerStore;
    interface MateEngineStatus as EngineStatus;
  }
}

The implementation needs to declare a context, and initialize it in StdControl.init():

implementation {
  MateContext counterContext;

  command result_t StdControl.init() {
    result_t rval;
    timerContext.which = MATE_CONTEXT_SENDCOUNTER;
    timerContext.rootHandler = MATE_HANDLER_SENDCOUNTER;

    rval = call HandlerStore.initializeHandler();
    call Synch.initializeContext(&counterContext);
    return rval;
  }

  command result_t StdControl.start() {return SUCCESS;}

  command result_t StdControl.stop() {return SUCCESS;}

It also needs to halt the context when code changes or the VM reboots:

  event void HandlerStore.handlerChanged() {
    dbg(DBG_USR3, "SendCounterContext: Handler changed.\n");
    if (counterContext.state != MATE_STATE_HALT) {
      call Synch.haltContext(&timerContext);
    }
  }

  event void EngineStatus.rebooted() {
    dbg(DBG_USR1, "SendCounterContext: VM rebooted.\n");
    if (counterContext.state != MATE_STATE_HALT) {
      call Synch.haltContext(&timerContext);
    }
  }

Finally, it needs to submit the context to run when the network stack finishes sending a packet:

  event result_t sendDone() {
    if (counterContext.state == MATE_STATE_HALT) {
      dbg(DBG_USR1, "VM: SendCounter context running.\n");
      call Synch.initializeContext(&counterContext);
      call Synch.resumeContext(&counterContext, &counterContext);
      return SUCCESS;
    }
    else {
      // drop the event
      return FAIL;
    }
  }

The module is complete. All you need is a configuration that wires it to all of the needed components. Open SendCounterContext.nc and implement it:

includes Mate;

configuration SendCounterContext {
}

implementation {
  components SendCounterContextM as Context;
  components MContextSynchProxy as ContextSynch;
  components MateEngine as VM;
  components MHandlerStoreProxy as Store;
  components GenericComm;
  
  Context.Synch -> ContextSynch;
  Context <- VM.SubControl;
  Context.HandlerStore -> Store.HandlerStore[MATE_HANDLER_SENDCOUNTER];
  Context.EngineStatus -> VM;
  Context.sendDone <- GenericComm.sendDone;
}
		    

Modify your simple.vmsf to include your new context:

<CONTEXT name=SendCounter>

Rebuild your simple VM with VMBuilder and compile it for TOSSIM. If set DBG to USR1 and run it TOSSIM with a single mote, you should see output like this:

0: VM: SendCounter context running.
0: VM: Context 1 submitted to run.
0: VM: Posting run task.
0: VM (1): OPhaltM executed.
0: VM: SendCounter context running.
0: VM: Context 1 submitted to run.
0: VM: Posting run task.
0: VM (1): OPhaltM executed.
0: VM: SendCounter context running.
0: VM: Context 1 submitted to run.
0: VM: Posting run task.
0: VM (1): OPhaltM executed.
0: VM: SendCounter context running.
0: VM: Context 1 submitted to run.
0: VM: Posting run task.
0: VM (1): OPhaltM executed.
		    

All of these sendDone events are from various services that periodically send packets. For example, multihop routing periodically sends beacons for link quality estimation, while the Maté code propagation subsystem periodically advertises what code it has.

Conclusion

You've completed the Maté tutorials. You've learned how to program TinyOS networks with Maté, as well as how to build VMs that meet your particular needs.


< Previous Lesson | Top