nesC Interfaces: abstracting definition from implementation

This lesson discusses the basic concept of interfaces in the nesC programming language. It includes an easy to understand example using the Blink application introduced in the TinyOS Lesson 1 Tutorial.

Introduction

Bug-free, maintainable software is rarely written first try. As code evolves, pieces are rewritten, refactored and improved. One large component may turn into several smaller components. Given enough common functionality between components, the behavior of a set of components may be defined independently of any particular implementation. nesC encourages such abstraction by the use of interfaces. In this tutorial, we will define an interface (BlinkHandler.nc) for handling blinks, and wrap the Blink application with a program that implements the BlinkHandler interface.

Interfaces

Let's start with a quote from the nesC reference manual:

Interfaces in nesC are bidirectional: they specify a multi-function interaction channel between two components, the provider and the user. The interface specifies a set of named functions, called commands, to be implemented by the interface's provider and a set of named functions, called events, to be implemented by the interface's user.

For our application, Blink will be the interface provider, and Blinker will be the interface user.

Componentizing Blink

To provide independence between the component that wants to blink an LED and the program that provides the actual blinking behavior, the Blink configuration needs to be slightly modified to remove Main:

Blink.nc
 
configuration Blink {

   provides {
      interface StdControl;
      interface BlinkHandler;
   }
}

implementation {

   components BlinkM, 
              TimerC, 
              LedsC;

   BlinkM.StdControl   = StdControl;
   StdControl          = TimerC;

   BlinkM.Timer        -> TimerC.Timer[unique("Timer")];
   BlinkM.Leds         -> LedsC;

   BlinkM.BlinkHandler = BlinkHandler;
}
		    

In addition to removing the Main, we also specify that the BlinkHandler interface is implemented. Since this tutorial is being written during the holiday season, the Leds interface is retained so that Blink can fire a red LED while the BlinkHandler implementation fires a green LED.

In the Blink module file (BlinkM.nc) we need to add a few lines of code:

BlinkM.nc
 

module BlinkM {

   provides {
      interface StdControl;
      interface BlinkHandler;
   }

   uses {
      interface Timer;
      interface Leds;
   }
}

implementation {

   ...

   event result_t Timer.fired() {

      signal BlinkHandler.Blink();
      call Leds.redToggle();
      return SUCCESS;
   }

   ...

   default event result_t BlinkHandler.Blink() {
      return SUCCESS;
   }

   ...
}
		    

Now when the timer fires, the BlinkHandler is signaled as well as toggling the red LED. Since the BlinkHandler interface is provided, a default Blink event is implemented.

The BlinkHandler interface

The interface is really simple. The entire BlinkHandler.nc file consists of 3 lines of code:
BlinkHandler.nc
 
          interface BlinkHandler {
             event result_t Blink ();
          }
		    

The Blinker program

Since Blink is a component, not a program, it can't run by itself: something, somewhere with a Main needs to wire to it. The Blinker program fits the bill:

Blinker.nc
 

configuration Blinker {

}

implementation {

   components  Main,
               Blink, 
               LedsC,
               BlinkerM;


   Main.StdControl       -> BlinkerM.StdControl;
   Main.StdControl       -> Blink.StdControl;

   BlinkerM.Leds         -> LedsC;
   BlinkerM.BlinkHandler -> Blink.BlinkHandler;
}
		    

Catching the BlinkHandler.Blink event

The final bit is to provide an overriding event for for catching the BlinkHandler.Blink signal. This is implemented like so:

BlinkerM.nc
 
module BlinkerM {

   provides {
      interface StdControl;
   }

   uses {
     interface BlinkHandler;
     interface StdControl as Blink;
     interface Leds;
   }
}

implementation {

   // StdControl implementation.

   ...

   event result_t BlinkHandler.Blink() {
      call Leds.greenToggle();
      return SUCCESS;
   }
}
		    

Summary

nesC interfaces are indispenable for rapidly and accurately implementing reusable components. This tutorial and accompanying code is intended to help clarify some of the implementation details for creating and implementing interfaces.

Exercise

Use a parameterized interface to make the yellow LED blink. Hint: Remember to parameterize the default implementation of the Blink event:

BlinkerM.nc
 
   ...

   default event result_t BlinkHandler.Blink[uint8_t id]() {
      return SUCCESS;
   }

   ...
		    

Comments and corrections to: doolin at ce dot berkeley dot edu.