SCHOOL OF ENGINEERING AND APPLIED SCIENCES
HARVARD UNIVERSITY

CS 263r. Wireless Sensor Networks

Prof. Matt Welsh
Spring 2009

Assignment #1 - Multihop Data-collection Protocol for Sensor Networks

Due Date: March 10, 2009, 11:59pm EST

This assignment is intended to introduce you to programming sensor networks using the Pixie operating system. In this assignment, you will develop a multihop data collection protocol. One node in the network will act as a base station, collecting sensor readings from all other nodes. Each sensor node will periodically sample its sensor and transmit the data to a chosen parent node, which will relay the data to the base station. You will prototype your protocol using a mote kit with your PC and then deploy and evaluate your protocol on MoteLab, a 184-node sensor network in Maxwell Dworkin hall.

Data collection is a standard operation used in a wide range of sensor network applications. When collecting data from sensor nodes at a single base station, we do not assume that all nodes can communicate with the base station in a single radio hop. That is, nodes in the network may need to relay messages for each other in order for the data to eventually reach the base. The simplest routing topology in this case is a spanning tree, in which the base station is the root and all other nodes are at a depth proportional to the number of radio hops they are from the base. At any moment in time, a given node has a single parent node which is responsible for relaying its messages to the base. Because radio link quality tends to vary over time, and nodes may crash or otherwise fail, nodes may need to select a new parent from time to time. In addition, new nodes may be added to the network over a given deployment. For these reasons, it is not appropriate to "hard code" the routing topology into the application: rather, nodes should dynamically discover potential parent node candidates and adjust the routing topology as needed.

Note that a spanning tree is a very specific type of routing. Unlike any-to-any routing mechanisms, in which nodes must potentially communicate with any other node in the network, a spanning tree requires each node to only keep track of its parent. That is, "routing tables" are unnecessary. However, nodes must still track the existence of other nodes in the network that might serve as their parent.

This assignment has 7 phases, and we encourage you to work in pairs. The first three phases involve installation and learning of the Pixie environment on a Linux machine. The fourth phase involves writing a simple test program that will help you learn the ropes of Pixie for simple sensing and communication tasks. The fifth phase involves designing and testing the multihop communication protocol with the mote kit. The sixth and final phase requires deploying the protocol on MoteLab and measuring its performance.

Step 0: Form a group and get a mote kit.

You are encouraged to find a partner to work on this assignment. Once you find your partner or have decided to work alone, obtain a mote kit from Bor-rong Chen, MD238. Each mote kit consists of four Telos sensor nodes. Please note that this equipment is expensive and you are responsible for returning the entire kit in working condition at the end of the semester. Also, be careful of static discharge and other things that can "fry" a mote. We do not have any replacements, so use caution.

Although a fresh pair of AA batteries will last for several days if you leave the mote running, keep in mind that you will be doing a lot of reprogramming and testing. You may need to buy more batteries (at your own expense) to complete the assignment. Note that the Telos motes do not have a power switch, so if you leave the batteries in the mote it is probably drawing power (even if the LEDs are not on). We suggest popping out one of the batteries when the mote is not in use. You do not need to put the batteries into the mote to program it: the mote receives power from the USB port when plugged in.

