SCHOOL OF ENGINEERING AND APPLIED SCIENCES
HARVARD UNIVERSITY
CS 263r. Wireless Sensor NetworksSpring 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
- Install VMWare player.
- 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
- Extract the image.
tar zxvf pixie-vmware.tar.gz- Boot the virtual machine in VMWare player.
- Login with:
username: pixie password: pixieManual 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:Update your Pixie installation
- Add TinyOS debian repository
sudo echo "deb http://tinyos.stanford.edu/tinyos/dists/ubuntu hardy main" >> /etc/apt/sources.list.d/tinyos.list
- 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
- Download .bash_tinyos to your $HOME
cd $HOME && wget http://5secondfuse.com/tinyos/.bash_tinyos && cd -
- Include $HOME/.bash_tinyos in $HOME/.bashrc
echo ". $HOME/.bash_tinyos" >> $HOME/.bashrc
- Check out Pixie Subversion tree:
svn co http://senseless.eecs.harvard.edu/repos/fiji/pixie/branches/pixie-cs263sp09 $HOME/pixie
- Configure PIXIESRC environment variable:
echo "export PIXIESRC=$HOME/pixie" >> $HOME/.bashrc
- Build SerialForwarder (C version):
cd $TOSROOT/support/sdk/c/sf ./bootstrap ./configure makePixie 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:
- cd $PIXIESRC
- svn update
You can also choose to check out a fresh tree by:
- Remove the old tree
mv $HOME/pixie /tmp/
- 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:
- cd $PIXIESRC/apps/Blink
- 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 imageCongratulations -- you have successfully compiled a Pixie program!
Test your motes by installing Blink to your motes.
- 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.
- 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)- 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:
- ProcessStageP.nc -- a nesC module
- ProcessStage.nc -- a nesC configuration
- BlinkC.nc -- top-level configuration for Blink
- Makefile -- makefile for compiling Blink
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.
- $PIXIESRC/apps/Blink/ProcessStageP.nc shows how a nesC component uses interfaces:
generic module ProcessStageP() { provides interface PixieStage; uses interface PixieSink; uses interface PixieMemAlloc; uses interface TicketAlloc as EnergyAlloc; } implementation { ticket_t energy_ticket = PIXIE_NULL_TICKET; /* * Asking for energy tick */ void allocTicket() { call EnergyAlloc.request(ENERGY_COST_uAs, TICKET_EXPIRE_TIME); } /* * Do some processing with the memref * return a new memref with ''result'' */ memref_t process(memref_t mr) { memref_t newMR; uint16_t *ptrVal; newMR = call PixieMemAlloc.allocate(sizeof(uint16_t)); //allocate new memref with uint16_t content ptrVal = call PixieMemAlloc.data(newMR); //access memref content *ptrVal = 0xbeef; //set content printf("ProcesStageP: process() called.\n"); printfflush(); return newMR; } command error_t PixieStage.init() { allocTicket(); return SUCCESS; } /* * Invoked by Pixie scheduler and get a memref here */ command error_t PixieStage.run(memref_t mr) { memref_t newMR = PIXIE_NULL_MEMREF; if (mr == PIXIE_NULL_MEMREF) { return FAIL; } else { if (energy_ticket != PIXIE_NULL_TICKET) { if(SUCCESS == call EnergyAlloc.redeem(energy_ticket)) { newMR = process(mr); } } } allocTicket(); if(newMR != PIXIE_NULL_MEMREF) { call PixieSink.enqueue(newMR); //pass the new memref downstream } call PixieMemAlloc.release(newMR); //must release after done with memref call PixieMemAlloc.release(mr); //must release after done with memref return SUCCESS; } /* * ticket granted by the allocator */ event void EnergyAlloc.granted(ticket_t ticket) { PXDBG(printf("ProcesStageP: granted ticket amount %lu.\n", ticket->amount);printfflush()); if (ticket != PIXIE_NULL_TICKET) energy_ticket = ticket; } }ProcessStageP module provides PixieStage interface and implements all commands for PixieStage interface. It also uses PixieSink, PixieMemAlloc, TicketAlloc interfaces and therefore implements all events for these interfaces.
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.
- $PIXIESRC/apps/Blink/ProcessStage.nc shows an example nesC configuration:
generic configuration ProcessStage(priority_t PRIORITY) { provides interface PixieSink as Input; uses interface PixieSink as Output; } implementation { components new ProcessStageP(); components new PixieStageWrapperC(PRIORITY, "ProcessStage") as Wrapper; Input = Wrapper.Input; Output = ProcessStageP.PixieSink; Wrapper.PixieStage -> ProcessStageP; components PixieC; ProcessStageP.PixieMemAlloc -> PixieC; ProcessStageP.EnergyAlloc -> PixieC.EnergyAlloc[unique(UQ_TICKET_ALLOC)]; }Wirings can be implicit. For example, this line:
ProcessStageP.PixieMemAlloc -> PixieC;is shorthand forProcessStageP.PixieMemAlloc -> PixieC.PixieMemAlloc;If no interface name is given on the right side of the arrow, the nesC compiler by default tries to bind to the same interface as on the left side of the arrow.Wirings can also be parameterized. This is useful when multiple components use the same interface provided by a single component but the provider needs to distinguish among the components that are all wired to it through the same interface. One example is to signal an event to only one of the components. This is done by assigning a parameter while wiring to a parameterized interface. For example, this line:
ProcessStageP.EnergyAlloc -> PixieC.EnergyAlloc[ALLOC_ID];means PixieC provides a parameterized EnergyAlloc interface and the wiring with ProcessStageP is identified by an integer number ALLOC_ID.The unique(UQ_TICKET_ALLOC) that you saw in ProcessStage is a constant function provided by nesC. This function is converted into an unique unsigned integer at compile time (among all unique(identifier) with the same identifier). The intended use of unique is for passing a unique integer to parameterised interface instances, so that a component providing a parameterised interface can uniquely identify the various components connected to that interface
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.
- $PIXIESRC/apps/Blink/BlinkC shows an example top-level nesC configuration
configuration BlinkC {} implementation { components new TimerStage(1024) as TimerStage; components new ProcessStage(PIXIE_PRIORITY_NORM) as ProcStage; components new BlinkStage(PIXIE_PRIORITY_NORM, 1) as BlinkStage; TimerStage.Output -> ProcStage.Input; ProcStage.Output -> BlinkStage.Input; }To compile a nesC program, you need a Makefile that defines which component contains the top-level application wiring and include necessary library paths.
- $PIXIESRC/apps/Blink/Makefile
COMPONENT=BlinkC include $(PIXIESRC)/Makefile.in include $(MAKERULES)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.
- RouteControl.nc:
interface RouteControl { /* get address of the parent node */ command am_addr_t getParent(); /* current hop count from base station */ command uint8_t getHopCount(); }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.
- Define a message format and AM type
A message format is just a struct using nx types to define the fields. Using nx types guarentees interoperability between motes using processors with different endiannesses.
AM type is used to multiplex radio packets.
For example, RadioCountToLeds.h in $PIXIESRC/apps/RadioCountToLeds defines a message struct and AM type for the message:
typedef nx_struct radio_count_msg { nx_uint16_t counter; } radio_count_msg_t; enum { AM_RADIO_COUNT_MSG = 6, };- Use SendStage to send packets
With SendStage for each AM type in your Pixie application, you can send packets by passing the payload to it. SendStage will wrap the payload in a TinyOS packet (message_t) with specified AM type and then send it to the specified destination address. For example, the following line instantiates a SendStage for sending AM_RADIO_COUNT_MSG to broadcast address:
components new SendStage(PIXIE_PRIORITY_NORM, TOS_BCAST_ADDR, radio_count_msg_t, AM_RADIO_COUNT_MSG);- Use ReceiveStage to receive packets
To receiver messages, instantiate ReceiveStage for each AM type. This stage will extract payload from each packet of the specified AM type and emit it to the next stage. For example, the following line use ReceiveStage to receive AM_RADIO_COUNT_MSG:
components new ReceiveStage(radio_count_msg_t, AM_RADIO_COUNT_MSG);- Use SendMessageStage and ReceiveMessageStage to send and receive raw TinyOS packets
Sometimes you will need control over the TinyOS packet header fields instead of just the payload. One example is to set the destination address dynamically. In this case, you need to use SendMessageStage and ReceiveMessageStage to send and receive packets. Here are examples for instantiating these two stages:
components new SendMessageStage(PIXIE_PRIORITY_NORM, radio_count_msg_t, AM_RADIO_COUNT_MSG);components new ReceiveMessageStage(radio_count_msg_t, AM_RADIO_COUNT_MSG);Keep in mind that you need to pass memref containing an entire message_t to SendMessageStage instead of just the payload. Likewise, when using ReceiveMessageStage, you need to interpret the content of the memref as message_t instead of the payload type.
To access packet header and payload, use AMPacket and Packet interfaces provided by PixieC
msgPtr = (message_t*) call PixieMemAlloc.data(dequeuedMR); rcmPtr = (radio_count_msg_t*) call Packet.getPayload(msgPtr, sizeof(radio_count_msg_t)); //access packet header fields src = call AMPacket.source(msgPtr);- Useful constants:
TOS_NODE_ID: the address of your self TOS_BCAST_ADDR: broadcast addressSerial 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.pyTo 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 telosYou 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 9002Problem 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 -rThis 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.
- Include the library:
#include "printf.h"
- Call printf() and printfflush()
printf("Blink!\n"); printfflush();
- Start SerialForwarder
$TOSROOT/support/sdk/c/sf/sf 9002 /dev/ttyUSB0 telos- run printfClient.py
$PIXIESRC/util/printfClient.py Blink! Blink! Blink!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:
- Nodes should sample and transmit their light sensor reading once every 10 seconds.
- Nodes should use the message format specified in the header file Multihop.h. This message format (and corresponding Active Message ID) should be used by all data messages transmitted by nodes in the network. Using a common format allows us to test your code and interface it with other tools. You are welcome to define your own message formats for other message types, e.g., tree formation messages.
- Nodes need not worry about packet loss, nor worry about reliable communication (that is, you should not implement acknowledgement and retransmission).
- Duplicate messages should be suppressed whenever possible. For example, a given sensor message should be received at the base station at most once.
- Only the parent node of a given node should forward the message towards the base station. That is, nodes should not flood or otherwise transmit multiple copies of their message. (Note, however, that it is acceptable to use the TOS_BCAST_ADDR broadcast mechanism for transmission of sensor readings, since this allows other nodes to "eavesdrop" on the messages being sent by other nodes, for the purpose of discovering other nodes in the network.)
- Routing loops, if they arise, should be rapidly suppressed. For example, say you have a path in the network that looks like this:
(Base) <--- A <--- B <--- CIf node "A" were to suddenly select node "C" as its parent, a routing loop has been induced: node C routes to node B, which routes to node A, which routes back to node C.There are several ways to manage routing loops. The first is to avoid allowing their creation. Depending on which scheme you use for spanning tree formation, this may or may not be possible. If a routing loop does occur, nodes should avoid sending multiple copies of the same message around the loop. A simple approach is to keep track of the sequence number of the last message that was forwarded, and drop any incoming messages with a sequence number less than this value. In addition, a routing loop should be "broken" rapidly, for example, by forcing all nodes on the loop to select a new parent (in a way that does not cause the loop to immediately re-emerge).
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.)
- Node ID
- Current light sensor value
- Current tree depth
- Total packets received from this node
- Total lost packets from this node
- Percentage of lost packets from this node
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 telosbThe 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 20090Debugging 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.
- Node ID
- Average light sensor value
- Average tree depth
- Total packets received from this node
- Total lost packets from this node
- Percentage of lost packets from this node
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 programEmail 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.