If a mote behaves fine at first, and later acts erratically (e.g., won't reprogram, radio messages not getting through, etc.) try a fresh pair of batteries.

Step 1: Install Pixie

For this assignment, you will develop and run your sensor network programs using Pixie operating system. Currently, Pixie requires TinyOS 2.1.0 and nesC 1.3.0. This step helps you set up the proper software environment. We assume you have access to a PC or laptop that is capable of running Ubuntu Linux as a virtual machine in VMWare Player.

Use Pixie VMWare image - supported by course staff

  1. Install VMWare player.
  2. Click here to download the Pixie VMWare image.

    warning: this is a large download, we suggest you do this using wired Ethernet inside Harvard campus network

  3. Extract the image.
    tar zxvf pixie-vmware.tar.gz
  4. Boot the virtual machine in VMWare player.
  5. Login with:
    username: pixie
    password: pixie
        

Manual Pixie installation - not supported

You can avoid using the VMWare image by installing Pixie on your own Linux system. However, the course staff will only support the VMWare option. You are on your own if you choose to do a custom installation. As an example, You can install Pixie on your Ubuntu Linux (or other Debian based distributions) by:
  1. Add TinyOS debian repository

    sudo echo "deb http://tinyos.stanford.edu/tinyos/dists/ubuntu hardy main" >> /etc/apt/sources.list.d/tinyos.list

  2. Install tinyos related packages and make a symlink:

    sudo apt-get install tinyos-2.1.0 tinyos-tools automake subversion

    sudo ln -s /opt/tinyos-2.1.0 /opt/tinyos-2.x

  3. Download .bash_tinyos to your $HOME

    cd $HOME && wget http://5secondfuse.com/tinyos/.bash_tinyos && cd -

  4. Include $HOME/.bash_tinyos in $HOME/.bashrc

    echo ". $HOME/.bash_tinyos" >> $HOME/.bashrc

  5. Check out Pixie Subversion tree:

    svn co http://senseless.eecs.harvard.edu/repos/fiji/pixie/branches/pixie-cs263sp09 $HOME/pixie

  6. Configure PIXIESRC environment variable:

    echo "export PIXIESRC=$HOME/pixie" >> $HOME/.bashrc

  7. Build SerialForwarder (C version):
    cd $TOSROOT/support/sdk/c/sf
    ./bootstrap
    ./configure
    make
    
Update your Pixie installation

Pixie is already installed as part of the virtual machine you downloaded. This is a version checked out from Pixie subversion repository. We may have more recent update or bug fixes for your assignment. To obtain these updates, do:

  1. cd $PIXIESRC
  2. svn update

You can also choose to check out a fresh tree by:

  1. Remove the old tree

    mv $HOME/pixie /tmp/

  2. Check out the fresh tree

    svn co http://senseless.eecs.harvard.edu/repos/fiji/pixie/branches/pixie-cs263sp09 $HOME/pixie

Test your Pixie installation by compiling "Blink" application under $PIXIESRC/apps. To do this:

  1. cd $PIXIESRC/apps/Blink

  2. make telos -- You should see something like:
    mkdir -p build/telos
        compiling BlinkC to a telos binary
    ncc -o build/telos/main.exe  -Os -O -DTOSMSG_MACACK_ENABLED
    :DBASESTATION_ADDR=0 -mdisable-hwmul -DCC2420_DEF_CHANNEL=24 -Wall -Wshadow
    -Wnesc-all -target=telos -fnesc-cfile=build/telos/app.c -board=
    -DDEFINED_TOS_AM_GROUP=0x22 -DPIXIE_SCHEDULER_PRIORITY -DPIXIE_STAGE_DIRECT
    -DPRINTFUART_ENABLED -DPIXIE_DEBUG_PROCESSSTAGEP -DPIXIE_DEBUG_ENERGYALLOCATORP
    -DIDENT_APPNAME=\"BlinkC\" -DIDENT_USERNAME=\"brchen\"
    -DIDENT_HOSTNAME=\"nitro\" -DIDENT_USERHASH=0x61269a27L
    -DIDENT_TIMESTAMP=0x4981eeedL -DIDENT_UIDHASH=0x86481ecdL  BlinkC.nc -lm
    /opt/tinyos-2.x/tos/chips/cc2420/lpl/DummyLplC.nc:39:2: warning: #warning "***
    LOW POWER COMMUNICATIONS DISABLED ***"
    /home/student/pixie-cs263/tos/chips/cc2420/bandwidthEmulator/BandwidthEmulatorC.nc:6:2: warning: #warning "*** USING BANDWIDTH EMULATOR"
    /home/student/pixie-cs263/tos/chips/cc2420/CC2420ActiveMessageP.nc:267: warning: `AMPacket.type' called asynchronously from `SubTransmitNotifier.aboutToTransmit'
        compiled BlinkC to build/telos/main.exe
               19574 bytes in ROM
                1086 bytes in RAM
    msp430-objcopy --output-target=ihex build/telos/main.exe build/telos/main.ihex
        writing TOS image
    

    Congratulations -- you have successfully compiled a Pixie program!

Test your motes by installing Blink to your motes.

  1. Plug a Telos mote into a USB port on your machine.

    You may need to specifically tell VMWare player to connect your mote (named Future Devices Telos) to the virtual machine by clicking a button on top of the window.

  2. Find out which USB port it is attached to using motelist.
    motelist
    
    Reference  Device           Description
    ---------- ---------------- ---------------------------------------------
    M4MSEY17   /dev/ttyUSB0     Moteiv Telos (Rev A 2004-04-27)
    
  3. install Blink

    To program a Telos mote appearing as /dev/ttyUSB0 on your PC with id 7, type:

    make telos install.7 bsl,/dev/ttyUSB0

    If the mote is programmed correctly, you should see the red LED starts to toggle once a second.

Step 2: Learn nesC basics

To use Pixie, it is necessary for you to learn the basics of nesC. nesC is a dialect of C. It is easy to learn if you already know C. You can think of it as C with some additional features for modularity and concurrency.

Let's start by looking at a simple example nesC code: the Blink application that you just compiled in Step 1.

We use the following files in $PIXIESRC/apps/Blink to illustrate key concepts in nesC programming:

Components and interfaces

A nesC application consists of one or more components assembled, or wired, to form an application executable. Components define two scopes: one for their specification which contains the names of their interfaces, and a second scope for their implementation. A component provides and uses interfaces. The provided interfaces are intended to represent the functionality that the component provides to its user in its specification; the used interfaces represent the functionality the component needs to perform its job in its implementation.

Interfaces are bidirectional: they specify a set of commands, which are functions to be implemented by the interface's provider, and a set of events, which are functions to be implemented by the interface's user. For a component to call the commands in an interface, it must implement the events of that interface. A single component may use or provide multiple interfaces and multiple instances of the same interface.

Wiring

There are two types of components in nesC: modules and configurations. Modules provide the implementations of one or more interfaces. Configurations are used to assemble components together, connecting interfaces used by components to interfaces provided by others. This is called wiring.

nesC uses arrows to determine relationships between interfaces. The right arrow (->) means "provided by". The component that uses an interface is on the left, and the component provides the interface is on the right.

Building an application

Every nesC application is described by a top-level configuration that wires together the components used by the application. A top-level configuration does not provide or use any interface.

To compile a nesC program, you need a Makefile that defines which component contains the top-level application wiring and include necessary library paths.

With the Makefile in place, you can compile and install your program for a mote as you did in Step 1.

Step 3: Learn Pixie

Now that you know enough about nesC. Let's go on to learn Pixie.

Pixie Stages: Dataflow model

A Pixie application is structured as a dataflow graph of stages. Each stage has input and output ports for being wired together. As an example, let's take a look at $PIXIESRC/apps/Blink again in more detail.

As shown in BlinkC.nc, this simple application consists of three stages that are wired in the following way:

TimerStage -> ProcStage -> BlinkStage.

TimerStage generates one Pixie memory object (memref) every 1 second and pushes it downstream. ProcStage is a dummy stage that is there to illustrate how to access content in a memref and allocate new ones to be passed downstream. BlinkStage toggles the red LED every time it receives a memref. Being programmed with this application, the mote blinks its red LED every second.

Pixie memory model

Pixie memory objects are represented as memrefs, a dynamically-allocated, contiguous region of memory, along with a reference count. Memrefs are untyped and stages producing and consuming memrefs must agree on their format. When a stage is scheduled, it is passed a memref (from any one of its input ports) as input. Pushing a memref onto an output port increments the refcount for each stage receiving the memref. A stage must explicitly release a memref, decrementing its reference count, if it no longer wishes to access the memref (e.g. because it has passed it downstream or consumed it). Deallocation is performed implicitly when a memref's reference count drops to zero. Memrefs are managed by Pixie memory manager. A Pixie stage uses PixieMemAlloc interface to allocate or release memrefs. You can see typical usage of memrefs in the example stage implementation ProcessStageP.nc that's shown in Step 2.

Resource tickets

Resource ticket is a core abstraction for Pixie to represent resource availability and reservation. Resource tickets allow applications to give the system visibility and fine-grained control over resource management. At the lowest level, resource tickets must be allocated via resource allocators. In cases where complicated policies are needed for managing tickets, resource brokers are introduced to manage resource tickets on behalf of the application. For this assignment, we do not make use of resource brokers for simplicity. i.e. your application directly interface with resource allocators.

Any Pixie stage that consumes significant amounts of system resources (e.g. bandwidth or energy) should allocate tickets from resource allocators for permission to use the specified system resources. A ticket specifies resource type, amount, and an expire time. A ticket must be redeemed before it expires. Pixie resource ticket has the following format, as defined in $PIXIESRC/interfaces/TicketAlloc.h:

typedef struct _resource_ticket_t {
  uint32_t expireTime;          //expire time
  resource_qty32_t amount;      //resource amount
  resource_t type;              //resource type
} _resource_ticket_t_struct;

typedef _resource_ticket_t_struct* ticket_t;

Before using the resources, a stage must redeem the tickets representing the amount of resources to be used. This step is crucial to allow Pixie to help applications adapt to resource availability. However, Pixie currently does not validate whether a stage is redeeming correct amount of resources that it uses. So, Pixie relies on the programmer to estimate resource consumption by a stage and make adaptation decisions based on the estimation.

ProcessStageP.nc in $PIXIESRC/apps/Blink shows an example on requesting and redeeming energy tickets.

CountMsgStageP.nc in $PIXIESRC/apps/RadioCountToLeds shows an example on requesting and redeeming bandwidth tickets.

Scheduling

By default, Pixie schedules stages by performing depth-first traversal of the stage graph, i.e. invoke downstream stages by directly calling the PixieStage.run() command of the downstream stage. Traversals are initiated at source stages, such as TimerStage or ReceiveStage, and terminate at sink stages which either do not push data to an output port, or have no output ports (e.g. SendMessageStage and BlinkStage)

Stage implementation

A stage is implemented by a nesC module which provides PixieStage interface and uses PixieSink interface to support Pixie-specific scheduling and wiring. ProcessStageP.nc, seen in Step 2, is a example stage that receives data as memrefs, processes the data and then emit results as new memrefs.

In addition, each stage needs a wrapper to make the stage usable by the Pixie scheduler. ProcessStage.nc in Step 2 shows how this is done as a nesC configuration.

The purpose of the Pixie stage wrapper is to hook the stage implementation into the Pixie scheduler. When writing your own stages, we recommend that you start with existing stage wrapper code as template instead of writing it from scratch.

Control wiring

Sometimes stages need to coordinate with each other to implement specific application logic. This is done through control wiring in Pixie. Control wirings are implemented as nesC interfaces that modify internal states of stages. For example, when implementing a routing protocol, the stage that decides the next hop for a data packet may need to obtain the parent address from another stage that maintains the neighbor table. In this case, the control wiring interface may look like: This interface could be provided by the stage managing the neighbor table and used by the stage that handles data packets.

Step 4: Write a simple program to display the value of the light sensor on each mote.

Your first task is to write a simple program in which the status of each node's light sensor is shown on the LEDs of the other two motes. Take three of your motes. One of them will be "red", the other "green", and the last "blue". The appropriate color LED should be lit on the mote, as well as the other two motes, when the light sensor reads above some threshold value. When the light sensor is below the threshold, the LED should be off on all three motes. This is a pretty silly program but requires you to deal with sensors, timing, and radio communication.

Light sensor

There are two light sensors on the Telos mote: A "total solar radiation" (TSR) sensor, and a "photosynthetically active radiation" (PAR) sensor. You should use TSR sensor for this assignment by including TSRSensorStage in your Pixie application. TSRSensorStage emits a memref containing uint16_t value of the sensor reading.

This sensor is fairly sensitive, so you will need to use a fairly low threshold (between 30 and 50) to distinguish between "light" and "dark" in your application.

Radio communication

To exchange packets over wireless channel in Pixie, you need to: See $PIXIESRC/apps/RadioCountToLeds and $PIXIESRC/apps/testBandwidth for complete examples on exchanging radio packets using the stages introduced above.

Serial communication with the mote

You will need to communicate with the mote through the serial connection, either for data collection or debugging purposes. To exchange messages over the serial port, use the SerialSendStage and SerialReceiveStage provided by Pixie. (see $PIXIESRC/apps/testSerial for example)

On the PC side, you can write Python programs using the tinyos.message library under $TOSROOT/support/sdk/python to communicate with the mote. You will need to use the mig tool to generate Python classes for interpreting the messages that are sent over the serial port. For example, this command generates a python class for radio_count_msg in RadioCountToLeds.h:

mig python -python-classname=RadioCountMsg RadioCountToLeds.h radio_count_msg > RadioCountMsg.py

To send/receive and parse the messages, you can write Python code similar to this:

  sys.path.append(os.path.join(os.environ["TOSROOT"], "support/sdk/python"))
  from tinyos.message import MoteIF
  import RadioCountMsg

  class MsgListener:
      def __init__(self):
          self.mif = MoteIF.MoteIF()
          self.mif.addSource("sf@localhost:9002")
          # Listen for messages of type RadioCountMsg (generated by the 'mig'
          # step above).
          self.mif.addListener(self, RadioCountMsg.RadioCountMsg)

      def receive(self, src, msg):
          if msg.get_amType() == RadioCountMsg.AM_TYPE:
              print "received count:", msg.get_counter()

      def sendMsg(self, addr, amType, amGroup, msg):
          self.mif.sendMsg(self.source, addr, amType, amGroup, msg)

  listener = MsgListener()
  msg = RadioCountMsg.RadioCountMsg()
  count = 0

  while True:
      count += 1
      print "send count %u" % (count)
      msg.set_counter(count)
      listener.sendMsg(0, RadioCountMsg.AM_TYPE, 0x22, msg)
      time.sleep(1)

To test your Python program, first run SerialForwarder to forward data from the serial port to a TCP socket:

$TOSROOT/support/sdk/c/sf/sf 9002 /dev/ttyUSB0 telos
You should see Note: sync if the SerialForwarder receives anything from the mote. To test your SerialForwarder setup, run sflisten to dump out all messages being received by SerialForwarder.
$TOSROOT/support/sdk/c/sf/sflisten localhost 9002

Problem with Telos motes: We have found that the serial port stack for Telos platform might behave incorrectly if you try to talk to a Telos mote without first programming it (even though it was loaded with the correct program before). i.e. if you unplug the mote from the USB port and plug it back in, serial communication might fail to work. We are currently trying to fix this problem, but if this happens, run the following command to reset the mote before you start SerialForwarder:

tos-bsl --telos -c /dev/ttyUSB0 -r
This is only a problem for your mote kit (Telos motes). On other platforms such as TelosB, used by MoteLab, SerialForwarder should work fine. Once you are getting data into the SerialForwarder, fire up your Python program, which connects to the SerialForwarder and receives the messages.

Debugging support: printf()

Debugging with motes is usually a pain. Fortunately, there is printf() library that allows you to send arbitrary strings through the serial port. The strings are formatted the same way as in printf() provided by standard C library. See $PIXIESRC/apps/Blink/ProcessStageP.nc for a working example. All you need to do is:

On the PC side, start SerialForwarder to forward the messages and then run $PIXIESRC/util/printfClient.py to display the strings printed from the mote.

printfUART() -- deprecated

There is another printf tool, prinfUART(), that you will see in many Pixie system stages. This is an older tool that were used while developing Pixie OS. Please do not use this tool because it does not work with SerialForwarder, which will be a problem when you move on to debug with nodes on MoteLab.

Step 5: Develop your multihop data collection protocol.

It's time to develop your multihop data collection protocol. As described above, we suggest you use a simple spanning tree approach to routing: each node in the network has a single parent in the spanning tree, which it sends its data to. The parent, in turn, is responsible for forwarding data from its childen to its own parent. The root of the spanning tree is the base station. When the base station receives a radio message, it simply forwards it to its serial port, allowing the host PC to receive the data.

Before nodes can transmit data, they must have a parent to send it to. How does a node learn about a parent? A simple approach is for nodes to periodically send a "tree formation" message. The base station would transmit such a message indicating its tree depth as 0 (the root of the tree). When another node hears such a message, it realizes it can transmit data to the base, so it chooses the base as its parent. This node would also rebroadcast the tree formation message, advertising its tree depth as 1.

This is a simple technique that does not take several factors into account. For example, a node may have multiple potential parents that it can choose from, perhaps with different tree depths. One approach is to always choose the parent with the smallest depth. However, such a node may be further away in the network, and therefore the radio link may be less reliable. In addition, network links may be asymmetric --- the fact that node A can hear node B does not imply that node B can hear node A. For this assignment, you are not expected to deal with all of these issues, although it is at least a good idea to consider how ignoring them may affect the performance of your design.

You have a great deal of leeway in terms of how you design your system. In general your design should be fairly simple, and if you find that your design is overly complex then you are probably going about things the wrong way.

Some basic requirements for your protocol:

Data collection program on the PC-side

In addition to the mote software itself, you should write a Python program to display the current state of the network.

Your Python program should periodically display a table of the current network state, with one row for each node in the network. The table should have the following columns:

You can simply output this table in text format to stdout. If you are so inclined you could write a GUI that displays the status of the network in graphical form. (No extra credit, though.)

You should test your multihop protocol using your mote kit. The easiest setup is to assign the base station node 0, plugged into your PC, and distribute the other nodes far enough away that at least 1 or 2 of the nodes have to route data through multiple hops to each the base station. (The typical indoor range of the Telos motes is quite good -- 50 to 100 meters. You may need to place nodes in other rooms in order to induce multihop paths.) On your PC, you should run the Python program that you developed above to monitor the network state.

Step 6: Test your multihop routing protocol on MoteLab.

Now you are ready to run your multihop routing application on MoteLab. First, login to MoteLab. (Your username and password will be emailed to you when the accounts are ready.) MoteLab uses a mote platform called TmoteSky. For historical reason, TmoteSky platform is named as telosb. The main difference between TmoteSky and TelosA is that TmoteSky has 10K of RAM instead of 2K.

Before uploading your executable to MoteLab, you need to compile your program for the telosb platform:

make telosb

The resulting executable will be left in build/telosb/main.exe. Create a job for your application and upload this executable as the file that runs on all motes. To create a MoteLab job, MoteLab will force you to upload/select a Java class file for data logging purpose. This is an old feature that we will not use in this assignment. So just upload the DummyMsg.class to get through the job creation process.

Schedule your multihop routing application to run for a 30-minute period. Use Mote 90 (located in Matt's office, MD233) as the base station node. Only the base station should forward any MultihopMsg it receives to its serial port. However, you are welcome to have other nodes send messages to their serial ports for debugging purposes.

Serial communication with MoteLab nodes

For data collection or debugging purposes, you will need to collect data from MoteLab motes via serial port. MoteLab runs a SerialForwarder for each mote. They can be accessed at TCP port number 20000+mote_id on motelab.eecs. For example, in your Python script, connect to sf@motelab.eecs.harvard.edu:20090 to receive serial messages from mote 90 on MoteLab.
mote.addSource("sf@motelab.eecs.harvard.edu:20090")

You can also test this with sflisten

cd $TOSROOT/support/sdk/c/sf
./sflisten motelab.eecs.harvard.edu 20090

Debugging with MoteLab

Since printf() library works over SerialForwarder protocol, you can debug MoteLab nodes with printf() too.

Final results

Once the program has finished running, generate a table (in a text file) consisting of one row for each node, and the following columns:

The totals and averages should be over the entire 30-minute run. You can easily generate this table by writing a script that parses the logged messages.

Step 7: Submit the source code and results from your evaluation.

The final step is to send us the results of your evaluation. Please create a gzipped tar file (or ZIP file) with the following directory structure:

   group-usernames/      (e.g., 'konrad-brchen/')
     README              Include your names, emails, brief description
     sensor/             Sensor and LED test program from Step 4
     multihop/           Multihop application
       evaluation.txt    PLAIN TEXT FILE of the results described above
       pixie/            Pixie code
       python/           Python code for receiver program

Email this as a .tar.gz or ZIP attachment to brchen@eecs by 11:59pm on March 10, 2009. No extensions to this deadline will be granted.